From daa9d8d34138c1f1e3b8e6b5b020613b6410cf82 Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 8 Sep 2026 01:00:52 +0800 Subject: [PATCH] fix: apply decision buy denials to exposure-increasing amendments --- crates/fidc-core/src/broker.rs | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index 652ee38..dd7cf0b 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -2838,6 +2838,18 @@ where } } + if existing.side == OrderSide::Buy + && (target_total_quantity > existing.requested_quantity + || target_limit_price > existing.limit_price) + && let Some(denial) = self.runtime_buy_denials.borrow().get(&existing.symbol) + { + Self::emit_open_order_update_rejected( + report, date, order_id, Some(&existing.symbol), Some(existing.side), + reason, denial, + ); + return; + } + let resets_queue_priority = target_limit_price.to_bits() != existing.limit_price.to_bits() || target_total_quantity > existing.requested_quantity; { @@ -8314,6 +8326,46 @@ mod tests { assert!(broker.runtime_buy_denials.borrow().is_empty()); } + #[test] + fn decision_buy_denial_rejects_increasing_amendments_without_mutation() { + let date = limit_test_snapshot().date; + let data = DataSet::from_components(vec![limit_test_instrument()], vec![limit_test_snapshot()], + Vec::new(), vec![limit_test_candidate(true, true)], vec![limit_test_benchmark()]).unwrap(); + for (quantity, price) in [(Some(300), None), (None, Some(10.5)), + (Some(100), Some(10.5)), (Some(300), Some(9.5))] { + let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks); + broker.upsert_open_order(test_open_order(1)); + broker.upsert_open_order(test_open_order(2)); + broker.runtime_buy_denials.borrow_mut().insert("000001.SZ".to_string(), "strategy_buy_condition_false".to_string()); + let portfolio = PortfolioState::new(100_000.0); + let mut report = BrokerExecutionReport::default(); + broker.modify_open_order(date, &portfolio, &data, 1, quantity, price, "amend", &mut report); + let orders = broker.open_orders.borrow(); + assert_eq!(orders.iter().map(|order| order.order_id).collect::>(), vec![1, 2]); + assert_eq!(orders[0].requested_quantity, 200); + assert_eq!(orders[0].remaining_quantity, 200); + assert_eq!(orders[0].limit_price, 10.0); + assert!(report.order_events.last().unwrap().reason.contains("strategy_buy_condition_false")); + } + } + + #[test] + fn decision_buy_denial_allows_reducing_an_existing_buy() { + let date = limit_test_snapshot().date; + let data = DataSet::from_components(vec![limit_test_instrument()], vec![limit_test_snapshot()], + Vec::new(), vec![limit_test_candidate(true, true)], vec![limit_test_benchmark()]).unwrap(); + let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks); + broker.upsert_open_order(test_open_order(1)); + broker.runtime_buy_denials.borrow_mut().insert("000001.SZ".to_string(), "strategy_buy_condition_false".to_string()); + let portfolio = PortfolioState::new(100_000.0); + let mut report = BrokerExecutionReport::default(); + broker.modify_open_order(date, &portfolio, &data, 1, Some(100), Some(9.5), "reduce", &mut report); + let orders = broker.open_orders.borrow(); + assert_eq!(orders[0].requested_quantity, 100); + assert_eq!(orders[0].limit_price, 9.5); + assert!(!report.order_events.last().unwrap().reason.contains("strategy_buy_condition_false")); + } + fn next_open_sell_decision() -> StrategyDecision { StrategyDecision { order_intents: vec![OrderIntent::Shares {