支持任意交易阶段调度时间
This commit is contained in:
@@ -2043,7 +2043,7 @@ where
|
||||
ProcessEventKind::BeforeTrading,
|
||||
"before_trading",
|
||||
)?;
|
||||
let mut before_trading_decision = collect_scheduled_decisions(
|
||||
let mut before_trading_decision = collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2059,7 +2059,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::BeforeTrading),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
@@ -2107,7 +2106,7 @@ where
|
||||
ProcessEventKind::PreOpenAuction,
|
||||
"open_auction:pre",
|
||||
)?;
|
||||
let mut auction_decision = collect_scheduled_decisions(
|
||||
let mut auction_decision = collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2123,7 +2122,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::OpenAuction),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
@@ -2300,7 +2298,7 @@ where
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
decision.merge_from(collect_scheduled_decisions(
|
||||
decision.merge_from(collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2316,7 +2314,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::OnDay),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?);
|
||||
@@ -2355,7 +2352,7 @@ where
|
||||
ProcessEventKind::PreBar,
|
||||
"bar:pre",
|
||||
)?;
|
||||
decision.merge_from(collect_scheduled_decisions(
|
||||
decision.merge_from(collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2371,7 +2368,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::Bar),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?);
|
||||
@@ -2770,7 +2766,7 @@ where
|
||||
ProcessEventKind::AfterTrading,
|
||||
"after_trading",
|
||||
)?;
|
||||
let mut after_trading_decision = collect_scheduled_decisions(
|
||||
let mut after_trading_decision = collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2786,7 +2782,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::AfterTrading),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
@@ -2899,7 +2894,7 @@ where
|
||||
ProcessEventKind::Settlement,
|
||||
"settlement",
|
||||
)?;
|
||||
let mut settlement_decision = collect_scheduled_decisions(
|
||||
let mut settlement_decision = collect_scheduled_decisions_for_stage(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
execution_date,
|
||||
@@ -2915,7 +2910,6 @@ where
|
||||
&self.subscriptions,
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::Settlement),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
@@ -3845,6 +3839,70 @@ fn collect_scheduled_decisions<S: Strategy>(
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
fn collect_scheduled_decisions_for_stage<S: Strategy>(
|
||||
strategy: &mut S,
|
||||
scheduler: &Scheduler<'_>,
|
||||
execution_date: NaiveDate,
|
||||
stage: ScheduleStage,
|
||||
rules: &[ScheduleRule],
|
||||
decision_date: NaiveDate,
|
||||
decision_index: usize,
|
||||
data: &crate::data::DataSet,
|
||||
portfolio: &PortfolioState,
|
||||
futures_account: Option<&FuturesAccountState>,
|
||||
open_orders: &[crate::strategy::OpenOrderView],
|
||||
dynamic_universe: Option<&BTreeSet<String>>,
|
||||
subscriptions: &BTreeSet<String>,
|
||||
process_events: &mut Vec<ProcessEvent>,
|
||||
process_event_bus: &mut ProcessEventBus,
|
||||
order_events: &[OrderEvent],
|
||||
fills: &[FillEvent],
|
||||
) -> Result<crate::strategy::StrategyDecision, BacktestError> {
|
||||
let mut times = BTreeSet::new();
|
||||
for rule in rules.iter().filter(|rule| rule.stage == stage) {
|
||||
let time = match rule.time_rule.as_ref() {
|
||||
Some(crate::scheduler::ScheduleTimeRule::MinuteOfDay(value)) => {
|
||||
let hour = value / 60;
|
||||
let minute = value % 60;
|
||||
Some(NaiveTime::from_hms_opt(hour, minute, 0).ok_or_else(|| {
|
||||
BacktestError::Execution(format!(
|
||||
"invalid schedule minute-of-day {} for rule {}",
|
||||
value, rule.name
|
||||
))
|
||||
})?)
|
||||
}
|
||||
Some(crate::scheduler::ScheduleTimeRule::BeforeTrading) | None => {
|
||||
default_stage_time(stage)
|
||||
}
|
||||
};
|
||||
times.insert(time);
|
||||
}
|
||||
let mut combined = crate::strategy::StrategyDecision::default();
|
||||
for time in times {
|
||||
combined.merge_from(collect_scheduled_decisions(
|
||||
strategy,
|
||||
scheduler,
|
||||
execution_date,
|
||||
stage,
|
||||
rules,
|
||||
decision_date,
|
||||
decision_index,
|
||||
data,
|
||||
portfolio,
|
||||
futures_account,
|
||||
open_orders,
|
||||
dynamic_universe,
|
||||
subscriptions,
|
||||
process_events,
|
||||
process_event_bus,
|
||||
time,
|
||||
order_events,
|
||||
fills,
|
||||
)?);
|
||||
}
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
fn publish_phase_event<S: Strategy>(
|
||||
strategy: &mut S,
|
||||
process_event_bus: &mut ProcessEventBus,
|
||||
|
||||
@@ -8532,24 +8532,6 @@ impl PlatformExprStrategy {
|
||||
})
|
||||
}
|
||||
|
||||
fn explicit_actions_active(
|
||||
&self,
|
||||
calendar: &crate::calendar::TradingCalendar,
|
||||
date: NaiveDate,
|
||||
) -> bool {
|
||||
let stage = match self.config.explicit_action_stage {
|
||||
PlatformExplicitActionStage::OpenAuction => ScheduleStage::OpenAuction,
|
||||
PlatformExplicitActionStage::OnDay => ScheduleStage::OnDay,
|
||||
PlatformExplicitActionStage::Minute => ScheduleStage::Minute,
|
||||
};
|
||||
self.config
|
||||
.explicit_action_schedule
|
||||
.as_ref()
|
||||
.is_none_or(|schedule| {
|
||||
schedule.matches(calendar, date, stage, default_stage_time(stage))
|
||||
})
|
||||
}
|
||||
|
||||
fn stock_passes_expr(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
@@ -10148,13 +10130,18 @@ impl Strategy for PlatformExprStrategy {
|
||||
}
|
||||
|
||||
fn schedule_rules(&self) -> Vec<ScheduleRule> {
|
||||
if self.config.explicit_action_stage != PlatformExplicitActionStage::Minute {
|
||||
if self.config.explicit_actions.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let stage = match self.config.explicit_action_stage {
|
||||
PlatformExplicitActionStage::OpenAuction => ScheduleStage::OpenAuction,
|
||||
PlatformExplicitActionStage::OnDay => ScheduleStage::OnDay,
|
||||
PlatformExplicitActionStage::Minute => ScheduleStage::Minute,
|
||||
};
|
||||
self.config
|
||||
.explicit_action_schedule
|
||||
.as_ref()
|
||||
.map(|schedule| schedule.as_schedule_rule(ScheduleStage::Minute))
|
||||
.map(|schedule| schedule.as_schedule_rule(stage))
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
@@ -10164,9 +10151,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
ctx: &StrategyContext<'_>,
|
||||
_rule: &ScheduleRule,
|
||||
) -> Result<StrategyDecision, BacktestError> {
|
||||
if self.config.explicit_action_stage == PlatformExplicitActionStage::Minute
|
||||
&& !self.config.explicit_actions.is_empty()
|
||||
{
|
||||
if !self.config.explicit_actions.is_empty() {
|
||||
return self.explicit_action_decision(ctx);
|
||||
}
|
||||
Ok(StrategyDecision::default())
|
||||
@@ -10210,7 +10195,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
) -> Result<StrategyDecision, BacktestError> {
|
||||
if self.config.explicit_action_stage == PlatformExplicitActionStage::OpenAuction
|
||||
&& !self.config.explicit_actions.is_empty()
|
||||
&& self.explicit_actions_active(ctx.data.calendar(), ctx.decision_date)
|
||||
&& self.config.explicit_action_schedule.is_none()
|
||||
{
|
||||
return self.explicit_action_decision(ctx);
|
||||
}
|
||||
@@ -10264,7 +10249,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
self.selection_dates(ctx);
|
||||
let (explicit_action_intents, mut explicit_action_diagnostics) = if !in_skip_window
|
||||
&& self.config.explicit_action_stage == PlatformExplicitActionStage::OnDay
|
||||
&& self.explicit_actions_active(ctx.data.calendar(), signal_date)
|
||||
&& self.config.explicit_action_schedule.is_none()
|
||||
{
|
||||
self.explicit_action_intents(ctx, decision_date, &day)?
|
||||
} else {
|
||||
@@ -11841,8 +11826,8 @@ mod tests {
|
||||
FactorTextValue, FuturesCommissionType, FuturesTradingParameter, Instrument,
|
||||
IntradayExecutionQuote, MatchingType, OpenOrderView, OrderIntent, OrderSide,
|
||||
PortfolioState, ProcessEvent, ProcessEventKind, RebalanceCashMode, ScheduleStage,
|
||||
ScheduleTimeRule, SlippageModel, Strategy, StrategyContext, TargetPortfolioOrderPricing,
|
||||
TradingCalendar, default_stage_time,
|
||||
ScheduleTimeRule, Scheduler, SlippageModel, Strategy, StrategyContext,
|
||||
TargetPortfolioOrderPricing, TradingCalendar, default_stage_time,
|
||||
};
|
||||
|
||||
fn d(year: i32, month: u32, day: u32) -> NaiveDate {
|
||||
@@ -31286,7 +31271,25 @@ mod tests {
|
||||
}];
|
||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||
|
||||
let auction_decision = strategy.open_auction(&ctx).expect("auction decision");
|
||||
assert!(
|
||||
strategy
|
||||
.open_auction(&ctx)
|
||||
.expect("direct auction decision")
|
||||
.order_intents
|
||||
.is_empty()
|
||||
);
|
||||
let rules = strategy.schedule_rules();
|
||||
let scheduler = Scheduler::new(ctx.data.calendar());
|
||||
let triggered = scheduler.triggered_rules_at(
|
||||
date,
|
||||
ScheduleStage::OpenAuction,
|
||||
default_stage_time(ScheduleStage::OpenAuction),
|
||||
&rules,
|
||||
);
|
||||
assert_eq!(triggered.len(), 1);
|
||||
let auction_decision = strategy
|
||||
.on_scheduled(&ctx, triggered[0])
|
||||
.expect("scheduled auction decision");
|
||||
assert_eq!(auction_decision.order_intents.len(), 1);
|
||||
assert!(
|
||||
auction_decision
|
||||
@@ -31411,8 +31414,25 @@ mod tests {
|
||||
}];
|
||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||
|
||||
let auction_decision = strategy.open_auction(&ctx).expect("auction decision");
|
||||
assert!(auction_decision.order_intents.is_empty());
|
||||
assert!(
|
||||
strategy
|
||||
.open_auction(&ctx)
|
||||
.expect("direct auction decision")
|
||||
.order_intents
|
||||
.is_empty()
|
||||
);
|
||||
let rules = strategy.schedule_rules();
|
||||
let scheduler = Scheduler::new(ctx.data.calendar());
|
||||
assert!(
|
||||
scheduler
|
||||
.triggered_rules_at(
|
||||
date,
|
||||
ScheduleStage::OpenAuction,
|
||||
default_stage_time(ScheduleStage::OpenAuction),
|
||||
&rules,
|
||||
)
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -678,6 +678,8 @@ impl Strategy for ScheduledProbeStrategy {
|
||||
.with_time_rule(ScheduleTimeRule::physical_time(10, 18)),
|
||||
ScheduleRule::monthly("first_trading_day_on_day", 1, ScheduleStage::OnDay)
|
||||
.with_time_rule(ScheduleTimeRule::physical_time(10, 18)),
|
||||
ScheduleRule::daily("daily_on_day_close", ScheduleStage::OnDay)
|
||||
.with_time_rule(ScheduleTimeRule::physical_time(15, 0)),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3217,12 +3219,15 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
"scheduled:daily_before_trading:2025-01-30",
|
||||
"scheduled:daily_market_open:2025-01-30",
|
||||
"scheduled:first_trading_day_on_day:2025-01-30",
|
||||
"scheduled:daily_on_day_close:2025-01-30",
|
||||
"scheduled:daily_before_trading:2025-01-31",
|
||||
"scheduled:daily_market_open:2025-01-31",
|
||||
"scheduled:friday_on_day:2025-01-31",
|
||||
"scheduled:daily_on_day_close:2025-01-31",
|
||||
"scheduled:daily_before_trading:2025-02-03",
|
||||
"scheduled:daily_market_open:2025-02-03",
|
||||
"scheduled:first_trading_day_on_day:2025-02-03",
|
||||
"scheduled:daily_on_day_close:2025-02-03",
|
||||
]
|
||||
);
|
||||
let process_log = process_log.borrow();
|
||||
|
||||
Reference in New Issue
Block a user