补齐回测交易日期审计字段
This commit is contained in:
@@ -46,6 +46,8 @@ struct ExecutionFill {
|
||||
#[derive(Debug, Clone)]
|
||||
struct OpenOrder {
|
||||
order_id: u64,
|
||||
decision_date: Option<NaiveDate>,
|
||||
order_created_date: Option<NaiveDate>,
|
||||
symbol: String,
|
||||
side: OrderSide,
|
||||
requested_quantity: u32,
|
||||
@@ -176,6 +178,8 @@ pub struct BrokerSimulator<C, R> {
|
||||
intraday_execution_start_time: Option<NaiveTime>,
|
||||
runtime_intraday_start_time: Cell<Option<NaiveTime>>,
|
||||
runtime_intraday_end_time: Cell<Option<NaiveTime>>,
|
||||
runtime_decision_date: Cell<Option<NaiveDate>>,
|
||||
runtime_order_created_date: Cell<Option<NaiveDate>>,
|
||||
next_order_id: Cell<u64>,
|
||||
open_orders: RefCell<Vec<OpenOrder>>,
|
||||
}
|
||||
@@ -201,6 +205,8 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
intraday_execution_start_time: None,
|
||||
runtime_intraday_start_time: Cell::new(None),
|
||||
runtime_intraday_end_time: Cell::new(None),
|
||||
runtime_decision_date: Cell::new(None),
|
||||
runtime_order_created_date: Cell::new(None),
|
||||
next_order_id: Cell::new(1),
|
||||
open_orders: RefCell::new(Vec::new()),
|
||||
}
|
||||
@@ -230,6 +236,8 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
intraday_execution_start_time: None,
|
||||
runtime_intraday_start_time: Cell::new(None),
|
||||
runtime_intraday_end_time: Cell::new(None),
|
||||
runtime_decision_date: Cell::new(None),
|
||||
runtime_order_created_date: Cell::new(None),
|
||||
next_order_id: Cell::new(1),
|
||||
open_orders: RefCell::new(Vec::new()),
|
||||
}
|
||||
@@ -640,6 +648,37 @@ where
|
||||
portfolio: &mut PortfolioState,
|
||||
data: &DataSet,
|
||||
decision: &StrategyDecision,
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
self.execute_with_event_dates(date, date, date, portfolio, data, decision)
|
||||
}
|
||||
|
||||
pub fn execute_with_event_dates(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
decision_date: NaiveDate,
|
||||
order_created_date: NaiveDate,
|
||||
portfolio: &mut PortfolioState,
|
||||
data: &DataSet,
|
||||
decision: &StrategyDecision,
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
let previous_decision_date = self.runtime_decision_date.get();
|
||||
let previous_order_created_date = self.runtime_order_created_date.get();
|
||||
self.runtime_decision_date.set(Some(decision_date));
|
||||
self.runtime_order_created_date
|
||||
.set(Some(order_created_date));
|
||||
let result = self.execute_with_runtime_dates(date, portfolio, data, decision);
|
||||
self.runtime_decision_date.set(previous_decision_date);
|
||||
self.runtime_order_created_date
|
||||
.set(previous_order_created_date);
|
||||
result
|
||||
}
|
||||
|
||||
fn execute_with_runtime_dates(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
portfolio: &mut PortfolioState,
|
||||
data: &DataSet,
|
||||
decision: &StrategyDecision,
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
let mut report = BrokerExecutionReport::default();
|
||||
let mut intraday_turnover = BTreeMap::<String, u32>::new();
|
||||
@@ -769,12 +808,35 @@ where
|
||||
decision: &StrategyDecision,
|
||||
start_time: Option<NaiveTime>,
|
||||
end_time: Option<NaiveTime>,
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
self.execute_between_with_event_dates(
|
||||
date, date, date, portfolio, data, decision, start_time, end_time,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute_between_with_event_dates(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
decision_date: NaiveDate,
|
||||
order_created_date: NaiveDate,
|
||||
portfolio: &mut PortfolioState,
|
||||
data: &DataSet,
|
||||
decision: &StrategyDecision,
|
||||
start_time: Option<NaiveTime>,
|
||||
end_time: Option<NaiveTime>,
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
let previous_start_time = self.runtime_intraday_start_time.get();
|
||||
let previous_end_time = self.runtime_intraday_end_time.get();
|
||||
self.runtime_intraday_start_time.set(start_time);
|
||||
self.runtime_intraday_end_time.set(end_time);
|
||||
let result = self.execute(date, portfolio, data, decision);
|
||||
let result = self.execute_with_event_dates(
|
||||
date,
|
||||
decision_date,
|
||||
order_created_date,
|
||||
portfolio,
|
||||
data,
|
||||
decision,
|
||||
);
|
||||
self.runtime_intraday_start_time.set(previous_start_time);
|
||||
self.runtime_intraday_end_time.set(previous_end_time);
|
||||
result
|
||||
@@ -1389,6 +1451,34 @@ where
|
||||
open_orders.push(open_order);
|
||||
}
|
||||
|
||||
fn current_decision_date(&self, date: NaiveDate) -> NaiveDate {
|
||||
self.runtime_decision_date.get().unwrap_or(date)
|
||||
}
|
||||
|
||||
fn current_order_created_date(&self, date: NaiveDate) -> NaiveDate {
|
||||
self.runtime_order_created_date.get().unwrap_or(date)
|
||||
}
|
||||
|
||||
fn annotate_report_range(
|
||||
report: &mut BrokerExecutionReport,
|
||||
order_event_start: usize,
|
||||
fill_event_start: usize,
|
||||
decision_date: NaiveDate,
|
||||
order_created_date: NaiveDate,
|
||||
execution_date: NaiveDate,
|
||||
) {
|
||||
for event in &mut report.order_events[order_event_start..] {
|
||||
event.decision_date.get_or_insert(decision_date);
|
||||
event.order_created_date.get_or_insert(order_created_date);
|
||||
event.execution_date.get_or_insert(execution_date);
|
||||
}
|
||||
for fill in &mut report.fill_events[fill_event_start..] {
|
||||
fill.decision_date.get_or_insert(decision_date);
|
||||
fill.order_created_date.get_or_insert(order_created_date);
|
||||
fill.execution_date.get_or_insert(execution_date);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_open_order(&self, order_id: u64) {
|
||||
self.open_orders
|
||||
.borrow_mut()
|
||||
@@ -1461,6 +1551,8 @@ where
|
||||
std::mem::take(&mut *open_orders)
|
||||
};
|
||||
for order in pending_orders {
|
||||
let order_event_start = report.order_events.len();
|
||||
let fill_event_start = report.fill_events.len();
|
||||
let signed_quantity = if order.side == OrderSide::Buy {
|
||||
order.remaining_quantity as i32
|
||||
} else {
|
||||
@@ -1482,6 +1574,14 @@ where
|
||||
commission_state,
|
||||
report,
|
||||
)?;
|
||||
Self::annotate_report_range(
|
||||
report,
|
||||
order_event_start,
|
||||
fill_event_start,
|
||||
order.decision_date.unwrap_or(date),
|
||||
order.order_created_date.unwrap_or(date),
|
||||
date,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1597,6 +1697,9 @@ where
|
||||
);
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: order.decision_date,
|
||||
order_created_date: order.order_created_date,
|
||||
execution_date: Some(date),
|
||||
order_id: Some(order.order_id),
|
||||
symbol: order.symbol.clone(),
|
||||
side: order.side,
|
||||
@@ -1632,6 +1735,9 @@ where
|
||||
);
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: order.decision_date,
|
||||
order_created_date: order.order_created_date,
|
||||
execution_date: Some(date),
|
||||
order_id: Some(order.order_id),
|
||||
symbol: order.symbol.clone(),
|
||||
side: order.side,
|
||||
@@ -1692,6 +1798,9 @@ where
|
||||
);
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side,
|
||||
@@ -1738,6 +1847,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side,
|
||||
@@ -2639,6 +2751,9 @@ where
|
||||
};
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2704,6 +2819,8 @@ where
|
||||
if allow_pending_limit {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -2714,6 +2831,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2735,6 +2855,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2765,6 +2888,8 @@ where
|
||||
.unwrap_or("no sellable quantity");
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -2775,6 +2900,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2796,6 +2924,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2891,6 +3022,8 @@ where
|
||||
if allow_pending_limit && Self::limit_order_can_remain_open(Some(detail)) {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -2901,6 +3034,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2922,6 +3058,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -2973,6 +3112,9 @@ where
|
||||
|
||||
report.fill_events.push(FillEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3032,6 +3174,8 @@ where
|
||||
if keep_open {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -3081,6 +3225,9 @@ where
|
||||
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3126,6 +3273,9 @@ where
|
||||
if current_qty == 0 {
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3210,6 +3360,9 @@ where
|
||||
} else {
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.to_string(),
|
||||
side: if current_qty > 0 {
|
||||
@@ -3269,6 +3422,9 @@ where
|
||||
if current_qty == 0 {
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3323,6 +3479,9 @@ where
|
||||
} else {
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.to_string(),
|
||||
side: if current_qty > 0 {
|
||||
@@ -3419,6 +3578,9 @@ where
|
||||
} else {
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.to_string(),
|
||||
side: if current_qty > 0 {
|
||||
@@ -4279,6 +4441,9 @@ where
|
||||
};
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4333,6 +4498,8 @@ where
|
||||
if allow_pending_limit {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -4343,6 +4510,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4364,6 +4534,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4523,6 +4696,8 @@ where
|
||||
if allow_pending_limit && Self::limit_order_can_remain_open(Some(detail)) {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -4533,6 +4708,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4554,6 +4732,9 @@ where
|
||||
}
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4609,6 +4790,9 @@ where
|
||||
|
||||
report.fill_events.push(FillEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -4666,6 +4850,8 @@ where
|
||||
if keep_open {
|
||||
self.upsert_open_order(OpenOrder {
|
||||
order_id,
|
||||
decision_date: Some(self.current_decision_date(date)),
|
||||
order_created_date: Some(self.current_order_created_date(date)),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
requested_quantity: requested_qty,
|
||||
@@ -4715,6 +4901,9 @@ where
|
||||
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: symbol.to_string(),
|
||||
side: OrderSide::Buy,
|
||||
|
||||
@@ -1192,6 +1192,9 @@ where
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: self
|
||||
.futures_open_orders
|
||||
@@ -1229,6 +1232,9 @@ where
|
||||
let mut report = FuturesExecutionReport::default();
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order_id),
|
||||
symbol: intent.symbol.clone(),
|
||||
side,
|
||||
@@ -1704,27 +1710,52 @@ where
|
||||
&mut portfolio,
|
||||
&mut corporate_action_notes,
|
||||
);
|
||||
self.extend_result(&mut result, pending_cash_flow_report);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
pending_cash_flow_report,
|
||||
execution_date,
|
||||
execution_date,
|
||||
);
|
||||
let corporate_action_report = self.apply_corporate_actions(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&mut corporate_action_notes,
|
||||
)?;
|
||||
self.extend_result(&mut result, corporate_action_report);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
corporate_action_report,
|
||||
execution_date,
|
||||
execution_date,
|
||||
);
|
||||
let receivable_report = self.settle_cash_receivables(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&mut corporate_action_notes,
|
||||
)?;
|
||||
self.extend_result(&mut result, receivable_report);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
receivable_report,
|
||||
execution_date,
|
||||
execution_date,
|
||||
);
|
||||
let delisting_report = self.settle_delisted_positions(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&mut corporate_action_notes,
|
||||
)?;
|
||||
self.extend_result(&mut result, delisting_report);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
delisting_report,
|
||||
execution_date,
|
||||
execution_date,
|
||||
);
|
||||
let futures_open_order_report = self.process_futures_open_orders(execution_date);
|
||||
self.extend_result(&mut result, futures_open_order_report);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
futures_open_order_report,
|
||||
execution_date,
|
||||
execution_date,
|
||||
);
|
||||
|
||||
let decision_slot = execution_idx
|
||||
.checked_sub(self.config.decision_lag_trading_days)
|
||||
@@ -1750,7 +1781,7 @@ where
|
||||
let day_fills = report.fill_events.clone();
|
||||
let broker_diagnostics = report.diagnostics.clone();
|
||||
let execution_risk_decisions = risk_decisions_from_order_events(&day_orders);
|
||||
self.extend_result(&mut result, report);
|
||||
self.extend_result(&mut result, report, execution_date, execution_date);
|
||||
result.risk_decisions.extend(execution_risk_decisions);
|
||||
|
||||
let benchmark =
|
||||
@@ -2014,8 +2045,10 @@ where
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let mut report = self.broker.execute(
|
||||
let mut report = self.broker.execute_with_event_dates(
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_date,
|
||||
&mut portfolio,
|
||||
&self.data,
|
||||
&auction_decision,
|
||||
@@ -2260,9 +2293,14 @@ where
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
let mut intraday_report =
|
||||
self.broker
|
||||
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
|
||||
let mut intraday_report = self.broker.execute_with_event_dates(
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_date,
|
||||
&mut portfolio,
|
||||
&self.data,
|
||||
&decision,
|
||||
)?;
|
||||
let post_intraday_open_orders = self.open_order_views();
|
||||
publish_process_events(
|
||||
&mut self.strategy,
|
||||
@@ -2426,8 +2464,10 @@ where
|
||||
Some(minute_time),
|
||||
Some(minute_time),
|
||||
)?;
|
||||
let mut minute_report = self.broker.execute_between(
|
||||
let mut minute_report = self.broker.execute_between_with_event_dates(
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_date,
|
||||
&mut portfolio,
|
||||
&self.data,
|
||||
&minute_decision,
|
||||
@@ -2750,7 +2790,7 @@ where
|
||||
let day_fills = report.fill_events.clone();
|
||||
let broker_diagnostics = report.diagnostics.clone();
|
||||
let execution_risk_decisions = risk_decisions_from_order_events(&day_orders);
|
||||
self.extend_result(&mut result, report);
|
||||
self.extend_result(&mut result, report, decision_date, execution_date);
|
||||
result.risk_decisions.extend(decision.risk_decisions);
|
||||
result.risk_decisions.extend(execution_risk_decisions);
|
||||
|
||||
@@ -2838,8 +2878,11 @@ where
|
||||
fn extend_result(
|
||||
&self,
|
||||
result: &mut BacktestResult,
|
||||
report: BrokerExecutionReport,
|
||||
mut report: BrokerExecutionReport,
|
||||
decision_date: NaiveDate,
|
||||
execution_date: NaiveDate,
|
||||
) -> BrokerExecutionReport {
|
||||
annotate_broker_report_dates(&mut report, decision_date, decision_date, execution_date);
|
||||
result.order_events.extend(report.order_events.clone());
|
||||
result.fills.extend(report.fill_events.clone());
|
||||
result
|
||||
@@ -3063,6 +3106,9 @@ where
|
||||
);
|
||||
report.fill_events.push(FillEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: receivable.symbol.clone(),
|
||||
side: OrderSide::Buy,
|
||||
@@ -3366,6 +3412,9 @@ where
|
||||
notes.push(reason.clone());
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.clone(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3376,6 +3425,9 @@ where
|
||||
});
|
||||
report.fill_events.push(FillEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: None,
|
||||
symbol: symbol.clone(),
|
||||
side: OrderSide::Sell,
|
||||
@@ -3827,6 +3879,24 @@ fn merge_futures_report(target: &mut BrokerExecutionReport, incoming: FuturesExe
|
||||
target.diagnostics.extend(incoming.diagnostics);
|
||||
}
|
||||
|
||||
fn annotate_broker_report_dates(
|
||||
report: &mut BrokerExecutionReport,
|
||||
decision_date: NaiveDate,
|
||||
order_created_date: NaiveDate,
|
||||
execution_date: NaiveDate,
|
||||
) {
|
||||
for event in &mut report.order_events {
|
||||
event.decision_date.get_or_insert(decision_date);
|
||||
event.order_created_date.get_or_insert(order_created_date);
|
||||
event.execution_date.get_or_insert(execution_date);
|
||||
}
|
||||
for fill in &mut report.fill_events {
|
||||
fill.decision_date.get_or_insert(decision_date);
|
||||
fill.order_created_date.get_or_insert(order_created_date);
|
||||
fill.execution_date.get_or_insert(execution_date);
|
||||
}
|
||||
}
|
||||
|
||||
fn risk_decisions_from_order_events(order_events: &[OrderEvent]) -> Vec<FidcRiskDecisionAudit> {
|
||||
order_events
|
||||
.iter()
|
||||
@@ -4021,6 +4091,9 @@ fn futures_cancel_report(
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id: Some(order.order_id),
|
||||
symbol: order.intent.symbol.clone(),
|
||||
side,
|
||||
@@ -4779,6 +4852,9 @@ mod tests {
|
||||
|
||||
assert_eq!(result.fills.len(), 1);
|
||||
assert_eq!(result.fills[0].date, d(2025, 1, 3));
|
||||
assert_eq!(result.fills[0].decision_date, Some(d(2025, 1, 2)));
|
||||
assert_eq!(result.fills[0].order_created_date, Some(d(2025, 1, 2)));
|
||||
assert_eq!(result.fills[0].execution_date, Some(d(2025, 1, 3)));
|
||||
assert_eq!(result.fills[0].price, 12.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,33 @@ mod date_format {
|
||||
}
|
||||
}
|
||||
|
||||
mod optional_date_format {
|
||||
use chrono::NaiveDate;
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
|
||||
const FORMAT: &str = "%Y-%m-%d";
|
||||
|
||||
pub fn serialize<S>(date: &Option<NaiveDate>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match date {
|
||||
Some(date) => serializer.serialize_some(&date.format(FORMAT).to_string()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<NaiveDate>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = Option::<String>::deserialize(deserializer)?;
|
||||
value
|
||||
.map(|text| NaiveDate::parse_from_str(&text, FORMAT).map_err(serde::de::Error::custom))
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum OrderSide {
|
||||
Buy,
|
||||
@@ -63,6 +90,12 @@ impl OrderStatus {
|
||||
pub struct OrderEvent {
|
||||
#[serde(with = "date_format")]
|
||||
pub date: NaiveDate,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub decision_date: Option<NaiveDate>,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub order_created_date: Option<NaiveDate>,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub execution_date: Option<NaiveDate>,
|
||||
#[serde(default)]
|
||||
pub order_id: Option<u64>,
|
||||
pub symbol: String,
|
||||
@@ -77,6 +110,12 @@ pub struct OrderEvent {
|
||||
pub struct FillEvent {
|
||||
#[serde(with = "date_format")]
|
||||
pub date: NaiveDate,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub decision_date: Option<NaiveDate>,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub order_created_date: Option<NaiveDate>,
|
||||
#[serde(default, with = "optional_date_format")]
|
||||
pub execution_date: Option<NaiveDate>,
|
||||
#[serde(default)]
|
||||
pub order_id: Option<u64>,
|
||||
pub symbol: String,
|
||||
|
||||
@@ -746,6 +746,9 @@ impl FuturesAccountState {
|
||||
);
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id,
|
||||
symbol: intent.symbol,
|
||||
side,
|
||||
@@ -823,6 +826,9 @@ impl FuturesAccountState {
|
||||
intent.price * intent.quantity as f64 * intent.spec.contract_multiplier;
|
||||
report.fill_events.push(FillEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id,
|
||||
symbol: intent.symbol.clone(),
|
||||
side,
|
||||
@@ -889,6 +895,9 @@ impl FuturesAccountState {
|
||||
});
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id,
|
||||
symbol: intent.symbol,
|
||||
side,
|
||||
@@ -915,6 +924,9 @@ impl FuturesAccountState {
|
||||
);
|
||||
report.order_events.push(OrderEvent {
|
||||
date,
|
||||
decision_date: None,
|
||||
order_created_date: None,
|
||||
execution_date: None,
|
||||
order_id,
|
||||
symbol: intent.symbol,
|
||||
side,
|
||||
|
||||
Reference in New Issue
Block a user