From 3633905459df3036274b06ba35caaa136494c30e Mon Sep 17 00:00:00 2001 From: boris Date: Thu, 18 Jun 2026 17:08:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=AD=96=E7=95=A5=E5=86=B3?= =?UTF-8?q?=E7=AD=96=E5=89=8D=E6=89=B9=E9=87=8F=E5=8A=A0=E8=BD=BD=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E4=BB=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/engine.rs | 56 +++++++++++++++++++ .../fidc-core/src/platform_expr_strategy.rs | 21 +++++++ crates/fidc-core/src/strategy.rs | 6 ++ 3 files changed, 83 insertions(+) diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index d7111c5..6ed45db 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -622,6 +622,35 @@ where Ok(()) } + fn ensure_execution_quotes_for_symbols_at_times( + &mut self, + execution_date: NaiveDate, + symbols: &BTreeSet, + quote_times: &[NaiveTime], + ) -> Result<(), BacktestError> { + if self.execution_quote_loader.is_none() || quote_times.is_empty() || symbols.is_empty() { + return Ok(()); + } + let base_symbols = symbols + .iter() + .filter(|symbol| !symbol.trim().is_empty()) + .cloned() + .collect::>(); + if base_symbols.is_empty() { + return Ok(()); + } + for quote_time in quote_times { + let mut symbols = base_symbols.clone(); + self.load_missing_execution_quotes( + execution_date, + Some(*quote_time), + None, + &mut symbols, + )?; + } + Ok(()) + } + fn apply_strategy_directives( &mut self, execution_date: NaiveDate, @@ -1951,6 +1980,33 @@ 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() { + let decision_quote_symbols = + self.strategy.decision_quote_symbols(&StrategyContext { + execution_date, + decision_date, + decision_index, + data: &self.data, + portfolio: &portfolio, + futures_account: self.futures_account.as_ref(), + open_orders: &on_day_open_orders, + dynamic_universe: self.dynamic_universe.as_ref(), + subscriptions: &self.subscriptions, + process_events: &process_events, + active_process_event: None, + active_datetime: stage_datetime( + execution_date, + default_stage_time(ScheduleStage::OnDay), + ), + order_events: result.order_events.as_slice(), + fills: result.fills.as_slice(), + })?; + self.ensure_execution_quotes_for_symbols_at_times( + execution_date, + &decision_quote_symbols, + &decision_quote_times, + )?; + } self.ensure_execution_quotes_for_portfolio_times( execution_date, &portfolio, diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 1d2a718..64c8fb2 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -6362,6 +6362,27 @@ impl Strategy for PlatformExprStrategy { times.into_iter().collect() } + fn decision_quote_symbols( + &mut self, + ctx: &StrategyContext<'_>, + ) -> Result, BacktestError> { + let mut symbols = ctx + .portfolio + .positions() + .keys() + .filter(|symbol| !symbol.trim().is_empty()) + .cloned() + .collect::>(); + if self.config.rotation_enabled && !self.config.in_skip_window(ctx.execution_date) { + let plan = self.selection_quote_plan(ctx, 0)?; + symbols.extend(plan.order_symbols); + if plan.requires_intraday_selection_quotes { + symbols.extend(plan.candidate_symbols); + } + } + Ok(symbols) + } + fn open_auction( &mut self, ctx: &StrategyContext<'_>, diff --git a/crates/fidc-core/src/strategy.rs b/crates/fidc-core/src/strategy.rs index 23b3e6c..1aa376d 100644 --- a/crates/fidc-core/src/strategy.rs +++ b/crates/fidc-core/src/strategy.rs @@ -43,6 +43,12 @@ pub trait Strategy { fn decision_quote_times(&self) -> Vec { Vec::new() } + fn decision_quote_symbols( + &mut self, + _ctx: &StrategyContext<'_>, + ) -> Result, BacktestError> { + Ok(BTreeSet::new()) + } fn on_scheduled( &mut self, _ctx: &StrategyContext<'_>,