//! Multi-Objective Hyperopt Tests //! //! Validates WAVE 11 multi-objective composite risk metrics implementation: //! - Composite score calculation (Sortino 40%, Calmar 30%, Sharpe 20%, Omega 10%) //! - CVaR tail risk penalty (10x if CVaR < -5%) //! - Objective function integration //! - Edge cases (zero metrics, extreme values, no trades) use approx::assert_relative_eq; /// Test composite score calculation with balanced metrics #[test] fn test_composite_score_balanced() { // Simulate balanced performance metrics let sortino = 1.5; let calmar = 2.0; let sharpe = 1.0; let omega = 1.8; // Calculate composite score (weights: 40%, 30%, 20%, 10%) let composite_score = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; // Expected: 0.4*1.5 + 0.3*2.0 + 0.2*1.0 + 0.1*1.8 = 0.6 + 0.6 + 0.2 + 0.18 = 1.58 assert_relative_eq!(composite_score, 1.58, epsilon = 0.001); } /// Test composite score with elite Sortino (highest weight 40%) #[test] fn test_composite_score_elite_sortino() { // Elite Sortino should dominate composite score let sortino = 3.0; // Excellent let calmar = 1.5; let sharpe = 0.8; let omega = 1.2; let composite_score = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; // Expected: 0.4*3.0 + 0.3*1.5 + 0.2*0.8 + 0.1*1.2 = 1.2 + 0.45 + 0.16 + 0.12 = 1.93 assert_relative_eq!(composite_score, 1.93, epsilon = 0.001); // Elite Sortino (3.0) should produce significantly higher composite than baseline (1.5) let baseline_composite = 0.4 * 1.5 + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; assert!(composite_score > baseline_composite + 0.5); } /// Test CVaR penalty triggers at -5% threshold #[test] fn test_cvar_penalty_threshold() { // CVaR = -4% (acceptable, no penalty) let cvar_acceptable = -0.04; let penalty_acceptable = if cvar_acceptable < -0.05 { 10.0 } else { 0.0 }; assert_relative_eq!(penalty_acceptable, 0.0, epsilon = 0.001); // CVaR = -5% (exactly at threshold, no penalty) let cvar_threshold = -0.05; let penalty_threshold = if cvar_threshold < -0.05 { 10.0 } else { 0.0 }; assert_relative_eq!(penalty_threshold, 0.0, epsilon = 0.001); // CVaR = -5.1% (exceeds threshold, 10x penalty) let cvar_excessive = -0.051; let penalty_excessive = if cvar_excessive < -0.05 { 10.0 } else { 0.0 }; assert_relative_eq!(penalty_excessive, 10.0, epsilon = 0.001); // CVaR = -10% (severe tail risk, 10x penalty) let cvar_severe = -0.10; let penalty_severe = if cvar_severe < -0.05 { 10.0 } else { 0.0 }; assert_relative_eq!(penalty_severe, 10.0, epsilon = 0.001); } /// Test objective function integration (minimize composite) #[test] fn test_objective_function_integration() { // Scenario 1: Good performance, no tail risk let sortino_1 = 2.0; let calmar_1 = 2.5; let sharpe_1 = 1.2; let omega_1 = 1.6; let cvar_1 = -0.03; // Acceptable tail risk let composite_1 = 0.4 * sortino_1 + 0.3 * calmar_1 + 0.2 * sharpe_1 + 0.1 * omega_1; let penalty_1 = if cvar_1 < -0.05 { 10.0 } else { 0.0 }; // Objective = -0.60 * composite + penalty (minimize) let objective_1 = -0.60 * composite_1 + penalty_1; // Expected: composite = 0.8 + 0.75 + 0.24 + 0.16 = 1.95, penalty = 0.0 // Objective = -0.60 * 1.95 + 0.0 = -1.17 assert_relative_eq!(composite_1, 1.95, epsilon = 0.001); assert_relative_eq!(penalty_1, 0.0, epsilon = 0.001); assert_relative_eq!(objective_1, -1.17, epsilon = 0.01); // Scenario 2: Poor performance, severe tail risk let sortino_2 = 0.5; let calmar_2 = 0.8; let sharpe_2 = 0.3; let omega_2 = 0.9; let cvar_2 = -0.08; // Severe tail risk (>5%) let composite_2 = 0.4 * sortino_2 + 0.3 * calmar_2 + 0.2 * sharpe_2 + 0.1 * omega_2; let penalty_2 = if cvar_2 < -0.05 { 10.0 } else { 0.0 }; let objective_2 = -0.60 * composite_2 + penalty_2; // Expected: composite = 0.2 + 0.24 + 0.06 + 0.09 = 0.59, penalty = 10.0 // Objective = -0.60 * 0.59 + 10.0 = -0.354 + 10.0 = 9.646 assert_relative_eq!(composite_2, 0.59, epsilon = 0.001); assert_relative_eq!(penalty_2, 10.0, epsilon = 0.001); assert_relative_eq!(objective_2, 9.646, epsilon = 0.01); // Scenario 1 (good performance, no tail risk) should have MUCH lower objective (better) assert!(objective_1 < objective_2 - 10.0); } /// Test edge case: Zero metrics (no trades scenario) #[test] fn test_zero_metrics_edge_case() { let sortino = 0.0; let calmar = 0.0; let sharpe = 0.0; let omega = 0.0; let cvar = 0.0; // No tail risk if no trades let composite_score = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; let penalty = if cvar < -0.05 { 10.0 } else { 0.0 }; let objective = -0.60 * composite_score + penalty; assert_relative_eq!(composite_score, 0.0, epsilon = 0.001); assert_relative_eq!(penalty, 0.0, epsilon = 0.001); assert_relative_eq!(objective, 0.0, epsilon = 0.001); } /// Test edge case: Negative metrics (poor performance) #[test] fn test_negative_metrics() { // Negative Sortino/Calmar/Sharpe indicate losses exceed gains let sortino = -0.5; let calmar = -0.3; let sharpe = -0.2; let omega = 0.5; // Omega can still be positive (losses less than gains) let cvar = -0.02; // Acceptable tail risk let composite_score = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; let penalty = if cvar < -0.05 { 10.0 } else { 0.0 }; let objective = -0.60 * composite_score + penalty; // Expected: composite = -0.2 + -0.09 + -0.04 + 0.05 = -0.28 // Objective = -0.60 * (-0.28) + 0.0 = 0.168 assert_relative_eq!(composite_score, -0.28, epsilon = 0.001); assert_relative_eq!(penalty, 0.0, epsilon = 0.001); assert_relative_eq!(objective, 0.168, epsilon = 0.001); } /// Test Sortino weight dominance (40% vs 30% Calmar) #[test] fn test_sortino_weight_dominance() { // Scenario A: High Sortino, low Calmar let sortino_a = 2.5; let calmar_a = 1.0; let sharpe_a = 1.0; let omega_a = 1.5; let composite_a = 0.4 * sortino_a + 0.3 * calmar_a + 0.2 * sharpe_a + 0.1 * omega_a; // Scenario B: Low Sortino, high Calmar let sortino_b = 1.0; let calmar_b = 2.5; let sharpe_b = 1.0; let omega_b = 1.5; let composite_b = 0.4 * sortino_b + 0.3 * calmar_b + 0.2 * sharpe_b + 0.1 * omega_b; // Expected A: 0.4*2.5 + 0.3*1.0 + 0.2*1.0 + 0.1*1.5 = 1.0 + 0.3 + 0.2 + 0.15 = 1.65 // Expected B: 0.4*1.0 + 0.3*2.5 + 0.2*1.0 + 0.1*1.5 = 0.4 + 0.75 + 0.2 + 0.15 = 1.50 assert_relative_eq!(composite_a, 1.65, epsilon = 0.001); assert_relative_eq!(composite_b, 1.50, epsilon = 0.001); // High Sortino (40% weight) should dominate over high Calmar (30% weight) assert!(composite_a > composite_b); } /// Test CVaR penalty impact on objective #[test] fn test_cvar_penalty_impact() { let sortino = 1.5; let calmar = 2.0; let sharpe = 1.0; let omega = 1.5; let composite = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; // Scenario 1: Acceptable tail risk (CVaR = -3%) let cvar_ok = -0.03; let penalty_ok = if cvar_ok < -0.05 { 10.0 } else { 0.0 }; let objective_ok = -0.60 * composite + penalty_ok; // Scenario 2: Excessive tail risk (CVaR = -8%) let cvar_bad = -0.08; let penalty_bad = if cvar_bad < -0.05 { 10.0 } else { 0.0 }; let objective_bad = -0.60 * composite + penalty_bad; // 10x penalty should massively increase objective (worse) assert_relative_eq!(penalty_ok, 0.0, epsilon = 0.001); assert_relative_eq!(penalty_bad, 10.0, epsilon = 0.001); assert!(objective_bad > objective_ok + 9.0); // Penalty should dominate } /// Test extreme Omega ratio (upside dominance) #[test] fn test_extreme_omega_ratio() { // Omega ratio > 3.0 indicates strong upside dominance let sortino = 1.5; let calmar = 2.0; let sharpe = 1.0; let omega = 4.0; // Extreme upside let composite = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; // Expected: 0.4*1.5 + 0.3*2.0 + 0.2*1.0 + 0.1*4.0 = 0.6 + 0.6 + 0.2 + 0.4 = 1.8 assert_relative_eq!(composite, 1.8, epsilon = 0.001); // Compare to baseline Omega = 1.5 let composite_baseline = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * 1.5; assert_relative_eq!(composite_baseline, 1.55, epsilon = 0.001); // Extreme Omega should improve composite by 0.25 (10% weight * 2.5 delta) assert_relative_eq!(composite - composite_baseline, 0.25, epsilon = 0.001); } /// Test complete objective function (all components) #[test] fn test_complete_objective_function() { // Simulate realistic hyperopt trial metrics let sortino = 1.8; let calmar = 2.2; let sharpe = 1.1; let omega = 1.6; let cvar = -0.04; // Acceptable tail risk let buy_pct = 35.0; let sell_pct = 25.0; let hold_pct = 40.0; let gradient_norm = 5.0; // Low gradient norm (stable) let q_value_std = 10.0; // Low Q-value volatility (stable) // Component 1: Composite score (60% weight) let composite_score = 0.4 * sortino + 0.3 * calmar + 0.2 * sharpe + 0.1 * omega; let cvar_penalty = if cvar < -0.05 { 10.0 } else { 0.0 }; // Component 2: HFT activity score (25% weight) // Simplified activity score: reward BUY+SELL ratio let buy_sell_ratio = (buy_pct + sell_pct) / (hold_pct + 1e-6); let hft_activity = 2.0 * (buy_sell_ratio / 3.0).min(1.0); // Cap at 3:1 ratio // Component 3: Stability penalty (15% weight) // Simplified: penalize high gradient norm and Q-value std let stability_penalty = if gradient_norm > 50.0 || q_value_std > 100.0 { 5.0 } else { 0.0 }; // Final objective (minimize) let objective = -0.60 * composite_score + cvar_penalty + -0.25 * hft_activity + 0.15 * stability_penalty; // Expected values: // composite_score = 0.4*1.8 + 0.3*2.2 + 0.2*1.1 + 0.1*1.6 = 0.72 + 0.66 + 0.22 + 0.16 = 1.76 // cvar_penalty = 0.0 // hft_activity = 2.0 * ((60/40)/3.0).min(1.0) = 2.0 * 0.5 = 1.0 // stability_penalty = 0.0 // objective = -0.60*1.76 + 0.0 + -0.25*1.0 + 0.15*0.0 = -1.056 + -0.25 = -1.306 assert_relative_eq!(composite_score, 1.76, epsilon = 0.001); assert_relative_eq!(cvar_penalty, 0.0, epsilon = 0.001); assert_relative_eq!(hft_activity, 1.0, epsilon = 0.001); assert_relative_eq!(stability_penalty, 0.0, epsilon = 0.001); assert_relative_eq!(objective, -1.306, epsilon = 0.01); }