#[cfg(test)] mod hyperopt_price_extraction_tests { use approx::assert_relative_eq; /// Helper function to simulate the price extraction logic in hyperopt /// This mirrors the logic in ml/src/hyperopt/adapters/dqn.rs around line 1657 fn extract_close_price_for_hyperopt(target: &[f64], feature_vec: &[f64]) -> f64 { if target.len() >= 4 { // NEW: Use target[2] (raw close price) when available target[2] } else if target.len() >= 2 { // FALLBACK: Use target[0] for backward compatibility target[0] } else { // LAST RESORT: Use feature_vec[3] feature_vec[3] } } #[test] fn test_hyperopt_uses_raw_prices_not_preprocessed() { // Given: Target vector with both preprocessed and raw prices // Layout: [preprocessed_close, preprocessed_next, raw_close, raw_next] let target = vec![ -0.35, // target[0] = preprocessed close (z-score) -0.40, // target[1] = preprocessed next (z-score) 5123.50, // target[2] = RAW close price ✅ CORRECT 5125.00, // target[3] = RAW next price ✅ ]; let feature_vec = vec![0.0, 0.0, 0.0, 0.0]; // When: Extract close price for hyperopt P&L calculation let close_price = extract_close_price_for_hyperopt(&target, &feature_vec); // Then: Should use RAW price (target[2]), NOT preprocessed (target[0]) assert_relative_eq!(close_price, 5123.50, epsilon = 1e-6); assert_ne!(close_price, -0.35); // Verify NOT using preprocessed // Sanity check: Raw prices should be in reasonable range (ES futures: 4000-6000) assert!(close_price > 1000.0 && close_price < 10000.0); } #[test] fn test_hyperopt_backward_compatibility_len2() { // Given: Old target vector with only 2 elements (no raw prices) // This is the format before Wave 16M preprocessing changes let target_old = vec![-0.35, -0.40]; let feature_vec = vec![0.0, 0.0, 0.0, 5000.0]; // When: Extract close price let close_price = extract_close_price_for_hyperopt(&target_old, &feature_vec); // Then: Should fall back to target[0] assert_relative_eq!(close_price, -0.35, epsilon = 1e-6); } #[test] fn test_hyperopt_backward_compatibility_len1() { // Given: Extremely old target vector with only 1 element let target_old = vec![5100.0]; let feature_vec = vec![0.0, 0.0, 0.0, 5000.0]; // When: Extract close price let close_price = extract_close_price_for_hyperopt(&target_old, &feature_vec); // Then: Should fall back to feature_vec[3] assert_relative_eq!(close_price, 5000.0, epsilon = 1e-6); } #[test] fn test_hyperopt_realistic_es_futures_prices() { // Given: Realistic ES futures prices from actual trading data let test_cases = vec![ (vec![-1.2, -0.8, 4523.75, 4525.50], 4523.75), // Typical ES price (vec![0.5, 0.3, 5234.25, 5235.00], 5234.25), // Higher ES price (vec![-2.1, -1.9, 4012.50, 4015.00], 4012.50), // Lower ES price ]; let feature_vec = vec![0.0, 0.0, 0.0, 0.0]; for (target, expected_price) in test_cases { let close_price = extract_close_price_for_hyperopt(&target, &feature_vec); assert_relative_eq!(close_price, expected_price, epsilon = 1e-6); // Verify we're NOT using preprocessed values assert_ne!(close_price, target[0]); } } #[test] fn test_hyperopt_price_extraction_error_magnitude() { // Given: Example showing the 631% error from using wrong index let target = vec![ -0.35, // Preprocessed: z-score -0.40, // Preprocessed: z-score 5123.50, // RAW: actual price 5125.00, // RAW: actual price ]; let feature_vec = vec![0.0, 0.0, 0.0, 0.0]; let correct_price = extract_close_price_for_hyperopt(&target, &feature_vec); let wrong_price = target[0]; // What we were using before (BUG) // Calculate error magnitude let error_magnitude = ((correct_price - wrong_price).abs() / correct_price) * 100.0; // Then: Error should be massive (>100%) when using wrong index assert!(error_magnitude > 100.0); // Verify correct extraction assert_relative_eq!(correct_price, 5123.50, epsilon = 1e-6); } }