feat: adaptive DSR reward shaping from training Sharpe EMA

w_dsr_adaptive = w_dsr_base * clamp(1 - sharpe_ema, 0.5, 3.0)
Negative training Sharpe increases DSR (forces risk reduction).
Positive training Sharpe decreases DSR (enables exploitation).
Adaptive EMA alpha tracks magnitude changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-15 19:36:33 +02:00
parent efdf355e8a
commit 7e97bb776d
3 changed files with 27 additions and 1 deletions

View File

@@ -616,6 +616,8 @@ impl DQNTrainer {
trade_history: VecDeque::with_capacity(500),
volatility_returns: VecDeque::with_capacity(20), // Use default instead of moved hyperparams
trade_stats_history: Vec::new(),
training_sharpe_ema: 0.0,
training_sharpe_ema_initialized: false,
// Wave 16 Portfolio Features
enable_action_masking,

View File

@@ -222,6 +222,12 @@ pub struct DQNTrainer {
/// Replaces pnl_history — real trade metrics instead of averaged rewards.
pub(crate) trade_stats_history: Vec<TradeStats>,
/// Training Sharpe EMA for adaptive DSR reward shaping.
/// Updated at epoch end from GPU experience collection Sharpe.
pub(crate) training_sharpe_ema: f32,
/// Whether training_sharpe_ema has been initialized.
pub(crate) training_sharpe_ema_initialized: bool,
// Wave 16 Portfolio Features
/// Enable action masking (filters invalid actions before Q-value computation)
pub enable_action_masking: bool,

View File

@@ -1143,7 +1143,11 @@ impl DQNTrainer {
feature_noise_scale: self.hyperparams.feature_noise_scale as f32,
vol_normalizer: self.epoch_vol_normalizer,
feature_mask: self.epoch_feature_mask.clone(),
w_dsr: self.hyperparams.w_dsr as f32,
w_dsr: {
let w_dsr_base = self.hyperparams.w_dsr as f32;
let adaptive_mult = (1.0_f32 - self.training_sharpe_ema).clamp(0.5, 3.0);
w_dsr_base * adaptive_mult
},
micro_reward_scale: self.hyperparams.micro_reward_scale as f32,
current_epoch: self.current_epoch as i32,
total_epochs: self.hyperparams.epochs as i32,
@@ -2187,6 +2191,20 @@ impl DQNTrainer {
self.val_loss_history.push(val_loss);
self.sharpe_history.push(epoch_sharpe);
// ── Adaptive DSR: w_dsr = w_dsr_base * clamp(1.0 - sharpe_ema, 0.5, 3.0) ──
{
let sharpe_f32 = epoch_sharpe as f32;
if !self.training_sharpe_ema_initialized {
self.training_sharpe_ema = sharpe_f32;
self.training_sharpe_ema_initialized = true;
} else {
// Adaptive EMA alpha: faster when signal changes magnitude
let err = (sharpe_f32 - self.training_sharpe_ema).abs();
let alpha = (0.05_f32 + 0.3 * err).min(0.5);
self.training_sharpe_ema = (1.0 - alpha) * self.training_sharpe_ema + alpha * sharpe_f32;
}
}
// Limit history vectors to prevent unbounded growth
const MAX_HISTORY_LEN: usize = 100;
if self.loss_history.len() > MAX_HISTORY_LEN {