feat: wire hyperopt search bounds from dqn-hyperopt.toml — zero hardcoded bounds

All 31 PSO search space bounds now loaded from config/training/dqn-hyperopt.toml
via HyperoptProfile::bound(). To change search ranges, edit the TOML — no code
changes needed.

Log-scale transforms (learning_rate, buffer_size, weight_decay, etc.) applied
in the adapter; TOML stores human-readable linear values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-22 09:40:48 +01:00
parent 3c8e177932
commit 11855e958a

View File

@@ -462,71 +462,75 @@ impl Default for DQNParams {
impl ParameterSpace for DQNParams {
fn continuous_bounds() -> Vec<(f64, f64)> {
// 31D search space (C7: added iqn_lambda)
// Fixed: curiosity_weight=0.0, noisy_epsilon_floor=0.10
// NoisyNet (noisy_sigma_init) + count bonus (UCB) provide exploration.
// All bounds loaded from config/training/dqn-hyperopt.toml [search_space].
// To change search ranges, edit the TOML — no code changes needed.
// Log-scale transforms are applied here; TOML stores linear values.
let hp = crate::training_profile::HyperoptProfile::load("dqn-hyperopt");
let b = |name: &str, default: (f64, f64)| hp.bound(name, default);
let lr = b("learning_rate", (1e-5, 3e-4));
let bs = b("batch_size", (64.0, 512.0));
let gm = b("gamma", (0.88, 0.99));
let bf = b("buffer_size", (50_000.0, 100_000.0));
let mp = b("max_position_absolute", (1.0, 4.0));
let hd = b("huber_delta", (10.0, 40.0));
let ec = b("entropy_coefficient", (0.05, 0.5));
let tc = b("transaction_cost_multiplier", (0.5, 2.0));
let pa = b("per_alpha", (0.4, 0.8));
let pb = b("per_beta_start", (0.2, 0.6));
let vr = b("v_range", (10.0, 50.0));
let ns = b("noisy_sigma_init", (0.1, 1.0));
let dh = b("dueling_hidden_dim", (128.0, 512.0));
let nst = b("n_steps", (3.0, 5.0));
let na = b("num_atoms", (11.0, 101.0));
let wd = b("weight_decay", (1e-4, 1e-2));
let kf = b("kelly_fractional", (0.25, 0.75));
let km = b("kelly_max_fraction", (0.1, 0.5));
let vw = b("volatility_window", (10.0, 30.0));
let tau = b("tau", (0.005, 0.01));
let hdim = b("hidden_dim_base", (128.0, 512.0));
let cql = b("cql_alpha", (0.0, 1.0));
let lrd = b("lr_decay_type", (0.0, 2.0));
let dsr = b("dsr_eta", (0.001, 0.05));
let mpf = b("minimum_profit_factor", (1.1, 2.0));
let cb = b("count_bonus_coefficient", (0.0, 0.3));
let sw = b("sharpe_weight", (0.0, 0.5));
let bh = b("branch_hidden_dim", (64.0, 256.0));
let ga = b("gradient_accumulation_steps", (1.0, 1.0));
let iq = b("iqn_lambda", (0.0, 2.0));
vec![
// Base parameters (10D)
(1e-5_f64.ln(), 3e-4_f64.ln()), // 0: learning_rate (log scale)
(64.0, 512.0), // 1: batch_size (capped: 4096 → too few gradient steps per epoch)
(0.88, 0.99), // 2: gamma (widened: allow longer-horizon strategies)
(50_000_f64.ln(), 100_000_f64.ln()), // 3: buffer_size (log scale)
(1.0, 4.0), // 4: max_position_absolute (linear)
(10.0_f64.ln(), 40.0_f64.ln()), // 5: huber_delta (log scale: 10.0-40.0)
(0.05, 0.5), // 6: entropy_coefficient (C1 FIX: 10x stronger — Q-values ~7.0 need coeff ~0.1-0.5 to matter)
(0.5, 2.0), // 7: transaction_cost_multiplier (linear)
(0.4, 0.8), // 8: per_alpha (linear)
(0.2, 0.6), // 9: per_beta_start (linear)
// Rainbow DQN extensions (6D)
// v_range must cover Q-value range, not reward range.
// Q_max = max_reward / (1-gamma): gamma=0.88→3.75, gamma=0.99→45.
// Range [10, 50] covers the full gamma search space [0.88, 0.99].
(10.0, 50.0), // 10: v_range (symmetric: v_min=-v_range, v_max=+v_range)
(1.0, 1.0), // 11: use_branching (FIXED: always true — GPU pipeline requires branching)
(0.1_f64.ln(), 1.0_f64.ln()), // 12: noisy_sigma_init (log scale)
(128.0, 512.0), // 13: dueling_hidden_dim (linear, step=128)
(3.0, 5.0), // 14: n_steps (raised min: n=3 is Rainbow standard)
(11.0, 101.0), // 15: num_atoms (11=RTX3050, 51=H100 default, 101=max — C51 resolution)
// Weight decay (1D)
(1e-4_f64.ln(), 1e-2_f64.ln()), // 16: weight_decay (C2 FIX: 10x stronger — prevents 14x val/train overfitting)
// Kelly risk parameters (2D — kelly_min_trades fixed to 20)
(0.25, 0.75), // 17: kelly_fractional (capped: full Kelly leads to ruin)
(0.1, 0.5), // 18: kelly_max_fraction
// Volatility window (1D)
(10.0, 30.0), // 19: volatility_window
// Soft update (1D) — curiosity_weight FIXED to 0.0 (not in search space)
(0.005_f64.ln(), 0.01_f64.ln()), // 20: tau (log scale, Polyak soft update)
// GPU-dynamic network sizing (1D)
(128.0, 512.0), // 21: hidden_dim_base (128-512: training is 0.7ms/step but backtest is sequential 32K bars × dim²)
// CQL regularization (1D)
(0.0, 1.0), // 22: cql_alpha (0=disabled, 0.1=mild, 1.0=full offline-RL strength)
// Training dynamics (3D)
(0.0, 2.0), // 23: lr_decay_type (discrete: 0=constant, 1=linear, 2=cosine)
(0.001_f64.ln(), 0.05_f64.ln()), // 24: dsr_eta (log scale: 0.001-0.05)
(1.1, 2.0), // 25: minimum_profit_factor (linear)
// Exploration (1D) — C3 FIX: re-enabled count bonus (UCB, complementary to NoisyNet)
(0.0, 0.3), // 26: count_bonus_coefficient (UCB exploration bonus β)
// Risk-adjusted returns (1D) — C4 FIX: wire from search space instead of hardcoded
(0.0, 0.5), // 27: sharpe_weight (Sharpe ratio blending in composite reward)
// Branching DQN (1D) — C5: per-head hidden dimension
(64.0, 256.0), // 28: branch_hidden_dim (linear, step=64)
// Gradient accumulation (1D) — C6: effective batch = batch_size × accum_steps
// Default: fixed at 1 (no accumulation). Large GPUs expand to [1, 8].
(1.0, 1.0), // 29: gradient_accumulation_steps (discrete power-of-2: 1,2,4,8)
// IQN dual-head lambda (1D) — C7: weight of IQN loss relative to C51
(0.0, 2.0), // 30: iqn_lambda (0.0=C51 only, 0.25=mild IQN, 1.0=equal weight)
(lr.0.ln(), lr.1.ln()), // 0: learning_rate (log scale)
bs, // 1: batch_size
gm, // 2: gamma
(bf.0.ln(), bf.1.ln()), // 3: buffer_size (log scale)
mp, // 4: max_position_absolute
(hd.0.ln(), hd.1.ln()), // 5: huber_delta (log scale)
ec, // 6: entropy_coefficient
tc, // 7: transaction_cost_multiplier
pa, // 8: per_alpha
pb, // 9: per_beta_start
vr, // 10: v_range
(1.0, 1.0), // 11: use_branching (FIXED: always true)
(ns.0.ln(), ns.1.ln()), // 12: noisy_sigma_init (log scale)
dh, // 13: dueling_hidden_dim
nst, // 14: n_steps
na, // 15: num_atoms
(wd.0.ln(), wd.1.ln()), // 16: weight_decay (log scale)
kf, // 17: kelly_fractional
km, // 18: kelly_max_fraction
vw, // 19: volatility_window
(tau.0.ln(), tau.1.ln()), // 20: tau (log scale)
hdim, // 21: hidden_dim_base
cql, // 22: cql_alpha
lrd, // 23: lr_decay_type
(dsr.0.ln(), dsr.1.ln()), // 24: dsr_eta (log scale)
mpf, // 25: minimum_profit_factor
cb, // 26: count_bonus_coefficient
sw, // 27: sharpe_weight
bh, // 28: branch_hidden_dim
ga, // 29: gradient_accumulation_steps
iq, // 30: iqn_lambda
]
}