Files
foxhunt/crates/ml/tests/test_extract_256_dim_features.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

207 lines
6.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Integration test for 54-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<OHLCVBar> = (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 54 dimensions
for (i, feature_vec) in features.iter().enumerate() {
assert_eq!(
feature_vec.len(),
54,
"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 {} 54-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<OHLCVBar> = (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, 54)
assert_eq!(features.len(), 10, "Wrong number of bars");
for feature_vec in &features {
assert_eq!(feature_vec.len(), 54, "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 × 54 features",
features.len()
);
}
#[test]
fn test_insufficient_data_error() {
// Create only 10 bars (below 50 warmup requirement)
let bars: Vec<OHLCVBar> = (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<OHLCVBar> = (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<OHLCVBar> = (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");
}