修复分钟事件轮动仅执行最后时点的问题

This commit is contained in:
boris
2026-09-09 23:52:35 +08:00
parent bbbd9cf3e0
commit 35acb1c7e7
2 changed files with 30 additions and 7 deletions
+12 -7
View File
@@ -672,6 +672,7 @@ pub struct PlatformExprStrategyConfig {
pub completed_session_factor_fields: BTreeSet<String>, pub completed_session_factor_fields: BTreeSet<String>,
pub candidate_symbols_by_date: BTreeMap<NaiveDate, BTreeSet<String>>, pub candidate_symbols_by_date: BTreeMap<NaiveDate, BTreeSet<String>>,
pub intraday_execution_time: Option<NaiveTime>, pub intraday_execution_time: Option<NaiveTime>,
pub session_event_times: Vec<NaiveTime>,
pub explicit_action_times: Vec<NaiveTime>, pub explicit_action_times: Vec<NaiveTime>,
pub delayed_limit_open_exit_enabled: bool, pub delayed_limit_open_exit_enabled: bool,
pub delayed_limit_open_exit_time: Option<NaiveTime>, pub delayed_limit_open_exit_time: Option<NaiveTime>,
@@ -753,6 +754,7 @@ impl PlatformExprStrategyConfig {
completed_session_factor_fields: BTreeSet::new(), completed_session_factor_fields: BTreeSet::new(),
candidate_symbols_by_date: BTreeMap::new(), candidate_symbols_by_date: BTreeMap::new(),
intraday_execution_time: None, intraday_execution_time: None,
session_event_times: Vec::new(),
explicit_action_times: Vec::new(), explicit_action_times: Vec::new(),
delayed_limit_open_exit_enabled: false, delayed_limit_open_exit_enabled: false,
delayed_limit_open_exit_time: None, delayed_limit_open_exit_time: None,
@@ -12294,13 +12296,15 @@ impl Strategy for PlatformExprStrategy {
.is_some() .is_some()
&& self.config.rotation_enabled && self.config.rotation_enabled
{ {
rules.push( let schedule=self.config.rebalance_schedule.as_ref().expect("checked timed rebalance schedule");
self.config if self.config.session_event_times.is_empty() {
.rebalance_schedule rules.push(schedule.as_schedule_rule(ScheduleStage::OnDay));
.as_ref() } else {
.expect("checked timed rebalance schedule") for time in &self.config.session_event_times {
.as_schedule_rule(ScheduleStage::OnDay), 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() { if self.config.explicit_actions.is_empty() {
return rules; return rules;
@@ -12363,6 +12367,7 @@ impl Strategy for PlatformExprStrategy {
fn decision_quote_times(&self) -> Vec<NaiveTime> { fn decision_quote_times(&self) -> Vec<NaiveTime> {
let mut times = BTreeSet::new(); let mut times = BTreeSet::new();
times.extend(self.config.session_event_times.iter().copied());
if self.uses_intraday_execution_quotes() { if self.uses_intraday_execution_quotes() {
if self.config.explicit_action_times.is_empty() { if self.config.explicit_action_times.is_empty() {
times.insert(self.intraday_execution_start_time()); times.insert(self.intraday_execution_start_time());
@@ -2538,6 +2538,10 @@ pub fn platform_expr_config_from_spec(
cfg.benchmark_symbol = normalize_symbol(&cfg.benchmark_symbol, None); cfg.benchmark_symbol = normalize_symbol(&cfg.benchmark_symbol, None);
} }
let trade_times = spec_trade_times(spec); 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 let explicit_trading_schedule = spec
.runtime_expressions .runtime_expressions
.as_ref() .as_ref()
@@ -4458,6 +4462,20 @@ mod tests {
assert_eq!(cfg.delayed_limit_open_exit_time, None); 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] #[test]
fn explicit_trading_schedule_overrides_rebalance_trade_times() { fn explicit_trading_schedule_overrides_rebalance_trade_times() {
let spec = serde_json::json!({ let spec = serde_json::json!({