//! Runtime Validation Script for 225-Feature Extraction //! //! Wave 3 Agent 28: Feature Dimension Runtime Validation //! //! This script validates: //! 1. Feature vector shape is (N, 225) where N â‰Ī 100 //! 2. Warmup period handling (50 bars) //! 3. No NaN or Inf values in output //! 4. All feature indices are populated correctly //! //! Usage: //! cargo run --example validate_225_features_runtime --release use anyhow::{Context, Result}; use chrono::{Duration, Utc}; use ml::features::extraction::{extract_ml_features, OHLCVBar}; use std::time::Instant; fn main() -> Result<()> { println!("🔍 Wave 3 Agent 28: 225-Feature Runtime Validation"); println!("{}", "=".repeat(70)); println!(); // Step 1: Create synthetic OHLCV data (100 bars) println!("📊 Step 1: Creating 100 synthetic OHLCV bars..."); let bars = create_synthetic_bars(100)?; println!("✓ Created {} OHLCV bars", bars.len()); println!(); // Step 2: Extract features and measure performance println!("🔎 Step 2: Extracting 225-dimensional features..."); let start = Instant::now(); let features = extract_ml_features(&bars).context("Failed to extract 225-dimensional features")?; let duration = start.elapsed(); println!( "✓ Extracted {} feature vectors in {:.3}ms", features.len(), duration.as_secs_f64() * 1000.0 ); println!( " Average: {:.3}Ξs per bar", duration.as_micros() as f64 / features.len() as f64 ); println!(); // Step 3: Verify output shape println!("📐 Step 3: Verifying feature dimensions..."); let expected_vectors = bars.len() - 50; // 100 bars - 50 warmup = 50 vectors println!(" Input bars: {}", bars.len()); println!(" Warmup period: 50 bars"); println!(" Expected vectors: {} (100 - 50)", expected_vectors); println!(" Actual vectors: {}", features.len()); if features.len() == expected_vectors { println!("✓ Feature vector count is CORRECT (N = {})", features.len()); } else { println!("❌ Feature vector count MISMATCH!"); println!(" Expected: {}, Got: {}", expected_vectors, features.len()); anyhow::bail!("Feature vector count validation failed"); } if !features.is_empty() && features[0].len() == 225 { println!("✓ Feature dimension is CORRECT (225 per vector)"); } else { println!("❌ Feature dimension MISMATCH!"); if !features.is_empty() { println!(" Expected: 225, Got: {}", features[0].len()); } anyhow::bail!("Feature dimension validation failed"); } println!(); // Step 4: Check for NaN/Inf values println!("🔍 Step 4: Validating feature values (NaN/Inf check)..."); let mut nan_count = 0; let mut inf_count = 0; let mut total_features = 0; for (vec_idx, feature_vec) in features.iter().enumerate() { for (feat_idx, &value) in feature_vec.iter().enumerate() { total_features += 1; if value.is_nan() { nan_count += 1; if nan_count <= 5 { println!(" ⚠ NaN at vector[{}], feature[{}]", vec_idx, feat_idx); } } if value.is_infinite() { inf_count += 1; if inf_count <= 5 { println!(" ⚠ Inf at vector[{}], feature[{}]", vec_idx, feat_idx); } } } } if nan_count == 0 && inf_count == 0 { println!("✓ All {} features are VALID (no NaN/Inf)", total_features); } else { println!("❌ INVALID features detected:"); println!( " NaN count: {} ({:.2}%)", nan_count, (nan_count as f64 / total_features as f64) * 100.0 ); println!( " Inf count: {} ({:.2}%)", inf_count, (inf_count as f64 / total_features as f64) * 100.0 ); anyhow::bail!("Feature validation failed: NaN or Inf values detected"); } println!(); // Step 5: Verify warmup period behavior println!("🕐 Step 5: Verifying warmup period behavior..."); // Test with exactly 50 bars (should fail) let warmup_bars = create_synthetic_bars(50)?; let warmup_result = extract_ml_features(&warmup_bars); match warmup_result { Ok(_) => { println!("❌ Should have failed with 50 bars (warmup period)"); anyhow::bail!("Warmup validation failed: extracted features from 50 bars"); }, Err(e) => { println!("✓ Correctly rejects 50 bars: {}", e); }, } // Test with 51 bars (should succeed with 1 vector) let minimal_bars = create_synthetic_bars(51)?; let minimal_result = extract_ml_features(&minimal_bars)?; if minimal_result.len() == 1 { println!("✓ Correctly extracts 1 vector from 51 bars (51 - 50 warmup)"); } else { println!( "❌ Expected 1 vector from 51 bars, got {}", minimal_result.len() ); anyhow::bail!("Warmup validation failed: incorrect vector count"); } println!(); // Step 6: Feature range analysis println!("📊 Step 6: Feature range analysis (first 10 features)..."); if !features.is_empty() { let first_vec = &features[0]; for i in 0..10.min(first_vec.len()) { let value = first_vec[i]; println!(" Feature[{}]: {:.6}", i, value); } } println!(); // Final summary println!("{}", "=".repeat(70)); println!("🎉 VALIDATION SUMMARY"); println!("{}", "=".repeat(70)); println!( "✓ Feature vector count: {} (N = 100 bars - 50 warmup)", features.len() ); println!("✓ Feature dimensions: 225 per vector"); println!("✓ NaN/Inf check: PASSED (0 invalid values)"); println!("✓ Warmup period: CORRECT (50 bars)"); println!( "✓ Performance: {:.3}Ξs per bar (target: <1000Ξs)", duration.as_micros() as f64 / features.len() as f64 ); println!(); println!("🚀 225-Feature extraction system is PRODUCTION READY!"); println!(); Ok(()) } /// Create synthetic OHLCV bars for testing fn create_synthetic_bars(count: usize) -> Result> { let mut bars = Vec::with_capacity(count); let base_time = Utc::now(); let base_price = 4500.0; // ES.FUT-like price for i in 0..count { let timestamp = base_time + Duration::minutes(i as i64); // Create realistic price movement (random walk with drift) let price_delta = (i as f64 * 0.5).sin() * 2.0; // Oscillating trend let close = base_price + price_delta; let high = close + (i as f64 * 0.1).sin().abs() * 1.5; let low = close - (i as f64 * 0.1).cos().abs() * 1.5; let open = close - price_delta * 0.3; // Realistic volume (1000-5000 contracts) let volume = 2000.0 + (i as f64 * 0.2).cos() * 1500.0; bars.push(OHLCVBar { timestamp, open, high, low, close, volume: volume.abs(), }); } Ok(bars) }