Add scheduled process hooks to strategy engine

This commit is contained in:
boris
2026-04-23 03:57:10 -07:00
parent 2bbfa35187
commit 6b5112a363
4 changed files with 195 additions and 39 deletions

View File

@@ -220,31 +220,39 @@ where
};
let schedule_rules = self.strategy.schedule_rules();
let mut process_events = Vec::new();
push_phase_event(
publish_phase_event(
&mut self.strategy,
&daily_context,
&mut process_events,
execution_date,
ProcessEventKind::PreBeforeTrading,
"before_trading:pre",
);
)?;
self.strategy.before_trading(&daily_context)?;
push_phase_event(
publish_phase_event(
&mut self.strategy,
&daily_context,
&mut process_events,
execution_date,
ProcessEventKind::BeforeTrading,
"before_trading",
);
push_phase_event(
)?;
publish_phase_event(
&mut self.strategy,
&daily_context,
&mut process_events,
execution_date,
ProcessEventKind::PostBeforeTrading,
"before_trading:post",
);
push_phase_event(
)?;
publish_phase_event(
&mut self.strategy,
&daily_context,
&mut process_events,
execution_date,
ProcessEventKind::PreOpenAuction,
"open_auction:pre",
);
)?;
let mut auction_decision = collect_scheduled_decisions(
&mut self.strategy,
&scheduler,
@@ -252,34 +260,53 @@ where
ScheduleStage::OpenAuction,
&schedule_rules,
&daily_context,
&mut process_events,
)?;
auction_decision.merge_from(self.strategy.open_auction(&daily_context)?);
push_phase_event(
publish_phase_event(
&mut self.strategy,
&daily_context,
&mut process_events,
execution_date,
ProcessEventKind::OpenAuction,
"open_auction",
);
)?;
let mut report = self.broker.execute(
execution_date,
&mut portfolio,
&self.data,
&auction_decision,
)?;
process_events.append(&mut report.process_events);
push_phase_event(
let post_auction_context = StrategyContext {
execution_date,
decision_date,
decision_index,
data: &self.data,
portfolio: &portfolio,
};
publish_process_events(
&mut self.strategy,
&post_auction_context,
&mut process_events,
&mut report.process_events,
)?;
publish_phase_event(
&mut self.strategy,
&post_auction_context,
&mut process_events,
execution_date,
ProcessEventKind::PostOpenAuction,
"open_auction:post",
);
)?;
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_auction_context,
&mut process_events,
execution_date,
ProcessEventKind::PreOnDay,
"on_day:pre",
);
)?;
let mut decision = decision_slot
.map(|(decision_idx, decision_date)| {
self.strategy.on_day(&StrategyContext {
@@ -305,18 +332,40 @@ where
data: &self.data,
portfolio: &portfolio,
},
&mut process_events,
)?);
push_phase_event(
let on_day_context = StrategyContext {
execution_date,
decision_date,
decision_index,
data: &self.data,
portfolio: &portfolio,
};
publish_phase_event(
&mut self.strategy,
&on_day_context,
&mut process_events,
execution_date,
ProcessEventKind::OnDay,
"on_day",
);
)?;
let mut intraday_report =
self.broker
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
process_events.append(&mut intraday_report.process_events);
let post_intraday_context = StrategyContext {
execution_date,
decision_date,
decision_index,
data: &self.data,
portfolio: &portfolio,
};
publish_process_events(
&mut self.strategy,
&post_intraday_context,
&mut process_events,
&mut intraday_report.process_events,
)?;
report.order_events.extend(intraday_report.order_events);
report.fill_events.extend(intraday_report.fill_events);
report
@@ -324,12 +373,14 @@ where
.extend(intraday_report.position_events);
report.account_events.extend(intraday_report.account_events);
report.diagnostics.extend(intraday_report.diagnostics);
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_intraday_context,
&mut process_events,
execution_date,
ProcessEventKind::PostOnDay,
"on_day:post",
);
)?;
portfolio.update_prices(execution_date, &self.data, PriceField::Close)?;
@@ -340,51 +391,68 @@ where
data: &self.data,
portfolio: &portfolio,
};
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::PreAfterTrading,
"after_trading:pre",
);
)?;
self.strategy.after_trading(&post_trade_context)?;
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::AfterTrading,
"after_trading",
);
)?;
let mut close_report = self.broker.after_trading(execution_date);
process_events.append(&mut close_report.process_events);
publish_process_events(
&mut self.strategy,
&post_trade_context,
&mut process_events,
&mut close_report.process_events,
)?;
report.order_events.extend(close_report.order_events);
report.fill_events.extend(close_report.fill_events);
report.position_events.extend(close_report.position_events);
report.account_events.extend(close_report.account_events);
report.diagnostics.extend(close_report.diagnostics);
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::PostAfterTrading,
"after_trading:post",
);
push_phase_event(
)?;
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::PreSettlement,
"settlement:pre",
);
)?;
self.strategy.on_settlement(&post_trade_context)?;
push_phase_event(
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::Settlement,
"settlement",
);
push_phase_event(
)?;
publish_phase_event(
&mut self.strategy,
&post_trade_context,
&mut process_events,
execution_date,
ProcessEventKind::PostSettlement,
"settlement:post",
);
)?;
let daily_fill_count = report.fill_events.len();
let day_orders = report.order_events.clone();
let day_fills = report.fill_events.clone();
@@ -876,28 +944,70 @@ fn collect_scheduled_decisions<S: Strategy>(
stage: ScheduleStage,
rules: &[ScheduleRule],
ctx: &StrategyContext<'_>,
process_events: &mut Vec<ProcessEvent>,
) -> Result<crate::strategy::StrategyDecision, BacktestError> {
let mut combined = crate::strategy::StrategyDecision::default();
for rule in scheduler.triggered_rules(execution_date, stage, rules) {
publish_phase_event(
strategy,
ctx,
process_events,
execution_date,
ProcessEventKind::PreScheduled,
format!("scheduled:{}:{}:pre", rule.name, stage_label(stage)),
)?;
combined.merge_from(strategy.on_scheduled(ctx, rule)?);
publish_phase_event(
strategy,
ctx,
process_events,
execution_date,
ProcessEventKind::PostScheduled,
format!("scheduled:{}:{}:post", rule.name, stage_label(stage)),
)?;
}
Ok(combined)
}
fn push_phase_event(
fn publish_phase_event<S: Strategy>(
strategy: &mut S,
ctx: &StrategyContext<'_>,
events: &mut Vec<ProcessEvent>,
date: NaiveDate,
kind: ProcessEventKind,
detail: impl Into<String>,
) {
events.push(ProcessEvent {
) -> Result<(), BacktestError> {
let event = ProcessEvent {
date,
kind,
order_id: None,
symbol: None,
side: None,
detail: detail.into(),
});
};
strategy.on_process_event(ctx, &event)?;
events.push(event);
Ok(())
}
fn publish_process_events<S: Strategy>(
strategy: &mut S,
ctx: &StrategyContext<'_>,
target: &mut Vec<ProcessEvent>,
incoming: &mut Vec<ProcessEvent>,
) -> Result<(), BacktestError> {
for event in incoming.drain(..) {
strategy.on_process_event(ctx, &event)?;
target.push(event);
}
Ok(())
}
fn stage_label(stage: ScheduleStage) -> &'static str {
match stage {
ScheduleStage::OpenAuction => "open_auction",
ScheduleStage::OnDay => "on_day",
}
}
mod date_format {