From 92e47f5efcc8e8fdf12721ca1b33479d6ec70dcf Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 4 Mar 2026 21:02:32 +0100 Subject: [PATCH] =?UTF-8?q?fix(ml):=20eliminate=20hold=20reward=20bias=20?= =?UTF-8?q?=E2=80=94=20remove=20/1000=20penalty=20divisor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hold penalty was divided by 1000 (producing ~0.00001), negligible vs transaction costs (0.05-0.15%). Now uses hold_penalty_weight directly from hyperopt (0.01-2.0 range). Co-Authored-By: Claude Opus 4.6 --- crates/ml/src/dqn/reward.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/ml/src/dqn/reward.rs b/crates/ml/src/dqn/reward.rs index cdba24ac1..2d0f22bec 100644 --- a/crates/ml/src/dqn/reward.rs +++ b/crates/ml/src/dqn/reward.rs @@ -926,13 +926,9 @@ impl RewardFunction { // |ln(P_{t+1} / P_t)| measures the magnitude of the price change (velocity) let volatility = next_log_return.abs(); - // BUG FIX: Scale hold_penalty_weight to percentage units - // Config value: 0.5-2.0 (raw scalar from hyperopt) - // Scaled value: 0.0005-0.002 (50-200 basis points = 0.05-0.2%) - // This makes hold penalty comparable to transaction costs (0.05-0.15%), not 30x weaker - let hold_penalty_scale = Decimal::try_from(1000.0) - .unwrap_or(Decimal::ONE); - let hold_penalty_pct = self.config.hold_penalty_weight / hold_penalty_scale; + // Hold penalty weight used directly (0.01-2.0 range from hyperopt). + // Previously divided by 1000, making it 0.00001 — negligible vs tx costs (0.05-0.15%). + let hold_penalty_pct = self.config.hold_penalty_weight; // Compare volatility to movement threshold let hold_reward = if volatility < self.config.movement_threshold {