From dd6b37be1670f9f10fd13af00e2406167653a1ba Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 9 Sep 2026 02:51:26 +0800 Subject: [PATCH] fix: preserve missing numeric factors and reject nonfinite truthiness --- .../fidc-core/src/platform_expr_strategy.rs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 21000e2..754a84e 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -4687,7 +4687,7 @@ impl PlatformExprStrategy { } else if intraday_same_day_factor { f64::NAN } else { - factor.extra_factors.get("amount").copied().unwrap_or(0.0) + factor.extra_factors.get("amount").copied().unwrap_or(f64::NAN) }; let market_cap_bn = decision_market_cap_bn(factor); let free_float_cap_bn = decision_free_float_cap_bn(factor); @@ -4769,8 +4769,8 @@ impl PlatformExprStrategy { minute_volume: market.minute_volume as i64, bid1_volume: market.bid1_volume as i64, ask1_volume: market.ask1_volume as i64, - turnover_ratio: factor.turnover_ratio.unwrap_or(0.0), - effective_turnover_ratio: factor.effective_turnover_ratio.unwrap_or(0.0), + turnover_ratio: factor.turnover_ratio.unwrap_or(f64::NAN), + effective_turnover_ratio: factor.effective_turnover_ratio.unwrap_or(f64::NAN), open: feature_market.day_open, high: expression_high, low: expression_low, @@ -8359,7 +8359,7 @@ impl PlatformExprStrategy { if let Some(value) = self.eval_numeric_vm(ctx, expr, day, stock, position)? { return match value { NumericVmValue::Boolean(boolean) => Ok(boolean), - NumericVmValue::Number(number) => Ok(number != 0.0), + NumericVmValue::Number(number) => Ok(number.is_finite() && number != 0.0), }; } let value = self.eval_dynamic(ctx, expr, day, stock, position)?; @@ -8367,7 +8367,7 @@ impl PlatformExprStrategy { return Ok(boolean); } if let Some(number) = value.clone().try_cast::() { - return Ok(number != 0.0); + return Ok(number.is_finite() && number != 0.0); } if let Some(number) = value.try_cast::() { return Ok(number != 0); @@ -16524,8 +16524,8 @@ mod tests { market_cap_bn: 12.0, free_float_cap_bn: 10.0, pe_ttm: 8.0, - turnover_ratio: Some(1.0), - effective_turnover_ratio: Some(1.0), + turnover_ratio: None, + effective_turnover_ratio: None, adjustment_factor_backward1: None, extra_factors: BTreeMap::new(), }, @@ -16535,8 +16535,8 @@ mod tests { market_cap_bn: 12.0, free_float_cap_bn: 10.0, pe_ttm: 8.0, - turnover_ratio: Some(1.0), - effective_turnover_ratio: Some(1.0), + turnover_ratio: Some(0.0), + effective_turnover_ratio: Some(0.0), adjustment_factor_backward1: None, extra_factors: BTreeMap::from([ ("model_score".into(), 2.0), @@ -16601,6 +16601,16 @@ mod tests { .expect("present stock state"); assert!(day.available_factor_names.contains("model_score")); + assert!(missing_stock.turnover_ratio.is_nan()); + assert!(missing_stock.effective_turnover_ratio.is_nan()); + assert_eq!(present_stock.turnover_ratio, 0.0); + assert!(!strategy.eval_bool(&ctx, "model_score", &day, Some(&missing_stock), None).unwrap()); + assert!(strategy.eval_bool(&ctx, "model_score", &day, Some(&present_stock), None).unwrap()); + for field in ["turnover_ratio", "effective_turnover_ratio"] { + let predicate = format!("{field} < 1.0"); + assert!(!strategy.eval_bool(&ctx, &predicate, &day, Some(&missing_stock), None).unwrap()); + assert!(strategy.eval_bool(&ctx, &predicate, &day, Some(&present_stock), None).unwrap()); + } assert!( !strategy .stock_passes_expr(&ctx, &day, &missing_stock)