fix: reject missing numeric sizing and execution parameters

This commit is contained in:
boris
2026-09-09 04:34:09 +08:00
parent ea58ab2166
commit e3f1028667
+26 -1
View File
@@ -8363,6 +8363,25 @@ impl PlatformExprStrategy {
day: &DayExpressionState, day: &DayExpressionState,
stock: Option<&StockExpressionState>, stock: Option<&StockExpressionState>,
position: Option<&PositionExpressionState>, position: Option<&PositionExpressionState>,
) -> Result<f64, BacktestError> {
let value = self.eval_float_or_missing(ctx, expr, day, stock, position)?;
if !value.is_finite() {
return Err(BacktestError::Execution(format!(
"missing_numeric_result: expression={expr:?}, symbol={}, decision_date={}, execution_date={}",
stock.map(|item| item.symbol.as_ref()).unwrap_or("portfolio"),
ctx.decision_date, ctx.execution_date,
)));
}
Ok(value)
}
fn eval_float_or_missing(
&self,
ctx: &StrategyContext<'_>,
expr: &str,
day: &DayExpressionState,
stock: Option<&StockExpressionState>,
position: Option<&PositionExpressionState>,
) -> Result<f64, BacktestError> { ) -> Result<f64, BacktestError> {
if let Some(value) = self.eval_numeric_vm(ctx, expr, day, stock, position)? { if let Some(value) = self.eval_numeric_vm(ctx, expr, day, stock, position)? {
return match value { return match value {
@@ -10441,7 +10460,7 @@ impl PlatformExprStrategy {
stock: &StockExpressionState, stock: &StockExpressionState,
) -> Result<f64, BacktestError> { ) -> Result<f64, BacktestError> {
if self.rank_expr_present { if self.rank_expr_present {
return match self.eval_float(ctx, &self.config.rank_expr, day, Some(stock), None) { return match self.eval_float_or_missing(ctx, &self.config.rank_expr, day, Some(stock), None) {
Ok(value) => Ok(value), Ok(value) => Ok(value),
Err(error) if Self::is_missing_rolling_mean_error(&error) => Ok(f64::NAN), Err(error) if Self::is_missing_rolling_mean_error(&error) => Ok(f64::NAN),
Err(error) if Self::is_non_numeric_expr_error(&error) => Ok(f64::NAN), Err(error) if Self::is_non_numeric_expr_error(&error) => Ok(f64::NAN),
@@ -16644,6 +16663,12 @@ mod tests {
assert!(missing_stock.turnover_ratio.is_nan()); assert!(missing_stock.turnover_ratio.is_nan());
assert!(missing_stock.effective_turnover_ratio.is_nan()); assert!(missing_stock.effective_turnover_ratio.is_nan());
assert_eq!(present_stock.turnover_ratio, 0.0); assert_eq!(present_stock.turnover_ratio, 0.0);
for expression in ["model_score", "min(model_score, 1.0)", "model_score / 100.0"] {
let error = strategy.eval_float(&ctx, expression, &day, Some(&missing_stock), None).unwrap_err();
assert!(error.to_string().contains("missing_numeric_result"), "{error}");
}
assert!(strategy.eval_float_or_missing(&ctx, "model_score", &day, Some(&missing_stock), None).unwrap().is_nan());
assert_eq!(strategy.eval_float(&ctx, "nz(model_score, 0.0)", &day, Some(&missing_stock), None).unwrap(), 0.0);
for predicate in [ for predicate in [
"!(model_score > 0.0)", "!(model_score > 0.0)",
"!(model_score != 0.0)", "!(model_score != 0.0)",