按策略引用投影额外因子字段

This commit is contained in:
boris
2026-08-29 08:16:58 +08:00
parent d5265619f3
commit ce5564408d
+54 -1
View File
@@ -990,6 +990,7 @@ pub struct PlatformExprStrategy {
stock_rolling_requirements: StockRollingRequirements,
stock_extra_factors_required: bool,
stock_extra_factor_identifiers: BTreeSet<String>,
stock_extra_factor_map_required: bool,
stock_text_factors_required: bool,
stock_state_cache_date: RefCell<Option<NaiveDate>>,
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<String>,
@@ -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]