diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index 9c3d7bc..4d42d5c 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -1980,6 +1980,12 @@ pub fn platform_expr_config_from_spec( explicit_actions.push(parsed); } cfg.explicit_actions = explicit_actions; + // An explicit action follows the strategy's declared schedule when + // it does not have a separate trading schedule. Otherwise the + // action can be parsed successfully but never be dispatched. + if cfg.explicit_action_schedule.is_none() && !cfg.explicit_actions.is_empty() { + cfg.explicit_action_schedule = cfg.rebalance_schedule.clone(); + } } } else if let Some(engine) = spec.engine_config.as_ref() { if let Some(dynamic_range) = engine.dynamic_range.as_ref() { @@ -2765,6 +2771,38 @@ mod tests { assert_eq!(cfg.explicit_actions.len(), 1); } + #[test] + fn explicit_actions_inherit_top_level_runtime_schedule() { + let spec = serde_json::json!({ + "runtimeExpressions": { + "schedule": {"frequency": "daily", "time": "15:00"}, + "trading": { + "rotationEnabled": false, + "actions": [{ + "kind": "modify_order", + "orderIdExpr": "42", + "quantityExpr": "200", + "limitPriceExpr": "10.25", + "symbol": "000001.SZ", + "reason": "modify_test" + }] + } + } + }); + + let cfg = platform_expr_config_from_value("modify", "000300.SH", &spec) + .expect("explicit action config"); + + assert_eq!(cfg.explicit_actions.len(), 1); + assert_eq!( + cfg.explicit_action_schedule, + Some(PlatformRebalanceSchedule { + frequency: PlatformScheduleFrequency::Daily, + time_rule: Some(ScheduleTimeRule::physical_time(15, 0)), + }) + ); + } + #[test] fn parses_typed_time_in_force_for_explicit_orders() { let spec = serde_json::json!({