merge: integrate causal capacity model with current order clocks and intent planning

This commit is contained in:
boris
2026-09-12 08:52:37 +08:00
13 changed files with 332 additions and 196 deletions
+21 -2
View File
@@ -122,6 +122,7 @@ impl DailyEquityPoint {
#[derive(Debug, Clone)]
pub struct BacktestResult {
pub capacity_audit: crate::execution_capacity::CapacityAuditSummary,
pub strategy_name: String,
pub equity_curve: Vec<DailyEquityPoint>,
pub benchmark_series: Vec<BenchmarkSnapshot>,
@@ -280,6 +281,7 @@ pub struct AnalyzerRiskSummary {
#[derive(Debug, Clone, Serialize)]
pub struct AnalyzerReport {
pub capacity_audit: crate::execution_capacity::CapacityAuditSummary,
pub strategy_name: String,
pub trades: Vec<AnalyzerTradeRow>,
pub positions: Vec<AnalyzerPositionRow>,
@@ -294,6 +296,7 @@ pub struct AnalyzerReport {
impl BacktestResult {
pub fn analyzer_report(&self) -> AnalyzerReport {
AnalyzerReport {
capacity_audit: self.capacity_audit.clone(),
strategy_name: self.strategy_name.clone(),
trades: self
.fills
@@ -2102,6 +2105,7 @@ where
.map(|(execution_date, _)| *execution_date)
.collect::<Vec<_>>();
let mut result = BacktestResult {
capacity_audit: self.broker.capacity_audit_summary(),
strategy_name: self.strategy.name().to_string(),
benchmark_series: self
.data
@@ -3423,6 +3427,16 @@ where
execution_date,
);
let daily_fill_count = result.fills.len() - day_fill_start;
for audit in self.broker.audit_completed_session_capacity(execution_date, &self.data)? {
result.capacity_audit.observe(&audit);
// Keep every audit in the durable event store, independent of
// debug phase retention. It never changes earlier executions.
result.process_events.push(ProcessEvent {
date: execution_date, kind: ProcessEventKind::SessionCapacityAudit,
order_id: None, symbol: Some(audit.symbol.clone()), side: None,
detail: serde_json::to_string(&audit).map_err(|error| BacktestError::Execution(error.to_string()))?,
});
}
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..]);
@@ -7038,6 +7052,7 @@ mod tests {
let third = d(2025, 1, 6);
let fourth = d(2025, 1, 7);
let broker = scheduled_next_open_broker(FidcRiskControlConfig::default())
.with_volume_capacity_mode(crate::execution_capacity::VolumeCapacityMode::SessionCapacityAudit)
.with_volume_limit(true)
.with_volume_percent(0.25);
let result = run_scheduled_round_trip_next_open_with_dataset_and_broker(
@@ -7065,12 +7080,13 @@ mod tests {
}
#[test]
fn next_bar_open_sell_volume_limit_rejects_execution_day_zero_volume() {
fn next_bar_open_session_audit_flags_zero_volume_without_rewriting_fills() {
let first = d(2025, 1, 2);
let second = d(2025, 1, 3);
let third = d(2025, 1, 6);
let fourth = d(2025, 1, 7);
let broker = scheduled_next_open_broker(FidcRiskControlConfig::default())
.with_volume_capacity_mode(crate::execution_capacity::VolumeCapacityMode::SessionCapacityAudit)
.with_volume_limit(true)
.with_volume_percent(0.25);
let result = run_scheduled_round_trip_next_open_with_dataset_and_broker(
@@ -7091,7 +7107,10 @@ mod tests {
broker,
);
assert_round_trip_sell_canceled_with_reason(&result, "daily volume limit");
assert!(result.fills.iter().any(|fill| fill.side == OrderSide::Sell && fill.date == fourth));
assert_eq!(result.capacity_audit.audit_passed, Some(false));
assert_eq!(result.capacity_audit.failed_symbol_sessions, 1);
assert!(result.process_events.iter().any(|event| event.kind == crate::ProcessEventKind::SessionCapacityAudit));
}
#[test]