From 35acb1c7e75e2e6e971422089b8eff5ae007ff3c Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 9 Sep 2026 23:52:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E9=92=9F=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E8=BD=AE=E5=8A=A8=E4=BB=85=E6=89=A7=E8=A1=8C=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E6=97=B6=E7=82=B9=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 19 ++++++++++++------- .../fidc-core/src/platform_strategy_spec.rs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 445ec81..d2d169c 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -672,6 +672,7 @@ pub struct PlatformExprStrategyConfig { pub completed_session_factor_fields: BTreeSet, pub candidate_symbols_by_date: BTreeMap>, pub intraday_execution_time: Option, + pub session_event_times: Vec, pub explicit_action_times: Vec, pub delayed_limit_open_exit_enabled: bool, pub delayed_limit_open_exit_time: Option, @@ -753,6 +754,7 @@ impl PlatformExprStrategyConfig { completed_session_factor_fields: BTreeSet::new(), candidate_symbols_by_date: BTreeMap::new(), intraday_execution_time: None, + session_event_times: Vec::new(), explicit_action_times: Vec::new(), delayed_limit_open_exit_enabled: false, delayed_limit_open_exit_time: None, @@ -12294,13 +12296,15 @@ impl Strategy for PlatformExprStrategy { .is_some() && self.config.rotation_enabled { - rules.push( - self.config - .rebalance_schedule - .as_ref() - .expect("checked timed rebalance schedule") - .as_schedule_rule(ScheduleStage::OnDay), - ); + let schedule=self.config.rebalance_schedule.as_ref().expect("checked timed rebalance schedule"); + if self.config.session_event_times.is_empty() { + rules.push(schedule.as_schedule_rule(ScheduleStage::OnDay)); + } else { + for time in &self.config.session_event_times { + let mut timed=schedule.clone();timed.time_rule=Some(ScheduleTimeRule::physical_time(time.hour(),time.minute())); + rules.push(timed.as_schedule_rule(ScheduleStage::OnDay)); + } + } } if self.config.explicit_actions.is_empty() { return rules; @@ -12363,6 +12367,7 @@ impl Strategy for PlatformExprStrategy { fn decision_quote_times(&self) -> Vec { let mut times = BTreeSet::new(); + times.extend(self.config.session_event_times.iter().copied()); if self.uses_intraday_execution_quotes() { if self.config.explicit_action_times.is_empty() { times.insert(self.intraday_execution_start_time()); diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index d4df80f..cc5ff87 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -2538,6 +2538,10 @@ pub fn platform_expr_config_from_spec( cfg.benchmark_symbol = normalize_symbol(&cfg.benchmark_symbol, None); } let trade_times = spec_trade_times(spec); + if crate::pattern_context::specs_in_value(&serde_json::to_value(spec).map_err(|e|e.to_string())?)?.iter().any(|p|p.template=="session_event") { + if trade_times.is_empty() {return Err("session_event_requires_explicit_trade_times".into());} + cfg.session_event_times=trade_times.clone(); + } let explicit_trading_schedule = spec .runtime_expressions .as_ref() @@ -4458,6 +4462,20 @@ mod tests { assert_eq!(cfg.delayed_limit_open_exit_time, None); } + #[test] + fn session_rotation_keeps_every_declared_clock_not_only_the_last_one() { + use crate::Strategy; + let literal=serde_json::to_string(&serde_json::json!({"template":"session_event","session_event":"INTRADAY_VOLUME_SPIKE","parameters":{}}).to_string()).unwrap(); + let mut spec=serde_json::json!({"rebalance":{"tradeTimes":["09:35","10:40","14:59"]},"runtimeExpressions":{"schedule":{"frequency":"daily","time":"14:59"},"trading":{"rotationEnabled":true,"buyFilterExpr":format!("pattern_signal({literal})")}},"execution":{"matchingType":"minute_last"}}); + let config=platform_expr_config_from_value("session","000300.SH",&spec).unwrap(); + assert_eq!(config.session_event_times.len(),3); + let strategy=crate::PlatformExprStrategy::new(config); + assert_eq!(strategy.schedule_rules().len(),3); + assert_eq!(strategy.decision_quote_times().len(),3); + spec["rebalance"]["tradeTimes"]=serde_json::json!([]); + assert!(platform_expr_config_from_value("session","000300.SH",&spec).unwrap_err().to_string().contains("explicit_trade_times")); + } + #[test] fn explicit_trading_schedule_overrides_rebalance_trade_times() { let spec = serde_json::json!({