//! Performance Benchmarks for Wave D Regime Detection Features //! //! Agent D17 - Wave D feature performance validation: //! - CUSUM Statistics (Agent D13, indices 201-210) //! - ADX & Directional Indicators (Agent D14, indices 211-215) //! - Regime Transition Probabilities (Agent D15, indices 216-220) //! - Adaptive Strategy Metrics (Agent D16, indices 221-224) //! //! ## Performance Targets //! - CUSUM: <50μs per bar //! - ADX: <80μs per bar //! - Transition: <50μs per bar //! - Adaptive: <100μs per bar //! //! ## Run Benchmarks //! ```bash //! cargo bench -p ml --bench wave_d_features_bench //! ``` use chrono::Utc; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ml::ensemble::MarketRegime; use ml::features::{ extraction::OHLCVBar, regime_adaptive::RegimeAdaptiveFeatures, regime_adx::{OHLCVBar as ADXBar, RegimeADXFeatures}, regime_cusum::RegimeCUSUMFeatures, regime_transition::RegimeTransitionFeatures, }; use std::time::Duration; // ============================================================================ // Test Data Generators // ============================================================================ /// Generate realistic log returns for CUSUM testing fn generate_log_returns(num_bars: usize, seed: u64) -> Vec { use std::f64::consts::PI; let mut rng = fastrand::Rng::with_seed(seed); let mut returns = Vec::with_capacity(num_bars); for i in 0..num_bars { // Simulate regime changes with drift shifts let regime_phase = (i / 50) % 4; let drift = match regime_phase { 0 => 0.0, // Normal regime 1 => 0.002, // Positive drift 2 => 0.0, // Return to normal 3 => -0.002, // Negative drift _ => 0.0, }; // Add cycle component let cycle = (i as f64 * 0.1 * PI).sin() * 0.0005; // Add noise let noise = (rng.f64() - 0.5) * 0.005; returns.push(drift + cycle + noise); } returns } /// Generate realistic OHLCV bars for ADX testing fn generate_ohlcv_bars(num_bars: usize, seed: u64) -> Vec { use std::f64::consts::PI; let mut rng = fastrand::Rng::with_seed(seed); let mut bars = Vec::with_capacity(num_bars); let mut close = 100.0; let base_time = Utc::now().timestamp(); for i in 0..num_bars { // Combine trend, cycle, and noise let trend = (i as f64 * 0.01) % 10.0 - 5.0; let cycle = (i as f64 * 0.1 * PI).sin() * 2.0; let noise = (rng.f64() - 0.5) * 0.5; close += trend * 0.01 + cycle * 0.05 + noise; close = close.max(50.0).min(150.0); // Generate realistic OHLC with typical 0.1-0.5% intrabar range let range = close * 0.003 * (1.0 + rng.f64()); let high = close + range * rng.f64(); let low = close - range * rng.f64(); let open = low + (high - low) * rng.f64(); let volume = 10000.0 + (i as f64 * 0.5 * PI).sin().abs() * 5000.0 + rng.f64() * 2000.0; bars.push(ADXBar { timestamp: base_time + (i as i64 * 60), open, high, low, close, volume, }); } bars } /// Generate realistic OHLCV bars (extraction format) for Adaptive testing fn generate_extraction_bars(num_bars: usize, seed: u64) -> Vec { use std::f64::consts::PI; let mut rng = fastrand::Rng::with_seed(seed); let mut bars = Vec::with_capacity(num_bars); let mut close = 100.0; let base_time = Utc::now(); for i in 0..num_bars { // Combine trend, cycle, and noise let trend = (i as f64 * 0.01) % 10.0 - 5.0; let cycle = (i as f64 * 0.1 * PI).sin() * 2.0; let noise = (rng.f64() - 0.5) * 0.5; close += trend * 0.01 + cycle * 0.05 + noise; close = close.max(50.0).min(150.0); // Generate realistic OHLC with typical 0.1-0.5% intrabar range let range = close * 0.003 * (1.0 + rng.f64()); let high = close + range * rng.f64(); let low = close - range * rng.f64(); let open = low + (high - low) * rng.f64(); let volume = 10000.0 + (i as f64 * 0.5 * PI).sin().abs() * 5000.0 + rng.f64() * 2000.0; bars.push(OHLCVBar { timestamp: base_time + chrono::Duration::seconds(i as i64 * 60), open, high, low, close, volume, }); } bars } /// Generate realistic regime sequence for Transition testing fn generate_regime_sequence(num_regimes: usize, seed: u64) -> Vec { let mut rng = fastrand::Rng::with_seed(seed); let regimes = vec![ MarketRegime::Normal, MarketRegime::Trending, MarketRegime::Sideways, MarketRegime::Bull, MarketRegime::Bear, MarketRegime::HighVolatility, MarketRegime::Crisis, ]; (0..num_regimes) .map(|_| regimes[rng.usize(0..regimes.len())]) .collect() } // ============================================================================ // CUSUM Features Benchmarks (Agent D13, indices 201-210) // ============================================================================ /// Benchmark CUSUM features cold start (single update) fn bench_cusum_features_cold(c: &mut Criterion) { let mut group = c.benchmark_group("cusum_features"); group.measurement_time(Duration::from_secs(5)); let returns = generate_log_returns(1000, 42); group.bench_function("single_update_cold", |b| { b.iter(|| { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); let result = features.update(black_box(returns[0])); black_box(result); }); }); group.finish(); } /// Benchmark CUSUM features warm state (incremental updates) fn bench_cusum_features_warm(c: &mut Criterion) { let mut group = c.benchmark_group("cusum_features_warm"); group.measurement_time(Duration::from_secs(5)); let returns = generate_log_returns(1000, 43); // Warm up with 100 bars let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); for &ret in returns.iter().take(100) { features.update(ret); } group.bench_function("single_update_warm", |b| { let mut feat = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Pre-warm for &ret in returns.iter().take(100) { feat.update(ret); } let mut idx = 100; b.iter(|| { let result = feat.update(black_box(returns[idx % returns.len()])); black_box(result); idx += 1; }); }); group.finish(); } /// Benchmark CUSUM features full sequence (all 10 features) fn bench_cusum_features_sequence(c: &mut Criterion) { let mut group = c.benchmark_group("cusum_features_sequence"); group.measurement_time(Duration::from_secs(10)); let returns = generate_log_returns(500, 44); group.bench_function("500_bars_full_pipeline", |b| { b.iter(|| { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); for &ret in returns.iter() { let result = features.update(black_box(ret)); black_box(result); } }); }); group.finish(); } // ============================================================================ // ADX Features Benchmarks (Agent D14, indices 211-215) // ============================================================================ /// Benchmark ADX features cold start fn bench_adx_features_cold(c: &mut Criterion) { let mut group = c.benchmark_group("adx_features"); group.measurement_time(Duration::from_secs(5)); let bars = generate_ohlcv_bars(1000, 45); group.bench_function("single_update_cold", |b| { b.iter(|| { let mut features = RegimeADXFeatures::new(14); let result = features.update(black_box(&bars[0])); black_box(result); }); }); group.finish(); } /// Benchmark ADX features warm state fn bench_adx_features_warm(c: &mut Criterion) { let mut group = c.benchmark_group("adx_features_warm"); group.measurement_time(Duration::from_secs(5)); let bars = generate_ohlcv_bars(1000, 46); // Warm up with 28 bars (2 * period for full ADX initialization) let mut features = RegimeADXFeatures::new(14); for bar in bars.iter().take(28) { features.update(bar); } group.bench_function("single_update_warm", |b| { let mut feat = RegimeADXFeatures::new(14); // Pre-warm for bar in bars.iter().take(28) { feat.update(bar); } let mut idx = 28; b.iter(|| { let result = feat.update(black_box(&bars[idx % bars.len()])); black_box(result); idx += 1; }); }); group.finish(); } /// Benchmark ADX features full sequence fn bench_adx_features_sequence(c: &mut Criterion) { let mut group = c.benchmark_group("adx_features_sequence"); group.measurement_time(Duration::from_secs(10)); let bars = generate_ohlcv_bars(500, 47); group.bench_function("500_bars_full_pipeline", |b| { b.iter(|| { let mut features = RegimeADXFeatures::new(14); for bar in bars.iter() { let result = features.update(black_box(bar)); black_box(result); } }); }); group.finish(); } // ============================================================================ // Transition Features Benchmarks (Agent D15, indices 216-220) // ============================================================================ /// Benchmark Transition features cold start fn bench_transition_features_cold(c: &mut Criterion) { let mut group = c.benchmark_group("transition_features"); group.measurement_time(Duration::from_secs(5)); let regimes = generate_regime_sequence(1000, 48); group.bench_function("single_update_cold", |b| { b.iter(|| { let mut features = RegimeTransitionFeatures::new(4, 0.1); let result = features.update(black_box(regimes[0])); black_box(result); }); }); group.finish(); } /// Benchmark Transition features warm state fn bench_transition_features_warm(c: &mut Criterion) { let mut group = c.benchmark_group("transition_features_warm"); group.measurement_time(Duration::from_secs(5)); let regimes = generate_regime_sequence(1000, 49); // Warm up with 50 regime observations let mut features = RegimeTransitionFeatures::new(4, 0.1); for ®ime in regimes.iter().take(50) { features.update(regime); } group.bench_function("single_update_warm", |b| { let mut feat = RegimeTransitionFeatures::new(4, 0.1); // Pre-warm for ®ime in regimes.iter().take(50) { feat.update(regime); } let mut idx = 50; b.iter(|| { let result = feat.update(black_box(regimes[idx % regimes.len()])); black_box(result); idx += 1; }); }); group.finish(); } /// Benchmark Transition features full sequence fn bench_transition_features_sequence(c: &mut Criterion) { let mut group = c.benchmark_group("transition_features_sequence"); group.measurement_time(Duration::from_secs(10)); let regimes = generate_regime_sequence(500, 50); group.bench_function("500_regimes_full_pipeline", |b| { b.iter(|| { let mut features = RegimeTransitionFeatures::new(4, 0.1); for ®ime in regimes.iter() { let result = features.update(black_box(regime)); black_box(result); } }); }); group.finish(); } // ============================================================================ // Adaptive Features Benchmarks (Agent D16, indices 221-224) // ============================================================================ /// Benchmark Adaptive features cold start fn bench_adaptive_features_cold(c: &mut Criterion) { let mut group = c.benchmark_group("adaptive_features"); group.measurement_time(Duration::from_secs(5)); let bars = generate_extraction_bars(100, 51); group.bench_function("single_update_cold", |b| { b.iter(|| { let mut features = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let result = features.update( black_box(MarketRegime::Normal), black_box(0.01), black_box(50_000.0), black_box(&bars), ); black_box(result); }); }); group.finish(); } /// Benchmark Adaptive features warm state fn bench_adaptive_features_warm(c: &mut Criterion) { let mut group = c.benchmark_group("adaptive_features_warm"); group.measurement_time(Duration::from_secs(5)); let bars = generate_extraction_bars(100, 52); let regimes = generate_regime_sequence(100, 53); // Warm up with 20 updates let mut features = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); for i in 0..20 { features.update(regimes[i], 0.01, 50_000.0, &bars); } group.bench_function("single_update_warm", |b| { let mut feat = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Pre-warm for i in 0..20 { feat.update(regimes[i], 0.01, 50_000.0, &bars); } let mut idx = 20; b.iter(|| { let result = feat.update( black_box(regimes[idx % regimes.len()]), black_box(0.01), black_box(50_000.0), black_box(&bars), ); black_box(result); idx += 1; }); }); group.finish(); } /// Benchmark Adaptive features full sequence fn bench_adaptive_features_sequence(c: &mut Criterion) { let mut group = c.benchmark_group("adaptive_features_sequence"); group.measurement_time(Duration::from_secs(10)); let bars = generate_extraction_bars(100, 54); let regimes = generate_regime_sequence(500, 55); group.bench_function("500_updates_full_pipeline", |b| { b.iter(|| { let mut features = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); for ®ime in regimes.iter() { let result = features.update( black_box(regime), black_box(0.01), black_box(50_000.0), black_box(&bars), ); black_box(result); } }); }); group.finish(); } // ============================================================================ // Criterion Configuration // ============================================================================ criterion_group!( benches, // CUSUM Features (Agent D13) bench_cusum_features_cold, bench_cusum_features_warm, bench_cusum_features_sequence, // ADX Features (Agent D14) bench_adx_features_cold, bench_adx_features_warm, bench_adx_features_sequence, // Transition Features (Agent D15) bench_transition_features_cold, bench_transition_features_warm, bench_transition_features_sequence, // Adaptive Features (Agent D16) bench_adaptive_features_cold, bench_adaptive_features_warm, bench_adaptive_features_sequence, ); criterion_main!(benches);