From 6a304e2fc2145d95b77135425d908b17d96394bf Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 5 Sep 2026 05:46:05 +0800 Subject: [PATCH] Revert "revert: benchmark generic transient selection" This reverts commit b2da70897a62874e418794bbe7135a53ed822cbc. --- .../fidc-core/src/platform_expr_strategy.rs | 144 ++++++++++-------- 1 file changed, 83 insertions(+), 61 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 282fe76..25f3a87 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -3981,32 +3981,6 @@ impl PlatformExprStrategy { ) } - fn selection_stock_state_with_factor_date_from_views<'a>( - &self, - ctx: &StrategyContext<'a>, - date: NaiveDate, - factor_date: NaiveDate, - symbol: &str, - execution_day: &DailySnapshotView<'a>, - factor_day: &DailySnapshotView<'a>, - ) -> Result, BacktestError> { - let source = ViewStockStateSnapshotSource { - execution: execution_day, - factor: factor_day, - same_date: factor_date == date, - }; - let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly; - self.stock_state_with_factor_date_and_time_from_source( - ctx, - date, - factor_date, - symbol, - None, - use_intraday_quote, - &source, - ) - } - #[allow(clippy::too_many_arguments)] fn uncached_selection_stock_state_from_views_by_symbol_id<'a>( &self, @@ -9497,6 +9471,7 @@ impl PlatformExprStrategy { ) } + #[cfg(test)] fn selectable_universe_on_with_options( &self, ctx: &StrategyContext<'_>, @@ -9514,6 +9489,7 @@ impl PlatformExprStrategy { .0 } + #[cfg(test)] fn selection_universe_and_risk_decisions_with_options( &self, ctx: &StrategyContext<'_>, @@ -10141,15 +10117,14 @@ impl PlatformExprStrategy { universe_factor_date, 5, ); - let execution_day = ctx.data.daily_snapshot_view(date); - let universe_factor_day = ctx.data.daily_snapshot_view(universe_factor_date); - let factor_day = ctx.data.daily_snapshot_view(stock_factor_date); - // The universe is already stably ordered by market cap. When the // strategy asks for that exact ascending order and does not need a // complete ranking for replacement limiting, select directly from the // ordered stream instead of materializing a second candidate vector. if self.rank_reuses_market_cap_order() && self.config.daily_replacement_limit == 0 { + let execution_day = ctx.data.daily_snapshot_view(date); + let universe_factor_day = ctx.data.daily_snapshot_view(universe_factor_date); + let factor_day = ctx.data.daily_snapshot_view(stock_factor_date); let mut selected = Vec::with_capacity(limit.min(universe_symbol_ids.len())); for symbol_id in universe_symbol_ids { let factor = universe_factor_day @@ -10190,28 +10165,57 @@ impl PlatformExprStrategy { return Ok((selected, diagnostics, risk_decisions)); } - let universe = universe_symbol_ids - .into_iter() - .map(|symbol_id| { - let factor = universe_factor_day - .factor(symbol_id) - .expect("market-cap order references missing factor row"); - EligibleUniverseSnapshot { - symbol: factor.symbol.clone(), - market_cap_bn: decision_market_cap_bn(factor), - free_float_cap_bn: decision_free_float_cap_bn(factor), - } - }) - .collect::>(); + let selected = self.select_ranked_symbols( + ctx, + date, + universe_factor_date, + stock_factor_date, + day, + band_low, + band_high, + limit, + universe_symbol_ids, + &mut diagnostics, + )?; + Ok((selected, diagnostics, risk_decisions)) + } - let mut candidates = Vec::new(); + #[inline(never)] + #[allow(clippy::too_many_arguments)] + fn select_ranked_symbols( + &self, + ctx: &StrategyContext<'_>, + date: NaiveDate, + universe_factor_date: NaiveDate, + stock_factor_date: NaiveDate, + day: &DayExpressionState, + band_low: f64, + band_high: f64, + limit: usize, + universe_symbol_ids: Vec, + diagnostics: &mut Vec, + ) -> Result, BacktestError> { + let execution_day = ctx.data.daily_snapshot_view(date); + let universe_factor_day = ctx.data.daily_snapshot_view(universe_factor_date); + let factor_day = ctx.data.daily_snapshot_view(stock_factor_date); + let mut candidates = Vec::with_capacity(universe_symbol_ids.len()); + let mut candidate_states = Vec::with_capacity(universe_symbol_ids.len()); let mut missing_rank_count = 0usize; let mut missing_rank_examples = Vec::new(); - for candidate in universe { - let stock = self.selection_stock_state_with_factor_date_from_views( + for symbol_id in universe_symbol_ids { + let factor = universe_factor_day + .factor(symbol_id) + .expect("market-cap order references missing factor row"); + let candidate = EligibleUniverseSnapshot { + symbol: factor.symbol.clone(), + market_cap_bn: decision_market_cap_bn(factor), + free_float_cap_bn: decision_free_float_cap_bn(factor), + }; + let stock = self.uncached_selection_stock_state_from_views_by_symbol_id( ctx, date, stock_factor_date, + symbol_id, &candidate.symbol, &execution_day, &factor_day, @@ -10269,12 +10273,14 @@ impl PlatformExprStrategy { } continue; } - candidates.push((candidate, stock, rank_value)); + let state_index = candidate_states.len(); + candidate_states.push(stock); + candidates.push((candidate, rank_value, state_index)); } if !self.rank_reuses_market_cap_order() { candidates.sort_by(|lhs, rhs| { - let lhs_value = lhs.2; - let rhs_value = rhs.2; + let lhs_value = lhs.1; + let rhs_value = rhs.1; let ordering = if self.config.rank_desc { rhs_value .partial_cmp(&lhs_value) @@ -10293,14 +10299,15 @@ impl PlatformExprStrategy { } let mut selected = Vec::new(); - for (candidate, stock, _) in candidates { + for (candidate, _, state_index) in candidates { + let stock = &candidate_states[state_index]; if !self.selection_candidate_passes_filters( ctx, date, day, &candidate.symbol, - &stock, - &mut diagnostics, + stock, + diagnostics, )? { continue; } @@ -10317,7 +10324,7 @@ impl PlatformExprStrategy { )); } - Ok((selected, diagnostics, risk_decisions)) + Ok(selected) } fn stock_filter_quote_usage(&self) -> StockFilterQuoteUsage { @@ -10913,7 +10920,6 @@ impl PlatformExprStrategy { selection_limit: usize, ) -> Result<(Vec, Vec, usize, Vec), BacktestError> { let mut diagnostics = Vec::new(); - let mut candidates = Vec::new(); let mut missing_rank_count = 0usize; let mut missing_rank_examples = Vec::new(); let quote_usage = self.stock_filter_quote_usage(); @@ -10926,20 +10932,33 @@ impl PlatformExprStrategy { } else { SelectionRiskDeferral::None }; - let universe = self.selectable_universe_on_with_options( + let (universe_symbol_ids, _) = self.selection_symbol_ids_and_risk_decisions_with_options( ctx, date, universe_factor_date, selection_risk_deferral, + false, ); + let mut candidates = Vec::with_capacity(universe_symbol_ids.len()); + let mut candidate_states = Vec::with_capacity(universe_symbol_ids.len()); let execution_day = ctx.data.daily_snapshot_view(date); + let universe_factor_day = ctx.data.daily_snapshot_view(universe_factor_date); let factor_day = ctx.data.daily_snapshot_view(stock_factor_date); let quote_candidate_limit = self.quote_plan_candidate_limit(selection_limit); - for candidate in universe { - let stock = self.selection_stock_state_with_factor_date_from_views( + for symbol_id in universe_symbol_ids { + let factor = universe_factor_day + .factor(symbol_id) + .expect("market-cap order references missing factor row"); + let candidate = EligibleUniverseSnapshot { + symbol: factor.symbol.clone(), + market_cap_bn: decision_market_cap_bn(factor), + free_float_cap_bn: decision_free_float_cap_bn(factor), + }; + let stock = self.uncached_selection_stock_state_from_views_by_symbol_id( ctx, date, stock_factor_date, + symbol_id, &candidate.symbol, &execution_day, &factor_day, @@ -10977,7 +10996,9 @@ impl PlatformExprStrategy { } continue; } - candidates.push((candidate.symbol.clone(), rank_value, stock)); + let state_index = candidate_states.len(); + candidate_states.push(stock); + candidates.push((candidate.symbol.clone(), rank_value, state_index)); } candidates.sort_by(|lhs, rhs| { let ordering = if self.config.rank_desc { @@ -10999,9 +11020,10 @@ impl PlatformExprStrategy { let mut quote_candidate_symbols = Vec::new(); let mut selected_symbols = Vec::new(); let mut processed_scope = 0usize; - for (symbol, _, stock) in &candidates { + for (symbol, _, state_index) in &candidates { + let stock = &candidate_states[*state_index]; processed_scope += 1; - if !self.stock_passes_quote_plan_filter(ctx, day, &stock, quote_usage)? { + if !self.stock_passes_quote_plan_filter(ctx, day, stock, quote_usage)? { if diagnostics.len() < 12 { diagnostics.push(format!("{symbol} quote_plan rejected by stock_expr")); } @@ -11019,7 +11041,7 @@ impl PlatformExprStrategy { continue; } if quote_usage == StockFilterQuoteUsage::IntradayQuote - && !self.stock_passes_expr(ctx, day, &stock)? + && !self.stock_passes_expr(ctx, day, stock)? { if diagnostics.len() < 12 { diagnostics.push(format!("{symbol} quote_plan rejected by stock_expr"));