From ffc9179cff9a6ac6b12a72dc166c2ebf0f82a172 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 17 Jul 2026 15:47:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BD=93=E5=89=8D=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E5=9B=A0=E5=AD=90=E8=BF=90=E8=A1=8C=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 112 +++++++++++++++--- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 90186e4..94cf717 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -754,6 +754,29 @@ fn precomputed_stock_rolling_mean( } } +fn precomputed_stock_current_rolling_mean( + extra_factors: &BTreeMap, + field: &str, + lookback: usize, +) -> Option { + if lookback == 0 { + return None; + } + let value_for = |key: &str| { + extra_factors + .get(key) + .copied() + .filter(|value| value.is_finite()) + }; + match field.trim().to_ascii_lowercase().as_str() { + "close" | "prev_close" | "stock_close" | "price" => { + value_for(&format!("ma{lookback}_current_close")) + } + "volume" | "stock_volume" => value_for(&format!("avg_volume{lookback}_current")), + _ => None, + } +} + pub struct PlatformExprStrategy { config: PlatformExprStrategyConfig, engine: Engine, @@ -3307,7 +3330,7 @@ impl PlatformExprStrategy { field: &str, lookback: usize, ) -> Option { - let precomputed = precomputed_stock_rolling_mean(extra_factors, field, lookback); + let precomputed = precomputed_stock_current_rolling_mean(extra_factors, field, lookback); let computed = || { ctx.data .market_current_numeric_moving_average(date, symbol, field, lookback) @@ -8213,9 +8236,12 @@ impl PlatformExprStrategy { return true; } if stock_rolling_helpers_require_extra - && identifiers - .iter() - .any(|name| matches!(name.as_str(), "rolling_mean" | "sma" | "ma" | "vma")) + && identifiers.iter().any(|name| { + matches!( + name.as_str(), + "rolling_mean" | "rolling_mean_current" | "sma" | "ma" | "vma" + ) + }) { return true; } @@ -8361,6 +8387,11 @@ impl PlatformExprStrategy { ) { 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, + "rolling_mean_current", + ); 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); @@ -28285,7 +28316,11 @@ mod tests { let symbol = "300001.SZ"; let mut extra_factors = BTreeMap::new(); extra_factors.insert("ma5_prev_close".to_string(), 99.0); + extra_factors.insert("ma10_prev_close".to_string(), 98.0); + extra_factors.insert("ma30_prev_close".to_string(), 97.0); extra_factors.insert("avg_volume5".to_string(), 88.0); + extra_factors.insert("avg_volume100".to_string(), 99.0); + extra_factors.insert("adjustment_factor_backward1".to_string(), 1.0); let data = DataSet::from_components( vec![Instrument { symbol: symbol.to_string(), @@ -28322,16 +28357,23 @@ mod tests { price_tick: 0.01, }) .collect(), - vec![DailyFactorSnapshot { - date, - symbol: symbol.to_string(), - market_cap_bn: 20.0, - free_float_cap_bn: 20.0, - pe_ttm: 0.0, - turnover_ratio: None, - effective_turnover_ratio: None, - extra_factors, - }], + dates + .into_iter() + .map(|factor_date| DailyFactorSnapshot { + date: factor_date, + symbol: symbol.to_string(), + market_cap_bn: 20.0, + free_float_cap_bn: 20.0, + pe_ttm: 0.0, + turnover_ratio: None, + effective_turnover_ratio: None, + extra_factors: if factor_date == date { + extra_factors.clone() + } else { + BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]) + }, + }) + .collect(), vec![CandidateEligibility { date, symbol: symbol.to_string(), @@ -28376,7 +28418,7 @@ mod tests { let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); cfg.signal_symbol = symbol.to_string(); cfg.prefer_precomputed_rolling_factors = true; - cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string(); + cfg.stock_filter_expr = "rolling_mean(\"close\", 5) > rolling_mean(\"close\", 10) && rolling_mean(\"close\", 10) > rolling_mean(\"close\", 30) && rolling_mean(\"volume\", 5) < rolling_mean(\"volume\", 100)".to_string(); let strategy = PlatformExprStrategy::new(cfg); let stock = strategy .stock_state_with_factor_date(&ctx, date, date, symbol) @@ -28384,19 +28426,51 @@ mod tests { assert_eq!(stock.stock_ma5, 99.0); assert_eq!(stock.stock_volume_ma5, 88.0); let day = strategy.day_state(&ctx, date).expect("day state"); + assert!( + strategy + .stock_passes_expr(&ctx, &day, &stock) + .expect("precomputed decision rolling filter") + ); assert_eq!( strategy - .resolve_current_rolling_mean(&ctx, &day, Some(&stock), "close", 5) - .expect("precomputed current close rolling mean"), + .resolve_rolling_mean(&ctx, &day, Some(&stock), "close", 5) + .expect("precomputed decision close rolling mean"), 99.0 ); assert_eq!( strategy - .resolve_current_rolling_mean(&ctx, &day, Some(&stock), "volume", 5) - .expect("precomputed current volume rolling mean"), + .resolve_rolling_mean(&ctx, &day, Some(&stock), "volume", 5) + .expect("precomputed decision volume rolling mean"), 88.0 ); + let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); + cfg.signal_symbol = symbol.to_string(); + cfg.prefer_precomputed_rolling_factors = true; + cfg.stock_filter_expr = "rolling_mean_current(\"close\", 5) == 10.0 && rolling_mean_current(\"volume\", 5) == 1000.0".to_string(); + let strategy = PlatformExprStrategy::new(cfg); + let stock = strategy + .stock_state_with_factor_date(&ctx, date, date, symbol) + .expect("stock state"); + let day = strategy.day_state(&ctx, date).expect("day state"); + assert!( + strategy + .stock_passes_expr(&ctx, &day, &stock) + .expect("current rolling filter") + ); + assert_eq!( + strategy + .resolve_current_rolling_mean(&ctx, &day, Some(&stock), "close", 5) + .expect("current close rolling mean"), + 10.0 + ); + assert_eq!( + strategy + .resolve_current_rolling_mean(&ctx, &day, Some(&stock), "volume", 5) + .expect("current volume rolling mean"), + 1_000.0 + ); + let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string(); let strategy = PlatformExprStrategy::new(cfg);