Add process event stream for backtests

This commit is contained in:
boris
2026-04-23 01:58:40 -07:00
parent e5fe1f0432
commit 23ba74909d
6 changed files with 384 additions and 11 deletions

View File

@@ -5,7 +5,10 @@ use thiserror::Error;
use crate::broker::{BrokerExecutionReport, BrokerSimulator};
use crate::cost::CostModel;
use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, PriceField};
use crate::events::{AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent};
use crate::events::{
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
ProcessEventKind,
};
use crate::metrics::{BacktestMetrics, compute_backtest_metrics};
use crate::portfolio::{CashReceivable, HoldingSummary, PortfolioState};
use crate::rules::EquityRuleHooks;
@@ -59,6 +62,7 @@ pub struct BacktestResult {
pub fills: Vec<FillEvent>,
pub position_events: Vec<PositionEvent>,
pub account_events: Vec<AccountEvent>,
pub process_events: Vec<ProcessEvent>,
pub holdings_summary: Vec<HoldingSummary>,
pub daily_holdings: Vec<HoldingSummary>,
pub metrics: BacktestMetrics,
@@ -82,6 +86,7 @@ pub struct BacktestDayProgress {
pub orders: Vec<OrderEvent>,
pub fills: Vec<FillEvent>,
pub holdings: Vec<HoldingSummary>,
pub process_events: Vec<ProcessEvent>,
}
pub struct BacktestEngine<S, C, R> {
@@ -166,6 +171,7 @@ where
fills: Vec::new(),
position_events: Vec::new(),
account_events: Vec::new(),
process_events: Vec::new(),
equity_curve: Vec::new(),
holdings_summary: Vec::new(),
daily_holdings: Vec::new(),
@@ -206,7 +212,32 @@ where
portfolio: &portfolio,
};
let schedule_rules = self.strategy.schedule_rules();
let mut process_events = Vec::new();
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PreBeforeTrading,
"before_trading:pre",
);
self.strategy.before_trading(&daily_context)?;
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::BeforeTrading,
"before_trading",
);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PostBeforeTrading,
"before_trading:post",
);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PreOpenAuction,
"open_auction:pre",
);
let mut auction_decision = collect_scheduled_decisions(
&mut self.strategy,
&scheduler,
@@ -216,13 +247,32 @@ where
&daily_context,
)?;
auction_decision.merge_from(self.strategy.open_auction(&daily_context)?);
push_phase_event(
&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(
&mut process_events,
execution_date,
ProcessEventKind::PostOpenAuction,
"open_auction:post",
);
push_phase_event(
&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 {
@@ -249,10 +299,17 @@ where
portfolio: &portfolio,
},
)?);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::OnDay,
"on_day",
);
let intraday_report =
let mut intraday_report =
self.broker
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
process_events.append(&mut intraday_report.process_events);
report.order_events.extend(intraday_report.order_events);
report.fill_events.extend(intraday_report.fill_events);
report
@@ -260,6 +317,12 @@ where
.extend(intraday_report.position_events);
report.account_events.extend(intraday_report.account_events);
report.diagnostics.extend(intraday_report.diagnostics);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PostOnDay,
"on_day:post",
);
let daily_fill_count = report.fill_events.len();
let day_orders = report.order_events.clone();
let day_fills = report.fill_events.clone();
@@ -275,8 +338,44 @@ where
data: &self.data,
portfolio: &portfolio,
};
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PreAfterTrading,
"after_trading:pre",
);
self.strategy.after_trading(&post_trade_context)?;
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::AfterTrading,
"after_trading",
);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PostAfterTrading,
"after_trading:post",
);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PreSettlement,
"settlement:pre",
);
self.strategy.on_settlement(&post_trade_context)?;
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::Settlement,
"settlement",
);
push_phase_event(
&mut process_events,
execution_date,
ProcessEventKind::PostSettlement,
"settlement:post",
);
let benchmark =
self.data
@@ -296,6 +395,7 @@ where
.collect::<Vec<_>>()
.join(" | ");
let holdings_for_day = portfolio.holdings_summary(execution_date);
let day_process_events = process_events.clone();
result.equity_curve.push(DailyEquityPoint {
date: execution_date,
@@ -335,7 +435,9 @@ where
orders: day_orders,
fills: day_fills,
holdings: holdings_for_day,
process_events: day_process_events,
});
result.process_events.extend(process_events);
}
if let Some(last_date) = execution_dates.last().copied() {
@@ -676,6 +778,22 @@ fn collect_scheduled_decisions<S: Strategy>(
Ok(combined)
}
fn push_phase_event(
events: &mut Vec<ProcessEvent>,
date: NaiveDate,
kind: ProcessEventKind,
detail: impl Into<String>,
) {
events.push(ProcessEvent {
date,
kind,
order_id: None,
symbol: None,
side: None,
detail: detail.into(),
});
}
mod date_format {
use chrono::NaiveDate;
use serde::Serializer;