#![allow( clippy::assertions_on_constants, clippy::assertions_on_result_states, clippy::clone_on_copy, clippy::decimal_literal_representation, clippy::doc_markdown, clippy::empty_line_after_doc_comments, clippy::field_reassign_with_default, clippy::get_unwrap, clippy::identity_op, clippy::inconsistent_digit_grouping, clippy::indexing_slicing, clippy::integer_division, clippy::len_zero, clippy::let_underscore_must_use, clippy::manual_div_ceil, clippy::manual_let_else, clippy::manual_range_contains, clippy::modulo_arithmetic, clippy::needless_range_loop, clippy::non_ascii_literal, clippy::redundant_clone, clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated, clippy::single_match_else, clippy::str_to_string, clippy::string_slice, clippy::tests_outside_test_module, clippy::too_many_lines, clippy::unnecessary_wraps, clippy::unseparated_literal_suffix, clippy::use_debug, clippy::useless_vec, clippy::wildcard_enum_match_arm, clippy::else_if_without_else, clippy::expect_used, clippy::missing_const_for_fn, clippy::similar_names, clippy::type_complexity, clippy::collapsible_else_if, clippy::doc_lazy_continuation, clippy::items_after_test_module, clippy::map_clone, clippy::multiple_unsafe_ops_per_block, clippy::unwrap_or_default, clippy::assign_op_pattern, clippy::needless_borrow, clippy::println_empty_string, clippy::unnecessary_cast, clippy::used_underscore_binding, clippy::create_dir, clippy::implicit_saturating_sub, clippy::exit, clippy::expect_fun_call, clippy::too_many_arguments, clippy::unnecessary_map_or, clippy::unwrap_used, dead_code, unused_imports, unused_variables, clippy::cloned_ref_to_slice_refs, clippy::neg_multiply, clippy::while_let_loop, clippy::bool_assert_comparison, clippy::excessive_precision, clippy::trivially_copy_pass_by_ref, clippy::op_ref, clippy::redundant_closure, clippy::unnecessary_lazy_evaluations, clippy::if_then_some_else_none, clippy::unnecessary_to_owned, clippy::single_component_path_imports, )] #[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); } }