移除引擎历史事件重复复制

This commit is contained in:
boris
2026-08-25 08:34:06 +08:00
parent 5482c8a52d
commit 85cfdca14c
+169 -78
View File
@@ -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,