1. **Update `adaptive-strategy/src/regime/mod.rs`**: Add a new `StructuralBreak` variant to the `MarketRegime` enum. This is necessary for the new tests to compile. 2. **Update `adaptive-strategy/tests/regime_transition_tests.rs`**: Add new helper functions and the 12 integration tests for Wave D features. These tests are designed to fail until the CUSUM and ADX features are implemented (the "Red" in Red-Green-Refactor). ```rust // context_start_text /// Market regime types #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum MarketRegime { /// Normal market - standard conditions Normal, /// Trending market - strong directional movement Trending, /// Bull market - upward trending with moderate volatility Bull, /// Bear market - downward trending with moderate volatility Bear, /// Sideways market - low volatility, range-bound Sideways, /// High volatility market - significant price swings HighVolatility, /// Low volatility market - stable, low movement LowVolatility, /// Crisis regime - extreme volatility, flight to quality Crisis, /// Recovery regime - transitioning from crisis Recovery, /// Bubble regime - unsustainable upward movement Bubble, /// Correction regime - temporary downward adjustment Correction, /// Structural break detected (e.g., by CUSUM) StructuralBreak, /// Unknown/unclassified regime Unknown, } /// Regime detection model trait // context_end_text ``` ```rust // context_start_text }, } } // context_end_text // ============================================================================ // Wave D Integration Tests // ============================================================================ /// Helper to generate data with a structural break in the mean fn generate_structural_break_data( count_before: usize, count_after: usize, price_before: f64, price_after: f64, ) -> Vec { let mut data = generate_stable_data(count_before, price_before); let mut after_data = generate_stable_data(count_after, price_after); // Adjust timestamps for the second segment let base_time = data.last().map(|p| p.timestamp).unwrap_or_else(Utc::now); for (i, point) in after_data.iter_mut().enumerate() { point.timestamp = base_time + Duration::seconds((i + 1) as i64); } data.extend(after_data); data } /// Generate choppy but directional data to test ADX fn generate_choppy_trend_data(count: usize, start_price: f64, trend: f64) -> Vec { let base_time = Utc::now(); (0..count) .map(|i| { let price = start_price + (i as f64 * trend); // Add significant noise/chop to obscure the simple linear trend let chop = 15.0 * (i as f64 * 1.5).sin(); let final_price = price + chop; PricePoint { timestamp: base_time + Duration::seconds(i as i64), price: final_price, high: final_price + 8.0, low: final_price - 8.0, open: final_price - 2.0, } }) .collect() } #[cfg(test)] mod wave_d_tests { use super::*; use adaptive_strategy::regime::MarketRegime::StructuralBreak; use std::time::Instant; // Test 1: E2E CUSUM detection #[tokio::test] async fn test_cusum_detects_structural_break_integration() { let config = RegimeConfig { // This will be changed to a CUSUM-specific method. For now, we use Threshold // and expect the test to fail, which is correct for the RED phase. detection_method: RegimeDetectionMethod::Threshold, lookback_window: 50, transition_threshold: 0.5, features: vec!["returns".to_string(), "trend".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); let break_data = generate_structural_break_data(50, 50, 50000.0, 50500.0); let volume_data = generate_volume_data(100, 500.0, 100.0); let detection = detector.detect_regime(&break_data, &volume_data).await.unwrap(); // This will fail because the Threshold detector does not identify StructuralBreak. assert_eq!( detection.regime, StructuralBreak, "Expected StructuralBreak regime, got {:?}", detection.regime ); } // Test 2: ADX identifies trending regime #[tokio::test] async fn test_adx_identifies_trending_regime() { let config = RegimeConfig { detection_method: RegimeDetectionMethod::Threshold, lookback_window: 50, transition_threshold: 0.7, // Add "adx" feature. The current extractor will ignore it, and the threshold // logic doesn't use it, so this test will fail on choppy data. features: vec!["trend".to_string(), "adx".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); // Choppy data has a weak linear trend but would have a high ADX. // The current trend detector (linear slope) will fail to see a strong trend. let choppy_trend_data = generate_choppy_trend_data(100, 50000.0, 2.0); let volume_data = generate_volume_data(100, 500.0, 100.0); let detection = detector.detect_regime(&choppy_trend_data, &volume_data).await.unwrap(); // This will fail because the simple slope on choppy data is low. assert_eq!( detection.regime, MarketRegime::Trending, "Expected Trending regime from high ADX, got {:?}", detection.regime ); } // Test 3: StructuralBreak -> Volatile transition #[tokio::test] async fn test_regime_transition_structural_break_to_volatile() { let config = RegimeConfig { detection_method: RegimeDetectionMethod::Threshold, // Placeholder lookback_window: 50, transition_threshold: 0.5, features: vec!["volatility".to_string(), "returns".to_string(), "trend".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); // Phase 1: Structural Break let break_data = generate_structural_break_data(50, 50, 50000.0, 50500.0); let volume_data = generate_volume_data(100, 500.0, 100.0); // We assume this would detect StructuralBreak once implemented. let _ = detector.detect_regime(&break_data, &volume_data).await.unwrap(); // Manually set for test progression detector.handle_regime_transition(StructuralBreak, 0.9).await.unwrap(); // Phase 2: High Volatility let volatile_data = generate_volatile_data(100, 50500.0, 500.0); let detection = detector.detect_regime(&volatile_data, &volume_data).await.unwrap(); assert_eq!( detection.regime, MarketRegime::HighVolatility, "Expected transition to HighVolatility, but got {:?}", detection.regime ); } // Test 4: Strategy adaptation on structural break #[tokio::test] async fn test_strategy_adaptation_on_structural_break() { let mut adaptation_config = StrategyAdaptationConfig::default(); let risk_adjustment = adaptive_strategy::regime::RiskAdjustment { position_size_multiplier: 0.1, // Drastically reduce size stop_loss_adjustment: 2.0, max_concentration: 0.05, var_multiplier: 3.0, }; adaptation_config.risk_adjustments.insert(StructuralBreak, risk_adjustment.clone()); let manager = StrategyAdaptationManager::new(adaptation_config); let break_detection = create_test_detection(StructuralBreak, 0.95); let actions = manager.process_regime_change(&break_detection).await.unwrap(); assert!(!actions.is_empty(), "Adaptation actions should be triggered on structural break"); let applied_risk = manager.get_risk_adjustment().await.unwrap(); assert_eq!(applied_risk.position_size_multiplier, risk_adjustment.position_size_multiplier); } // Test 5: Verify feature pipeline includes Wave D features #[tokio::test] #[ignore = "Belongs in `ml` crate; depends on Wave D FeatureConfig and pipeline implementation"] async fn test_feature_pipeline_includes_wave_d() { unimplemented!("This test needs to be implemented in the `ml` crate test suite."); // 1. Create a `FeatureConfig` for Wave D. // 2. Instantiate `FeatureExtractionPipeline` with this config. // 3. Provide warmup data. // 4. Extract features. // 5. Assert that the feature vector length is 225. } // Test 6: ADX feature extraction performance #[tokio::test] #[ignore = "Belongs in `ml` crate; depends on ADX implementation"] async fn test_adx_feature_extraction_performance() { unimplemented!("This test needs to be implemented in the `ml` crate test suite against the real ADX calculation."); // 1. Get a realistic set of bars (e.g., 100). // 2. Run the `compute_adx` function in a loop (e.g., 1000 times). // 3. Measure the average execution time. // 4. Assert that the average time is less than 50 microseconds. } // Test 7: CUSUM reset after regime stabilization #[tokio::test] async fn test_cusum_reset_after_regime_stabilization() { let config = RegimeConfig { detection_method: RegimeDetectionMethod::Threshold, // Placeholder lookback_window: 50, transition_threshold: 0.5, features: vec!["returns".to_string(), "trend".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); // Phase 1: Trigger a break let break_data = generate_structural_break_data(50, 50, 50000.0, 50500.0); let volume_data = generate_volume_data(100, 500.0, 100.0); let _ = detector.detect_regime(&break_data, &volume_data).await.unwrap(); // Assume it detected a break. Manually set for test. detector.handle_regime_transition(StructuralBreak, 0.9).await.unwrap(); // Phase 2: Market stabilizes let stable_data = generate_stable_data(100, 50500.0); let stable_detection = detector.detect_regime(&stable_data, &volume_data).await.unwrap(); // This will fail until CUSUM logic is implemented to reset and return to a stable regime. assert!( matches!(stable_detection.regime, MarketRegime::Normal | MarketRegime::LowVolatility | MarketRegime::Sideways), "Detector should return to a stable regime after the break, but got {:?}", stable_detection.regime ); } // Test 8: Multiple regime transitions sequence #[tokio::test] async fn test_multiple_regime_transitions_sequence() { let config = RegimeConfig { detection_method: RegimeDetectionMethod::Threshold, // Placeholder lookback_window: 30, transition_threshold: 0.5, features: vec!["volatility".to_string(), "returns".to_string(), "trend".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); let mut regimes = vec![]; // 1. Stable let stable_data = generate_stable_data(50, 50000.0); let vol_data = generate_volume_data(50, 500.0, 100.0); regimes.push(detector.detect_regime(&stable_data, &vol_data).await.unwrap().regime); // 2. Break let break_data = generate_stable_data(50, 50500.0); // Simple mean shift // Manually setting to StructuralBreak as the current detector won't find it. detector.handle_regime_transition(StructuralBreak, 0.9).await.unwrap(); regimes.push(*detector.get_current_regime()); // 3. Volatile let volatile_data = generate_volatile_data(50, 50500.0, 500.0); regimes.push(detector.detect_regime(&volatile_data, &vol_data).await.unwrap().regime); // This test is designed to fail until all detectors are integrated. // The sequence is hard to predict exactly, but we expect changes. let transition_count = regimes.windows(2).filter(|w| w[0] != w[1]).count(); assert!(transition_count >= 2, "Expected at least 2 transitions, got {}", transition_count); assert_eq!(regimes.get(1), Some(&StructuralBreak)); assert_eq!(regimes.get(2), Some(&MarketRegime::HighVolatility)); } // Test 9: Wave D config indices correct #[tokio::test] #[ignore = "Belongs in `ml` crate; tests `ml::features::config`"] async fn test_wave_d_config_indices_correct() { unimplemented!("This test should be in the `ml` crate to verify FeatureConfig."); // 1. Call `FeatureConfig::wave_d_indices()`. // 2. Assert the range is `201..225`. // 3. Call `FeatureConfig::total_features_with_wave_d()`. // 4. Assert the total is 225. } // Test 10: Structural break false positive rate #[tokio::test] async fn test_structural_break_false_positive_rate() { let config = RegimeConfig { detection_method: RegimeDetectionMethod::Threshold, // Placeholder for CUSUM lookback_window: 50, transition_threshold: 0.9, // High threshold features: vec!["returns".to_string()], }; let mut detector = RegimeDetector::new(config).await.unwrap(); let stable_data = generate_stable_data(1000, 50000.0); let volume_data = generate_volume_data(1000, 500.0, 100.0); let mut break_count = 0; for i in 50..1000 { let window = &stable_data[i-50..i]; let vol_window = &volume_data[i-50..i]; let detection = detector.detect_regime(window, vol_window).await.unwrap(); if detection.regime == StructuralBreak { break_count += 1; } } let false_positive_rate = break_count as f64 / (1000.0 - 50.0); // This will pass now (0 false positives), but will correctly test the CUSUM implementation later. assert!( false_positive_rate < 0.05, "False positive rate for structural breaks should be < 5%, but was {:.2}%", false_positive_rate * 100.0 ); } // Test 11: End-to-end with real ES.FUT data #[tokio::test] #[ignore = "Requires real ES.FUT data loader and full Wave D implementation"] async fn test_end_to_end_adaptive_strategy_with_wave_d() { unimplemented!("Full E2E test requires ES.FUT data loader and complete Wave D feature pipeline."); } // Test 12: Performance of Wave D pipeline latency #[tokio::test] #[ignore = "Belongs in `ml` crate; performance test for the full pipeline"] async fn test_performance_wave_d_pipeline_latency() { unimplemented!("This test should be in the `ml` crate to benchmark the feature pipeline."); } } ```