修正当前滚动因子运行语义

This commit is contained in:
boris
2026-07-17 15:47:51 +08:00
parent ef491340f6
commit ffc9179cff
+87 -13
View File
@@ -754,6 +754,29 @@ fn precomputed_stock_rolling_mean(
} }
} }
fn precomputed_stock_current_rolling_mean(
extra_factors: &BTreeMap<String, f64>,
field: &str,
lookback: usize,
) -> Option<f64> {
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 { pub struct PlatformExprStrategy {
config: PlatformExprStrategyConfig, config: PlatformExprStrategyConfig,
engine: Engine, engine: Engine,
@@ -3307,7 +3330,7 @@ impl PlatformExprStrategy {
field: &str, field: &str,
lookback: usize, lookback: usize,
) -> Option<f64> { ) -> Option<f64> {
let precomputed = precomputed_stock_rolling_mean(extra_factors, field, lookback); let precomputed = precomputed_stock_current_rolling_mean(extra_factors, field, lookback);
let computed = || { let computed = || {
ctx.data ctx.data
.market_current_numeric_moving_average(date, symbol, field, lookback) .market_current_numeric_moving_average(date, symbol, field, lookback)
@@ -8213,9 +8236,12 @@ impl PlatformExprStrategy {
return true; return true;
} }
if stock_rolling_helpers_require_extra if stock_rolling_helpers_require_extra
&& identifiers && identifiers.iter().any(|name| {
.iter() matches!(
.any(|name| matches!(name.as_str(), "rolling_mean" | "sma" | "ma" | "vma")) name.as_str(),
"rolling_mean" | "rolling_mean_current" | "sma" | "ma" | "vma"
)
})
{ {
return true; return true;
} }
@@ -8361,6 +8387,11 @@ impl PlatformExprStrategy {
) { ) {
let compact = Self::compact_expr(expr); 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");
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, "sma");
Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma"); Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma");
Self::require_stock_rollings_for_vma_helper(requirements, &compact); Self::require_stock_rollings_for_vma_helper(requirements, &compact);
@@ -28285,7 +28316,11 @@ mod tests {
let symbol = "300001.SZ"; let symbol = "300001.SZ";
let mut extra_factors = BTreeMap::new(); let mut extra_factors = BTreeMap::new();
extra_factors.insert("ma5_prev_close".to_string(), 99.0); 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_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( let data = DataSet::from_components(
vec![Instrument { vec![Instrument {
symbol: symbol.to_string(), symbol: symbol.to_string(),
@@ -28322,16 +28357,23 @@ mod tests {
price_tick: 0.01, price_tick: 0.01,
}) })
.collect(), .collect(),
vec![DailyFactorSnapshot { dates
date, .into_iter()
.map(|factor_date| DailyFactorSnapshot {
date: factor_date,
symbol: symbol.to_string(), symbol: symbol.to_string(),
market_cap_bn: 20.0, market_cap_bn: 20.0,
free_float_cap_bn: 20.0, free_float_cap_bn: 20.0,
pe_ttm: 0.0, pe_ttm: 0.0,
turnover_ratio: None, turnover_ratio: None,
effective_turnover_ratio: None, effective_turnover_ratio: None,
extra_factors, extra_factors: if factor_date == date {
}], extra_factors.clone()
} else {
BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)])
},
})
.collect(),
vec![CandidateEligibility { vec![CandidateEligibility {
date, date,
symbol: symbol.to_string(), symbol: symbol.to_string(),
@@ -28376,7 +28418,7 @@ mod tests {
let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.signal_symbol = symbol.to_string(); cfg.signal_symbol = symbol.to_string();
cfg.prefer_precomputed_rolling_factors = true; 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 strategy = PlatformExprStrategy::new(cfg);
let stock = strategy let stock = strategy
.stock_state_with_factor_date(&ctx, date, date, symbol) .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_ma5, 99.0);
assert_eq!(stock.stock_volume_ma5, 88.0); assert_eq!(stock.stock_volume_ma5, 88.0);
let day = strategy.day_state(&ctx, date).expect("day state"); 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!( assert_eq!(
strategy strategy
.resolve_current_rolling_mean(&ctx, &day, Some(&stock), "close", 5) .resolve_rolling_mean(&ctx, &day, Some(&stock), "close", 5)
.expect("precomputed current close rolling mean"), .expect("precomputed decision close rolling mean"),
99.0 99.0
); );
assert_eq!( assert_eq!(
strategy strategy
.resolve_current_rolling_mean(&ctx, &day, Some(&stock), "volume", 5) .resolve_rolling_mean(&ctx, &day, Some(&stock), "volume", 5)
.expect("precomputed current volume rolling mean"), .expect("precomputed decision volume rolling mean"),
88.0 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(); let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string(); cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string();
let strategy = PlatformExprStrategy::new(cfg); let strategy = PlatformExprStrategy::new(cfg);