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<'_>,