From d7e11be01ffeabf632916dd91c08560d2f9e7b23 Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 5 Sep 2026 06:07:23 +0800 Subject: [PATCH] perf: rank transient candidates by symbol id --- .../fidc-core/src/platform_expr_strategy.rs | 139 +++++++++--------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 58b1357..b477003 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -8,9 +8,11 @@ use rhai::{AST, Dynamic, Engine, Map, Scope}; use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel}; use crate::cost::ChinaAShareCostModel; +#[cfg(test)] +use crate::data::EligibleUniverseSnapshot; use crate::data::{ CandidateEligibility, DailyFactorSnapshot, DailyMarketSnapshot, DailySnapshotView, DataSet, - EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn, decision_market_cap_bn, + PriceField, decision_free_float_cap_bn, decision_market_cap_bn, }; use crate::engine::BacktestError; use crate::events::OrderSide; @@ -9441,10 +9443,6 @@ impl PlatformExprStrategy { } } - fn field_value(&self, row: &EligibleUniverseSnapshot) -> f64 { - self.field_value_from_caps(row.market_cap_bn, row.free_float_cap_bn) - } - fn field_value_from_caps(&self, market_cap_bn: f64, free_float_cap_bn: f64) -> f64 { match self.config.market_cap_field.as_str() { "market_cap_bn" => market_cap_bn, @@ -9754,6 +9752,7 @@ impl PlatformExprStrategy { .is_some_and(|suffix| suffix.eq_ignore_ascii_case(".BSE")) } + #[cfg(test)] fn stock_numeric_field_value( &self, candidate: &EligibleUniverseSnapshot, @@ -9850,18 +9849,6 @@ impl PlatformExprStrategy { } } - fn selection_field_value( - &self, - candidate: &EligibleUniverseSnapshot, - stock: &StockExpressionState, - ) -> f64 { - self.selection_field_value_from_caps( - candidate.market_cap_bn, - candidate.free_float_cap_bn, - stock, - ) - } - fn selection_field_value_from_caps( &self, market_cap_bn: f64, @@ -9888,11 +9875,12 @@ impl PlatformExprStrategy { .unwrap_or_else(|| self.field_value_from_caps(market_cap_bn, free_float_cap_bn)) } - fn rank_value( + fn rank_value_from_caps( &self, ctx: &StrategyContext<'_>, day: &DayExpressionState, - candidate: &EligibleUniverseSnapshot, + market_cap_bn: f64, + free_float_cap_bn: f64, stock: &StockExpressionState, ) -> Result { if !self.config.rank_expr.trim().is_empty() { @@ -9906,22 +9894,18 @@ impl PlatformExprStrategy { let rank_by = self.config.rank_by.as_str(); match rank_by { "market_cap" => { - return Ok(Self::market_cap_storage_to_strategy_unit( - candidate.market_cap_bn, - )); + return Ok(Self::market_cap_storage_to_strategy_unit(market_cap_bn)); } - "market_cap_bn" => return Ok(candidate.market_cap_bn), + "market_cap_bn" => return Ok(market_cap_bn), "free_float_cap" | "free_float_market_cap" => { - return Ok(Self::market_cap_storage_to_strategy_unit( - candidate.free_float_cap_bn, - )); + return Ok(Self::market_cap_storage_to_strategy_unit(free_float_cap_bn)); } - "free_float_cap_bn" => return Ok(candidate.free_float_cap_bn), + "free_float_cap_bn" => return Ok(free_float_cap_bn), _ => {} } Ok(self - .stock_numeric_field_value(candidate, stock, rank_by) - .unwrap_or_else(|| self.field_value(candidate))) + .stock_numeric_field_value_from_caps(market_cap_bn, free_float_cap_bn, stock, rank_by) + .unwrap_or_else(|| self.field_value_from_caps(market_cap_bn, free_float_cap_bn))) } fn rank_reuses_market_cap_order(&self) -> bool { @@ -10236,34 +10220,31 @@ impl PlatformExprStrategy { 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 symbol = factor.symbol.as_str(); + let market_cap_bn = decision_market_cap_bn(factor); + let 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, + symbol, &execution_day, &factor_day, )?; - let field_value = self.selection_field_value(&candidate, &stock); + let field_value = + self.selection_field_value_from_caps(market_cap_bn, free_float_cap_bn, &stock); if !field_value.is_finite() { if diagnostics.len() < 12 { - diagnostics.push(format!( - "{} rejected by missing selection field", - candidate.symbol - )); + diagnostics.push(format!("{symbol} rejected by missing selection field")); } continue; } if field_value < band_low || field_value > band_high { continue; } - let rank_value = self.rank_value(ctx, day, &candidate, &stock)?; + let rank_value = + self.rank_value_from_caps(ctx, day, market_cap_bn, free_float_cap_bn, &stock)?; if !rank_value.is_finite() { // Model-score artifacts intentionally contain only the PIT-eligible // ranked universe. Do not report a missing score for a symbol that @@ -10272,13 +10253,13 @@ impl PlatformExprStrategy { && let Some(reason) = self.stock_selection_limit_rejection_reason(&stock) { if diagnostics.len() < 12 { - diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason)); + diagnostics.push(format!("{symbol} rejected by {reason}")); } continue; } if !self.stock_passes_expr(ctx, day, &stock)? { if diagnostics.len() < 12 { - diagnostics.push(format!("{} rejected by stock_expr", candidate.symbol)); + diagnostics.push(format!("{symbol} rejected by stock_expr")); } continue; } @@ -10286,26 +10267,25 @@ impl PlatformExprStrategy { == PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose && ctx .data - .market_latest_back_adjusted_close(date, &candidate.symbol) + .market_latest_back_adjusted_close(date, symbol) .is_none() { if diagnostics.len() < 12 { diagnostics.push(format!( - "{} rejected by missing signal-day post-adjusted close", - candidate.symbol + "{symbol} rejected by missing signal-day post-adjusted close" )); } continue; } missing_rank_count += 1; if missing_rank_examples.len() < 5 { - missing_rank_examples.push(candidate.symbol.clone()); + missing_rank_examples.push(factor.symbol.clone()); } continue; } let state_index = candidate_states.len(); candidate_states.push(stock); - candidates.push((candidate, rank_value, state_index)); + candidates.push((symbol_id, rank_value, state_index)); } if !self.rank_reuses_market_cap_order() { candidates.sort_by(|lhs, rhs| { @@ -10321,7 +10301,15 @@ impl PlatformExprStrategy { .unwrap_or(std::cmp::Ordering::Equal) }; if ordering == std::cmp::Ordering::Equal { - lhs.0.symbol.cmp(&rhs.0.symbol) + let lhs_symbol = &universe_factor_day + .factor(lhs.0) + .expect("ranked candidate references missing factor row") + .symbol; + let rhs_symbol = &universe_factor_day + .factor(rhs.0) + .expect("ranked candidate references missing factor row") + .symbol; + lhs_symbol.cmp(rhs_symbol) } else { ordering } @@ -10329,19 +10317,22 @@ impl PlatformExprStrategy { } let mut selected = Vec::new(); - for (candidate, _, state_index) in candidates { + for (symbol_id, _, state_index) in candidates { + let factor = universe_factor_day + .factor(symbol_id) + .expect("ranked candidate references missing factor row"); let stock = &candidate_states[state_index]; if !self.selection_candidate_passes_filters( ctx, date, day, - &candidate.symbol, + &factor.symbol, stock, diagnostics, )? { continue; } - selected.push(candidate.symbol.clone()); + selected.push(factor.symbol.clone()); if selected.len() >= limit { break; } @@ -10979,26 +10970,24 @@ impl PlatformExprStrategy { 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 symbol = factor.symbol.as_str(); + let market_cap_bn = decision_market_cap_bn(factor); + let 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, + symbol, &execution_day, &factor_day, )?; - let field_value = self.selection_field_value(&candidate, &stock); + let field_value = + self.selection_field_value_from_caps(market_cap_bn, free_float_cap_bn, &stock); if !field_value.is_finite() { if diagnostics.len() < 12 { diagnostics.push(format!( - "{} quote_plan rejected by missing selection field", - candidate.symbol + "{symbol} quote_plan rejected by missing selection field" )); } continue; @@ -11006,29 +10995,27 @@ impl PlatformExprStrategy { if field_value < band_low || field_value > band_high { continue; } - let rank_value = self.rank_value(ctx, day, &candidate, &stock)?; + let rank_value = + self.rank_value_from_caps(ctx, day, market_cap_bn, free_float_cap_bn, &stock)?; if !rank_value.is_finite() { // The score universe is already filtered by the signal-day // candidate contract. Missing rank is actionable only when the // daily portion of that same contract still admits the symbol. if !self.stock_passes_quote_plan_filter(ctx, day, &stock, quote_usage)? { if diagnostics.len() < 12 { - diagnostics.push(format!( - "{} quote_plan rejected by stock_expr", - candidate.symbol - )); + diagnostics.push(format!("{symbol} quote_plan rejected by stock_expr")); } continue; } missing_rank_count += 1; if missing_rank_examples.len() < 5 { - missing_rank_examples.push(candidate.symbol.clone()); + missing_rank_examples.push(factor.symbol.clone()); } continue; } let state_index = candidate_states.len(); candidate_states.push(stock); - candidates.push((candidate.symbol.clone(), rank_value, state_index)); + candidates.push((symbol_id, rank_value, state_index)); } candidates.sort_by(|lhs, rhs| { let ordering = if self.config.rank_desc { @@ -11041,7 +11028,15 @@ impl PlatformExprStrategy { .unwrap_or(std::cmp::Ordering::Equal) }; if ordering == std::cmp::Ordering::Equal { - lhs.0.cmp(&rhs.0) + let lhs_symbol = &universe_factor_day + .factor(lhs.0) + .expect("quote-plan candidate references missing factor row") + .symbol; + let rhs_symbol = &universe_factor_day + .factor(rhs.0) + .expect("quote-plan candidate references missing factor row") + .symbol; + lhs_symbol.cmp(rhs_symbol) } else { ordering } @@ -11050,7 +11045,11 @@ impl PlatformExprStrategy { let mut quote_candidate_symbols = Vec::new(); let mut selected_symbols = Vec::new(); let mut processed_scope = 0usize; - for (symbol, _, state_index) in &candidates { + for (symbol_id, _, state_index) in &candidates { + let symbol = &universe_factor_day + .factor(*symbol_id) + .expect("quote-plan candidate references missing factor row") + .symbol; let stock = &candidate_states[*state_index]; processed_scope += 1; if !self.stock_passes_quote_plan_filter(ctx, day, stock, quote_usage)? {