diff --git a/crates/fidc-core/tests/decision_quote_preload.rs b/crates/fidc-core/tests/decision_quote_preload.rs index 2ce5211..5b7c8bf 100644 --- a/crates/fidc-core/tests/decision_quote_preload.rs +++ b/crates/fidc-core/tests/decision_quote_preload.rs @@ -162,6 +162,45 @@ fn single_day_quote_plan_data(date: NaiveDate) -> DataSet { .expect("dataset") } +#[test] +fn runtime_account_dependent_quote_scope_cannot_be_replaced_by_an_empty_preplan() { + struct AccountDependentQuoteReader; + impl Strategy for AccountDependentQuoteReader { + fn name(&self) -> &str { "account_dependent_quote_reader" } + fn decision_quote_times(&self) -> Vec { vec![t(10, 18, 0)] } + fn decision_quote_symbols(&mut self, ctx: &StrategyContext<'_>) -> Result, fidc_core::BacktestError> { + Ok(if ctx.portfolio.cash() < 50_000.0 { + BTreeSet::from(["000001.SZ".into()]) + } else { BTreeSet::new() }) + } + fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result { + assert!(ctx.data.execution_quotes_on(ctx.execution_date, "000001.SZ").iter().any(|quote| + quote.timestamp.time()==t(10,17,59) && quote.last_price==10.0), + "actual account-dependent quote scope must be loaded before decision"); + Ok(StrategyDecision::default()) + } + } + let date = d(2026, 1, 5); + let broker = BrokerSimulator::new_with_execution_price( + ChinaAShareCostModel::default(), ChinaEquityRuleHooks, PriceField::Close, + ).with_volume_capacity_mode(fidc_core::execution_capacity::VolumeCapacityMode::SessionCapacityAudit) + .with_matching_type(MatchingType::CurrentBarClose); + let config = BacktestConfig { + initial_cash:10_000.0, benchmark_code:"000852.SH".into(), + start_date:Some(date), end_date:Some(date), decision_lag_trading_days:0, + execution_price_field:PriceField::Close, + }; + let mut engine = BacktestEngine::new(single_day_quote_plan_data(date), AccountDependentQuoteReader, broker, config) + .with_execution_quote_loader(move |request| Ok(request.symbols.into_iter().map(|symbol| IntradayExecutionQuote { + observation_kind:Default::default(), date:request.date, symbol, + timestamp:request.date.and_time(t(10,17,59)), last_price:10.0,bid1:10.0,ask1:10.0, + bid1_volume:10_000,ask1_volume:10_000,volume_delta:10_000,amount_delta:100_000.0, + trading_phase:Some("continuous".into()), + }).collect())) + .with_preplanned_decision_quote_symbols_by_date(Arc::new(BTreeMap::new())); + engine.run().expect("account-dependent quote planning"); +} + #[test] fn engine_uses_preplanned_decision_symbols_without_recomputing_strategy_plan() { let date = d(2026, 1, 5);