fix: reject missing numeric sizing and execution parameters
This commit is contained in:
@@ -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)",
|
||||||
|
|||||||
Reference in New Issue
Block a user