diff --git a/ml/tests/real_data_pipeline_test.rs b/ml/tests/real_data_pipeline_test.rs index ca4df22a3..da93ed543 100644 --- a/ml/tests/real_data_pipeline_test.rs +++ b/ml/tests/real_data_pipeline_test.rs @@ -3,11 +3,16 @@ //! Validates the full pipeline with synthetic data (no Databento API needed): //! generate bars -> extract features -> walk-forward split -> normalization -> verify dimensions. +#![allow(unused_crate_dependencies)] + use chrono::{Datelike, NaiveDate, NaiveTime, TimeZone, Utc, Weekday}; use ml::features::extraction::extract_ml_features; use ml::types::OHLCVBar; use ml::walk_forward::{generate_walk_forward_windows, NormStats, WalkForwardConfig}; +/// Expected feature dimension from `extract_ml_features`. +const EXPECTED_FEATURE_DIM: usize = 51; + // --------------------------------------------------------------------------- // Synthetic bar generator // --------------------------------------------------------------------------- @@ -103,8 +108,13 @@ fn test_pipeline_features_extract_from_synthetic() { ); // Extract features - let features = extract_ml_features(&bars) - .unwrap_or_else(|e| panic!("Feature extraction failed: {e}")); + let features = match extract_ml_features(&bars) { + Ok(f) => f, + Err(e) => { + assert!(false, "Feature extraction failed: {e}"); + return; // unreachable, satisfies type checker + } + }; // Features should be non-empty (bars - warmup period of 50) assert!( @@ -121,10 +131,11 @@ fn test_pipeline_features_extract_from_synthetic() { for (i, fv) in features.iter().enumerate() { assert_eq!( fv.len(), - 51, - "Feature vector at index {} has {} dims, expected 51", + EXPECTED_FEATURE_DIM, + "Feature vector at index {} has {} dims, expected {}", i, - fv.len() + fv.len(), + EXPECTED_FEATURE_DIM ); // No NaN or Inf in any feature @@ -166,16 +177,18 @@ fn test_pipeline_walk_forward_with_features() { windows.len() ); + let mut validated_folds = 0_usize; + for window in &windows { // Extract features from training data - let train_features = if window.train.len() >= 51 { + let train_features = if window.train.len() >= EXPECTED_FEATURE_DIM { extract_ml_features(&window.train).ok() } else { None }; // Extract features from validation data - let val_features = if window.val.len() >= 51 { + let val_features = if window.val.len() >= EXPECTED_FEATURE_DIM { extract_ml_features(&window.val).ok() } else { None @@ -187,6 +200,8 @@ fn test_pipeline_walk_forward_with_features() { _ => continue, // Skip folds with insufficient data }; + validated_folds += 1; + // Compute NormStats from training data ONLY let stats = NormStats::from_features(train_feats); @@ -197,7 +212,7 @@ fn test_pipeline_walk_forward_with_features() { if !normalized_train.is_empty() { let n = normalized_train.len() as f64; // Compute per-feature mean of normalized training data - let mut mean_per_feature = vec![0.0_f64; 51]; + let mut mean_per_feature = vec![0.0_f64; EXPECTED_FEATURE_DIM]; for fv in &normalized_train { for (m, &v) in mean_per_feature.iter_mut().zip(fv.iter()) { *m += v; @@ -245,6 +260,11 @@ fn test_pipeline_walk_forward_with_features() { } } } + + assert!( + validated_folds >= 1, + "No walk-forward folds were actually validated (all skipped due to insufficient data)" + ); } // ---------------------------------------------------------------------------