diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index 7f9439f..39c1edf 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -427,6 +427,12 @@ where symbol: &str, snapshot: &crate::data::DailyMarketSnapshot, ) -> f64 { + if self.matching_type == MatchingType::NextBarOpen + && snapshot.prev_close.is_finite() + && snapshot.prev_close > 0.0 + { + return snapshot.prev_close; + } if self.aiquant_execution_rules && self.execution_price_field == PriceField::Last { let start_cursor = self .runtime_intraday_start_time @@ -7147,6 +7153,38 @@ mod tests { ); } + #[test] + fn next_open_target_value_valuation_uses_previous_close() { + let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date"); + let broker = BrokerSimulator::new_with_execution_price( + ChinaAShareCostModel::default(), + ChinaEquityRuleHooks, + PriceField::Open, + ) + .with_matching_type(MatchingType::NextBarOpen); + let mut snapshot = limit_test_snapshot(); + snapshot.date = date; + snapshot.prev_close = 10.0; + snapshot.open = 11.0; + snapshot.close = 20.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 snapshot = data.market(date, "000001.SZ").expect("market snapshot"); + + assert_eq!( + broker.target_value_valuation_price(date, &data, "000001.SZ", snapshot), + 10.0 + ); + } + #[test] fn target_portfolio_smart_ignores_zero_weight_symbols_without_market_snapshot() { let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");