From ce5564408da14b75f28713e107b12c226bf6f25a Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 29 Aug 2026 08:16:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=89=E7=AD=96=E7=95=A5=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E6=8A=95=E5=BD=B1=E9=A2=9D=E5=A4=96=E5=9B=A0=E5=AD=90=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 14be2ce..9ca70f8 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -990,6 +990,7 @@ pub struct PlatformExprStrategy { stock_rolling_requirements: StockRollingRequirements, stock_extra_factors_required: bool, stock_extra_factor_identifiers: BTreeSet, + stock_extra_factor_map_required: bool, stock_text_factors_required: bool, stock_state_cache_date: RefCell>, stock_state_cache: RefCell< @@ -1267,6 +1268,7 @@ impl PlatformExprStrategy { Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers); let stock_extra_factor_identifiers = Self::stock_extra_factor_identifiers_for_config(&config, &prelude_declared_identifiers); + let stock_extra_factor_map_required = Self::stock_extra_factor_map_required_for_config(&config); let stock_text_factors_required = Self::stock_text_factors_required_for_config( &config, &normalized_stock_filter_expr, @@ -1304,6 +1306,7 @@ impl PlatformExprStrategy { stock_rolling_requirements, stock_extra_factors_required, stock_extra_factor_identifiers, + stock_extra_factor_map_required, stock_text_factors_required, stock_state_cache_date: RefCell::new(None), stock_state_cache: RefCell::new(AHashMap::new()), @@ -4055,6 +4058,10 @@ impl PlatformExprStrategy { factor .extra_factors .iter() + .filter(|(field, _)| { + self.stock_extra_factor_map_required + || self.stock_extra_factor_identifiers.contains(field.as_ref()) + }) .map(|(field, value)| (field.to_string(), *value)) .collect() } else { @@ -9665,6 +9672,30 @@ impl PlatformExprStrategy { .any(|expr| Self::expr_requires_stock_extra_factors(expr, prelude_declared_identifiers)) } + fn stock_extra_factor_map_required_for_config(config: &PlatformExprStrategyConfig) -> bool { + // A dynamic factors/factor map is part of the expression contract: its + // caller may access any published field by key. Keep the full map for + // that case and for explicit actions whose predicates are evaluated at + // runtime; direct field references use the projected identifier set. + if Self::has_stock_explicit_actions(config) { + return true; + } + [ + config.prelude.as_str(), + config.stock_filter_expr.as_str(), + config.buy_scale_expr.as_str(), + config.stop_loss_expr.as_str(), + config.take_profit_expr.as_str(), + config.rank_expr.as_str(), + config.market_cap_field.as_str(), + config.rank_by.as_str(), + ] + .into_iter() + .map(Self::normalize_expr) + .map(|expr| Self::extract_identifier_candidates(&expr)) + .any(|identifiers| identifiers.contains("factors") || identifiers.contains("factor")) + } + fn stock_extra_factor_identifiers_for_config( config: &PlatformExprStrategyConfig, prelude_declared_identifiers: &BTreeSet, @@ -13921,7 +13952,10 @@ mod tests { pe_ttm: 8.0, turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0), - extra_factors: BTreeMap::from([("model_score".into(), 2.0)]), + extra_factors: BTreeMap::from([ + ("model_score".into(), 2.0), + ("unused_factor".into(), 9.0), + ]), }, ], symbols @@ -13991,6 +14025,25 @@ mod tests { .stock_passes_expr(&ctx, &day, &present_stock) .expect("present stock filter") ); + assert!(!strategy.stock_extra_factor_map_required); + assert!(present_stock.extra_factors.contains_key("model_score")); + assert!(!present_stock.extra_factors.contains_key("unused_factor")); + + let mut map_cfg = PlatformExprStrategyConfig::microcap_rotation(); + map_cfg.signal_symbol = present_symbol.to_string(); + map_cfg.stock_filter_expr = "factors[\"unused_factor\"] > 0".to_string(); + let map_strategy = PlatformExprStrategy::new(map_cfg); + assert!(map_strategy.stock_extra_factor_map_required); + let map_stock = map_strategy + .stock_state_with_factor_date(&ctx, date, date, present_symbol) + .expect("factor map stock state"); + assert!(map_stock.extra_factors.contains_key("unused_factor")); + let map_day = map_strategy.day_state(&ctx, date).expect("factor map day state"); + assert!( + map_strategy + .stock_passes_expr(&ctx, &map_day, &map_stock) + .expect("factor map filter") + ); } #[test]