Commit Graph

17 Commits

Author SHA1 Message Date
jgrusewski
2b85fa2fdf feat: add min_hold_bars config (default 5) — prevents per-bar churning
Add min_hold_bars to DQNHyperparameters (usize, default 5), ExperienceSection
in training_profile, and both TOML configs (smoketest=3, production=5).
Wired through apply_to() so TOML overrides land correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 23:06:28 +01:00
jgrusewski
a400288ae0 feat: comprehensive TOML profiles + config consistency test
Task 7: Update all three DQN TOML profiles with every configurable parameter.
- dqn-smoketest: add [distributional], [advanced], [exploration] (noisy_sigma,
  entropy_coefficient, count_bonus), [risk] (max_position, loss_aversion),
  fix learning_rate 0.0003 -> 0.00003, add reward_scale + gamma
- dqn-production: add reward_scale, exploration params (noisy/entropy/count_bonus),
  advanced params (n_steps/tau/c51_warmup/her/iqn_lambda/spectral_norm),
  remove hardcoded v_min/v_max (now computed), remove duplicate tau from [training]
- dqn-hyperopt: add search space bounds for spectral_norm_sigma_max,
  c51_warmup_epochs, her_ratio

Profile system additions (training_profile.rs):
- TrainingSection: add reward_scale (recomputes v_min/v_max on apply)
- ExplorationSection: add noisy_sigma_init, entropy_coefficient,
  count_bonus_coefficient, q_gap_threshold
- AdvancedSection: add n_steps, tau, c51_warmup_epochs, her_ratio,
  iqn_lambda, spectral_norm_sigma_max, gradient_clip_norm
- RiskSection: add max_position (alias for max_position_absolute), loss_aversion
- RewardSection: add reward_scale
- SearchSpaceSection: add spectral_norm_sigma_max, c51_warmup_epochs, her_ratio
- apply_to: gamma change now recomputes v_min/v_max automatically

Task 8: Config consistency integration tests (5 tests):
- test_config_consistency_across_structs: v_range computed not hardcoded,
  fill simulation bounds, q_clip symmetry
- test_toml_profile_applies_all_fields: smoketest profile applies every
  new section field correctly
- test_production_profile_applies_all_sections: production profile end-to-end
- test_hyperopt_search_space_has_new_bounds: new search space fields parse
- test_reward_scale_recomputes_v_range: gamma override triggers v_range recomputation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:27:45 +01:00
jgrusewski
e8f54c37c1 feat: expose experience collector params in TOML training profiles
Add 7 new fields to DQNHyperparameters (fill_ioc_fill_prob,
fill_limit_fill_min, fill_limit_fill_max, fill_spread_cost_frac,
fill_spread_capture_frac, q_clip_min, q_clip_max) and wire them
through training_profile.rs into ExperienceCollectorConfig construction
in training_loop.rs. Previously these 7 values were hardcoded at the
construction site; now they flow from TOML [experience.fill_simulation]
and [risk] sections. Default values match the prior hardcoded constants
so existing behavior is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:05:01 +01:00
jgrusewski
04d8802c94 refactor: remove 8 always-on use_ booleans — features are mandatory
Remove use_double_dqn, use_dueling, use_per, use_branching,
use_distributional, use_noisy_nets, use_huber_loss, and use_cql
from DQNConfig, DQNHyperparameters, and DqnParams structs.

These features are always enabled (Rainbow DQN standard). The boolean
flags were dead code — every constructor set them to true, and the
only code paths that set them to false were in tests that disabled
features for simplicity. With the fields removed, the features are
unconditionally active, eliminating ~490 lines of dead configuration.

Key changes:
- Struct field declarations removed from 3 core config structs
- Conditional branches (if use_X { ... } else { ... }) simplified:
  dueling/branching/PER network creation is now unconditional
- Checkpoint metadata hardcodes "true" for backward compatibility
- Hyperopt search space index 11 (use_branching) fixed at 1.0
- TOML/YAML config files cleaned of removed fields
- Tests that toggled these flags updated or rewritten

45 files changed, -487 net lines. Zero new test failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:45:54 +01:00
jgrusewski
825db90f23 feat: comprehensive DQN training pipeline overhaul — 16 bug fixes, MSE warmup, financial metrics
Major fixes:
- C51 v_range calibrated for reward v4 (±2.0, was ±25/±0.5)
- Wrong Flat index in Q-gap filter (qe[4]→qe[2] in branching_action_select)
- hold_time tracks total position duration (was only losing bars)
- Entropy coefficient wired to C51 backward kernel (0.001, was unwired)
- Count bonus wired to GPU action selection (per-branch UCB)
- Q-gap warmup ramp (0→threshold over 5 epochs, was static)
- IQN lambda gradient scaling (max_grad_norm × (1+lambda))
- PER beta annealing 4x faster (500 steps, was 2000)
- Reward normalization disabled (scrambled per-bar returns)
- Capital floor uses natural return (was hardcoded -1.0)
- Financial metrics pipeline: real per-trade GPU stats (was Trades=1)

New features:
- MSE loss CUDA kernel for C51 warmup phase
- Blended MSE→C51 loss with linear alpha ramp
- GPU trade_stats_reduce kernel for per-trade financial metrics
- TradeStats struct with real win/loss/PF from portfolio states
- Behavioral smoke test (Q-values, action entropy, trades)
- 50-epoch convergence test with anomaly detection
- c51_warmup_epochs in hyperopt search space (41D)

Dead code removed:
- portfolio_sim_kernel (150 lines CUDA)
- DSR/PnL/drawdown reward v2 computations
- 7 dead kernel params from env_step signature
- GpuPortfolioSimulator (never called)
- Reward normalization block + state fields

0 warnings, 0 errors, 1241 unit tests + 8 smoke tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:48:38 +01:00
jgrusewski
66dfcf599f feat: 9-exposure branch_sizes + num_actions defaults across workspace
- gpu_experience_collector: branch_sizes [5,3,3]→[9,3,3]
- gpu_iqn_head: branch_0_size 5→9
- All DQN configs: num_actions 5→9
- Production TOML: branch_0_size=9
- Removed use_branching from TOMLs (always enabled)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:24:55 +01:00
jgrusewski
e8a3104159 feat: DSR warm-up, num_atoms=101, clear stale cache on H100
- DSR warm-up: skip first 50 steps when EMA has insufficient history
- Phase Fast num_atoms: 51 → 101 (H100 can afford finer resolution,
  1.19 per atom vs 2.35 — critical for distinguishing Q-values)
- Argo template: clear stale feature cache before hyperopt (ensures
  fresh computation with VPIN/trades enrichment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:32:48 +01:00
jgrusewski
0b37ff77b0 fix: reward v2 + dynamic C51 support — root cause of Q-value collapse
ROOT CAUSE: 5 interlocking bugs made learning impossible:
1. DSR denominator floor 1e-12 produced values in millions → drowned all signal
2. Global [-1,+1] clamp destroyed Bellman equation signal (can't distinguish
   catastrophic loss from mild loss)
3. v_range=20 exactly equals V_max for gamma=0.95 → Bellman target pins at
   ceiling → Q-values saturate → Q-gap collapses to 0.0000
4. num_atoms=11 over 40-unit range = 4.0 per atom (C51 paper min is 51)
5. 6/7 reward components were penalties → mean_reward=-0.311 regardless of action

FIXES:
- DSR denominator floor: 1e-12 → 0.01 (prevents million-scale spikes)
- Each component individually clamped BEFORE weighting (DSR to [-1,+1],
  z-score to [-3,+3], drawdown to [0,1], time decay to [0,0.3])
- Removed global [-1,+1] clamp (no longer needed with bounded components)
- profit_take_bonus: 0.1 → 0.01 (was 100x too large, caused reward hacking)
- Removed confidence scaling (positive feedback loop destabilized learning)
- Removed regime scaling (non-stationary reward confused the model)
- Dynamic v_range from gamma: v_range = 2.5/(1-gamma)*1.2 (always covers Q range)
- num_atoms minimum: 11 → 51 (C51 paper standard)
- gamma default: 0.99 → 0.95

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:39:39 +01:00
jgrusewski
95d9fdb034 config: set q_gap_threshold=0.1 as default — force trade selectivity
Q-gap was 0.0 (disabled) meaning the model traded on every bar regardless
of conviction. With 0.1, the model must have Q(best) - Q(flat) > 0.1
before entering a position. Local test showed 21% fewer trades.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:45:41 +01:00
jgrusewski
5cb400a73b feat: Q-gap conviction filter + remove dead use_branching kernel arg
Add q_gap_threshold to action selection kernel: when greedy Q(best) - Q(flat)
< threshold, default to flat. Teaches model to trade only with conviction.
39D search space (was 38D). Default 0.0 (disabled), hyperopt range [0.0, 0.5].

Remove use_branching parameter from experience_action_select — GPU pipeline
always uses branching DQN. Flat mode was dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:05:46 +01:00
jgrusewski
81a7ce2d43 config: tighten dd_threshold [0.005, 0.03] for HFT — 1% default
6% drawdown tolerance too generous for HFT. Tightened search range
to 0.5%-3%, default 1%. Forces aggressive loss cutting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:06:13 +01:00
jgrusewski
af940671bc feat: reward config pipeline — DQNHyperparameters + TOML profiles
Add 7 composite reward fields to DQNHyperparameters: w_dsr, w_pnl,
w_dd, w_idle, dd_threshold, loss_aversion, time_decay_rate.

Add RewardSection to training_profile.rs with Option<f64> fields and
apply_to() mapping. Add [reward] section to all 3 DQN TOML profiles
(production, smoketest, hyperopt) with identical defaults.

Remove hold_reward from ExperienceSection (replaced by w_idle).
Add 7 reward search bounds to SearchSpaceSection and bound() match.
Add 7 reward phase_fast defaults to PhaseFastSection.

hold_penalty kept as deprecated field for hyperopt adapter compat
(Task 4 will clean it up).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:50:23 +01:00
jgrusewski
6219491db6 refactor: move smoke test config to dqn-smoketest.toml, restore check_err
Smoke test loads all hyperparams from TOML profile instead of hardcoding.
TOML: hidden_dim=64, batch=64, lr=0.0003 (stable on RTX 3050 + H100).

Restored check_err() drain in device.rs — required to clear stale CUDA
errors from primary context reuse between tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:07:49 +01:00
jgrusewski
43998a330a feat: two-phase hyperopt + backtest evaluator VRAM leak fix
Two-phase hyperopt splits 31D PSO search into sequential phases:
- Phase 1 (--phase fast, default): fix architecture to small network
  (hidden_dim=128, num_atoms=11), search learning dynamics (~15D).
- Phase 2 (--phase full): fix dynamics from Phase 1 JSON, search
  architecture (~5D). Halves dimensionality per phase → better convergence.
- Phase 1 output includes best_continuous_vector for Phase 2 consumption.

GpuBacktestEvaluator Drop impl: sync forked stream, destroy CUDA graph
and cuBLAS handles before CudaSlice buffers drop. Fixes 261MB/trial
VRAM leak on H100 hyperopt.

ml-core clippy fixes: hex literal, remove dead check_err drain,
unnecessary safety comment, unused OnceLock import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:32:37 +01:00
jgrusewski
e97c30b50a perf: cap hyperopt hidden_dim=256, num_atoms=51 — 4x faster trials
Production defaults are sufficient for hyperopt exploration. Larger networks
can be tested in a separate phase with the best hyperparams found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:00:26 +01:00
jgrusewski
3c8e177932 feat: HyperoptProfile with TOML search space bounds
Add SearchSpaceSection, PsoSection, HyperoptProfile structs to
training_profile.rs. All 31 PSO search bounds now configurable in
config/training/dqn-hyperopt.toml — no code changes needed to
adjust search ranges.

HyperoptProfile::bound("field", default) returns the TOML value
or falls back to the hardcoded default. Adapter wiring is next step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:33:55 +01:00
jgrusewski
a75a98bd0d feat: TOML training profile system — config-driven hyperparameters
Training Profile Loader:
- 3-tier resolution: $FOXHUNT_TRAINING_PROFILE > filesystem > embedded defaults
- DqnTrainingProfile with 10 sections, all Option<T> for sparse profiles
- apply_to() applies only Some fields, preserving struct defaults
- 11 unit tests, all passing

TOML Profiles (config/training/):
- dqn-production.toml: full Rainbow DQN (40+ params)
- dqn-smoketest.toml: CI fast path (sparse, 8 overrides)
- dqn-hyperopt.toml: PSO search space ranges + fixed flags
- ppo-production.toml, ppo-smoketest.toml
- supervised-production.toml, supervised-smoketest.toml
- walk-forward.toml: window sizes

CLI Integration:
- train_baseline_rl: --training-profile (default: dqn-production)
- train_baseline_supervised: --training-profile (default: supervised-production)
- Merge priority: CLI args > TOML profile > GPU profile > struct defaults

Smoke Tests:
- smoke_params() now loads dqn-smoketest.toml instead of hardcoding
- Production features set as manual overrides (testing flags, not config)

Infrastructure:
- K8s job-template.yaml: TRAINING_PROFILE env var + --training-profile arg
- Delete old config/ml/training.toml (replaced, zero callers)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:35:41 +01:00