From 1f10a6bb3decdd40c77397a7596c6f23a9f90a18 Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 5 Sep 2026 00:24:05 +0800 Subject: [PATCH] Revert "perf: reuse daily snapshot views in stock selection" This reverts commit 1df0081479b93a051f9a40d2acf7060d0eb6929d. --- crates/fidc-core/src/data.rs | 69 +++--------- .../fidc-core/src/platform_expr_strategy.rs | 105 ++++-------------- 2 files changed, 36 insertions(+), 138 deletions(-) diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 8dca400..b977138 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -522,7 +522,6 @@ pub struct YieldCurvePoint { #[derive(Debug, Clone)] pub struct EligibleUniverseSnapshot { pub symbol: String, - pub symbol_id: u32, pub market_cap_bn: f64, pub free_float_cap_bn: f64, } @@ -1358,7 +1357,8 @@ impl<'a, T> DailySymbolRows<'a, T> { /// the already indexed slices once and keeps all lookups read-only. pub(crate) struct DailySnapshotView<'a> { market: DailySymbolRows<'a, DailyMarketSnapshot>, - factors: DailySymbolRows<'a, DailyFactorSnapshot>, + factor_rows: &'a [DailyFactorSnapshot], + factor_symbol_ids: &'a [u32], candidates: DailySymbolRows<'a, CandidateEligibility>, } @@ -1371,16 +1371,12 @@ impl<'a> DailySnapshotView<'a> { self.candidates.get(symbol_id) } - pub(crate) fn factor(&self, symbol_id: u32) -> Option<&'a DailyFactorSnapshot> { - self.factors.get(symbol_id) - } - pub(crate) fn factor_rows(&self) -> &'a [DailyFactorSnapshot] { - self.factors.rows + self.factor_rows } pub(crate) fn factor_symbol_ids(&self) -> &'a [u32] { - self.factors.symbol_ids + self.factor_symbol_ids } } @@ -1954,12 +1950,16 @@ impl DataSet { &self.market_symbol_ids_by_date, &self.market_row_positions_by_date, ), - factors: rows_on( - date, - &self.factor_by_date, - &self.factor_symbol_ids_by_date, - &self.factor_row_positions_by_date, - ), + factor_rows: self + .factor_by_date + .get(&date) + .map(Vec::as_slice) + .unwrap_or(&[]), + factor_symbol_ids: self + .factor_symbol_ids_by_date + .get(&date) + .map(Vec::as_slice) + .unwrap_or(&[]), candidates: rows_on( date, &self.candidate_by_date, @@ -3650,25 +3650,14 @@ impl DataSet { pub fn eligible_universe_on(&self, date: NaiveDate) -> &[EligibleUniverseSnapshot] { self.eligible_universe_by_date - .get_or_init(|| { - build_eligible_universe( - &self.factor_by_date, - &self.market_by_date, - &self.symbol_id_by_code, - ) - }) + .get_or_init(|| build_eligible_universe(&self.factor_by_date, &self.market_by_date)) .get(&date) .map(Vec::as_slice) .unwrap_or(&[]) } pub fn fundamental_universe_on(&self, date: NaiveDate) -> Vec { - build_fundamental_universe_for_date( - date, - &self.factor_by_date, - &self.market_by_date, - &self.symbol_id_by_code, - ) + build_fundamental_universe_for_date(date, &self.factor_by_date, &self.market_by_date) } pub fn eligible_universe_on_with_risk_config( @@ -3682,7 +3671,6 @@ impl DataSet { &self.candidate_by_date, &self.market_by_date, &self.instruments, - &self.symbol_id_by_code, risk_config, ) } @@ -4462,17 +4450,11 @@ fn build_order_book_depth_index( fn build_eligible_universe( factor_by_date: &BTreeMap>, market_by_date: &BTreeMap>, - symbol_id_by_code: &AHashMap, ) -> BTreeMap> { let mut per_date = BTreeMap::>::new(); for date in factor_by_date.keys() { - let rows = build_fundamental_universe_for_date( - *date, - factor_by_date, - market_by_date, - symbol_id_by_code, - ); + let rows = build_fundamental_universe_for_date(*date, factor_by_date, market_by_date); per_date.insert(*date, rows); } @@ -4483,16 +4465,12 @@ fn build_fundamental_universe_for_date( date: NaiveDate, factor_by_date: &BTreeMap>, market_by_date: &BTreeMap>, - symbol_id_by_code: &AHashMap, ) -> Vec { let mut rows = Vec::new(); let Some(factors) = factor_by_date.get(&date) else { return rows; }; for factor in factors { - let Some(symbol_id) = symbol_id_by_code.get(&factor.symbol).copied() else { - continue; - }; if market_by_date .get(&date) .and_then(|rows| find_by_symbol(rows, &factor.symbol, |row| row.symbol.as_str())) @@ -4506,7 +4484,6 @@ fn build_fundamental_universe_for_date( } rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), - symbol_id, market_cap_bn, free_float_cap_bn: decision_free_float_cap_bn(factor), }); @@ -4526,7 +4503,6 @@ fn build_eligible_universe_for_date( candidate_by_date: &BTreeMap>, market_by_date: &BTreeMap>, instruments: &HashMap, - symbol_id_by_code: &AHashMap, risk_config: &FidcRiskControlConfig, ) -> Vec { factor_by_date @@ -4538,7 +4514,6 @@ fn build_eligible_universe_for_date( candidate_by_date, market_by_date, instruments, - symbol_id_by_code, risk_config, ) }) @@ -4551,14 +4526,10 @@ fn build_eligible_universe_for_date_from_factors( candidate_by_date: &BTreeMap>, market_by_date: &BTreeMap>, instruments: &HashMap, - symbol_id_by_code: &AHashMap, risk_config: &FidcRiskControlConfig, ) -> Vec { let mut rows = Vec::new(); for factor in factors { - let Some(symbol_id) = symbol_id_by_code.get(&factor.symbol).copied() else { - continue; - }; if factor.market_cap_bn <= 0.0 || !factor.market_cap_bn.is_finite() { continue; } @@ -4596,7 +4567,6 @@ fn build_eligible_universe_for_date_from_factors( let free_float_cap_bn = decision_free_float_cap_bn(factor); rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), - symbol_id, market_cap_bn, free_float_cap_bn, }); @@ -5035,10 +5005,6 @@ mod tests { .map(|row| row.symbol.as_str()), Some(symbol) ); - assert_eq!( - day.factor(symbol_id).map(|row| row.symbol.as_str()), - Some(symbol) - ); assert_eq!( data.candidate_by_symbol_id(date, symbol_id) .map(|row| row.symbol.as_str()), @@ -5058,7 +5024,6 @@ mod tests { Some("000300.SH") ); assert!(data.factor_by_symbol_id(date, signal_id).is_none()); - assert!(day.factor(signal_id).is_none()); assert!(data.candidate_by_symbol_id(date, signal_id).is_none()); assert!(day.candidate(signal_id).is_none()); assert_eq!( diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 38e3063..5d96d81 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -9,8 +9,8 @@ use rhai::{AST, Dynamic, Engine, Map, Scope}; use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel}; use crate::cost::ChinaAShareCostModel; use crate::data::{ - DailyMarketSnapshot, DailySnapshotView, EligibleUniverseSnapshot, PriceField, - decision_free_float_cap_bn, decision_market_cap_bn, + DailyMarketSnapshot, EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn, + decision_market_cap_bn, }; use crate::engine::BacktestError; use crate::events::OrderSide; @@ -3883,7 +3883,6 @@ impl PlatformExprStrategy { self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None, true) } - #[cfg(test)] fn selection_stock_state_with_factor_date( &self, ctx: &StrategyContext<'_>, @@ -3902,29 +3901,6 @@ impl PlatformExprStrategy { ) } - fn selection_stock_state_with_factor_date_from_views( - &self, - ctx: &StrategyContext<'_>, - date: NaiveDate, - factor_date: NaiveDate, - candidate: &EligibleUniverseSnapshot, - execution_day: &DailySnapshotView<'_>, - factor_day: &DailySnapshotView<'_>, - ) -> Result, BacktestError> { - let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly; - self.stock_state_with_factor_date_and_time_by_symbol_id( - ctx, - date, - factor_date, - candidate.symbol_id, - &candidate.symbol, - None, - use_intraday_quote, - Some(execution_day), - Some(factor_day), - ) - } - fn stock_decision_rolling_mean( &self, ctx: &StrategyContext<'_>, @@ -4037,32 +4013,6 @@ impl PlatformExprStrategy { symbol: symbol.to_string(), }) })?; - self.stock_state_with_factor_date_and_time_by_symbol_id( - ctx, - date, - factor_date, - symbol_id, - symbol, - execution_time, - use_intraday_quote, - None, - None, - ) - } - - #[allow(clippy::too_many_arguments)] - fn stock_state_with_factor_date_and_time_by_symbol_id( - &self, - ctx: &StrategyContext<'_>, - date: NaiveDate, - factor_date: NaiveDate, - symbol_id: u32, - symbol: &str, - execution_time: Option, - use_intraday_quote: bool, - execution_day: Option<&DailySnapshotView<'_>>, - factor_day: Option<&DailySnapshotView<'_>>, - ) -> Result, BacktestError> { let calendar_index = { let mut cache_date = self.stock_state_cache_date.borrow_mut(); if *cache_date != Some(date) { @@ -4083,9 +4033,9 @@ impl PlatformExprStrategy { return Ok(Arc::clone(state)); } - let market = execution_day - .and_then(|view| view.market(symbol_id)) - .or_else(|| ctx.data.market_by_symbol_id(date, symbol_id)) + let market = ctx + .data + .market_by_symbol_id(date, symbol_id) .ok_or_else(|| { BacktestError::Data(crate::data::DataSetError::MissingSnapshot { kind: "market", @@ -4093,9 +4043,9 @@ impl PlatformExprStrategy { symbol: symbol.to_string(), }) })?; - let candidate = execution_day - .and_then(|view| view.candidate(symbol_id)) - .or_else(|| ctx.data.candidate_by_symbol_id(date, symbol_id)) + let candidate = ctx + .data + .candidate_by_symbol_id(date, symbol_id) .ok_or_else(|| { BacktestError::Data(crate::data::DataSetError::MissingSnapshot { kind: "candidate", @@ -4106,14 +4056,13 @@ impl PlatformExprStrategy { let feature_market = if factor_date == date { market } else { - factor_day - .and_then(|view| view.market(symbol_id)) - .or_else(|| ctx.data.market_by_symbol_id(factor_date, symbol_id)) + ctx.data + .market_by_symbol_id(factor_date, symbol_id) .unwrap_or(market) }; - let factor = factor_day - .and_then(|view| view.factor(symbol_id)) - .or_else(|| ctx.data.factor_by_symbol_id(factor_date, symbol_id)) + let factor = ctx + .data + .factor_by_symbol_id(factor_date, symbol_id) .ok_or_else(|| { BacktestError::Data(crate::data::DataSetError::MissingSnapshot { kind: "factor", @@ -9366,7 +9315,6 @@ impl PlatformExprStrategy { let free_float_cap_bn = decision_free_float_cap_bn(factor); rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), - symbol_id, market_cap_bn, free_float_cap_bn, }); @@ -9845,8 +9793,6 @@ impl PlatformExprStrategy { universe_factor_date, 5, ); - let execution_day = ctx.data.daily_snapshot_view(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 @@ -9855,13 +9801,11 @@ impl PlatformExprStrategy { if self.rank_reuses_market_cap_order() && self.config.daily_replacement_limit == 0 { let mut selected = Vec::with_capacity(limit.min(universe.len())); for candidate in universe { - let stock = self.selection_stock_state_with_factor_date_from_views( + let stock = self.selection_stock_state_with_factor_date( ctx, date, stock_factor_date, - &candidate, - &execution_day, - &factor_day, + &candidate.symbol, )?; let field_value = self.selection_field_value(&candidate, &stock); if !field_value.is_finite() || field_value < band_low || field_value > band_high { @@ -9888,13 +9832,11 @@ impl PlatformExprStrategy { 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( + let stock = self.selection_stock_state_with_factor_date( ctx, date, stock_factor_date, - &candidate, - &execution_day, - &factor_day, + &candidate.symbol, )?; let field_value = self.selection_field_value(&candidate, &stock); if !field_value.is_finite() { @@ -10575,17 +10517,13 @@ impl PlatformExprStrategy { universe_factor_date, selection_risk_deferral, ); - let execution_day = ctx.data.daily_snapshot_view(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( + let stock = self.selection_stock_state_with_factor_date( ctx, date, stock_factor_date, - &candidate, - &execution_day, - &factor_day, + &candidate.symbol, )?; let field_value = self.selection_field_value(&candidate, &stock); if !field_value.is_finite() { @@ -18457,16 +18395,11 @@ mod tests { .expect("bjse state"); let star_candidate = EligibleUniverseSnapshot { symbol: star_st_symbol.to_string(), - symbol_id: ctx - .data - .symbol_id(star_st_symbol) - .expect("star st symbol id"), market_cap_bn: 1.0, free_float_cap_bn: 1.0, }; let bjse_candidate = EligibleUniverseSnapshot { symbol: bjse_symbol.to_string(), - symbol_id: ctx.data.symbol_id(bjse_symbol).expect("bjse symbol id"), market_cap_bn: 2.0, free_float_cap_bn: 2.0, };