diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index ab1f0ac..071c5d8 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -7420,6 +7420,83 @@ mod tests { ); } + #[test] + fn target_portfolio_smart_custom_valuation_does_not_create_limit_orders() { + let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date"); + let mut snapshot = limit_test_snapshot(); + snapshot.day_open = 12.0; + snapshot.open = 12.0; + snapshot.last_price = 12.0; + snapshot.prev_close = 10.0; + snapshot.upper_limit = 20.0; + snapshot.lower_limit = 1.0; + let data = DataSet::from_components_with_actions_and_quotes( + vec![limit_test_instrument()], + vec![snapshot], + Vec::new(), + vec![limit_test_candidate(true, true)], + vec![limit_test_benchmark()], + Vec::new(), + Vec::new(), + ) + .expect("valid dataset"); + let broker = BrokerSimulator::new_with_execution_price( + ChinaAShareCostModel::default(), + ChinaEquityRuleHooks, + PriceField::DayOpen, + ) + .with_volume_limit(false) + .with_liquidity_limit(false) + .with_inactive_limit(false); + let mut portfolio = PortfolioState::new(20_000.0); + let mut target_weights = BTreeMap::new(); + target_weights.insert("000001.SZ".to_string(), 1.0); + let mut valuation_prices = BTreeMap::new(); + valuation_prices.insert("000001.SZ".to_string(), 12.0); + let mut report = BrokerExecutionReport::default(); + + broker + .process_target_portfolio_smart( + date, + &mut portfolio, + &data, + &target_weights, + None, + Some(&valuation_prices), + "custom_valuation_market_order_test", + &mut BTreeMap::new(), + &mut BTreeMap::new(), + &mut None, + &mut BTreeMap::new(), + &mut report, + ) + .expect("custom valuation should not turn rebalance into limit order"); + + assert!( + report + .order_events + .iter() + .any(|event| event.symbol == "000001.SZ" && event.status == OrderStatus::Filled), + "{:?}", + report.order_events + ); + assert!( + report + .order_events + .iter() + .all(|event| !event.reason.contains("limit price not marketable yet")), + "{:?}", + report.order_events + ); + assert!( + portfolio + .position("000001.SZ") + .is_some_and(|position| position.quantity == 1_600), + "{:?}", + portfolio.position("000001.SZ") + ); + } + #[test] fn target_value_zero_rejects_sell_when_market_snapshot_missing() { let trade_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");