Files
foxhunt/testing/integration/standalone/test_hurst_div_zero.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

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");
}