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>
109 lines
4.6 KiB
Rust
109 lines
4.6 KiB
Rust
/// Hurst Exponent Division by Zero Validation Test
|
|
///
|
|
/// This test validates the fixes for Hurst exponent division by zero errors:
|
|
/// 1. ml/src/regime/trending.rs:394 - Window size validation (n > 1.5)
|
|
/// 2. ml/src/features/price_features.rs - Same validation in feature extraction
|
|
///
|
|
/// Expected behavior:
|
|
/// - 1-bar window: Returns 0.5 (random walk assumption)
|
|
/// - 2-bar window: Returns 0.5 (n=1.0, below threshold)
|
|
/// - Small windows (<10): Returns 0.5 (fallback)
|
|
/// - Valid windows (>=10): Calculates Hurst exponent normally
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
#[path = "ml/src/features/price_features.rs"]
|
|
mod price_features;
|
|
use price_features::{OHLCVBar, PriceFeatureExtractor};
|
|
|
|
fn create_test_bar(close: f64) -> OHLCVBar {
|
|
OHLCVBar {
|
|
timestamp: chrono::Utc::now(),
|
|
open: close,
|
|
high: close * 1.01,
|
|
low: close * 0.99,
|
|
close,
|
|
volume: 1000.0,
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
println!("=== Hurst Exponent Division by Zero Validation ===\n");
|
|
|
|
// Test 1: 1-bar window (edge case)
|
|
let mut bars_1 = VecDeque::new();
|
|
bars_1.push_back(create_test_bar(100.0));
|
|
let hurst_1 = PriceFeatureExtractor::compute_hurst_exponent(&bars_1, 1);
|
|
println!("✓ Test 1: 1-bar window");
|
|
println!(" Hurst exponent: {:.4}", hurst_1);
|
|
println!(" Expected: 0.5000 (random walk)");
|
|
assert_eq!(hurst_1, 0.5, "1-bar window should return 0.5");
|
|
println!(" PASS: No division by zero\n");
|
|
|
|
// Test 2: 2-bar window (n=1.0 after returns, below 1.5 threshold)
|
|
let mut bars_2 = VecDeque::new();
|
|
bars_2.push_back(create_test_bar(100.0));
|
|
bars_2.push_back(create_test_bar(101.0));
|
|
let hurst_2 = PriceFeatureExtractor::compute_hurst_exponent(&bars_2, 2);
|
|
println!("✓ Test 2: 2-bar window");
|
|
println!(" Hurst exponent: {:.4}", hurst_2);
|
|
println!(" Expected: 0.5000 (n=1.0, below threshold)");
|
|
assert_eq!(hurst_2, 0.5, "2-bar window should return 0.5");
|
|
println!(" PASS: No division by zero\n");
|
|
|
|
// Test 3: Small window (<10 bars)
|
|
let mut bars_small = VecDeque::new();
|
|
for i in 0..5 {
|
|
bars_small.push_back(create_test_bar(100.0 + i as f64));
|
|
}
|
|
let hurst_small = PriceFeatureExtractor::compute_hurst_exponent(&bars_small, 5);
|
|
println!("✓ Test 3: 5-bar window (insufficient data)");
|
|
println!(" Hurst exponent: {:.4}", hurst_small);
|
|
println!(" Expected: 0.5000 (fallback for <10 bars)");
|
|
assert_eq!(hurst_small, 0.5, "Small window should return 0.5");
|
|
println!(" PASS: Fallback to random walk\n");
|
|
|
|
// Test 4: Valid window (30 bars, trending)
|
|
let mut bars_valid = VecDeque::new();
|
|
for i in 0..30 {
|
|
bars_valid.push_back(create_test_bar(100.0 + (i as f64 * 0.5)));
|
|
}
|
|
let hurst_valid = PriceFeatureExtractor::compute_hurst_exponent(&bars_valid, 20);
|
|
println!("✓ Test 4: 30-bar trending series");
|
|
println!(" Hurst exponent: {:.4}", hurst_valid);
|
|
println!(" Expected: 0.5-1.0 (valid calculation)");
|
|
assert!(hurst_valid >= 0.0 && hurst_valid <= 1.0, "Valid Hurst should be in [0, 1]");
|
|
assert!(hurst_valid.is_finite(), "Valid Hurst should be finite");
|
|
println!(" PASS: Valid Hurst calculation (no NaN/Inf)\n");
|
|
|
|
// Test 5: Constant prices (zero volatility)
|
|
let mut bars_const = VecDeque::new();
|
|
for _ in 0..30 {
|
|
bars_const.push_back(create_test_bar(100.0));
|
|
}
|
|
let hurst_const = PriceFeatureExtractor::compute_hurst_exponent(&bars_const, 20);
|
|
println!("✓ Test 5: 30-bar constant series (zero volatility)");
|
|
println!(" Hurst exponent: {:.4}", hurst_const);
|
|
println!(" Expected: 0.5000 (zero volatility fallback)");
|
|
assert_eq!(hurst_const, 0.5, "Zero volatility should return 0.5");
|
|
println!(" PASS: Zero volatility handled\n");
|
|
|
|
// Test 6: Feature extraction with edge cases
|
|
let mut bars_feature = VecDeque::new();
|
|
bars_feature.push_back(create_test_bar(100.0));
|
|
let features = PriceFeatureExtractor::extract_all(&bars_feature);
|
|
println!("✓ Test 6: Feature extraction with 1-bar window");
|
|
println!(" Feature 13 (Hurst): {:.4}", features[13]);
|
|
println!(" Feature 14 (Fractal): {:.4}", features[14]);
|
|
assert!(features[13].is_finite(), "Hurst feature should be finite");
|
|
assert!(features[14].is_finite(), "Fractal dimension should be finite");
|
|
println!(" PASS: No NaN/Inf in feature extraction\n");
|
|
|
|
println!("=== All Tests Passed ===");
|
|
println!("✓ Window size validation working (n > 1.5)");
|
|
println!("✓ 1-bar window returns 0.5 (no division by zero)");
|
|
println!("✓ Zero volatility returns 0.5 (no NaN)");
|
|
println!("✓ Valid windows calculate Hurst correctly");
|
|
println!("✓ No NaN or Inf values in feature extraction");
|
|
}
|