diff --git a/ml/tests/kelly_position_sizing_integration.rs b/ml/tests/kelly_position_sizing_integration.rs deleted file mode 100644 index 054169333..000000000 --- a/ml/tests/kelly_position_sizing_integration.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Kelly Criterion Position Sizing Integration Test -// -// Validates that Kelly fraction is correctly applied to position sizing -// in the backtesting pipeline (PortfolioTracker → EvaluationEngine → Hyperopt). - -use ml::evaluation::engine::{Action, EvaluationEngine}; -use ml::evaluation::metrics::OHLCVBarF32; - -/// Compute expected net PnL after transaction costs (0.15% per side). -/// This mirrors EvaluationEngine::close_position(). -fn expected_net_pnl(entry: f32, exit: f32, kelly: f64, is_long: bool) -> f32 { - let base_pnl = if is_long { exit - entry } else { entry - exit }; - let gross = base_pnl * kelly as f32; - let fee: f32 = 0.0015; - let entry_cost = entry * kelly as f32 * fee; - let exit_cost = exit * kelly as f32 * fee; - gross - entry_cost - exit_cost -} - -#[test] -fn test_kelly_scales_pnl_correctly() { - let bar1 = OHLCVBarF32 { timestamp: 0, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 0.0 }; - let bar2 = OHLCVBarF32 { timestamp: 1, open: 110.0, high: 110.0, low: 110.0, close: 110.0, volume: 0.0 }; - - // Test 1: Full Kelly (1.0) - baseline - let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0); - engine_full.process_bar(0, &bar1, Action::Buy); - engine_full.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_full.trades.len(), 1); - let expected = expected_net_pnl(100.0, 110.0, 1.0, true); - assert!((engine_full.trades[0].pnl - expected).abs() < 0.01, - "Full Kelly: expected {:.4}, got {:.4}", expected, engine_full.trades[0].pnl); - - // Test 2: Half Kelly (0.5) - reduced position - let mut engine_half = EvaluationEngine::new_with_kelly(10_000.0, 0.5); - engine_half.process_bar(0, &bar1, Action::Buy); - engine_half.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_half.trades.len(), 1); - let expected = expected_net_pnl(100.0, 110.0, 0.5, true); - assert!((engine_half.trades[0].pnl - expected).abs() < 0.01, - "Half Kelly: expected {:.4}, got {:.4}", expected, engine_half.trades[0].pnl); - - // Test 3: Quarter Kelly (0.25) - conservative - let mut engine_quarter = EvaluationEngine::new_with_kelly(10_000.0, 0.25); - engine_quarter.process_bar(0, &bar1, Action::Buy); - engine_quarter.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_quarter.trades.len(), 1); - let expected = expected_net_pnl(100.0, 110.0, 0.25, true); - assert!((engine_quarter.trades[0].pnl - expected).abs() < 0.01, - "Quarter Kelly: expected {:.4}, got {:.4}", expected, engine_quarter.trades[0].pnl); - - // Verify proportional scaling: half Kelly PnL ~= full Kelly PnL * 0.5 - let ratio = engine_half.trades[0].pnl / engine_full.trades[0].pnl; - assert!((ratio - 0.5).abs() < 0.01, "Half/Full ratio should be ~0.5, got {:.4}", ratio); -} - -#[test] -fn test_kelly_scales_losses_correctly() { - let bar1 = OHLCVBarF32 { timestamp: 0, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 0.0 }; - let bar2 = OHLCVBarF32 { timestamp: 1, open: 90.0, high: 90.0, low: 90.0, close: 90.0, volume: 0.0 }; - - // Full Kelly (1.0) - loss - let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0); - engine_full.process_bar(0, &bar1, Action::Buy); - engine_full.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_full.trades.len(), 1); - let expected = expected_net_pnl(100.0, 90.0, 1.0, true); - assert!((engine_full.trades[0].pnl - expected).abs() < 0.01, - "Full Kelly loss: expected {:.4}, got {:.4}", expected, engine_full.trades[0].pnl); - - // Half Kelly (0.5) - reduced loss - let mut engine_half = EvaluationEngine::new_with_kelly(10_000.0, 0.5); - engine_half.process_bar(0, &bar1, Action::Buy); - engine_half.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_half.trades.len(), 1); - let expected = expected_net_pnl(100.0, 90.0, 0.5, true); - assert!((engine_half.trades[0].pnl - expected).abs() < 0.01, - "Half Kelly loss: expected {:.4}, got {:.4}", expected, engine_half.trades[0].pnl); -} - -#[test] -fn test_kelly_with_short_positions() { - let bar1 = OHLCVBarF32 { timestamp: 0, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 0.0 }; - let bar2 = OHLCVBarF32 { timestamp: 1, open: 90.0, high: 90.0, low: 90.0, close: 90.0, volume: 0.0 }; - - // Full Kelly (1.0) - short profit - let mut engine_full = EvaluationEngine::new_with_kelly(10_000.0, 1.0); - engine_full.process_bar(0, &bar1, Action::Sell); - engine_full.process_bar(1, &bar2, Action::Buy); - assert_eq!(engine_full.trades.len(), 1); - let expected = expected_net_pnl(100.0, 90.0, 1.0, false); - assert!((engine_full.trades[0].pnl - expected).abs() < 0.01, - "Full Kelly short: expected {:.4}, got {:.4}", expected, engine_full.trades[0].pnl); - - // Half Kelly (0.5) - let mut engine_half = EvaluationEngine::new_with_kelly(10_000.0, 0.5); - engine_half.process_bar(0, &bar1, Action::Sell); - engine_half.process_bar(1, &bar2, Action::Buy); - assert_eq!(engine_half.trades.len(), 1); - let expected = expected_net_pnl(100.0, 90.0, 0.5, false); - assert!((engine_half.trades[0].pnl - expected).abs() < 0.01, - "Half Kelly short: expected {:.4}, got {:.4}", expected, engine_half.trades[0].pnl); -} - -#[test] -fn test_default_new_uses_full_kelly() { - let bar1 = OHLCVBarF32 { timestamp: 0, open: 100.0, high: 100.0, low: 100.0, close: 100.0, volume: 0.0 }; - let bar2 = OHLCVBarF32 { timestamp: 1, open: 110.0, high: 110.0, low: 110.0, close: 110.0, volume: 0.0 }; - - // Verify that EvaluationEngine::new() defaults to Kelly=1.0 - let mut engine_default = EvaluationEngine::new(10_000.0); - engine_default.process_bar(0, &bar1, Action::Buy); - engine_default.process_bar(1, &bar2, Action::Sell); - assert_eq!(engine_default.trades.len(), 1); - - // Compare with explicit Kelly=1.0 - let mut engine_explicit = EvaluationEngine::new_with_kelly(10_000.0, 1.0); - engine_explicit.process_bar(0, &bar1, Action::Buy); - engine_explicit.process_bar(1, &bar2, Action::Sell); - - assert!((engine_default.trades[0].pnl - engine_explicit.trades[0].pnl).abs() < 0.001, - "Default should match explicit Kelly=1.0"); -}