diff --git a/crates/ml/src/trainers/dqn/trainer/training_loop.rs b/crates/ml/src/trainers/dqn/trainer/training_loop.rs index 3de566dfb..a5d43089f 100644 --- a/crates/ml/src/trainers/dqn/trainer/training_loop.rs +++ b/crates/ml/src/trainers/dqn/trainer/training_loop.rs @@ -2457,7 +2457,12 @@ impl DQNTrainer { } // 2. Save checkpoint when improving - let prev_sharpe = self.sharpe_history.last().copied().unwrap_or(0.0) as f32; + // Use second-to-last sharpe (last is current epoch, already pushed before this runs) + let prev_sharpe = if self.sharpe_history.len() >= 2 { + self.sharpe_history[self.sharpe_history.len() - 2] as f32 + } else { + 0.0 + }; let improvement_rate = val_sharpe_f32 - prev_sharpe; // per epoch if improvement_rate > 0.01 && !self.backtracking.route_active { self.save_backtracking_checkpoint(epoch, improvement_rate, val_sharpe_f32)?; @@ -2522,10 +2527,10 @@ impl DQNTrainer { // reset (zeroes m/v for indices 8-41, preserves trunk momentum). This may break // Adam fixed point without changing weights. Give it 3 epochs to take effect. // Full rewind triggers at plateau_threshold + 3. + // Branch Adam reset doesn't need checkpoints — it's a lightweight in-place fix. if self.backtracking.plateau_epochs == self.backtracking.plateau_threshold && !self.backtracking.route_active && epoch.saturating_sub(self.backtracking.last_rewind_epoch) >= self.backtracking.min_rewind_interval - && !self.backtracking.checkpoints.is_empty() { if let Some(ref mut fused) = self.fused_ctx { if let Err(e) = fused.reset_branch_adam_momentum() {