perf: type static current rolling helpers

This commit is contained in:
boris
2026-09-05 02:42:47 +08:00
parent 1410aa588a
commit 75ab0c06c6
2 changed files with 126 additions and 26 deletions
+48 -25
View File
@@ -3454,31 +3454,13 @@ impl DataSet {
let field = normalized_field(field);
match field.as_ref() {
"close" | "prev_close" | "stock_close" | "price" => self
.adjusted_close_series_by_symbol_id(symbol_id)
.and_then(|series| {
series_end
.map(|end| series.moving_average_at_end(end, lookback))
.unwrap_or_else(|| series.current_moving_average(date, lookback))
}),
"volume" | "stock_volume" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
series_end
.map(|end| {
series
.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
})
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
})
}
.market_current_close_moving_average_with_end_by_symbol_id(
date, symbol_id, lookback, series_end,
),
"volume" | "stock_volume" => self
.market_current_volume_moving_average_with_end_by_symbol_id(
date, symbol_id, lookback, series_end,
),
"day_open" | "dayopen" => {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
@@ -3514,6 +3496,47 @@ impl DataSet {
}
}
pub(crate) fn market_current_close_moving_average_with_end_by_symbol_id(
&self,
date: NaiveDate,
symbol_id: u32,
lookback: usize,
series_end: Option<usize>,
) -> Option<f64> {
self.adjusted_close_series_by_symbol_id(symbol_id)
.and_then(|series| {
series_end
.map(|end| series.moving_average_at_end(end, lookback))
.unwrap_or_else(|| series.current_moving_average(date, lookback))
})
}
pub(crate) fn market_current_volume_moving_average_with_end_by_symbol_id(
&self,
date: NaiveDate,
symbol_id: u32,
lookback: usize,
series_end: Option<usize>,
) -> Option<f64> {
self.market_series_by_symbol_id(symbol_id)
.and_then(|series| {
series_end
.map(|end| {
series
.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(series.valid_volume_sum_prefix[end]
- series.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
})
.unwrap_or_else(|| series.current_volume_moving_average(date, lookback))
})
}
pub fn market_latest_back_adjusted_close(&self, date: NaiveDate, symbol: &str) -> Option<f64> {
self.adjusted_close_series(symbol)
.and_then(|series| series.latest_back_adjusted_close(date))
+78 -1
View File
@@ -972,6 +972,10 @@ enum CompiledRuntimeHelperArgs {
lookback: usize,
current: bool,
},
StockCurrentRollingMean {
field: StockRollingField,
lookback: usize,
},
RollingMaxCurrent {
field: String,
lookback: usize,
@@ -5124,6 +5128,41 @@ impl PlatformExprStrategy {
};
Ok(RuntimeHelperResolution::Number(value))
}
CompiledRuntimeHelperArgs::StockCurrentRollingMean { field, lookback } => {
let stock = stock.ok_or_else(|| {
BacktestError::Execution(format!(
"rolling_mean_current requires stock context for lookback {lookback}"
))
})?;
let value = match field {
StockRollingField::Close => ctx
.data
.market_current_close_moving_average_with_end_by_symbol_id(
day.date,
stock.symbol_id,
*lookback,
stock.current_series_end,
),
StockRollingField::Volume => ctx
.data
.market_current_volume_moving_average_with_end_by_symbol_id(
day.date,
stock.symbol_id,
*lookback,
stock.current_series_end,
),
}
.ok_or_else(|| {
BacktestError::Execution(format!(
"missing current rolling mean for field {} with lookback {lookback}",
match field {
StockRollingField::Close => "close",
StockRollingField::Volume => "volume",
}
))
})?;
Ok(RuntimeHelperResolution::Number(value))
}
CompiledRuntimeHelperArgs::RollingMaxCurrent { field, lookback } => {
let values =
self.resolve_current_rolling_values(ctx, day, stock, field, *lookback)?;
@@ -6216,6 +6255,19 @@ impl PlatformExprStrategy {
}
"rolling_mean_current" => {
let (field, lookback) = field_lookback()?;
let stock_field = match field.trim().to_ascii_lowercase().as_str() {
"close" | "prev_close" | "stock_close" | "price" => {
Some(StockRollingField::Close)
}
"volume" | "stock_volume" => Some(StockRollingField::Volume),
_ => None,
};
if let Some(field) = stock_field {
return Some(CompiledRuntimeHelperArgs::StockCurrentRollingMean {
field,
lookback,
});
}
Some(CompiledRuntimeHelperArgs::RollingMean {
field,
lookback,
@@ -12675,7 +12727,7 @@ mod tests {
PlatformExprStrategyConfig, PlatformPortfolioDrawdownControlConfig,
PlatformPortfolioDrawdownController, PlatformRebalanceSchedule, PlatformScheduleFrequency,
PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind,
RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage,
RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage, StockRollingField,
StockSnapshotFieldRequirements, framework_stock_rolling_factor_requirement,
scheduled_position_exposure,
};
@@ -33593,6 +33645,31 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
.all(|binding| binding.compiled_args.is_some()),
"all static numeric helper arguments must be parsed once at plan compilation"
);
assert!(bindings.iter().any(|binding| {
if binding.name != "rolling_mean_current" {
return false;
}
matches!(
binding.compiled_args.as_ref(),
Some(CompiledRuntimeHelperArgs::StockCurrentRollingMean {
field: StockRollingField::Volume,
lookback: 10,
})
)
}));
let signal_args = vec!["\"signal_close\"".to_string(), "20".to_string()];
assert!(matches!(
PlatformExprStrategy::compile_runtime_helper_args(
"rolling_mean_current",
&signal_args
),
Some(CompiledRuntimeHelperArgs::RollingMean {
field,
lookback: 20,
current: true,
}) if field == "signal_close"
));
}
#[test]