#![allow(unexpected_cfgs)] #![cfg(feature = "__backtesting_integration")] //! Wave D Regime Backtesting Integration Test - TDD RED Phase //! //! This test validates regime-adaptive strategy backtesting: //! 1. Load ES.FUT data (5000 bars, full trading day) //! 2. Initialize backtesting engine with Wave D features enabled //! 3. Run regime-adaptive strategy with position sizing and stop-loss adjustments //! 4. Track regime-conditioned performance (Sharpe, PnL, win rate by regime) //! 5. Compare vs. baseline (no regime adaptation) //! //! **Expected Improvement**: +25-50% Sharpe, -15-30% drawdown //! //! TDD Workflow: //! - RED: This test will fail initially (missing regime feature integration) //! - GREEN: Implement minimal code to pass //! - REFACTOR: Optimize and clean up use anyhow::Result; use backtesting_service::ml_strategy_engine::MLStrategyEngine; use num_traits::ToPrimitive; use backtesting_service::service::{BacktestContext, BacktestStatus}; use backtesting_service::storage::StorageManager; use rust_decimal::Decimal; use std::collections::HashMap; use std::sync::Arc; // Import fixtures for real ES.FUT data mod fixtures; use fixtures::{get_es_fut_bars, get_regime_sample, RegimeType}; /// Helper to create backtest context with custom parameters fn create_backtest_context( strategy_name: &str, symbol: &str, start_nanos: i64, end_nanos: i64, parameters: HashMap, ) -> BacktestContext { BacktestContext { id: uuid::Uuid::new_v4().to_string(), status: BacktestStatus::Pending, progress: 0.0, current_date: String::new(), trades_executed: 0, current_pnl: 0.0, started_at: start_nanos, completed_at: Some(end_nanos), error_message: None, strategy_name: strategy_name.to_string(), symbols: vec![symbol.to_string()], initial_capital: 100000.0, parameters, } } /// Helper to calculate Sharpe ratio from PnL series fn calculate_sharpe_ratio(pnl_series: &[f64]) -> f64 { if pnl_series.len() < 2 { return 0.0; } let mean = pnl_series.iter().sum::() / pnl_series.len() as f64; let variance = pnl_series.iter().map(|x| (x - mean).powi(2)).sum::() / pnl_series.len() as f64; let std_dev = variance.sqrt(); if std_dev == 0.0 { return 0.0; } // Annualized Sharpe ratio (assuming 252 trading days) mean / std_dev * (252.0_f64).sqrt() } /// Helper to calculate maximum drawdown fn calculate_max_drawdown(equity_curve: &[f64]) -> f64 { if equity_curve.is_empty() { return 0.0; } let mut max_equity = equity_curve[0]; let mut max_drawdown = 0.0; for &equity in equity_curve.iter() { if equity > max_equity { max_equity = equity; } let drawdown = (max_equity - equity) / max_equity; if drawdown > max_drawdown { max_drawdown = drawdown; } } max_drawdown } /// Helper to calculate win rate from trades fn calculate_win_rate(pnl_series: &[f64]) -> f64 { if pnl_series.is_empty() { return 0.0; } let winning_trades = pnl_series.iter().filter(|&&pnl| pnl > 0.0).count(); winning_trades as f64 / pnl_series.len() as f64 } #[tokio::test] async fn test_red_regime_adaptive_backtest_basic() -> Result<()> { // RED: This test will fail because regime feature integration doesn't exist yet println!("\nšŸ”“ RED Phase: Testing basic regime-adaptive backtest"); // Load ES.FUT data (5000 bars) let market_data = get_es_fut_bars().await?; assert!( market_data.len() >= 5000, "Need at least 5000 bars for regime detection" ); println!("āœ… Loaded {} ES.FUT bars", market_data.len()); // Create storage manager and ML strategy engine let storage_manager = Arc::new( StorageManager::new(&config::structures::BacktestingDatabaseConfig::default()).await?, ); let config = config::structures::BacktestingStrategyConfig::default(); let mut ml_engine = MLStrategyEngine::new(&config, storage_manager).await?; // Create backtest context with Wave D regime features enabled let start_nanos = market_data[0].timestamp.timestamp_nanos_opt().unwrap(); let end_nanos = market_data .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); let mut parameters = HashMap::new(); parameters.insert("enable_regime_features".to_string(), "true".to_string()); parameters.insert("regime_position_sizing".to_string(), "true".to_string()); parameters.insert("regime_stop_loss".to_string(), "true".to_string()); parameters.insert("trending_multiplier".to_string(), "1.5".to_string()); parameters.insert("volatile_multiplier".to_string(), "0.5".to_string()); parameters.insert("crisis_multiplier".to_string(), "0.2".to_string()); let context = create_backtest_context("ml_ensemble", "ES.FUT", start_nanos, end_nanos, parameters); // Execute backtest with regime adaptation let (trades, model_performance) = ml_engine.execute_ml_backtest(&context).await?; // Verify trades were executed assert!(!trades.is_empty(), "Should have executed trades"); println!("āœ… Executed {} trades", trades.len()); // Verify model performance includes regime metrics assert!( !model_performance.is_empty(), "Should have model performance metrics" ); println!( "āœ… Tracked performance for {} models", model_performance.len() ); // Calculate basic metrics let pnl_series: Vec = trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let sharpe = calculate_sharpe_ratio(&pnl_series); let win_rate = calculate_win_rate(&pnl_series); println!("\nšŸ“Š Regime-Adaptive Backtest Results:"); println!(" Total Trades: {}", trades.len()); println!(" Sharpe Ratio: {:.3}", sharpe); println!(" Win Rate: {:.2}%", win_rate * 100.0); // Basic validation (strict requirements in next test) assert!(sharpe > 0.0, "Sharpe ratio should be positive"); assert!(win_rate > 0.4, "Win rate should be >40%"); Ok(()) } #[tokio::test] async fn test_red_regime_vs_baseline_comparison() -> Result<()> { // RED: This test will fail because regime performance comparison doesn't exist yet println!("\nšŸ”“ RED Phase: Testing regime-adaptive vs baseline comparison"); // Load ES.FUT data let market_data = get_es_fut_bars().await?; assert!(market_data.len() >= 5000, "Need at least 5000 bars"); let start_nanos = market_data[0].timestamp.timestamp_nanos_opt().unwrap(); let end_nanos = market_data .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); // Create ML strategy engine let storage_manager = Arc::new( StorageManager::new(&config::structures::BacktestingDatabaseConfig::default()).await?, ); let config = config::structures::BacktestingStrategyConfig::default(); let mut ml_engine = MLStrategyEngine::new(&config, storage_manager.clone()).await?; // Run BASELINE backtest (NO regime adaptation) let mut baseline_params = HashMap::new(); baseline_params.insert("enable_regime_features".to_string(), "false".to_string()); let baseline_context = create_backtest_context( "ml_ensemble", "ES.FUT", start_nanos, end_nanos, baseline_params, ); let (baseline_trades, _) = ml_engine.execute_ml_backtest(&baseline_context).await?; // Calculate baseline metrics let baseline_pnl: Vec = baseline_trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let baseline_sharpe = calculate_sharpe_ratio(&baseline_pnl); let baseline_win_rate = calculate_win_rate(&baseline_pnl); // Build equity curve for drawdown let mut baseline_equity = vec![100000.0]; for pnl in &baseline_pnl { let new_equity = baseline_equity.last().expect("INVARIANT: Collection should be non-empty") + pnl; baseline_equity.push(new_equity); } let baseline_drawdown = calculate_max_drawdown(&baseline_equity); println!("\nšŸ“‰ Baseline (No Regime Adaptation):"); println!(" Trades: {}", baseline_trades.len()); println!(" Sharpe: {:.3}", baseline_sharpe); println!(" Win Rate: {:.2}%", baseline_win_rate * 100.0); println!(" Max Drawdown: {:.2}%", baseline_drawdown * 100.0); // Run REGIME-ADAPTIVE backtest let mut ml_engine2 = MLStrategyEngine::new(&config, storage_manager).await?; let mut regime_params = HashMap::new(); regime_params.insert("enable_regime_features".to_string(), "true".to_string()); regime_params.insert("regime_position_sizing".to_string(), "true".to_string()); regime_params.insert("regime_stop_loss".to_string(), "true".to_string()); regime_params.insert("trending_multiplier".to_string(), "1.5".to_string()); regime_params.insert("volatile_multiplier".to_string(), "0.5".to_string()); regime_params.insert("crisis_multiplier".to_string(), "0.2".to_string()); let regime_context = create_backtest_context( "ml_ensemble", "ES.FUT", start_nanos, end_nanos, regime_params, ); let (regime_trades, _) = ml_engine2.execute_ml_backtest(®ime_context).await?; // Calculate regime-adaptive metrics let regime_pnl: Vec = regime_trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let regime_sharpe = calculate_sharpe_ratio(®ime_pnl); let regime_win_rate = calculate_win_rate(®ime_pnl); let mut regime_equity = vec![100000.0]; for pnl in ®ime_pnl { let new_equity = regime_equity.last().expect("INVARIANT: Collection should be non-empty") + pnl; regime_equity.push(new_equity); } let regime_drawdown = calculate_max_drawdown(®ime_equity); println!("\nšŸ“ˆ Regime-Adaptive Strategy:"); println!(" Trades: {}", regime_trades.len()); println!(" Sharpe: {:.3}", regime_sharpe); println!(" Win Rate: {:.2}%", regime_win_rate * 100.0); println!(" Max Drawdown: {:.2}%", regime_drawdown * 100.0); // Calculate improvement let sharpe_improvement = ((regime_sharpe - baseline_sharpe) / baseline_sharpe.abs()) * 100.0; let drawdown_improvement = ((baseline_drawdown - regime_drawdown) / baseline_drawdown.abs()) * 100.0; println!("\nšŸŽÆ Improvement vs Baseline:"); println!(" Sharpe: {:+.1}%", sharpe_improvement); println!(" Drawdown: {:+.1}%", drawdown_improvement); // Verify improvement targets (Wave D goals: +25-50% Sharpe, -15-30% drawdown) assert!( regime_sharpe >= baseline_sharpe, "Regime-adaptive should match or beat baseline Sharpe" ); assert!( regime_drawdown <= baseline_drawdown, "Regime-adaptive should have lower drawdown" ); // Aspirational targets (may not hit immediately with untrained models) if sharpe_improvement >= 25.0 { println!(" āœ… ACHIEVED Sharpe improvement target (+25%)!"); } if drawdown_improvement >= 15.0 { println!(" āœ… ACHIEVED Drawdown improvement target (-15%)!"); } Ok(()) } #[tokio::test] async fn test_red_regime_conditioned_performance() -> Result<()> { // RED: This test will fail because per-regime performance tracking doesn't exist yet println!("\nšŸ”“ RED Phase: Testing regime-conditioned performance tracking"); // Load regime-specific samples let trending_bars = get_regime_sample(RegimeType::Trending).await?; let volatile_bars = get_regime_sample(RegimeType::Volatile).await?; let ranging_bars = get_regime_sample(RegimeType::Ranging).await?; println!("āœ… Loaded regime samples:"); println!(" Trending: {} bars", trending_bars.len()); println!(" Volatile: {} bars", volatile_bars.len()); println!(" Ranging: {} bars", ranging_bars.len()); // Create ML strategy engine let storage_manager = Arc::new( StorageManager::new(&config::structures::BacktestingDatabaseConfig::default()).await?, ); let config = config::structures::BacktestingStrategyConfig::default(); let mut ml_engine = MLStrategyEngine::new(&config, storage_manager).await?; // Run backtest on TRENDING regime let trending_start = trending_bars[0].timestamp.timestamp_nanos_opt().unwrap(); let trending_end = trending_bars .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); let mut trending_params = HashMap::new(); trending_params.insert("enable_regime_features".to_string(), "true".to_string()); trending_params.insert("regime_position_sizing".to_string(), "true".to_string()); trending_params.insert("trending_multiplier".to_string(), "1.5".to_string()); let trending_context = create_backtest_context( "ml_ensemble", "ES.FUT", trending_start, trending_end, trending_params, ); let (trending_trades, _) = ml_engine.execute_ml_backtest(&trending_context).await?; // Calculate trending regime metrics let trending_pnl: Vec = trending_trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let trending_sharpe = calculate_sharpe_ratio(&trending_pnl); let trending_win_rate = calculate_win_rate(&trending_pnl); println!("\nšŸ“Š Trending Regime Performance:"); println!(" Trades: {}", trending_trades.len()); println!(" Sharpe: {:.3}", trending_sharpe); println!(" Win Rate: {:.2}%", trending_win_rate * 100.0); // Trending regime should benefit from 1.5x position multiplier assert!( trending_sharpe > 0.0, "Trending regime should be profitable" ); assert!( trending_trades.len() > 0, "Should execute trades in trending regime" ); // Run backtest on VOLATILE regime (reduced position sizing) let volatile_start = volatile_bars[0].timestamp.timestamp_nanos_opt().unwrap(); let volatile_end = volatile_bars .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); let mut volatile_params = HashMap::new(); volatile_params.insert("enable_regime_features".to_string(), "true".to_string()); volatile_params.insert("regime_position_sizing".to_string(), "true".to_string()); volatile_params.insert("volatile_multiplier".to_string(), "0.5".to_string()); let volatile_context = create_backtest_context( "ml_ensemble", "ES.FUT", volatile_start, volatile_end, volatile_params, ); let mut ml_engine2 = MLStrategyEngine::new(&config, storage_manager.clone()).await?; let (volatile_trades, _) = ml_engine2.execute_ml_backtest(&volatile_context).await?; // Calculate volatile regime metrics let volatile_pnl: Vec = volatile_trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let volatile_sharpe = calculate_sharpe_ratio(&volatile_pnl); let volatile_win_rate = calculate_win_rate(&volatile_pnl); println!("\nšŸ“Š Volatile Regime Performance:"); println!(" Trades: {}", volatile_trades.len()); println!(" Sharpe: {:.3}", volatile_sharpe); println!(" Win Rate: {:.2}%", volatile_win_rate * 100.0); // Volatile regime should have reduced drawdown due to 0.5x position multiplier assert!( volatile_trades.len() > 0, "Should execute trades in volatile regime" ); // Verify regime-specific metrics tracked println!("\nāœ… Regime-conditioned performance tracking validated"); Ok(()) } #[tokio::test] async fn test_red_regime_attribution_analysis() -> Result<()> { // RED: This test will fail because PnL attribution by regime doesn't exist yet println!("\nšŸ”“ RED Phase: Testing PnL attribution by regime"); // Load full ES.FUT dataset let market_data = get_es_fut_bars().await?; let start_nanos = market_data[0].timestamp.timestamp_nanos_opt().unwrap(); let end_nanos = market_data .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); // Create ML strategy engine with regime tracking let storage_manager = Arc::new( StorageManager::new(&config::structures::BacktestingDatabaseConfig::default()).await?, ); let config = config::structures::BacktestingStrategyConfig::default(); let mut ml_engine = MLStrategyEngine::new(&config, storage_manager).await?; let mut params = HashMap::new(); params.insert("enable_regime_features".to_string(), "true".to_string()); params.insert("regime_attribution".to_string(), "true".to_string()); params.insert("regime_position_sizing".to_string(), "true".to_string()); let context = create_backtest_context("ml_ensemble", "ES.FUT", start_nanos, end_nanos, params); let (trades, _) = ml_engine.execute_ml_backtest(&context).await?; // Aggregate PnL by regime (this will require regime detection integration) // For now, we validate the structure exists assert!(!trades.is_empty(), "Should have executed trades"); // Future: Extract regime_type from trade metadata // let mut pnl_by_regime: HashMap> = HashMap::new(); // for trade in &trades { // let regime = trade.metadata.get("regime_type").unwrap_or(&"Unknown".to_string()); // pnl_by_regime.entry(regime.clone()).or_default() // .push(trade.pnl.to_string().parse::().unwrap_or(0.0)); // } println!("\nšŸ“Š PnL Attribution by Regime:"); println!(" (Implementation pending - requires regime metadata in trades)"); println!(" Total Trades: {}", trades.len()); // Verify basic structure assert!( trades.len() > 100, "Should have sufficient trades for attribution analysis" ); Ok(()) } #[tokio::test] async fn test_red_regime_performance_targets() -> Result<()> { // RED: This test validates production targets are met println!("\nšŸ”“ RED Phase: Testing regime-adaptive performance targets"); let market_data = get_es_fut_bars().await?; let start_nanos = market_data[0].timestamp.timestamp_nanos_opt().unwrap(); let end_nanos = market_data .last() .unwrap() .timestamp .timestamp_nanos_opt() .unwrap(); let storage_manager = Arc::new( StorageManager::new(&config::structures::BacktestingDatabaseConfig::default()).await?, ); let config = config::structures::BacktestingStrategyConfig::default(); let mut ml_engine = MLStrategyEngine::new(&config, storage_manager).await?; let mut params = HashMap::new(); params.insert("enable_regime_features".to_string(), "true".to_string()); params.insert("regime_position_sizing".to_string(), "true".to_string()); params.insert("regime_stop_loss".to_string(), "true".to_string()); let context = create_backtest_context("ml_ensemble", "ES.FUT", start_nanos, end_nanos, params); let (trades, model_performance) = ml_engine.execute_ml_backtest(&context).await?; // Calculate final metrics let pnl_series: Vec = trades .iter() .map(|t| t.pnl.to_f64().unwrap_or(0.0)) .collect(); let sharpe = calculate_sharpe_ratio(&pnl_series); let win_rate = calculate_win_rate(&pnl_series); let mut equity_curve = vec![100000.0]; for pnl in &pnl_series { equity_curve.push(equity_curve.last().expect("INVARIANT: Collection should be non-empty") + pnl); } let max_drawdown = calculate_max_drawdown(&equity_curve); println!("\nšŸŽÆ Production Performance Targets:"); println!(" Sharpe Ratio: {:.3} (target: >1.5)", sharpe); println!(" Win Rate: {:.2}% (target: >55%)", win_rate * 100.0); println!( " Max Drawdown: {:.2}% (target: <20%)", max_drawdown * 100.0 ); println!(" Total Trades: {} (target: >100)", trades.len()); // Validate minimum performance assert!(sharpe > 0.0, "Sharpe ratio should be positive"); assert!(win_rate > 0.4, "Win rate should be >40%"); assert!(max_drawdown < 0.5, "Max drawdown should be <50%"); assert!(trades.len() > 50, "Should execute >50 trades"); // Check if production targets achieved let mut targets_met = 0; let mut total_targets = 4; if sharpe > 1.5 { println!(" āœ… Sharpe target MET"); targets_met += 1; } if win_rate > 0.55 { println!(" āœ… Win rate target MET"); targets_met += 1; } if max_drawdown < 0.20 { println!(" āœ… Drawdown target MET"); targets_met += 1; } if trades.len() > 100 { println!(" āœ… Trade count target MET"); targets_met += 1; } println!("\nšŸ“ˆ Targets Achieved: {}/{}", targets_met, total_targets); // Verify model performance tracking assert!( !model_performance.is_empty(), "Should track model performance" ); for (model_id, perf) in &model_performance { println!("\nModel: {}", model_id); println!(" Sharpe: {:.3}", perf.sharpe_ratio); println!(" Accuracy: {:.2}%", perf.accuracy_percentage); } Ok(()) }