移除引擎历史事件重复复制
This commit is contained in:
+169
-78
@@ -319,6 +319,7 @@ pub struct BacktestDayProgress {
|
|||||||
pub total_return: f64,
|
pub total_return: f64,
|
||||||
pub benchmark_close: f64,
|
pub benchmark_close: f64,
|
||||||
pub daily_fill_count: usize,
|
pub daily_fill_count: usize,
|
||||||
|
pub daily_order_count: usize,
|
||||||
pub cumulative_trade_count: usize,
|
pub cumulative_trade_count: usize,
|
||||||
pub holding_count: usize,
|
pub holding_count: usize,
|
||||||
pub notes: String,
|
pub notes: String,
|
||||||
@@ -1716,11 +1717,20 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<BacktestResult, BacktestError> {
|
pub fn run(&mut self) -> Result<BacktestResult, BacktestError> {
|
||||||
self.run_with_progress(|_| {})
|
self.run_with_progress_options(false, false, |_| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_progress<F>(
|
pub fn run_with_progress<F>(&mut self, on_progress: F) -> Result<BacktestResult, BacktestError>
|
||||||
|
where
|
||||||
|
F: FnMut(&BacktestDayProgress),
|
||||||
|
{
|
||||||
|
self.run_with_progress_options(true, true, on_progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_progress_options<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
include_progress_details: bool,
|
||||||
|
include_progress_diagnostics: bool,
|
||||||
mut on_progress: F,
|
mut on_progress: F,
|
||||||
) -> Result<BacktestResult, BacktestError>
|
) -> Result<BacktestResult, BacktestError>
|
||||||
where
|
where
|
||||||
@@ -1866,6 +1876,8 @@ where
|
|||||||
execution_date,
|
execution_date,
|
||||||
execution_date,
|
execution_date,
|
||||||
);
|
);
|
||||||
|
let day_order_start = result.order_events.len();
|
||||||
|
let day_fill_start = result.fills.len();
|
||||||
|
|
||||||
let decision_slot = decision_slots.get(execution_idx).copied().flatten();
|
let decision_slot = decision_slots.get(execution_idx).copied().flatten();
|
||||||
let Some((decision_index, decision_date)) = decision_slot else {
|
let Some((decision_index, decision_date)) = decision_slot else {
|
||||||
@@ -1884,13 +1896,13 @@ where
|
|||||||
let futures_expiration_report = self.settle_futures_expirations(execution_date);
|
let futures_expiration_report = self.settle_futures_expirations(execution_date);
|
||||||
merge_broker_report(&mut report, futures_expiration_report);
|
merge_broker_report(&mut report, futures_expiration_report);
|
||||||
|
|
||||||
let daily_fill_count = report.fill_events.len();
|
let broker_diagnostics = std::mem::take(&mut report.diagnostics);
|
||||||
let day_orders = report.order_events.clone();
|
let execution_risk_decisions =
|
||||||
let day_fills = report.fill_events.clone();
|
risk_decisions_from_order_events(&report.order_events);
|
||||||
let broker_diagnostics = report.diagnostics.clone();
|
|
||||||
let execution_risk_decisions = risk_decisions_from_order_events(&day_orders);
|
|
||||||
self.extend_result(&mut result, report, execution_date, execution_date);
|
self.extend_result(&mut result, report, execution_date, execution_date);
|
||||||
result.risk_decisions.extend(execution_risk_decisions);
|
result.risk_decisions.extend(execution_risk_decisions);
|
||||||
|
let daily_fill_count = result.fills.len() - day_fill_start;
|
||||||
|
let daily_order_count = result.order_events.len() - day_order_start;
|
||||||
|
|
||||||
let benchmark =
|
let benchmark =
|
||||||
self.data
|
self.data
|
||||||
@@ -1907,7 +1919,11 @@ where
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" | ");
|
.join(" | ");
|
||||||
let holdings_for_day = portfolio.holdings_summary(execution_date);
|
let holdings_for_day = portfolio.holdings_summary(execution_date);
|
||||||
let day_process_events = process_events.clone();
|
let holding_start = result.daily_holdings.len();
|
||||||
|
let holding_count = holdings_for_day.len();
|
||||||
|
result.daily_holdings.extend(holdings_for_day);
|
||||||
|
let progress_process_start = result.process_events.len();
|
||||||
|
result.process_events.append(&mut process_events);
|
||||||
let aggregate_cash = self.aggregate_cash(&portfolio);
|
let aggregate_cash = self.aggregate_cash(&portfolio);
|
||||||
let aggregate_market_value = self.aggregate_market_value(&portfolio);
|
let aggregate_market_value = self.aggregate_market_value(&portfolio);
|
||||||
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
||||||
@@ -1928,7 +1944,6 @@ where
|
|||||||
notes,
|
notes,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
});
|
});
|
||||||
result.daily_holdings.extend(holdings_for_day.clone());
|
|
||||||
let latest = result
|
let latest = result
|
||||||
.equity_curve
|
.equity_curve
|
||||||
.last()
|
.last()
|
||||||
@@ -1943,16 +1958,28 @@ where
|
|||||||
total_return: latest.unit_nav - 1.0,
|
total_return: latest.unit_nav - 1.0,
|
||||||
benchmark_close: latest.benchmark_close,
|
benchmark_close: latest.benchmark_close,
|
||||||
daily_fill_count,
|
daily_fill_count,
|
||||||
|
daily_order_count,
|
||||||
cumulative_trade_count: result.fills.len(),
|
cumulative_trade_count: result.fills.len(),
|
||||||
holding_count: holdings_for_day.len(),
|
holding_count,
|
||||||
notes: latest.notes.clone(),
|
notes: include_progress_diagnostics
|
||||||
diagnostics: latest.diagnostics.clone(),
|
.then(|| latest.notes.clone())
|
||||||
orders: day_orders,
|
.unwrap_or_default(),
|
||||||
fills: day_fills,
|
diagnostics: include_progress_diagnostics
|
||||||
holdings: holdings_for_day,
|
.then(|| latest.diagnostics.clone())
|
||||||
process_events: day_process_events,
|
.unwrap_or_default(),
|
||||||
|
orders: include_progress_details
|
||||||
|
.then(|| result.order_events[day_order_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
fills: include_progress_details
|
||||||
|
.then(|| result.fills[day_fill_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
holdings: include_progress_details
|
||||||
|
.then(|| result.daily_holdings[holding_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
process_events: include_progress_details
|
||||||
|
.then(|| result.process_events[progress_process_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
result.process_events.append(&mut process_events);
|
|
||||||
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -2683,18 +2710,13 @@ where
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let post_trade_open_orders = self.open_order_views();
|
let post_trade_open_orders = self.open_order_views();
|
||||||
let visible_order_events = result
|
let mut broker_diagnostics = std::mem::take(&mut report.diagnostics);
|
||||||
.order_events
|
self.extend_result(
|
||||||
.iter()
|
&mut result,
|
||||||
.cloned()
|
std::mem::take(&mut report),
|
||||||
.chain(report.order_events.iter().cloned())
|
decision_date,
|
||||||
.collect::<Vec<_>>();
|
execution_date,
|
||||||
let visible_fills = result
|
);
|
||||||
.fills
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(report.fill_events.iter().cloned())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
publish_phase_event(
|
publish_phase_event(
|
||||||
&mut self.strategy,
|
&mut self.strategy,
|
||||||
&mut self.process_event_bus,
|
&mut self.process_event_bus,
|
||||||
@@ -2728,8 +2750,8 @@ where
|
|||||||
decision_date,
|
decision_date,
|
||||||
default_stage_time(ScheduleStage::AfterTrading),
|
default_stage_time(ScheduleStage::AfterTrading),
|
||||||
),
|
),
|
||||||
order_events: visible_order_events.as_slice(),
|
order_events: result.order_events.as_slice(),
|
||||||
fills: visible_fills.as_slice(),
|
fills: result.fills.as_slice(),
|
||||||
})?;
|
})?;
|
||||||
publish_phase_event(
|
publish_phase_event(
|
||||||
&mut self.strategy,
|
&mut self.strategy,
|
||||||
@@ -2765,8 +2787,8 @@ where
|
|||||||
&mut process_events,
|
&mut process_events,
|
||||||
&mut self.process_event_bus,
|
&mut self.process_event_bus,
|
||||||
default_stage_time(ScheduleStage::AfterTrading),
|
default_stage_time(ScheduleStage::AfterTrading),
|
||||||
visible_order_events.as_slice(),
|
result.order_events.as_slice(),
|
||||||
visible_fills.as_slice(),
|
result.fills.as_slice(),
|
||||||
)?;
|
)?;
|
||||||
self.apply_strategy_directives(
|
self.apply_strategy_directives(
|
||||||
execution_date,
|
execution_date,
|
||||||
@@ -2800,18 +2822,13 @@ where
|
|||||||
report.account_events.extend(close_report.account_events);
|
report.account_events.extend(close_report.account_events);
|
||||||
report.diagnostics.extend(close_report.diagnostics);
|
report.diagnostics.extend(close_report.diagnostics);
|
||||||
let post_close_open_orders = self.open_order_views();
|
let post_close_open_orders = self.open_order_views();
|
||||||
let visible_order_events_after_close = result
|
broker_diagnostics.append(&mut report.diagnostics);
|
||||||
.order_events
|
self.extend_result(
|
||||||
.iter()
|
&mut result,
|
||||||
.cloned()
|
std::mem::take(&mut report),
|
||||||
.chain(report.order_events.iter().cloned())
|
decision_date,
|
||||||
.collect::<Vec<_>>();
|
execution_date,
|
||||||
let visible_fills_after_close = result
|
);
|
||||||
.fills
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(report.fill_events.iter().cloned())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
publish_phase_event(
|
publish_phase_event(
|
||||||
&mut self.strategy,
|
&mut self.strategy,
|
||||||
&mut self.process_event_bus,
|
&mut self.process_event_bus,
|
||||||
@@ -2862,8 +2879,8 @@ where
|
|||||||
decision_date,
|
decision_date,
|
||||||
default_stage_time(ScheduleStage::Settlement),
|
default_stage_time(ScheduleStage::Settlement),
|
||||||
),
|
),
|
||||||
order_events: visible_order_events_after_close.as_slice(),
|
order_events: result.order_events.as_slice(),
|
||||||
fills: visible_fills_after_close.as_slice(),
|
fills: result.fills.as_slice(),
|
||||||
})?;
|
})?;
|
||||||
publish_phase_event(
|
publish_phase_event(
|
||||||
&mut self.strategy,
|
&mut self.strategy,
|
||||||
@@ -2899,8 +2916,8 @@ where
|
|||||||
&mut process_events,
|
&mut process_events,
|
||||||
&mut self.process_event_bus,
|
&mut self.process_event_bus,
|
||||||
default_stage_time(ScheduleStage::Settlement),
|
default_stage_time(ScheduleStage::Settlement),
|
||||||
visible_order_events_after_close.as_slice(),
|
result.order_events.as_slice(),
|
||||||
visible_fills_after_close.as_slice(),
|
result.fills.as_slice(),
|
||||||
)?;
|
)?;
|
||||||
self.apply_strategy_directives(
|
self.apply_strategy_directives(
|
||||||
execution_date,
|
execution_date,
|
||||||
@@ -2927,8 +2944,8 @@ where
|
|||||||
dynamic_universe_snapshot.as_ref(),
|
dynamic_universe_snapshot.as_ref(),
|
||||||
&subscriptions_snapshot,
|
&subscriptions_snapshot,
|
||||||
&mut process_events,
|
&mut process_events,
|
||||||
visible_order_events_after_close.as_slice(),
|
result.order_events.as_slice(),
|
||||||
visible_fills_after_close.as_slice(),
|
result.fills.as_slice(),
|
||||||
)?;
|
)?;
|
||||||
merge_broker_report(&mut directive_report, management_fee_report);
|
merge_broker_report(&mut directive_report, management_fee_report);
|
||||||
publish_phase_event(
|
publish_phase_event(
|
||||||
@@ -2949,12 +2966,17 @@ where
|
|||||||
"settlement:post",
|
"settlement:post",
|
||||||
)?;
|
)?;
|
||||||
merge_broker_report(&mut report, directive_report);
|
merge_broker_report(&mut report, directive_report);
|
||||||
let daily_fill_count = report.fill_events.len();
|
broker_diagnostics.append(&mut report.diagnostics);
|
||||||
let day_orders = report.order_events.clone();
|
self.extend_result(
|
||||||
let day_fills = report.fill_events.clone();
|
&mut result,
|
||||||
let broker_diagnostics = report.diagnostics.clone();
|
std::mem::take(&mut report),
|
||||||
let execution_risk_decisions = risk_decisions_from_order_events(&day_orders);
|
decision_date,
|
||||||
self.extend_result(&mut result, report, decision_date, execution_date);
|
execution_date,
|
||||||
|
);
|
||||||
|
let daily_fill_count = result.fills.len() - day_fill_start;
|
||||||
|
let daily_order_count = result.order_events.len() - day_order_start;
|
||||||
|
let execution_risk_decisions =
|
||||||
|
risk_decisions_from_order_events(&result.order_events[day_order_start..]);
|
||||||
result.risk_decisions.extend(decision.risk_decisions);
|
result.risk_decisions.extend(decision.risk_decisions);
|
||||||
result.risk_decisions.extend(execution_risk_decisions);
|
result.risk_decisions.extend(execution_risk_decisions);
|
||||||
|
|
||||||
@@ -2976,7 +2998,11 @@ where
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" | ");
|
.join(" | ");
|
||||||
let holdings_for_day = portfolio.holdings_summary(execution_date);
|
let holdings_for_day = portfolio.holdings_summary(execution_date);
|
||||||
let day_process_events = process_events.clone();
|
let holding_start = result.daily_holdings.len();
|
||||||
|
let holding_count = holdings_for_day.len();
|
||||||
|
result.daily_holdings.extend(holdings_for_day);
|
||||||
|
let progress_process_start = result.process_events.len();
|
||||||
|
result.process_events.append(&mut process_events);
|
||||||
let aggregate_cash = self.aggregate_cash(&portfolio);
|
let aggregate_cash = self.aggregate_cash(&portfolio);
|
||||||
let aggregate_market_value = self.aggregate_market_value(&portfolio);
|
let aggregate_market_value = self.aggregate_market_value(&portfolio);
|
||||||
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
||||||
@@ -2997,7 +3023,6 @@ where
|
|||||||
notes,
|
notes,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
});
|
});
|
||||||
result.daily_holdings.extend(holdings_for_day.clone());
|
|
||||||
let latest = result
|
let latest = result
|
||||||
.equity_curve
|
.equity_curve
|
||||||
.last()
|
.last()
|
||||||
@@ -3012,16 +3037,28 @@ where
|
|||||||
total_return: latest.unit_nav - 1.0,
|
total_return: latest.unit_nav - 1.0,
|
||||||
benchmark_close: latest.benchmark_close,
|
benchmark_close: latest.benchmark_close,
|
||||||
daily_fill_count,
|
daily_fill_count,
|
||||||
|
daily_order_count,
|
||||||
cumulative_trade_count: result.fills.len(),
|
cumulative_trade_count: result.fills.len(),
|
||||||
holding_count: holdings_for_day.len(),
|
holding_count,
|
||||||
notes: latest.notes.clone(),
|
notes: include_progress_diagnostics
|
||||||
diagnostics: latest.diagnostics.clone(),
|
.then(|| latest.notes.clone())
|
||||||
orders: day_orders,
|
.unwrap_or_default(),
|
||||||
fills: day_fills,
|
diagnostics: include_progress_diagnostics
|
||||||
holdings: holdings_for_day,
|
.then(|| latest.diagnostics.clone())
|
||||||
process_events: day_process_events,
|
.unwrap_or_default(),
|
||||||
|
orders: include_progress_details
|
||||||
|
.then(|| result.order_events[day_order_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
fills: include_progress_details
|
||||||
|
.then(|| result.fills[day_fill_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
holdings: include_progress_details
|
||||||
|
.then(|| result.daily_holdings[holding_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
process_events: include_progress_details
|
||||||
|
.then(|| result.process_events[progress_process_start..].to_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
result.process_events.extend(process_events);
|
|
||||||
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3045,16 +3082,13 @@ where
|
|||||||
mut report: BrokerExecutionReport,
|
mut report: BrokerExecutionReport,
|
||||||
decision_date: NaiveDate,
|
decision_date: NaiveDate,
|
||||||
execution_date: NaiveDate,
|
execution_date: NaiveDate,
|
||||||
) -> BrokerExecutionReport {
|
) {
|
||||||
annotate_broker_report_dates(&mut report, decision_date, decision_date, execution_date);
|
annotate_broker_report_dates(&mut report, decision_date, decision_date, execution_date);
|
||||||
result.order_events.extend(report.order_events.clone());
|
result.order_events.append(&mut report.order_events);
|
||||||
result.fills.extend(report.fill_events.clone());
|
result.fills.append(&mut report.fill_events);
|
||||||
result
|
result.position_events.append(&mut report.position_events);
|
||||||
.position_events
|
result.account_events.append(&mut report.account_events);
|
||||||
.extend(report.position_events.clone());
|
result.process_events.append(&mut report.process_events);
|
||||||
result.account_events.extend(report.account_events.clone());
|
|
||||||
result.process_events.extend(report.process_events.clone());
|
|
||||||
report
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_corporate_actions(
|
fn apply_corporate_actions(
|
||||||
@@ -4872,11 +4906,12 @@ mod tests {
|
|||||||
.expect("dataset")
|
.expect("dataset")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_with_matching(
|
fn engine_with_matching(
|
||||||
matching_type: MatchingType,
|
matching_type: MatchingType,
|
||||||
execution_price_field: PriceField,
|
execution_price_field: PriceField,
|
||||||
decision_lag_trading_days: usize,
|
decision_lag_trading_days: usize,
|
||||||
) -> super::BacktestResult {
|
) -> BacktestEngine<BuyWhenDecisionDateStrategy, ChinaAShareCostModel, ChinaEquityRuleHooks>
|
||||||
|
{
|
||||||
let first = d(2025, 1, 2);
|
let first = d(2025, 1, 2);
|
||||||
let broker = BrokerSimulator::new_with_execution_price(
|
let broker = BrokerSimulator::new_with_execution_price(
|
||||||
ChinaAShareCostModel::default(),
|
ChinaAShareCostModel::default(),
|
||||||
@@ -4904,10 +4939,66 @@ mod tests {
|
|||||||
broker,
|
broker,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_with_matching(
|
||||||
|
matching_type: MatchingType,
|
||||||
|
execution_price_field: PriceField,
|
||||||
|
decision_lag_trading_days: usize,
|
||||||
|
) -> super::BacktestResult {
|
||||||
|
engine_with_matching(
|
||||||
|
matching_type,
|
||||||
|
execution_price_field,
|
||||||
|
decision_lag_trading_days,
|
||||||
|
)
|
||||||
.run()
|
.run()
|
||||||
.expect("backtest run")
|
.expect("backtest run")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_progress_keeps_counts_without_event_payload_clones() {
|
||||||
|
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, 0);
|
||||||
|
let mut progress = Vec::new();
|
||||||
|
let result = engine
|
||||||
|
.run_with_progress_options(false, false, |event| {
|
||||||
|
progress.push((
|
||||||
|
event.daily_order_count,
|
||||||
|
event.daily_fill_count,
|
||||||
|
event.orders.len(),
|
||||||
|
event.fills.len(),
|
||||||
|
event.holdings.len(),
|
||||||
|
event.process_events.len(),
|
||||||
|
event.notes.len(),
|
||||||
|
event.diagnostics.len(),
|
||||||
|
));
|
||||||
|
})
|
||||||
|
.expect("compact progress run");
|
||||||
|
assert!(!progress.is_empty());
|
||||||
|
assert!(
|
||||||
|
progress
|
||||||
|
.iter()
|
||||||
|
.any(|(orders, fills, ..)| *orders > 0 && *fills > 0)
|
||||||
|
);
|
||||||
|
assert!(progress.iter().all(
|
||||||
|
|(_, _, orders, fills, holdings, process_events, notes, diagnostics)| {
|
||||||
|
*orders == 0
|
||||||
|
&& *fills == 0
|
||||||
|
&& *holdings == 0
|
||||||
|
&& *process_events == 0
|
||||||
|
&& *notes == 0
|
||||||
|
&& *diagnostics == 0
|
||||||
|
}
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
progress.iter().map(|value| value.0).sum::<usize>(),
|
||||||
|
result.order_events.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
progress.iter().map(|value| value.1).sum::<usize>(),
|
||||||
|
result.fills.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn full_day_coverage_engine(
|
fn full_day_coverage_engine(
|
||||||
data: DataSet,
|
data: DataSet,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
|
|||||||
Reference in New Issue
Block a user