diff --git a/crates/ml/examples/evaluate_baseline.rs b/crates/ml/examples/evaluate_baseline.rs index 2a7243fb8..fb2bfea37 100644 --- a/crates/ml/examples/evaluate_baseline.rs +++ b/crates/ml/examples/evaluate_baseline.rs @@ -174,6 +174,11 @@ struct Args { #[arg(long, default_value_t = 100_000.0)] initial_capital: f64, + /// Maximum absolute position size in contracts (must match training config). + /// Disable leverage cap to match training env (no leverage constraint during training). + #[arg(long, default_value_t = 2.0)] + max_position: f64, + /// Use CUDA Graph capture for the DQN evaluation step loop. /// /// When enabled, the entire step loop (gather + forward + env_step for @@ -1075,10 +1080,11 @@ fn evaluate_dqn_fold_gpu( } let gpu_config = GpuBacktestConfig { - max_position: 1.0, + max_position: args.max_position as f32, tx_cost_bps: args.tx_cost_bps as f32, spread_cost: (args.tick_size * args.spread_ticks) as f32, initial_capital: args.initial_capital as f32, + max_leverage: 0.0, // Disabled: match training env (no leverage cap) ..Default::default() }; @@ -1361,10 +1367,11 @@ fn evaluate_ppo_fold_gpu( } let gpu_config = GpuBacktestConfig { - max_position: 1.0, + max_position: args.max_position as f32, tx_cost_bps: args.tx_cost_bps as f32, spread_cost: (args.tick_size * args.spread_ticks) as f32, initial_capital: args.initial_capital as f32, + max_leverage: 0.0, // Disabled: match training env (no leverage cap) ..Default::default() }; @@ -1478,10 +1485,11 @@ fn evaluate_supervised_fold_gpu( } let gpu_config = GpuBacktestConfig { - max_position: 1.0, + max_position: args.max_position as f32, tx_cost_bps: args.tx_cost_bps as f32, spread_cost: (args.tick_size * args.spread_ticks) as f32, initial_capital: args.initial_capital as f32, + max_leverage: 0.0, // Disabled: match training env (no leverage cap) ..Default::default() }; diff --git a/crates/ml/src/hyperopt/adapters/dqn.rs b/crates/ml/src/hyperopt/adapters/dqn.rs index c2e6d5723..81c98ae03 100644 --- a/crates/ml/src/hyperopt/adapters/dqn.rs +++ b/crates/ml/src/hyperopt/adapters/dqn.rs @@ -1650,6 +1650,7 @@ impl DQNTrainer { window_size: usize, stride: usize, device: &candle_core::Device, + max_position_absolute: f64, ) -> Result, MLError> { use crate::cuda_pipeline::gpu_backtest_evaluator::{ GpuBacktestConfig, GpuBacktestEvaluator, @@ -1721,11 +1722,18 @@ impl DQNTrainer { window_prices.push(prices); window_features.push(features); } + // Use the hyperopt param's position limit — NOT hardcoded 1.0. + // Training env uses max_position_absolute directly, so the walk-forward + // evaluator must match. Leverage cap disabled (max_leverage=0) because + // the training env doesn't have one; applying it here creates a + // train/eval mismatch that makes transaction costs dominate any alpha. + #[allow(clippy::cast_possible_truncation)] let config = GpuBacktestConfig { - max_position: 1.0, + max_position: max_position_absolute as f32, tx_cost_bps: self.tx_cost_bps as f32, spread_cost: (self.tick_size * self.spread_ticks) as f32, initial_capital: self.initial_capital as f32, + max_leverage: 0.0, // Disabled: match training env (no leverage cap) ..Default::default() }; @@ -3165,6 +3173,7 @@ impl HyperparameterOptimizable for DQNTrainer { window_size, stride, &device, + params.max_position_absolute, ) { Ok(m) => { if m.is_some() {