#![allow( clippy::assertions_on_constants, clippy::assertions_on_result_states, clippy::clone_on_copy, clippy::decimal_literal_representation, clippy::doc_markdown, clippy::empty_line_after_doc_comments, clippy::field_reassign_with_default, clippy::get_unwrap, clippy::identity_op, clippy::inconsistent_digit_grouping, clippy::indexing_slicing, clippy::integer_division, clippy::len_zero, clippy::let_underscore_must_use, clippy::manual_div_ceil, clippy::manual_let_else, clippy::manual_range_contains, clippy::modulo_arithmetic, clippy::needless_range_loop, clippy::non_ascii_literal, clippy::redundant_clone, clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated, clippy::single_match_else, clippy::str_to_string, clippy::string_slice, clippy::tests_outside_test_module, clippy::too_many_lines, clippy::unnecessary_wraps, clippy::unseparated_literal_suffix, clippy::use_debug, clippy::useless_vec, clippy::wildcard_enum_match_arm, clippy::else_if_without_else, clippy::expect_used, clippy::missing_const_for_fn, clippy::similar_names, clippy::type_complexity, clippy::collapsible_else_if, clippy::doc_lazy_continuation, clippy::items_after_test_module, clippy::map_clone, clippy::multiple_unsafe_ops_per_block, clippy::unwrap_or_default, clippy::assign_op_pattern, clippy::needless_borrow, clippy::println_empty_string, clippy::unnecessary_cast, clippy::used_underscore_binding, clippy::create_dir, clippy::implicit_saturating_sub, clippy::exit, clippy::expect_fun_call, clippy::too_many_arguments, clippy::unnecessary_map_or, clippy::unwrap_used, dead_code, unused_imports, unused_variables, clippy::cloned_ref_to_slice_refs, clippy::neg_multiply, clippy::while_let_loop, clippy::bool_assert_comparison, clippy::excessive_precision, clippy::trivially_copy_pass_by_ref, clippy::op_ref, clippy::redundant_closure, clippy::unnecessary_lazy_evaluations, clippy::if_then_some_else_none, clippy::unnecessary_to_owned, clippy::single_component_path_imports, )] //! Comprehensive Edge Case Tests for Wave D Feature Extractors //! //! This test suite validates the robustness of all Wave D feature extractors against: //! - Missing data (gaps, zero volume, insufficient bars) //! - Invalid inputs (NaN, Inf, division by zero) //! - Extreme values (circuit breakers, volume spikes, zero volatility) //! - Initialization edge cases (cold start, insufficient data) //! - Graceful degradation (error recovery, defensive programming) //! //! ## Test Coverage (Wave D Feature Extractors) //! //! 1. **CUSUM Features (Indices 201-210)** - RegimeCUSUMFeatures //! - Missing data: NaN inputs, Inf returns //! - Division by zero: Zero threshold, zero std //! - Cold start: <2 bars //! - Extreme values: 100x jumps //! //! 2. **ADX Features (Indices 211-215)** - AdxFeatureExtractor //! - Missing data: Gap in bars, zero volume //! - Invalid OHLC: H OHLCVBar { OHLCVBar { timestamp: Utc::now(), open: price, high: price * high_mult, low: price * low_mult, close: price, volume, } } /// Create ADX OHLCV bar (for ADX-specific tests) fn create_adx_bar(price: f64, high_mult: f64, low_mult: f64, volume: f64) -> AdxOHLCVBar { AdxOHLCVBar { timestamp: Utc::now(), open: price, high: price * high_mult, low: price * low_mult, close: price, volume, } } /// Create bars with a gap (missing bar represented by NaN) - ADX version fn create_adx_bars_with_gap(prices: Vec>) -> Vec { prices .into_iter() .filter_map(|p| p.map(|price| create_adx_bar(price, 1.01, 0.99, 1000.0))) .collect() } /// Create invalid OHLC bar (high < low) - ADX version fn create_invalid_adx_bar(price: f64) -> AdxOHLCVBar { AdxOHLCVBar { timestamp: Utc::now(), open: price, high: price * 0.99, // Invalid: high < low low: price * 1.01, close: price, volume: 1000.0, } } /// Create bar with close outside OHLC range - ADX version fn create_adx_bar_with_invalid_close(price: f64) -> AdxOHLCVBar { AdxOHLCVBar { timestamp: Utc::now(), open: price, high: price * 1.02, low: price * 0.98, close: price * 1.05, // Invalid: close > high volume: 1000.0, } } /// Create bars with zero volatility - ADX version fn create_flat_adx_bars(count: usize, price: f64) -> Vec { (0..count) .map(|_| AdxOHLCVBar { timestamp: Utc::now(), open: price, high: price, low: price, close: price, volume: 1000.0, }) .collect() } /// Create bars with zero volatility - extraction version fn create_flat_bars(count: usize, price: f64) -> Vec { (0..count) .map(|_| OHLCVBar { timestamp: Utc::now(), open: price, high: price, low: price, close: price, volume: 1000.0, }) .collect() } // ============================================================================ // 1. CUSUM Features Edge Cases (Indices 201-210) // ============================================================================ #[test] fn test_cusum_nan_input() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed NaN input let result = features.update(f64::NAN); // Verify: All features should be valid (no NaN propagation) for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with NaN input, got {}", 201 + i, feature ); } // Verify: Break indicator should be 0.0 (no false detection) assert_eq!( result[2], 0.0, "NaN input should not trigger break detection" ); } #[test] fn test_cusum_inf_input() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed infinity input let result = features.update(f64::INFINITY); // Verify: All features should be finite for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with Inf input, got {}", 201 + i, feature ); } } #[test] fn test_cusum_negative_inf_input() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed negative infinity input let result = features.update(f64::NEG_INFINITY); // Verify: All features should be finite for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with -Inf input, got {}", 201 + i, feature ); } } #[test] fn test_cusum_zero_threshold() { // Edge case: Zero threshold (division by zero) let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 0.0); let result = features.update(1.0); // Verify: Features should handle zero threshold gracefully for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero threshold, got {}", 201 + i, feature ); } } #[test] fn test_cusum_zero_std() { // Edge case: Zero standard deviation let mut features = RegimeCUSUMFeatures::new(0.0, 0.0, 0.5, 4.0); let result = features.update(1.0); // Verify: Features should handle zero std gracefully for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero std, got {}", 201 + i, feature ); } } #[test] fn test_cusum_extreme_positive_value() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed extreme value (100x jump) let result = features.update(100.0); // Verify: Features should be clamped to valid ranges // Feature 201: S+ Normalized should be clamped to [0.0, 1.5] assert!( result[0] >= 0.0 && result[0] <= 1.5, "S+ normalized should be in [0, 1.5], got {}", result[0] ); // Feature 202: S- Normalized should be clamped to [0.0, 1.5] assert!( result[1] >= 0.0 && result[1] <= 1.5, "S- normalized should be in [0, 1.5], got {}", result[1] ); // All features should be finite for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with extreme value, got {}", 201 + i, feature ); } } #[test] fn test_cusum_extreme_negative_value() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed extreme negative value let result = features.update(-100.0); // Verify: All features are finite and in valid ranges for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with extreme negative value, got {}", 201 + i, feature ); } } #[test] fn test_cusum_rapid_oscillation() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0); // Rapid oscillation: +10, -10, +10, -10 for i in 0..100 { let value = if i % 2 == 0 { 10.0 } else { -10.0 }; let result = features.update(value); // Verify: All features remain finite despite rapid oscillation for (j, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite during oscillation, got {}", 201 + j, feature ); } } } #[test] fn test_cusum_cold_start_insufficient_data() { let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); // Feed only 1 observation let result = features.update(0.5); // Verify: All features should be valid (no panic on cold start) assert_eq!(result.len(), 10); for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite on cold start, got {}", 201 + i, feature ); } } // ============================================================================ // 2. ADX Features Edge Cases (Indices 211-215) // ============================================================================ #[test] fn test_adx_nan_in_close_price() { let mut extractor = AdxFeatureExtractor::new(); // Feed bar with NaN close price let bar = AdxOHLCVBar { timestamp: Utc::now(), open: 100.0, high: 102.0, low: 98.0, close: f64::NAN, volume: 1000.0, }; let features = extractor.update(&bar); // Verify: All features should be finite (NaN handled gracefully) for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with NaN close, got {}", 211 + i, feature ); } } #[test] fn test_adx_inf_in_volume() { let mut extractor = AdxFeatureExtractor::new(); // Feed bar with infinite volume let bar = AdxOHLCVBar { timestamp: Utc::now(), open: 100.0, high: 102.0, low: 98.0, close: 101.0, volume: f64::INFINITY, }; let features = extractor.update(&bar); // Verify: All features should be finite for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with Inf volume, got {}", 211 + i, feature ); } } #[test] fn test_adx_zero_volume_bar() { let mut extractor = AdxFeatureExtractor::new(); // Initialize with 30 normal bars for i in 0..30 { let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0); extractor.update(&bar); } // Feed bar with zero volume let bar = create_adx_bar(130.0, 1.02, 0.98, 0.0); let features = extractor.update(&bar); // Verify: Zero volume should not crash, features should be finite for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero volume, got {}", 211 + i, feature ); } } #[test] fn test_adx_invalid_ohlc_high_less_than_low() { let mut extractor = AdxFeatureExtractor::new(); // Feed invalid bar (high < low) for _ in 0..30 { let bar = create_invalid_adx_bar(100.0); let features = extractor.update(&bar); // Verify: All features should be finite despite invalid OHLC for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with invalid OHLC, got {}", 211 + i, feature ); } } } #[test] fn test_adx_close_outside_ohlc_range() { let mut extractor = AdxFeatureExtractor::new(); // Feed bars with close outside [low, high] for _ in 0..30 { let bar = create_adx_bar_with_invalid_close(100.0); let features = extractor.update(&bar); // Verify: Features should be finite for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with close outside range, got {}", 211 + i, feature ); } } } #[test] fn test_adx_cold_start_less_than_14_bars() { let mut extractor = AdxFeatureExtractor::new(); // Feed only 5 bars (insufficient for ADX initialization) for i in 0..5 { let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0); let features = extractor.update(&bar); // Verify: Features should be zeros until initialization assert_eq!( features, [0.0; 5], "ADX should return zeros before initialization" ); } } #[test] fn test_adx_zero_volatility_100_bars() { let mut extractor = AdxFeatureExtractor::new(); // Feed 100 bars with zero volatility (flat prices) let bars = create_flat_adx_bars(100, 100.0); let mut features = [0.0; 5]; for bar in bars.iter() { features = extractor.update(bar); } // Verify: ADX should be very low (<5) with zero volatility assert!( extractor.is_initialized(), "ADX should be initialized after 100 bars" ); assert!( features[0] < 5.0, "ADX should be <5 with zero volatility, got {}", features[0] ); // Verify: All features are finite for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero volatility, got {}", 211 + i, feature ); } } #[test] fn test_adx_price_jump_50_percent() { let mut extractor = AdxFeatureExtractor::new(); // Initialize with normal bars for i in 0..20 { let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0); extractor.update(&bar); } // Circuit breaker event: 50% price jump let bar = create_adx_bar(180.0, 1.02, 0.98, 5000.0); let features = extractor.update(&bar); // Verify: Features should remain finite (extreme values handled) for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite after 50% jump, got {}", 211 + i, feature ); } // Verify: ADX should remain in [0, 100] range assert!( features[0] >= 0.0 && features[0] <= 100.0, "ADX should be in [0, 100], got {}", features[0] ); } #[test] fn test_adx_volume_spike_1000x() { let mut extractor = AdxFeatureExtractor::new(); // Initialize with normal bars for i in 0..20 { let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0); extractor.update(&bar); } // Volume spike: 1000x normal let bar = create_adx_bar(120.0, 1.02, 0.98, 1_000_000.0); let features = extractor.update(&bar); // Verify: Volume spike should not crash, features should be finite for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite after volume spike, got {}", 211 + i, feature ); } } #[test] fn test_adx_gaps_in_data() { let mut extractor = AdxFeatureExtractor::new(); // Create bars with gaps (None = missing bar) let prices = vec![ Some(100.0), Some(101.0), None, // Gap Some(103.0), None, // Gap Some(105.0), ]; let bars = create_adx_bars_with_gap(prices); // Feed bars with gaps for bar in bars.iter() { let features = extractor.update(bar); // Verify: Gaps should not cause issues for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with data gaps, got {}", 211 + i, feature ); } } } // ============================================================================ // 3. Transition Features Edge Cases (Indices 216-220) // ============================================================================ #[test] fn test_transition_rapid_regime_cycling() { let mut features = RegimeTransitionFeatures::new(4, 0.1); // Rapid cycling: 10 regime changes in 10 bars let regimes = vec![ MarketRegime::Bull, MarketRegime::Bear, MarketRegime::Sideways, MarketRegime::HighVolatility, MarketRegime::Bull, MarketRegime::Bear, MarketRegime::Sideways, MarketRegime::HighVolatility, MarketRegime::Bull, MarketRegime::Bear, ]; for regime in regimes { let result = features.update(regime); // Verify: Rapid cycling should not crash assert_eq!(result.len(), 5); for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite during rapid cycling, got {}", 216 + i, feature ); } } } #[test] fn test_transition_single_regime_persistence() { let mut features = RegimeTransitionFeatures::new(4, 0.1); // Single regime for 100 bars (no transitions) for _ in 0..100 { let result = features.update(MarketRegime::Bull); // Verify: Single regime persistence should be handled assert_eq!(result.len(), 5); for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with single regime, got {}", 216 + i, feature ); } } } #[test] fn test_transition_cold_start() { let mut features = RegimeTransitionFeatures::new(4, 0.1); // First update (cold start) let result = features.update(MarketRegime::Sideways); // Verify: Cold start should not crash assert_eq!(result.len(), 5); for (i, &feature) in result.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite on cold start, got {}", 216 + i, feature ); } } // ============================================================================ // 4. Adaptive Features Edge Cases (Indices 221-224) // ============================================================================ #[test] fn test_adaptive_zero_position_size() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // Zero position size let features = adaptive.update(MarketRegime::Normal, 0.01, 0.0, &bars); // Verify: Zero position should be handled gracefully for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero position, got {}", 221 + i, feature ); } // Feature 224 (risk budget) should be 0.0 assert_eq!( features[3], 0.0, "Risk budget should be 0.0 with zero position" ); } #[test] fn test_adaptive_zero_max_position() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 0.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // Zero max position (division by zero) let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars); // Verify: Zero max position should be handled (no division by zero) for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero max position, got {}", 221 + i, feature ); } } #[test] fn test_adaptive_zero_atr_flat_prices() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Flat prices (zero ATR) let bars = create_flat_bars(20, 100.0); let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars); // Verify: Zero ATR should be handled gracefully for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with zero ATR, got {}", 221 + i, feature ); } // Feature 222 (stop multiplier) should be close to zero (0 * multiplier) assert!( features[1].abs() < 0.1, "Stop multiplier should be near zero with flat prices, got {}", features[1] ); } #[test] fn test_adaptive_extreme_positive_return() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // Extreme positive return (+100%) let features = adaptive.update(MarketRegime::Normal, 1.0, 50_000.0, &bars); // Verify: Extreme return should not crash for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with +100% return, got {}", 221 + i, feature ); } } #[test] fn test_adaptive_extreme_negative_return() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // Extreme negative return (-100%) let features = adaptive.update(MarketRegime::Crisis, -1.0, 50_000.0, &bars); // Verify: Extreme negative return should be handled for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with -100% return, got {}", 221 + i, feature ); } } #[test] fn test_adaptive_nan_return() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // NaN return let features = adaptive.update(MarketRegime::Normal, f64::NAN, 50_000.0, &bars); // Verify: NaN return should be handled gracefully for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with NaN return, got {}", 221 + i, feature ); } } #[test] fn test_adaptive_insufficient_bars_for_atr() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Only 5 bars (insufficient for 14-period ATR) let bars: Vec = (0..5) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars); // Verify: Insufficient bars should be handled for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with insufficient bars, got {}", 221 + i, feature ); } // Feature 222 (stop multiplier) should be 0.0 (no ATR available) assert_eq!( features[1], 0.0, "Stop multiplier should be 0.0 with insufficient bars for ATR" ); } #[test] fn test_adaptive_max_position_size_exceeded() { let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); let bars: Vec = (0..20) .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0)) .collect(); // Position size exceeds max (150% of max) let features = adaptive.update(MarketRegime::Normal, 0.01, 150_000.0, &bars); // Verify: Over-leveraged position should be handled for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "Feature {} should be finite with over-leverage, got {}", 221 + i, feature ); } // Feature 224 (risk budget) should be clamped to 1.0 assert!( features[3] <= 1.0, "Risk budget should be clamped to 1.0, got {}", features[3] ); } // ============================================================================ // 5. Integration Edge Cases (Cross-Module) // ============================================================================ #[test] fn test_integration_all_extractors_with_nan_inputs() { // Initialize all extractors let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); let mut adx = AdxFeatureExtractor::new(); let mut transition = RegimeTransitionFeatures::new(4, 0.1); let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Create ADX bar with NaN values let adx_bar = AdxOHLCVBar { timestamp: Utc::now(), open: f64::NAN, high: f64::NAN, low: f64::NAN, close: f64::NAN, volume: 1000.0, }; // Create extraction bar with NaN values let bar = OHLCVBar { timestamp: Utc::now(), open: f64::NAN, high: f64::NAN, low: f64::NAN, close: f64::NAN, volume: 1000.0, }; let bars = vec![bar.clone()]; // Update all extractors with NaN inputs let cusum_features = cusum.update(f64::NAN); let adx_features = adx.update(&adx_bar); let transition_features = transition.update(MarketRegime::Unknown); let adaptive_features = adaptive.update(MarketRegime::Unknown, f64::NAN, 50_000.0, &bars); // Verify: All extractors handle NaN gracefully let all_features = [ ("CUSUM", cusum_features.as_slice()), ("ADX", adx_features.as_slice()), ("Transition", transition_features.as_slice()), ("Adaptive", adaptive_features.as_slice()), ]; for (extractor, features) in all_features.iter() { for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "{} feature {} should be finite with NaN inputs, got {}", extractor, i, feature ); } } } #[test] fn test_integration_all_extractors_with_extreme_values() { // Initialize all extractors let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); let mut adx = AdxFeatureExtractor::new(); let mut transition = RegimeTransitionFeatures::new(4, 0.1); let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Feed 30 bars to initialize ADX for i in 0..30 { let adx_bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0); adx.update(&adx_bar); cusum.update(i as f64 * 0.01); transition.update(MarketRegime::Normal); } // Extreme event: 100x price jump, 1000x volume spike let extreme_adx_bar = create_adx_bar(10000.0, 1.5, 0.5, 1_000_000.0); let extreme_bar = create_bar(10000.0, 1.5, 0.5, 1_000_000.0); let bars = vec![extreme_bar]; let cusum_features = cusum.update(100.0); let adx_features = adx.update(&extreme_adx_bar); let transition_features = transition.update(MarketRegime::Crisis); let adaptive_features = adaptive.update(MarketRegime::Crisis, 1.0, 100_000.0, &bars); // Verify: All extractors handle extreme values gracefully let all_features = [ ("CUSUM", cusum_features.as_slice()), ("ADX", adx_features.as_slice()), ("Transition", transition_features.as_slice()), ("Adaptive", adaptive_features.as_slice()), ]; for (extractor, features) in all_features.iter() { for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "{} feature {} should be finite with extreme values, got {}", extractor, i, feature ); } } } #[test] fn test_integration_cold_start_all_extractors() { // Initialize all extractors let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); let mut adx = AdxFeatureExtractor::new(); let mut transition = RegimeTransitionFeatures::new(4, 0.1); let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Cold start: first bar let adx_bar = create_adx_bar(100.0, 1.02, 0.98, 1000.0); let bar = create_bar(100.0, 1.02, 0.98, 1000.0); let bars = vec![bar]; let cusum_features = cusum.update(0.01); let adx_features = adx.update(&adx_bar); let transition_features = transition.update(MarketRegime::Normal); let adaptive_features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars); // Verify: All extractors handle cold start gracefully (no panic) let all_features = [ ("CUSUM", cusum_features.as_slice()), ("ADX", adx_features.as_slice()), ("Transition", transition_features.as_slice()), ("Adaptive", adaptive_features.as_slice()), ]; for (extractor, features) in all_features.iter() { for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "{} feature {} should be finite on cold start, got {}", extractor, i, feature ); } } } #[test] fn test_integration_zero_volatility_all_extractors() { // Initialize all extractors let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0); let mut adx = AdxFeatureExtractor::new(); let mut transition = RegimeTransitionFeatures::new(4, 0.1); let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14); // Zero volatility: 50 flat bars let adx_bars = create_flat_adx_bars(50, 100.0); let bars = create_flat_bars(50, 100.0); let mut features_snapshot = None; for (i, adx_bar) in adx_bars.iter().enumerate() { let cusum_features = cusum.update(0.0); let adx_features = adx.update(adx_bar); let transition_features = transition.update(MarketRegime::Sideways); let adaptive_features = adaptive.update( MarketRegime::Sideways, 0.0, 50_000.0, &bars[..20.min(i + 1)], ); features_snapshot = Some(( cusum_features, adx_features, transition_features, adaptive_features, )); } let (cusum_features, adx_features, transition_features, adaptive_features) = features_snapshot.unwrap(); // Verify: All extractors handle zero volatility gracefully let all_features = [ ("CUSUM", cusum_features.as_slice()), ("ADX", adx_features.as_slice()), ("Transition", transition_features.as_slice()), ("Adaptive", adaptive_features.as_slice()), ]; for (extractor, features) in all_features.iter() { for (i, &feature) in features.iter().enumerate() { assert!( feature.is_finite(), "{} feature {} should be finite with zero volatility, got {}", extractor, i, feature ); } } // Verify: ADX should be very low with zero volatility assert!( adx_features[0] < 5.0, "ADX should be <5 with zero volatility, got {}", adx_features[0] ); }