//! Integration test for 225-dimension feature extraction //! //! Tests the extract_ml_features() function with real OHLCV data use chrono::Utc; use ml::features::extraction::{extract_ml_features, OHLCVBar}; #[test] fn test_extract_256_dim_features() { // Create synthetic OHLCV bars (100 bars to exceed warmup period of 50) let bars: Vec = (0..100) .map(|i| OHLCVBar { timestamp: Utc::now() + chrono::Duration::hours(i), open: 4500.0 + i as f64 * 0.5, high: 4510.0 + i as f64 * 0.5, low: 4490.0 + i as f64 * 0.5, close: 4505.0 + i as f64 * 0.5, volume: 10000.0 + i as f64 * 100.0, }) .collect(); // Extract features let result = extract_ml_features(&bars); assert!( result.is_ok(), "Feature extraction failed: {:?}", result.err() ); let features = result.unwrap(); // Should return features for bars after warmup period (100 - 50 = 50) assert_eq!( features.len(), 50, "Expected 50 feature vectors (100 bars - 50 warmup), got {}", features.len() ); // Each feature vector should be exactly 225 dimensions for (i, feature_vec) in features.iter().enumerate() { assert_eq!( feature_vec.len(), 225, "Feature vector {} has wrong dimension: {}", i, feature_vec.len() ); // Validate no NaN/Inf values for (j, &val) in feature_vec.iter().enumerate() { assert!( val.is_finite(), "Feature vector {} has non-finite value at index {}: {}", i, j, val ); } } println!( "✅ Successfully extracted {} 225-dim feature vectors", features.len() ); println!( "✅ First feature vector sample (first 10 features): {:?}", &features[0][0..10] ); } #[test] fn test_feature_dimensions() { // Create 60 bars (10 above minimum warmup) let bars: Vec = (0..60) .map(|i| { OHLCVBar { timestamp: Utc::now() + chrono::Duration::minutes(i), open: 4500.0, high: 4510.0, low: 4490.0, close: 4505.0 + (i as f64 * 0.1).sin() * 5.0, // Add some variation volume: 10000.0, } }) .collect(); let features = extract_ml_features(&bars).unwrap(); // Should have 10 feature vectors (60 - 50 warmup) assert_eq!(features.len(), 10); // Check output shape (num_bars, 225) assert_eq!(features.len(), 10, "Wrong number of bars"); for feature_vec in &features { assert_eq!(feature_vec.len(), 225, "Wrong feature dimension"); } // Validate no NaN/Inf for feature_vec in &features { for &val in feature_vec.iter() { assert!(val.is_finite(), "Found non-finite value: {}", val); } } println!( "✅ Feature dimensions validated: {} bars × 225 features", features.len() ); } #[test] fn test_insufficient_data_error() { // Create only 10 bars (below 50 warmup requirement) let bars: Vec = (0..10) .map(|i| OHLCVBar { timestamp: Utc::now() + chrono::Duration::hours(i), open: 4500.0, high: 4510.0, low: 4490.0, close: 4505.0, volume: 10000.0, }) .collect(); let result = extract_ml_features(&bars); assert!(result.is_err(), "Should fail with insufficient data"); let error_msg = result.unwrap_err().to_string(); assert!( error_msg.contains("Insufficient data"), "Expected 'Insufficient data' error, got: {}", error_msg ); println!("✅ Insufficient data error handled correctly"); } #[test] fn test_feature_normalization() { // Create bars with extreme values to test normalization let bars: Vec = (0..100) .map(|i| { OHLCVBar { timestamp: Utc::now() + chrono::Duration::hours(i), open: 4500.0 + i as f64 * 10.0, // Large price changes high: 4600.0 + i as f64 * 10.0, low: 4400.0 + i as f64 * 10.0, close: 4500.0 + i as f64 * 10.0, volume: 100000.0 + i as f64 * 5000.0, // Large volume changes } }) .collect(); let features = extract_ml_features(&bars).unwrap(); // Check that features are reasonably normalized for (i, feature_vec) in features.iter().enumerate() { for (j, &val) in feature_vec.iter().enumerate() { // Most features should be in reasonable range (not all, but most) // This is a sanity check, not strict validation if !(-10.0..=10.0).contains(&val) { // Log but don't fail - some features may legitimately be outside this range println!( "⚠️ Feature {} in vector {} has value outside [-10, 10]: {}", j, i, val ); } } } println!("✅ Feature normalization validated"); } #[test] fn test_feature_consistency() { // Test that same input produces same output (deterministic) let bars: Vec = (0..100) .map(|i| OHLCVBar { timestamp: Utc::now() + chrono::Duration::hours(i), open: 4500.0, high: 4510.0, low: 4490.0, close: 4505.0, volume: 10000.0, }) .collect(); let features1 = extract_ml_features(&bars).unwrap(); let features2 = extract_ml_features(&bars).unwrap(); assert_eq!(features1.len(), features2.len()); for (vec1, vec2) in features1.iter().zip(features2.iter()) { for (&val1, &val2) in vec1.iter().zip(vec2.iter()) { assert!( (val1 - val2).abs() < 1e-10, "Features not consistent: {} vs {}", val1, val2 ); } } println!("✅ Feature extraction is deterministic"); }