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:
@@ -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
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user