移除引擎历史事件重复复制
This commit is contained in:
+169
-78
@@ -319,6 +319,7 @@ pub struct BacktestDayProgress {
|
||||
pub total_return: f64,
|
||||
pub benchmark_close: f64,
|
||||
pub daily_fill_count: usize,
|
||||
pub daily_order_count: usize,
|
||||
pub cumulative_trade_count: usize,
|
||||
pub holding_count: usize,
|
||||
pub notes: String,
|
||||
@@ -1716,11 +1717,20 @@ where
|
||||
}
|
||||
|
||||
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,
|
||||
include_progress_details: bool,
|
||||
include_progress_diagnostics: bool,
|
||||
mut on_progress: F,
|
||||
) -> Result<BacktestResult, BacktestError>
|
||||
where
|
||||
@@ -1866,6 +1876,8 @@ where
|
||||
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 Some((decision_index, decision_date)) = decision_slot else {
|
||||
@@ -1884,13 +1896,13 @@ where
|
||||
let futures_expiration_report = self.settle_futures_expirations(execution_date);
|
||||
merge_broker_report(&mut report, futures_expiration_report);
|
||||
|
||||
let daily_fill_count = report.fill_events.len();
|
||||
let day_orders = report.order_events.clone();
|
||||
let day_fills = report.fill_events.clone();
|
||||
let broker_diagnostics = report.diagnostics.clone();
|
||||
let execution_risk_decisions = risk_decisions_from_order_events(&day_orders);
|
||||
let broker_diagnostics = std::mem::take(&mut report.diagnostics);
|
||||
let execution_risk_decisions =
|
||||
risk_decisions_from_order_events(&report.order_events);
|
||||
self.extend_result(&mut result, report, execution_date, execution_date);
|
||||
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 =
|
||||
self.data
|
||||
@@ -1907,7 +1919,11 @@ where
|
||||
.collect::<Vec<_>>()
|
||||
.join(" | ");
|
||||
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_market_value = self.aggregate_market_value(&portfolio);
|
||||
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
||||
@@ -1928,7 +1944,6 @@ where
|
||||
notes,
|
||||
diagnostics,
|
||||
});
|
||||
result.daily_holdings.extend(holdings_for_day.clone());
|
||||
let latest = result
|
||||
.equity_curve
|
||||
.last()
|
||||
@@ -1943,16 +1958,28 @@ where
|
||||
total_return: latest.unit_nav - 1.0,
|
||||
benchmark_close: latest.benchmark_close,
|
||||
daily_fill_count,
|
||||
daily_order_count,
|
||||
cumulative_trade_count: result.fills.len(),
|
||||
holding_count: holdings_for_day.len(),
|
||||
notes: latest.notes.clone(),
|
||||
diagnostics: latest.diagnostics.clone(),
|
||||
orders: day_orders,
|
||||
fills: day_fills,
|
||||
holdings: holdings_for_day,
|
||||
process_events: day_process_events,
|
||||
holding_count,
|
||||
notes: include_progress_diagnostics
|
||||
.then(|| latest.notes.clone())
|
||||
.unwrap_or_default(),
|
||||
diagnostics: include_progress_diagnostics
|
||||
.then(|| latest.diagnostics.clone())
|
||||
.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());
|
||||
continue;
|
||||
};
|
||||
@@ -2683,18 +2710,13 @@ where
|
||||
)?;
|
||||
|
||||
let post_trade_open_orders = self.open_order_views();
|
||||
let visible_order_events = result
|
||||
.order_events
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(report.order_events.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
let visible_fills = result
|
||||
.fills
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(report.fill_events.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
let mut broker_diagnostics = std::mem::take(&mut report.diagnostics);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
std::mem::take(&mut report),
|
||||
decision_date,
|
||||
execution_date,
|
||||
);
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&mut self.process_event_bus,
|
||||
@@ -2728,8 +2750,8 @@ where
|
||||
decision_date,
|
||||
default_stage_time(ScheduleStage::AfterTrading),
|
||||
),
|
||||
order_events: visible_order_events.as_slice(),
|
||||
fills: visible_fills.as_slice(),
|
||||
order_events: result.order_events.as_slice(),
|
||||
fills: result.fills.as_slice(),
|
||||
})?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
@@ -2765,8 +2787,8 @@ where
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::AfterTrading),
|
||||
visible_order_events.as_slice(),
|
||||
visible_fills.as_slice(),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
self.apply_strategy_directives(
|
||||
execution_date,
|
||||
@@ -2800,18 +2822,13 @@ where
|
||||
report.account_events.extend(close_report.account_events);
|
||||
report.diagnostics.extend(close_report.diagnostics);
|
||||
let post_close_open_orders = self.open_order_views();
|
||||
let visible_order_events_after_close = result
|
||||
.order_events
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(report.order_events.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
let visible_fills_after_close = result
|
||||
.fills
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(report.fill_events.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
broker_diagnostics.append(&mut report.diagnostics);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
std::mem::take(&mut report),
|
||||
decision_date,
|
||||
execution_date,
|
||||
);
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&mut self.process_event_bus,
|
||||
@@ -2862,8 +2879,8 @@ where
|
||||
decision_date,
|
||||
default_stage_time(ScheduleStage::Settlement),
|
||||
),
|
||||
order_events: visible_order_events_after_close.as_slice(),
|
||||
fills: visible_fills_after_close.as_slice(),
|
||||
order_events: result.order_events.as_slice(),
|
||||
fills: result.fills.as_slice(),
|
||||
})?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
@@ -2899,8 +2916,8 @@ where
|
||||
&mut process_events,
|
||||
&mut self.process_event_bus,
|
||||
default_stage_time(ScheduleStage::Settlement),
|
||||
visible_order_events_after_close.as_slice(),
|
||||
visible_fills_after_close.as_slice(),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
self.apply_strategy_directives(
|
||||
execution_date,
|
||||
@@ -2927,8 +2944,8 @@ where
|
||||
dynamic_universe_snapshot.as_ref(),
|
||||
&subscriptions_snapshot,
|
||||
&mut process_events,
|
||||
visible_order_events_after_close.as_slice(),
|
||||
visible_fills_after_close.as_slice(),
|
||||
result.order_events.as_slice(),
|
||||
result.fills.as_slice(),
|
||||
)?;
|
||||
merge_broker_report(&mut directive_report, management_fee_report);
|
||||
publish_phase_event(
|
||||
@@ -2949,12 +2966,17 @@ where
|
||||
"settlement:post",
|
||||
)?;
|
||||
merge_broker_report(&mut report, directive_report);
|
||||
let daily_fill_count = report.fill_events.len();
|
||||
let day_orders = report.order_events.clone();
|
||||
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, decision_date, execution_date);
|
||||
broker_diagnostics.append(&mut report.diagnostics);
|
||||
self.extend_result(
|
||||
&mut result,
|
||||
std::mem::take(&mut report),
|
||||
decision_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(execution_risk_decisions);
|
||||
|
||||
@@ -2976,7 +2998,11 @@ where
|
||||
.collect::<Vec<_>>()
|
||||
.join(" | ");
|
||||
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_market_value = self.aggregate_market_value(&portfolio);
|
||||
let aggregate_total_equity = self.aggregate_total_equity(&portfolio);
|
||||
@@ -2997,7 +3023,6 @@ where
|
||||
notes,
|
||||
diagnostics,
|
||||
});
|
||||
result.daily_holdings.extend(holdings_for_day.clone());
|
||||
let latest = result
|
||||
.equity_curve
|
||||
.last()
|
||||
@@ -3012,16 +3037,28 @@ where
|
||||
total_return: latest.unit_nav - 1.0,
|
||||
benchmark_close: latest.benchmark_close,
|
||||
daily_fill_count,
|
||||
daily_order_count,
|
||||
cumulative_trade_count: result.fills.len(),
|
||||
holding_count: holdings_for_day.len(),
|
||||
notes: latest.notes.clone(),
|
||||
diagnostics: latest.diagnostics.clone(),
|
||||
orders: day_orders,
|
||||
fills: day_fills,
|
||||
holdings: holdings_for_day,
|
||||
process_events: day_process_events,
|
||||
holding_count,
|
||||
notes: include_progress_diagnostics
|
||||
.then(|| latest.notes.clone())
|
||||
.unwrap_or_default(),
|
||||
diagnostics: include_progress_diagnostics
|
||||
.then(|| latest.diagnostics.clone())
|
||||
.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());
|
||||
}
|
||||
|
||||
@@ -3045,16 +3082,13 @@ where
|
||||
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
|
||||
.position_events
|
||||
.extend(report.position_events.clone());
|
||||
result.account_events.extend(report.account_events.clone());
|
||||
result.process_events.extend(report.process_events.clone());
|
||||
report
|
||||
result.order_events.append(&mut report.order_events);
|
||||
result.fills.append(&mut report.fill_events);
|
||||
result.position_events.append(&mut report.position_events);
|
||||
result.account_events.append(&mut report.account_events);
|
||||
result.process_events.append(&mut report.process_events);
|
||||
}
|
||||
|
||||
fn apply_corporate_actions(
|
||||
@@ -4872,11 +4906,12 @@ mod tests {
|
||||
.expect("dataset")
|
||||
}
|
||||
|
||||
fn run_with_matching(
|
||||
fn engine_with_matching(
|
||||
matching_type: MatchingType,
|
||||
execution_price_field: PriceField,
|
||||
decision_lag_trading_days: usize,
|
||||
) -> super::BacktestResult {
|
||||
) -> BacktestEngine<BuyWhenDecisionDateStrategy, ChinaAShareCostModel, ChinaEquityRuleHooks>
|
||||
{
|
||||
let first = d(2025, 1, 2);
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
@@ -4904,10 +4939,66 @@ mod tests {
|
||||
broker,
|
||||
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()
|
||||
.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(
|
||||
data: DataSet,
|
||||
date: NaiveDate,
|
||||
|
||||
Reference in New Issue
Block a user