From e542e52bdb60c732e638b22b9c21c8edc2510ee0 Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 7 Sep 2026 09:22:15 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=B3=E8=BF=87=E6=97=A0=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E8=A1=8C=E6=83=85=E8=A7=84=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/engine.rs | 2 +- .../fidc-core/tests/decision_quote_preload.rs | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index ca2fe8c..6a9df02 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -2519,7 +2519,7 @@ where )?; let on_day_open_orders = self.open_order_views(); let decision_quote_times = self.strategy.decision_quote_times(); - if !decision_quote_times.is_empty() { + if self.execution_quote_loader.is_some() && !decision_quote_times.is_empty() { let decision_quote_symbols = self.strategy.decision_quote_symbols(&StrategyContext { execution_date, diff --git a/crates/fidc-core/tests/decision_quote_preload.rs b/crates/fidc-core/tests/decision_quote_preload.rs index b63d739..e0b4202 100644 --- a/crates/fidc-core/tests/decision_quote_preload.rs +++ b/crates/fidc-core/tests/decision_quote_preload.rs @@ -62,6 +62,78 @@ impl Strategy for DecisionQuoteReader { } } +struct NoLoaderDecisionQuoteStrategy { + symbol_plan_calls: Arc>, +} + +impl Strategy for NoLoaderDecisionQuoteStrategy { + fn name(&self) -> &str { + "no_loader_decision_quote_strategy" + } + + fn decision_quote_times(&self) -> Vec { + vec![t(10, 18, 0)] + } + + fn decision_quote_symbols( + &mut self, + _ctx: &StrategyContext<'_>, + ) -> Result, fidc_core::BacktestError> { + *self + .symbol_plan_calls + .lock() + .expect("symbol plan counter mutex") += 1; + Ok(std::collections::BTreeSet::new()) + } +} + +#[test] +fn engine_skips_decision_quote_symbol_plan_without_loader() { + let date = d(2026, 1, 5); + let data = DataSet::from_components( + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + vec![BenchmarkSnapshot { + date, + benchmark: "000852.SH".to_string(), + open: 1000.0, + close: 1001.0, + prev_close: 999.0, + volume: 1_000_000, + }], + ) + .expect("dataset"); + let broker = BrokerSimulator::new_with_execution_price( + ChinaAShareCostModel::default(), + ChinaEquityRuleHooks, + PriceField::Close, + ) + .with_matching_type(MatchingType::CurrentBarClose); + let config = BacktestConfig { + initial_cash: 10_000.0, + benchmark_code: "000852.SH".to_string(), + start_date: Some(date), + end_date: Some(date), + decision_lag_trading_days: 0, + execution_price_field: PriceField::Close, + }; + let symbol_plan_calls = Arc::new(Mutex::new(0usize)); + let strategy = NoLoaderDecisionQuoteStrategy { + symbol_plan_calls: Arc::clone(&symbol_plan_calls), + }; + let mut engine = BacktestEngine::new(data, strategy, broker, config); + + engine.run().expect("backtest should run"); + + assert_eq!( + *symbol_plan_calls.lock().expect("symbol plan counter mutex"), + 0, + "a preloaded/no-loader engine cannot use a newly computed quote symbol plan" + ); +} + #[test] fn engine_preloads_declared_decision_quotes_for_current_positions() { let first = d(2026, 1, 5);