diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 298f66d..c1de838 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -614,6 +614,13 @@ impl PlatformExprStrategy { lhs / rhs } }); + engine.register_fn("safe_div", |lhs: f64, rhs: f64| { + if rhs.abs() <= f64::EPSILON { + 0.0 + } else { + lhs / rhs + } + }); engine.register_fn( "iff", |condition: bool, when_true: Dynamic, when_false: Dynamic| { @@ -8432,6 +8439,32 @@ mod tests { assert!(!rewritten.contains('?')); } + #[test] + fn platform_expr_safe_div_supports_two_and_three_arg_forms() { + let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation()); + assert_eq!( + strategy + .engine + .eval::("safe_div(6.0, 3.0)") + .expect("two-arg safe_div should evaluate"), + 2.0 + ); + assert_eq!( + strategy + .engine + .eval::("safe_div(6.0, 0.0)") + .expect("two-arg safe_div should use zero fallback"), + 0.0 + ); + assert_eq!( + strategy + .engine + .eval::("safe_div(6.0, 0.0, -1.0)") + .expect("three-arg safe_div should preserve explicit fallback"), + -1.0 + ); + } + #[test] fn platform_expr_limit_flags_are_directional() { assert!(PlatformExprStrategy::price_is_at_or_above_upper_limit( diff --git a/crates/fidc-core/src/platform_runtime_schema.rs b/crates/fidc-core/src/platform_runtime_schema.rs index 6270125..cc9fda1 100644 --- a/crates/fidc-core/src/platform_runtime_schema.rs +++ b/crates/fidc-core/src/platform_runtime_schema.rs @@ -140,6 +140,7 @@ const RESERVED_SCOPE_NAMES: &[&str] = &[ "ask1_volume", "turnover_ratio", "effective_turnover_ratio", + "up_days_stock", "open", "high", "low", @@ -328,6 +329,7 @@ mod tests { "avg_cost", "current_price", "stock_ma_short", + "up_days_stock", ] { assert!( names.contains(required),