From ccac2a239998cf1afaf003a375bb78888e9305bd Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 29 Aug 2026 20:56:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=8F=E5=B0=91=E7=AD=96=E7=95=A5=E8=82=A1?= =?UTF-8?q?=E7=A5=A8=E7=8A=B6=E6=80=81=E7=9A=84=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/data.rs | 14 ++++++++++ .../fidc-core/src/platform_expr_strategy.rs | 27 ++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 153e97a..44e1cda 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -1332,6 +1332,9 @@ pub struct DataSet { market_series_end_positions_by_symbol_id: Arc>, benchmark_series_cache: Arc, symbol_id_by_code: Arc>, + /// Interned symbol text indexed by immutable symbol id so per-day + /// expression states can share the same allocation. + symbol_text_by_id: Arc>>, eligible_universe_by_date: Arc>>>, benchmark_code: String, futures_params_by_symbol: Arc>>, @@ -1737,6 +1740,12 @@ impl DataSet { &factor_by_date, &candidate_by_date, ); + let mut symbol_text_by_id = vec![Arc::::from(""); symbol_id_by_code.len()]; + for (symbol, symbol_id) in &symbol_id_by_code { + if let Some(slot) = symbol_text_by_id.get_mut(*symbol_id as usize) { + *slot = Arc::::from(symbol.as_str()); + } + } let market_symbol_ids_by_date = build_group_symbol_ids(&market_by_date, &symbol_id_by_code, |item| { item.symbol.as_str() @@ -1815,6 +1824,7 @@ impl DataSet { ), benchmark_series_cache: Arc::new(benchmark_series_cache), symbol_id_by_code: Arc::new(symbol_id_by_code), + symbol_text_by_id: Arc::new(symbol_text_by_id), eligible_universe_by_date: Arc::new(OnceLock::new()), benchmark_code, futures_params_by_symbol: Arc::new(futures_params_by_symbol), @@ -1862,6 +1872,10 @@ impl DataSet { self.symbol_id_by_code.get(symbol).copied() } + pub(crate) fn symbol_text_by_id(&self, symbol_id: u32) -> Option<&Arc> { + self.symbol_text_by_id.get(symbol_id as usize) + } + pub fn market(&self, date: NaiveDate, symbol: &str) -> Option<&DailyMarketSnapshot> { let symbol_id = self.symbol_id(symbol)?; self.market_by_symbol_id(date, symbol_id) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index c266eeb..e7a2d87 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -656,7 +656,7 @@ struct DayExpressionState { #[derive(Debug, Clone)] struct StockExpressionState { - symbol: String, + symbol: Arc, symbol_id: u32, market_cap: f64, market_cap_bn: f64, @@ -4169,7 +4169,17 @@ impl PlatformExprStrategy { }; let state = Arc::new(StockExpressionState { - symbol: symbol.to_string(), + symbol: ctx + .data + .symbol_text_by_id(symbol_id) + .cloned() + .ok_or_else(|| { + BacktestError::Data(crate::data::DataSetError::MissingSnapshot { + kind: "symbol_text_index", + date, + symbol: symbol.to_string(), + }) + })?, symbol_id, market_cap, market_cap_bn, @@ -4640,7 +4650,7 @@ impl PlatformExprStrategy { stock.lower_limit, stock.price_tick, ); - scope.push("symbol", stock.symbol.clone()); + scope.push("symbol", stock.symbol.as_ref()); scope.push("market_cap", stock.market_cap); scope.push("market_cap_bn", stock.market_cap_bn); scope.push("free_float_cap", stock.free_float_cap); @@ -4739,7 +4749,10 @@ impl PlatformExprStrategy { scope.push("volume_ma100", stock.stock_volume_ma100); if include_factors_map { let mut factors = Map::new(); - factors.insert("symbol".into(), Dynamic::from(stock.symbol.clone())); + factors.insert( + "symbol".into(), + Dynamic::from(stock.symbol.as_ref().to_string()), + ); factors.insert("market_cap".into(), Dynamic::from(stock.market_cap)); factors.insert("market_cap_bn".into(), Dynamic::from(stock.market_cap_bn)); factors.insert("free_float_cap".into(), Dynamic::from(stock.free_float_cap)); @@ -6586,7 +6599,7 @@ impl PlatformExprStrategy { let matched = ctx .get_margin_stocks(&margin_type) .iter() - .any(|symbol| symbol == &stock.symbol); + .any(|symbol| symbol.as_str() == stock.symbol.as_ref()); Ok(RuntimeHelperResolution::Boolean(matched)) } "dominant_future" | "get_dominant_future" => { @@ -6656,14 +6669,14 @@ impl PlatformExprStrategy { let stock = stock.ok_or_else(|| { BacktestError::Execution(format!("{helper} requires stock context")) })?; - return Ok((stock.symbol.clone(), default_lookback)); + return Ok((stock.symbol.as_ref().to_string(), default_lookback)); } if args.len() == 1 { if let Ok(lookback) = Self::parse_positive_usize(&args[0]) { let stock = stock.ok_or_else(|| { BacktestError::Execution(format!("{helper} requires stock context")) })?; - return Ok((stock.symbol.clone(), lookback)); + return Ok((stock.symbol.as_ref().to_string(), lookback)); } return Ok(( Self::parse_string_or_identifier(&args[0])?,