Add process event stream for backtests
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user