//! DQN Backtest Validation Script //! //! Validates trained DQN checkpoints against production criteria: //! - Sharpe ratio > 2.0 //! - Win rate > 55% //! - Max drawdown < 20% //! //! # Usage //! //! ```bash //! # Evaluate single checkpoint //! cargo run -p ml --example backtest_dqn --release --features cuda -- \ //! --checkpoint ml/trained_models/dqn_epoch_5.safetensors \ //! --data test_data/ES_FUT_180d.parquet //! //! # Compare against baseline //! cargo run -p ml --example backtest_dqn --release --features cuda -- \ //! --checkpoint ml/trained_models/dqn_epoch_5.safetensors \ //! --baseline ml/trained_models/dqn_baseline.safetensors \ //! --data test_data/ES_FUT_180d.parquet //! //! # Export results to JSON //! cargo run -p ml --example backtest_dqn --release --features cuda -- \ //! --checkpoint ml/trained_models/dqn_epoch_5.safetensors \ //! --data test_data/ES_FUT_180d.parquet \ //! --output-json backtest_results.json //! //! # Export markdown report //! cargo run -p ml --example backtest_dqn --release --features cuda -- \ //! --checkpoint ml/trained_models/dqn_epoch_5.safetensors \ //! --baseline ml/trained_models/dqn_baseline.safetensors \ //! --data test_data/ES_FUT_180d.parquet \ //! --output-markdown backtest_report.md //! ``` //! //! # Architecture //! //! ```text //! ┌─────────────────────────────────────────────────────────────────┐ //! │ BACKTEST VALIDATION PIPELINE │ //! │ │ //! │ 1. LOAD CHECKPOINTS │ //! │ ├─ Primary checkpoint (required) │ //! │ └─ Baseline checkpoint (optional) │ //! │ │ //! │ 2. LOAD VALIDATION DATA │ //! │ ├─ Parquet file → OHLCV bars │ //! │ └─ Extract 128-dim features (Wave D) │ //! │ │ //! │ 3. RUN BACKTEST │ //! │ ├─ Primary model inference (greedy actions) │ //! │ ├─ Baseline model inference (if provided) │ //! │ ├─ Execute trades via EvaluationEngine │ //! │ └─ Record trade history │ //! │ │ //! │ 4. CALCULATE METRICS │ //! │ ├─ Sharpe ratio (risk-adjusted return) │ //! │ ├─ Win rate (% profitable trades) │ //! │ ├─ Max drawdown (peak-to-trough decline) │ //! │ └─ Total return (% gain/loss) │ //! │ │ //! │ 5. VALIDATE CRITERIA │ //! │ ├─ Sharpe > 2.0 ✅/❌ │ //! │ ├─ Win rate > 55% ✅/❌ │ //! │ └─ Drawdown < 20% ✅/❌ │ //! │ │ //! │ 6. GENERATE REPORT │ //! │ ├─ Console output (always) │ //! │ ├─ JSON output (--output-json) │ //! │ └─ Markdown report (--output-markdown) │ //! └─────────────────────────────────────────────────────────────────┘ //! ``` use anyhow::{Context, Result}; use candle_core::{Device, Tensor}; use clap::Parser; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::time::Instant; use tracing::{info, warn}; use tracing_subscriber::FmtSubscriber; use ml::data_loaders::load_parquet_data_with_timestamps; use ml::dqn::dqn::{WorkingDQN, WorkingDQNConfig}; use ml::evaluation::{EvaluationEngine, PerformanceMetrics}; use ml::features::extraction::OHLCVBar; use ml::preprocessing::{preprocess_prices, PreprocessConfig}; // ============================================================================ // CLI Configuration // ============================================================================ /// Backtest validation configuration #[derive(Parser, Debug)] #[command( name = "backtest_dqn", about = "Validate DQN checkpoint against production criteria", long_about = "Comprehensive backtest validation pipeline with Sharpe, win rate, \ and drawdown metrics. Compares against baseline if provided." )] struct BacktestConfig { /// Path to DQN checkpoint to validate (SafeTensors format) #[arg(long, required = true)] checkpoint: PathBuf, /// Path to baseline checkpoint for comparison (optional) #[arg(long)] baseline: Option, /// Path to validation data (Parquet format) #[arg(long, required = true)] data: PathBuf, /// Device selection: cpu, cuda, or auto #[arg(long, default_value = "auto")] device: String, /// Initial capital for backtest (default: $100,000) #[arg(long, default_value = "100000.0")] initial_capital: f32, /// Warmup bars to skip (insufficient feature history) #[arg(long, default_value = "50")] warmup_bars: usize, /// Output JSON file path (optional) #[arg(long)] output_json: Option, /// Output markdown report path (optional) #[arg(long)] output_markdown: Option, /// Verbose logging (DEBUG level) #[arg(short, long)] verbose: bool, /// Success criteria: minimum Sharpe ratio #[arg(long, default_value = "2.0")] min_sharpe: f64, /// Success criteria: minimum win rate (%) #[arg(long, default_value = "55.0")] min_win_rate: f64, /// Success criteria: maximum drawdown (%) #[arg(long, default_value = "20.0")] max_drawdown: f64, } impl BacktestConfig { /// Validate configuration parameters fn validate(&self) -> Result<()> { // Validate checkpoint exists if !self.checkpoint.exists() { anyhow::bail!( "Checkpoint file not found: {}\n\ Suggestion: Train a model first using train_dqn example", self.checkpoint.display() ); } // Validate baseline (if specified) if let Some(ref baseline) = self.baseline { if !baseline.exists() { anyhow::bail!("Baseline checkpoint not found: {}", baseline.display()); } } // Validate data file if !self.data.exists() { anyhow::bail!( "Data file not found: {}\n\ Suggestion: Use test_data/ES_FUT_180d.parquet", self.data.display() ); } // Validate device string if !["cpu", "cuda", "auto"].contains(&self.device.as_str()) { anyhow::bail!( "Invalid device: '{}'. Must be: cpu, cuda, or auto", self.device ); } // Validate output paths (if specified) if let Some(ref output_path) = self.output_json { if let Some(parent) = output_path.parent() { if !parent.exists() { anyhow::bail!( "Output JSON directory does not exist: {}\n\ Suggestion: mkdir -p {}", parent.display(), parent.display() ); } } } if let Some(ref output_path) = self.output_markdown { if let Some(parent) = output_path.parent() { if !parent.exists() { anyhow::bail!( "Output markdown directory does not exist: {}\n\ Suggestion: mkdir -p {}", parent.display(), parent.display() ); } } } Ok(()) } } // ============================================================================ // Model Loading // ============================================================================ /// Load DQN checkpoint from SafeTensors file fn load_checkpoint(path: &Path, _device: &Device) -> Result { info!("📦 Loading checkpoint: {}", path.display()); // Create WorkingDQN configuration (matches training architecture) let config = WorkingDQNConfig { state_dim: 128, // Wave D: 128 features hidden_dims: vec![256, 128, 64], // Match trainer architecture num_actions: 3, learning_rate: 0.001, gamma: 0.99, epsilon_start: 0.0, // No exploration during evaluation epsilon_end: 0.0, epsilon_decay: 1.0, replay_buffer_capacity: 1000, batch_size: 32, min_replay_size: 64, target_update_freq: 1000, use_double_dqn: true, use_huber_loss: true, huber_delta: 1.0, leaky_relu_alpha: 0.01, gradient_clip_norm: 10.0, tau: 1.0, use_soft_updates: false, warmup_steps: 0, }; // Create model let mut dqn = WorkingDQN::new(config).context("Failed to create WorkingDQN")?; // Load weights let path_str = path .to_str() .ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?; dqn.load_from_safetensors(path_str) .context(format!("Failed to load checkpoint: {}", path.display()))?; info!("✅ Checkpoint loaded successfully"); Ok(dqn) } // ============================================================================ // Backtest Execution // ============================================================================ /// Run backtest on validation data fn run_backtest( dqn: &mut WorkingDQN, features: &[[f64; 128]], bars: &[OHLCVBar], initial_capital: f32, ) -> Result { info!("🔄 Running backtest ({} bars)...", bars.len()); if features.len() != bars.len() { anyhow::bail!( "Feature/bar mismatch: {} features, {} bars", features.len(), bars.len() ); } let mut engine = EvaluationEngine::new(initial_capital); let start = Instant::now(); // Run inference for each bar for (i, (feature_vec, bar)) in features.iter().zip(bars.iter()).enumerate() { // Convert f64 features to f32 let state_f32: Vec = feature_vec.iter().map(|&x| x as f32).collect(); // Get greedy action (epsilon=0) let trading_action = dqn .select_action(state_f32.as_slice()) .context(format!("Inference failed at bar {}", i))?; // Convert to evaluation Action enum let action = match trading_action.to_index() { 0 => ml::evaluation::engine::Action::Buy, 1 => ml::evaluation::engine::Action::Hold, 2 => ml::evaluation::engine::Action::Sell, _ => ml::evaluation::engine::Action::Hold, }; // Convert OHLCVBar to evaluation OHLCVBar let eval_bar = ml::evaluation::metrics::OHLCVBar { timestamp: bar.timestamp.timestamp(), open: bar.open as f32, high: bar.high as f32, low: bar.low as f32, close: bar.close as f32, volume: bar.volume as f32, }; // Process bar engine.process_bar(i, &eval_bar, action); } // Close any open position at end if engine.current_position.is_some() { let last_bar = &bars[bars.len() - 1]; let eval_bar = ml::evaluation::metrics::OHLCVBar { timestamp: last_bar.timestamp.timestamp(), open: last_bar.open as f32, high: last_bar.high as f32, low: last_bar.low as f32, close: last_bar.close as f32, volume: last_bar.volume as f32, }; engine.close_position(bars.len() - 1, &eval_bar); } let elapsed = start.elapsed(); info!("✅ Backtest complete ({:.2}s)", elapsed.as_secs_f64()); // Calculate performance metrics let eval_bars: Vec = bars .iter() .map(|b| ml::evaluation::metrics::OHLCVBar { timestamp: b.timestamp.timestamp(), open: b.open as f32, high: b.high as f32, low: b.low as f32, close: b.close as f32, volume: b.volume as f32, }) .collect(); let metrics = PerformanceMetrics::from_trades(&engine.trades, initial_capital, &eval_bars); // Log action distribution let dist = engine.get_action_distribution(); info!("📊 Action Distribution:"); info!(" BUY: {} ({:.1}%)", dist.buy_count, dist.buy_pct); info!(" HOLD: {} ({:.1}%)", dist.hold_count, dist.hold_pct); info!(" SELL: {} ({:.1}%)", dist.sell_count, dist.sell_pct); Ok(metrics) } // ============================================================================ // Validation & Reporting // ============================================================================ /// Backtest validation result #[derive(Debug, Clone, Serialize, Deserialize)] struct ValidationResult { checkpoint_name: String, baseline_name: Option, metrics: PerformanceMetrics, baseline_metrics: Option, success_criteria: SuccessCriteria, verdict: Verdict, } #[derive(Debug, Clone, Serialize, Deserialize)] struct SuccessCriteria { min_sharpe: f64, min_win_rate: f64, max_drawdown: f64, sharpe_passed: bool, win_rate_passed: bool, drawdown_passed: bool, overall_passed: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] enum Verdict { ProductionReady, Failed { reasons: Vec }, } /// Validate backtest results against production criteria fn validate_results( checkpoint_name: String, baseline_name: Option, metrics: PerformanceMetrics, baseline_metrics: Option, config: &BacktestConfig, ) -> ValidationResult { info!("📋 Validating against production criteria..."); let sharpe_passed = metrics.sharpe_ratio >= config.min_sharpe; let win_rate_passed = metrics.win_rate >= config.min_win_rate; let drawdown_passed = metrics.max_drawdown_pct <= config.max_drawdown; let overall_passed = sharpe_passed && win_rate_passed && drawdown_passed; let mut reasons = Vec::new(); if !sharpe_passed { reasons.push(format!( "Sharpe ratio {:.2} < {:.2} (required)", metrics.sharpe_ratio, config.min_sharpe )); } if !win_rate_passed { reasons.push(format!( "Win rate {:.1}% < {:.1}% (required)", metrics.win_rate, config.min_win_rate )); } if !drawdown_passed { reasons.push(format!( "Max drawdown {:.1}% > {:.1}% (limit)", metrics.max_drawdown_pct, config.max_drawdown )); } let verdict = if overall_passed { Verdict::ProductionReady } else { Verdict::Failed { reasons } }; ValidationResult { checkpoint_name, baseline_name, metrics, baseline_metrics, success_criteria: SuccessCriteria { min_sharpe: config.min_sharpe, min_win_rate: config.min_win_rate, max_drawdown: config.max_drawdown, sharpe_passed, win_rate_passed, drawdown_passed, overall_passed, }, verdict, } } /// Print validation report to console fn print_report(result: &ValidationResult) { println!("\n╔══════════════════════════════════════════════════════════════════════╗"); println!("║ DQN BACKTEST VALIDATION REPORT ║"); println!("╚══════════════════════════════════════════════════════════════════════╝"); println!(); // Checkpoint info println!("═══ Checkpoint ═══"); println!(" Primary: {}", result.checkpoint_name); if let Some(ref baseline) = result.baseline_name { println!(" Baseline: {}", baseline); } println!(); // Performance metrics println!("═══ Performance Metrics ═══"); println!( " Total Return: {:>8.2}%", result.metrics.total_return_pct ); println!(" Sharpe Ratio: {:>8.2}", result.metrics.sharpe_ratio); println!( " Max Drawdown: {:>8.2}%", result.metrics.max_drawdown_pct ); println!(" Win Rate: {:>8.1}%", result.metrics.win_rate); println!(" Total Trades: {:>8}", result.metrics.total_trades); println!(" Avg Trade PnL: {:>8.2}", result.metrics.avg_trade_pnl); println!(" Final Equity: {:>8.2}", result.metrics.final_equity); println!(); // Baseline comparison (if available) if let Some(ref baseline) = result.baseline_metrics { println!("═══ Baseline Comparison ═══"); let sharpe_diff = result.metrics.sharpe_ratio - baseline.sharpe_ratio; let return_diff = result.metrics.total_return_pct - baseline.total_return_pct; let drawdown_diff = baseline.max_drawdown_pct - result.metrics.max_drawdown_pct; let win_rate_diff = result.metrics.win_rate - baseline.win_rate; println!( " Sharpe Ratio: {:>8.2} → {:>8.2} ({:+.2})", baseline.sharpe_ratio, result.metrics.sharpe_ratio, sharpe_diff ); println!( " Total Return: {:>8.2}% → {:>8.2}% ({:+.2}%)", baseline.total_return_pct, result.metrics.total_return_pct, return_diff ); println!( " Max Drawdown: {:>8.2}% → {:>8.2}% ({:+.2}%)", baseline.max_drawdown_pct, result.metrics.max_drawdown_pct, drawdown_diff ); println!( " Win Rate: {:>8.1}% → {:>8.1}% ({:+.1}%)", baseline.win_rate, result.metrics.win_rate, win_rate_diff ); println!(); } // Success criteria println!("═══ Success Criteria ═══"); println!( " {} Sharpe Ratio ≥ {:.2}: {}", if result.success_criteria.sharpe_passed { "✅" } else { "❌" }, result.success_criteria.min_sharpe, result.metrics.sharpe_ratio ); println!( " {} Win Rate ≥ {:.1}%: {:.1}%", if result.success_criteria.win_rate_passed { "✅" } else { "❌" }, result.success_criteria.min_win_rate, result.metrics.win_rate ); println!( " {} Max Drawdown ≤ {:.1}%: {:.1}%", if result.success_criteria.drawdown_passed { "✅" } else { "❌" }, result.success_criteria.max_drawdown, result.metrics.max_drawdown_pct ); println!(); // Verdict println!("═══ Verdict ═══"); match &result.verdict { Verdict::ProductionReady => { println!(" ✅ PRODUCTION READY"); println!(" Checkpoint meets all success criteria"); }, Verdict::Failed { reasons } => { println!(" ❌ FAILED VALIDATION"); println!(" Reasons:"); for reason in reasons { println!(" • {}", reason); } }, } println!(); } /// Generate markdown report fn generate_markdown(result: &ValidationResult) -> String { let mut md = String::new(); md.push_str("# DQN Backtest Validation Report\n\n"); // Checkpoint info md.push_str("## Checkpoint\n\n"); md.push_str(&format!("**Primary**: `{}`\n", result.checkpoint_name)); if let Some(ref baseline) = result.baseline_name { md.push_str(&format!("**Baseline**: `{}`\n", baseline)); } md.push_str("\n"); // Performance metrics md.push_str("## Performance Metrics\n\n"); md.push_str("| Metric | Value |\n"); md.push_str("|--------|-------|\n"); md.push_str(&format!( "| Total Return | {:.2}% |\n", result.metrics.total_return_pct )); md.push_str(&format!( "| Sharpe Ratio | {:.2} |\n", result.metrics.sharpe_ratio )); md.push_str(&format!( "| Max Drawdown | {:.2}% |\n", result.metrics.max_drawdown_pct )); md.push_str(&format!("| Win Rate | {:.1}% |\n", result.metrics.win_rate)); md.push_str(&format!( "| Total Trades | {} |\n", result.metrics.total_trades )); md.push_str(&format!( "| Avg Trade PnL | {:.2} |\n", result.metrics.avg_trade_pnl )); md.push_str(&format!( "| Final Equity | {:.2} |\n", result.metrics.final_equity )); md.push_str("\n"); // Baseline comparison if let Some(ref baseline) = result.baseline_metrics { md.push_str("## Baseline Comparison\n\n"); md.push_str("| Metric | Baseline | Primary | Change |\n"); md.push_str("|--------|----------|---------|--------|\n"); let sharpe_diff = result.metrics.sharpe_ratio - baseline.sharpe_ratio; let return_diff = result.metrics.total_return_pct - baseline.total_return_pct; let drawdown_diff = baseline.max_drawdown_pct - result.metrics.max_drawdown_pct; let win_rate_diff = result.metrics.win_rate - baseline.win_rate; md.push_str(&format!( "| Sharpe Ratio | {:.2} | {:.2} | {:+.2} |\n", baseline.sharpe_ratio, result.metrics.sharpe_ratio, sharpe_diff )); md.push_str(&format!( "| Total Return | {:.2}% | {:.2}% | {:+.2}% |\n", baseline.total_return_pct, result.metrics.total_return_pct, return_diff )); md.push_str(&format!( "| Max Drawdown | {:.2}% | {:.2}% | {:+.2}% |\n", baseline.max_drawdown_pct, result.metrics.max_drawdown_pct, drawdown_diff )); md.push_str(&format!( "| Win Rate | {:.1}% | {:.1}% | {:+.1}% |\n", baseline.win_rate, result.metrics.win_rate, win_rate_diff )); md.push_str("\n"); } // Success criteria md.push_str("## Success Criteria\n\n"); md.push_str(&format!( "- {} **Sharpe Ratio ≥ {:.2}**: {:.2}\n", if result.success_criteria.sharpe_passed { "✅" } else { "❌" }, result.success_criteria.min_sharpe, result.metrics.sharpe_ratio )); md.push_str(&format!( "- {} **Win Rate ≥ {:.1}%**: {:.1}%\n", if result.success_criteria.win_rate_passed { "✅" } else { "❌" }, result.success_criteria.min_win_rate, result.metrics.win_rate )); md.push_str(&format!( "- {} **Max Drawdown ≤ {:.1}%**: {:.1}%\n", if result.success_criteria.drawdown_passed { "✅" } else { "❌" }, result.success_criteria.max_drawdown, result.metrics.max_drawdown_pct )); md.push_str("\n"); // Verdict md.push_str("## Verdict\n\n"); match &result.verdict { Verdict::ProductionReady => { md.push_str("✅ **PRODUCTION READY**\n\n"); md.push_str( "Checkpoint meets all success criteria and is ready for production deployment.\n", ); }, Verdict::Failed { reasons } => { md.push_str("❌ **FAILED VALIDATION**\n\n"); md.push_str("Checkpoint failed the following criteria:\n\n"); for reason in reasons { md.push_str(&format!("- {}\n", reason)); } }, } md } // ============================================================================ // Main Orchestrator // ============================================================================ #[tokio::main] async fn main() -> Result<()> { // Parse CLI options let config = BacktestConfig::parse(); // Setup logging let level = if config.verbose { tracing::Level::DEBUG } else { tracing::Level::INFO }; let subscriber = FmtSubscriber::builder().with_max_level(level).finish(); tracing::subscriber::set_global_default(subscriber) .context("Failed to set tracing subscriber")?; // Print banner info!("╔══════════════════════════════════════════════════════════════════════╗"); info!("║ DQN Backtest Validation Pipeline - v1.0.0 ║"); info!( "║ Timestamp: {} ║", chrono::Local::now().format("%Y-%m-%d %H:%M:%S") ); info!("╚══════════════════════════════════════════════════════════════════════╝"); info!(""); // Validate configuration info!("🔍 Validating configuration..."); config .validate() .context("Configuration validation failed")?; info!("✅ Configuration validated"); info!(""); // Create device let device = match config.device.as_str() { "cpu" => { info!("🖥️ Using CPU device"); Device::Cpu }, "cuda" => { info!("🎮 Using CUDA device"); Device::new_cuda(0).context("CUDA unavailable. Use --device cpu or --device auto")? }, "auto" => match Device::cuda_if_available(0) { Ok(cuda_device) => { info!("🎮 Using CUDA device (auto-detected)"); cuda_device }, Err(_) => { warn!("⚠️ CUDA unavailable, falling back to CPU"); Device::Cpu }, }, _ => unreachable!(), }; info!(""); let total_start = Instant::now(); // Phase 1: Load data info!("📂 Phase 1: Loading validation data"); let data_start = Instant::now(); let (mut features, _timestamps, bars) = load_parquet_data_with_timestamps(&config.data, config.warmup_bars) .context("Failed to load validation data")?; info!( "✅ Loaded {} bars ({:.2}s)", bars.len(), data_start.elapsed().as_secs_f64() ); info!(""); // Phase 1.5: Preprocessing info!("🔬 Phase 1.5: Preprocessing data"); let close_prices: Vec = bars.iter().map(|b| b.close as f32).collect(); let close_tensor = Tensor::from_slice(&close_prices, (close_prices.len(),), &device) .context("Failed to create tensor")?; let preprocess_config = PreprocessConfig { window_size: 50, clip_sigma: 5.0, use_log_returns: true, }; let preprocessed = preprocess_prices(&close_tensor, preprocess_config).context("Preprocessing failed")?; let preprocessed_vec: Vec = preprocessed .to_vec1() .context("Failed to convert tensor to vec")?; // Update features with preprocessed close prices for (i, feature_vec) in features.iter_mut().enumerate() { if i < preprocessed_vec.len() { feature_vec[3] = preprocessed_vec[i] as f64; // Index 3 is close price } } info!("✅ Preprocessing complete"); info!(""); // Phase 2: Load checkpoints info!("📦 Phase 2: Loading checkpoints"); let mut primary_dqn = load_checkpoint(&config.checkpoint, &device)?; let mut baseline_dqn = if let Some(ref baseline_path) = config.baseline { Some(load_checkpoint(baseline_path, &device)?) } else { None }; info!(""); // Phase 3: Run backtests info!("🔄 Phase 3: Running backtests"); // Primary backtest let primary_metrics = run_backtest(&mut primary_dqn, &features, &bars, config.initial_capital)?; // Baseline backtest (if provided) let baseline_metrics = if let Some(ref mut baseline) = baseline_dqn { info!(""); info!("🔄 Running baseline backtest..."); Some(run_backtest( baseline, &features, &bars, config.initial_capital, )?) } else { None }; info!(""); // Phase 4: Validate results info!("📋 Phase 4: Validating results"); let result = validate_results( config.checkpoint.display().to_string(), config.baseline.as_ref().map(|p| p.display().to_string()), primary_metrics, baseline_metrics, &config, ); info!(""); // Phase 5: Generate reports info!("📝 Phase 5: Generating reports"); // Console output (always) print_report(&result); // JSON output (if requested) if let Some(ref json_path) = config.output_json { let json = serde_json::to_string_pretty(&result).context("Failed to serialize to JSON")?; std::fs::write(json_path, json) .context(format!("Failed to write JSON: {}", json_path.display()))?; info!("✅ JSON report saved: {}", json_path.display()); } // Markdown output (if requested) if let Some(ref md_path) = config.output_markdown { let markdown = generate_markdown(&result); std::fs::write(md_path, markdown) .context(format!("Failed to write markdown: {}", md_path.display()))?; info!("✅ Markdown report saved: {}", md_path.display()); } let total_elapsed = total_start.elapsed(); info!(""); info!("╔══════════════════════════════════════════════════════════════════════╗"); info!("║ VALIDATION COMPLETE ║"); info!("╚══════════════════════════════════════════════════════════════════════╝"); info!(" Total runtime: {:.2}s", total_elapsed.as_secs_f64()); info!(""); // Exit with appropriate code match result.verdict { Verdict::ProductionReady => { info!("✅ EXIT CODE 0: Production ready"); std::process::exit(0); }, Verdict::Failed { .. } => { warn!("❌ EXIT CODE 1: Validation failed"); std::process::exit(1); }, } }