From 2f62d82420c53d724b8f0515133bc067f87c9201 Mon Sep 17 00:00:00 2001 From: boris Date: Sun, 21 Jun 2026 03:31:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=9E=E6=B5=8B=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=9B=86=E5=86=85=E5=AD=98=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?rolling=E4=BE=9D=E8=B5=96=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/data.rs | 25 +++-- .../fidc-core/src/platform_expr_strategy.rs | 91 +++++++++++++++++++ 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 4248e87..0623626 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::fs; use std::path::Path; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use chrono::{NaiveDate, NaiveDateTime}; use serde::{Deserialize, Serialize}; @@ -997,7 +997,7 @@ pub struct DataSet { benchmark_by_date: BTreeMap, market_series_by_symbol: HashMap, benchmark_series_cache: BenchmarkPriceSeries, - eligible_universe_by_date: BTreeMap>, + eligible_universe_by_date: Arc>>>, benchmark_code: String, futures_params_by_symbol: HashMap>, } @@ -1303,12 +1303,6 @@ impl DataSet { let market_series_by_symbol = build_market_series(&market_by_date); let benchmark_series_cache = BenchmarkPriceSeries::new(&benchmark_by_date.values().cloned().collect::>()); - let eligible_universe_by_date = build_eligible_universe( - &factor_by_date, - &candidate_index, - &market_index, - &instruments, - ); let futures_params_by_symbol = build_futures_params_index(futures_params); Ok(Self { @@ -1328,7 +1322,7 @@ impl DataSet { benchmark_by_date, market_series_by_symbol, benchmark_series_cache, - eligible_universe_by_date, + eligible_universe_by_date: Arc::new(OnceLock::new()), benchmark_code, futures_params_by_symbol, }) @@ -2625,6 +2619,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.candidate_index, + &self.market_index, + &self.instruments, + ) + }) .get(&date) .map(Vec::as_slice) .unwrap_or(&[]) @@ -3595,7 +3597,10 @@ fn build_market_series( let mut grouped = HashMap::>::new(); for rows in market_by_date.values() { for row in rows { - grouped.entry(row.symbol.clone()).or_default().push(row.as_ref()); + grouped + .entry(row.symbol.clone()) + .or_default() + .push(row.as_ref()); } } diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 4b079c5..5c31abf 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -6374,6 +6374,7 @@ impl PlatformExprStrategy { return requirements; } Self::require_stock_rollings_for_identifiers(&mut requirements, config, &normalized); + Self::require_stock_rollings_for_helper_calls(&mut requirements, &normalized); } Self::require_stock_rollings_for_fast_filter( &mut requirements, @@ -6612,6 +6613,96 @@ impl PlatformExprStrategy { } } + fn require_stock_rollings_for_helper_calls( + requirements: &mut StockRollingRequirements, + expr: &str, + ) { + let compact = Self::compact_expr(expr); + Self::require_stock_rollings_for_named_helper(requirements, &compact, "rolling_mean"); + Self::require_stock_rollings_for_named_helper(requirements, &compact, "sma"); + Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma"); + Self::require_stock_rollings_for_vma_helper(requirements, &compact); + } + + fn require_stock_rollings_for_named_helper( + requirements: &mut StockRollingRequirements, + compact: &str, + helper: &str, + ) { + let needle = format!("{helper}("); + let mut offset = 0usize; + while let Some(relative) = compact[offset..].find(&needle) { + let args_start = offset + relative + needle.len(); + let args = &compact[args_start..]; + let Some((field, rest)) = Self::parse_quoted_arg(args) else { + offset = args_start; + continue; + }; + let Some(rest) = rest.strip_prefix(',') else { + offset = args_start; + continue; + }; + let Some(lookback) = Self::parse_usize_prefix(rest) else { + offset = args_start; + continue; + }; + let normalized_field = field.trim().to_ascii_lowercase(); + match normalized_field.as_str() { + "close" | "prev_close" | "stock_close" | "price" => { + requirements.require(StockRollingField::Close, lookback) + } + "volume" | "stock_volume" => { + requirements.require(StockRollingField::Volume, lookback) + } + _ => {} + } + offset = args_start; + } + } + + fn require_stock_rollings_for_vma_helper( + requirements: &mut StockRollingRequirements, + compact: &str, + ) { + let needle = "vma("; + let mut offset = 0usize; + while let Some(relative) = compact[offset..].find(needle) { + let args_start = offset + relative + needle.len(); + if let Some(lookback) = Self::parse_usize_prefix(&compact[args_start..]) { + requirements.require(StockRollingField::Volume, lookback); + } + offset = args_start; + } + } + + fn parse_quoted_arg(input: &str) -> Option<(String, &str)> { + let mut chars = input.char_indices(); + let (_, quote) = chars.next()?; + if quote != '"' && quote != '\'' { + return None; + } + for (idx, ch) in chars { + if ch == quote { + let value = input[1..idx].to_string(); + let rest = &input[idx + ch.len_utf8()..]; + return Some((value, rest)); + } + } + None + } + + fn parse_usize_prefix(input: &str) -> Option { + let end = input + .char_indices() + .take_while(|(_, ch)| ch.is_ascii_digit()) + .map(|(idx, ch)| idx + ch.len_utf8()) + .last()?; + input[..end] + .parse::() + .ok() + .filter(|value| *value > 0) + } + fn require_stock_rollings_for_fast_filter( requirements: &mut StockRollingRequirements, config: &PlatformExprStrategyConfig,