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

This commit is contained in:
boris
2026-07-17 15:47:51 +08:00
parent ef491340f6
commit ffc9179cff
+93 -19
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 {
config: PlatformExprStrategyConfig,
engine: Engine,
@@ -3307,7 +3330,7 @@ impl PlatformExprStrategy {
field: &str,
lookback: usize,
) -> 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 = || {
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);