Commit Graph

71 Commits

Author SHA1 Message Date
jgrusewski
5a9fa1534b feat(ml): multi-window backtest objective + top-K ensemble training
Multi-window backtest: splits validation data into 3 non-overlapping
windows and aggregates with mean(Sharpe) - 0.5*std(Sharpe), penalizing
inconsistency and reducing overfit to a single data segment.

Top-K ensemble: hyperopt now emits top_k_params (top 5 trials) in JSON
output. train_baseline_rl gains --ensemble-top-k flag to train multiple
models per fold from different hyperopt configs, saving checkpoints as
dqn_ensemble_{k}_fold_{n}.safetensors.

Workflow template: adds ensemble-top-k parameter (default 5) and passes
--ensemble-top-k to the train-best step.

2720 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:42:03 +01:00
jgrusewski
98694a0ca2 fix(ml): resolve DQN action diversity collapse in hyperopt
4 root causes of 45-action DQN collapsing to 1-6 actions:

1. Batch epsilon ignoring noisy_epsilon_floor: select_actions_batch()
   and select_actions_batch_gpu() used get_epsilon() which returns 0.0
   with noisy nets — zero random exploration in the training path.
   Added get_effective_epsilon() that respects noisy_epsilon_floor.

2. Entropy coefficient too weak: default 0.05 with bounds (0.01, 0.2)
   produced max ~0.19 bonus vs TD loss of 4+. Bumped default to 0.1,
   widened bounds to (0.05, 0.5) for effective anti-collapse.

3. count_bonus_coefficient not in search space: was hardcoded at 0.1
   in from_continuous(). Promoted to 31st search dimension with bounds
   (0.05, 1.0) so PSO/TPE can optimize exploration strength.

4. Diversity penalty too coarse: objective had <10 unique actions
   short-circuit but nothing for 10-20. Added graduated penalty that
   linearly ramps from 3.0 (10 actions) to 0.0 (20 actions).

Also fixes pre-existing clippy impl_trait_in_params in optimizer.rs.

2720 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:27:54 +01:00
jgrusewski
b6f6d9c7c9 fix(ml): remove warmup_steps bug that prevented DQN training
The batch training path (select_actions_batch → forward()) does NOT
increment DQN::total_steps. Only select_action() does. When
warmup_steps > 0, train_step() checks total_steps < warmup_steps
and returns (0.0, 0.0) — zero loss, zero gradients. The model
never trained; "results" were random initialization Q-values.

Fix: force warmup_steps=0 in hyperopt adapter and remove
warmup_ratio from the 31D→30D search space (saves a dimension
for TPE/PSO effectiveness).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:29:42 +01:00
jgrusewski
f2938b19e8 fix(ml): improve TPE exploitation with Scott bandwidth, best-trial injection
- Replace Silverman's bandwidth (h = 1.06σn^(-1/5)) with Scott's rule
  (h = 0.7σn^(-1/(d+4))) for tighter kernels in high-D parameter spaces
- Add best-trial injection: always evaluate EI at best known point plus
  5 small perturbations (±5%), preventing optimizer from forgetting peaks
- Scale n_candidates dynamically: max(256, 8*n_dims) instead of fixed 100
- Reduce gamma from 0.25 to 0.15 when trials < 50 for tighter exploitation
- Wire model_name through PSO/TPE paths for per-trial Prometheus metrics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:54:30 +01:00
jgrusewski
e9481c6107 fix(ml): disable val-loss early stopping in hyperopt, fix penalty metrics
Two issues causing all 20 hyperopt trials to have identical f64::MAX
objective (TPE optimizer blind):

1. Val-loss plateau early stopping fired at epoch 5-6 of every 8-epoch
   trial (plateau_window=5 too aggressive for short runs). Disabled
   early_stopping_enabled for hyperopt; gradient-collapse patience
   still active as safety net.

2. Penalty metrics used f64::MAX for gradient_norm/q_value_std which
   produced ~3.6e+308 objective. Changed to 100.0 so TPE can still
   differentiate between early-stopped trials by other metric fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:45:03 +01:00
jgrusewski
248b2fec70 fix(ml,infra): handle early stopping in hyperopt, GPU containerd registry
- Hyperopt: catch early stopping errors and return penalty metrics
  instead of aborting entire run. Trials that stop early are scored
  as poor (objective=-500) so optimizer avoids those configs.
- DaemonSet: detect GPU nodes (NVIDIA conf.d overlay) and create
  v3-compatible registry config drop-in for containerd v2.1+.
  Old grpc.v1.cri.registry path is silently ignored by containerd v2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:07:09 +01:00
jgrusewski
7d9808ecf0 fix(ml): smooth CVaR penalty, fix clip leakage, align noisy sigma, fix eval_supervised
Fixes from deep investigation audit (LOW/MEDIUM priority):

1. CVaR penalty: hard cliff (0 or 10) → smooth ramp with gradient signal
   for PSO. Formula: min(10, max(0, -cvar-0.05)*200).

2. Clip outliers leakage: data_loading.rs now computes clip bounds from
   training portion only (first 80%), then applies to full series.
   Log returns and windowed normalize are causal (no leakage).

3. Noisy sigma scheduler: hyperopt now matches conservative() defaults
   (enabled, initial=0.8, final=0.4) so hyperopt-found params
   generalize to train_best without scheduler mismatch.

4. evaluate_supervised.rs: NormStats fallback from test data (leakage)
   replaced with bail! matching evaluate_baseline.rs behavior.

5. Doc comments: stale 27D references updated to 31D (4 locations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:14:31 +01:00
jgrusewski
c033a34fec fix(ml): wire minimum_profit_factor, fix eval capital, HFT score, Sortino
4 bugs found by deep investigation agents:

1. HIGH: minimum_profit_factor (search dim 30) was never forwarded from
   DQNHyperparameters to DQNConfig — trainer hardcoded 1.5, making the
   entire dimension wasted. Added field to DQNHyperparameters, wired
   through trainer.rs.

2. HIGH: Backtest EvaluationEngine used hardcoded $10K initial capital
   while training used $35K (self.initial_capital). Returns/Sharpe were
   3.5x distorted. Now uses self.initial_capital.

3. MEDIUM: calculate_hft_activity_score_wave10 multiplied already-100x
   buy_pct/sell_pct by 100 again, making the diversity penalty threshold
   (15%) unreachable (values were ~2700). Removed double multiplication.

4. MEDIUM: Sortino ratio returned 0.0 for all-positive returns (no
   downside deviation), penalizing perfect strategies in the 40%-weighted
   composite score. Now returns 100.0 (capped) when mean return > 0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:07:07 +01:00
jgrusewski
f39219be9e feat(ml): expand DQN hyperopt search space 27D→31D + GPU-batched eval inference
Root cause: catastrophic OOS eval (Sharpe -280) traced to 11 issues
including hardcoded training dynamics and per-bar CPU inference.

Search space (dqn.rs):
- Add warmup_ratio [0.0, 0.15] — was hardcoded to 0
- Add lr_decay_type [Constant/Linear/Cosine] — was hardcoded Constant
- Add min_epochs_before_stopping [2, 6] — was 1000 (disabled)
- Add minimum_profit_factor [1.1, 2.0] — was hardcoded 1.5
- Widen entropy_coefficient [0.01, 0.2] for 45-action factored space

GPU-batched eval (evaluate_baseline.rs):
- 1024-bar chunked inference for both DQN and PPO
- DQN: batch_greedy_actions per chunk (was per-bar select_action)
- PPO: action_probabilities + GPU argmax per chunk
- ~1000x fewer GPU kernel launches
- Add trade_sharpe_ratio for hyperopt-comparable metric

Preprocessing (preprocessing.rs):
- Add compute_clip_bounds/clip_outliers_with_bounds for leakage-free
  clipping across train/val/test splits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 20:45:13 +01:00
jgrusewski
b0ac0afe63 test(ml): smoke tests for hierarchical softmax and diversity short-circuit
5 new tests:
- batch_hierarchical_softmax_actions_smoke: valid outputs, diversity, exposure coverage
- batch_hierarchical_softmax_low_temp_valid: edge case at near-zero temperature
- batch_hierarchical_vs_flat_softmax_diversity: hierarchical >= flat exposure levels
- eval_softmax_temp_floor_is_half: bounds [0.5, 2.0] enforced with clamp
- diversity_short_circuit_blocks_phantom_sharpe: 2/45 actions -> positive penalty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:30:24 +01:00
jgrusewski
53317d9af6 fix(ml): diversity short-circuit in objective, prevent phantom Sharpe from TPE
Degenerate trials with <10/45 unique actions now short-circuit the objective
to a graduated penalty (8.0 for 1 action → 0 for 10+), ignoring composite
score entirely. Previously, phantom Sharpe=2317 drove objective to -369k,
trapping TPE in the low-temp region.

Combined with temp floor raise (0.1→0.5 from prior commit), this eliminates
both the supply (no low temps) and demand (no reward) for degenerate trials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:54:53 +01:00
jgrusewski
d3a3403053 fix(ml): raise eval_softmax_temp floor to 0.5, add action diversity penalty
Hyperopt TPE was stuck chasing phantom Sharpe=2317 from degenerate trials
with 2/45 unique actions (temp 0.15-0.20). Two fixes:

1. Raise eval_softmax_temp bounds from [0.1, 2.0] to [0.5, 2.0] — temps
   below 0.3 consistently produce 1-3/45 actions regardless of model quality
2. Add diversity_penalty to objective: 50*(1 - unique/10) for <10/45 actions,
   so degenerate trials score badly even if they accidentally reach the TPE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:48:03 +01:00
jgrusewski
34a78def71 fix(ml): hierarchical factored softmax for hyperopt eval
Flat Gumbel-max over 45 actions can collapse to a single exposure
bucket (e.g., 2/45 unique actions all in Long100) even with adequate
temperature, because order/urgency diversity doesn't produce trades.

Two-stage sampling: first Gumbel-max over 5 exposure levels (max Q
per level), then Gumbel-max over 9 order×urgency combos within the
chosen exposure. Guarantees exposure-level diversity proportional to
actual Q-value differences.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:20:06 +01:00
jgrusewski
b14c60d4cc fix(ml,infra): raise eval_softmax_temp floor to 0.1, fix CI sccache TLS
- Raise eval_softmax_temp lower bound from 0.01 to 0.1 (log scale) to
  prevent near-greedy collapse in hyperopt walk-forward eval. Temps below
  0.05 produced degenerate 1-trade trials even with softmax sampling.

- Mount MinIO CA cert in CI compile pods and append to system trust store
  so sccache S3 backend can verify MinIO's self-signed TLS certificate.

- Add openssh-client to ci-builder-cpu Dockerfile (missing, broke git
  clone over SSH).

- Bump MinIO memory limits from 512Mi to 2Gi (OOMKilled under load).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:41:21 +01:00
jgrusewski
a31512643b feat(ml): multi-GPU trial parallelism for hyperopt
Add device_pool to DQN/PPO hyperopt trainers for round-robin GPU assignment
per trial. Binary detects all CUDA devices, scales VRAM budget by GPU count.
Single-GPU: no behavior change (pool of 1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:37:58 +01:00
jgrusewski
e99b258610 feat(ml): add eval_softmax_temp to DQN hyperopt search space (27D)
T=0.1 was too conservative for degenerate configs — trials with uniform
Q-values still collapsed to 1 unique action. Making temperature a
hyperopt parameter lets TPE learn the optimal exploration-exploitation
balance per config. Range [0.01, 2.0] log-scale.

Also sets training-workflow default gpu-pool to ci-training-h100.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:52:29 +01:00
jgrusewski
c10340708f feat(ml): replace greedy argmax with Gumbel-max softmax for hyperopt eval
DQN hyperopt walk-forward eval collapsed to 1 trade when Q-values were
nearly uniform (greedy argmax always picked the same action). Replace
batch_greedy_actions with batch_softmax_actions using the Gumbel-max
trick (argmax(Q/T + Gumbel(0,1))) — fully GPU-resident, no CPU
softmax/sampling. Temperature 0.1: nearly greedy when Q-values are
separated, diverse when uniform. Logs unique_actions/45 per backtest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:57:48 +01:00
jgrusewski
504732b68f feat(ml): wire factored eval into hyperopt backtest loop
Replace legacy Buy/Sell/Hold collapse with exposure-aware evaluation.
Long100→Long50 now generates a partial-close trade instead of being
a no-op. Combined with graduated trade penalty, PSO now gets gradient
signal across the entire 26D search space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:43:42 +01:00
jgrusewski
997b2d7026 feat(ml): add graduated trade insufficiency penalty to hyperopt objective
Add calculate_trade_insufficiency_penalty() that produces monotonically
decreasing penalties for low trade counts (10.0 at 0 trades, 0.0 at 100+),
giving PSO gradient signal across the degenerate plateau where all trials
produce the same 1.25 objective.

Wire the penalty into extract_objective() with short-circuit for <10 trades
(skip composite score since it's all zeros anyway).

Include 4 unit tests verifying zero/one/graduated/no-plateau properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:43:42 +01:00
jgrusewski
f0e6845fb3 feat(ml): add cql_alpha to 26D hyperopt search space, widen v_min/v_max
- Expand search space from 25D to 26D with cql_alpha (0.0-0.5)
- v_min bounds: (-3,-1) → (-15,-3), v_max: (1,3) → (3,15)
- Default use_qr_dqn: true → false (IQN disabled)
- Wire cql_alpha into DQNHyperparameters construction
- Update all 18 adapter tests for new dimensions and ranges

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:58:40 +01:00
jgrusewski
93104e8545 refactor(ml): eliminate f64↔f32 round-trips and dead deps across ML crate
Change PercentileScaler, RewardNormalizer, CompositeReward, and
PPORewardShaper public APIs from f64 to f32 — matching what callers
actually pass. Internal EMA/percentile math stays f64 for precision.

Keep data_loader SMA/EMA/MACD accumulators in f64 throughout (was
f64→f32→f64 per step), cast to f32 only at output boundary.

Remove dead _transition computation in rainbow_agent_impl and unused
bigdecimal deps from broker_gateway_service and trading_service.

2704 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:10:04 +01:00
jgrusewski
81fc16fb9b chore: demote verbose multi-objective logs to debug level
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:16:14 +01:00
jgrusewski
4292a9ac56 refactor(hyperopt): delete deprecated egobox_tuner module
egobox is not a dependency (replaced by argmin PSO long ago).
The egobox_tuner.rs module was pure dead code — optimize_mamba2()
just returned a deprecation error. Deleted the module, its 26 tests,
and the integration test. Cleaned up stale egobox references in
ParameterSpace trait docs.

174 hyperopt tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:26:29 +01:00
jgrusewski
1bf752d387 fix(dqn,ppo): production inference hardening, remove legacy aliases
DQN: disable all exploration for production (warmup=0, epsilon=0,
noisy_nets=false, count_bonus=false), read feature_count from
checkpoint metadata instead of hardcoding 54, add loaded guard
and NaN check on predict output.

PPO: fix confidence formula — use act_with_log_prob() instead of
act() which returns value_estimate not log_prob, add loaded guard
and NaN check, remove WorkingPPO alias.

Liquid: add loaded flag for is_ready()/predict() guards.

Remove EgoboxOptimizer/EgoboxOptimizerBuilder backward-compat
aliases — replaced with canonical ArgminOptimizer everywhere.

323 trading_service tests, 200 hyperopt tests, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:19:02 +01:00
jgrusewski
cdb32cd8bf fix(dqn,ppo): comprehensive verification audit fixes — 14 files, 7 agents
DQN correctness:
- N-step Bellman target uses gamma^n (was gamma^1), fixing ~2% Q-value bias
- Cash reserve enforcement actually reduces position (was warn-only)
- CVaR sort_last_dim(true) for IQN random tau ordering
- Marsaglia-Tsang gamma sampling uses normal(0,1) not uniform(0,1)
- DQNConfig::default state_dim 51→54 (51 market + 3 portfolio)

PPO correctness:
- set_learning_rate preserves trained weights (was recreating entire model)
- update_value_only() for critic pretraining (was training both networks)
- PolicyNetwork::entropy() single forward pass (was 2x GPU compute)
- LSTM entropy uses proper H=-sum(p*log(p)) (was -mean(log_probs))
- grad_norm metric set to None (was reporting policy loss as gradient norm)

Config parity (train/eval/hyperopt/enhanced_ml):
- PPO hyperopt state_dim 51→54, value_hidden_dims 3→5 layer
- enhanced_ml feature_count 16→54, PPO policy_hidden_dims [128,64,32]→[128,64]
- Eval: tensor core alignment, Rainbow from hyperopt params, warmup alignment
- GPU batch model_state_dim 51→54 (matches kernel output)

Infrastructure:
- QNetwork dropout training mode (AtomicBool toggle, was always disabled)
- reward_history Vec→VecDeque (O(1) front removal, was O(n))
- Plateau detection distinguishes worsening from plateau in log messages

2732 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:16:08 +01:00
jgrusewski
f60df5a65f fix(dqn): clippy let_underscore_must_use in CUDA sync cleanup
Replace `let _ = sync_tensor.to_vec0::<f32>()` with `drop()` to
satisfy clippy's let_underscore_must_use lint on the Result return.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:08:29 +01:00
jgrusewski
3ef80b9efe fix(dqn): atomic checkpoints, CUDA sync, NormStats hard error, q_value_std warning
- Atomic checkpoint writes: write to .tmp then fs::rename() (POSIX atomic)
- Remove redundant thread::sleep(50ms) after CUDA tensor readback sync
- NormStats: bail on missing file instead of computing from test data (lookahead bias)
- q_value_std: warn when missing from training metrics instead of silent 0.0 fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:34:19 +01:00
jgrusewski
4b15108895 feat(ppo): wire reward shaping, composite reward, trajectory replay into hyperopt
Integrate three standalone PPO modules into the hyperopt training loop:
- PPORewardShaper: hold penalty + rolling Sharpe + diversity bonus per step
- CompositeReward: risk-adjusted reward (downside dev + differential return)
- TrajectoryReplayBuffer: ExO-PPO M=4 rollouts with IS-weighted replay

Also fixes GAE hardcoded gamma=0.99/lambda=0.95 — now uses hyperopt params.

2726 tests pass, 0 clippy errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:20:48 +01:00
jgrusewski
ba8fb8eedd feat(ppo): wire symlog, adaptive entropy, percentile scaling into training loop
Integrate all PPO improvement modules into the core training paths:
- Symlog value predictions in compute_value_loss (MLP + LSTM)
- Adaptive entropy auto-tuning replaces fixed entropy_coeff
- Percentile P5/P95 advantage scaling for heavy-tailed returns
- DAPO clip_epsilon_high wired in all 7 PPOConfig construction sites
- Shape mismatch fix in adaptive_entropy (unsqueeze scalar)

2726 tests pass, 0 clippy errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:12:00 +01:00
jgrusewski
50b75b8a5f feat(ppo): curiosity port + reward shaping + ExO-PPO replay + composite reward
Phase 6: CuriosityModule wired into PPO hyperopt (14D). Intrinsic reward
from forward dynamics prediction error scales by curiosity_weight param.

Phase 7: PPORewardShaper with hold penalty (discourages flat position),
rolling Sharpe (20-step window), diversity bonus (smooth quadratic
entropy). 8 tests.

Phase 8: ExO-PPO trajectory replay buffer (M=4 FIFO). Importance-weighted
surrogate with exponential attenuation outside clip bounds (alpha=5).
4x sample efficiency over standard PPO. 10 tests.

Phase 9: CompositeReward with annualized return, downside deviation
penalty, and differential return vs SMA baseline. 10 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:48:48 +01:00
jgrusewski
235cee5b50 feat(ppo): expand search space 7D→13D + symlog + adaptive entropy + percentile scaling
Phase 1: PPOParams expanded with gae_gamma, gae_lambda, mini_batch_size,
max_grad_norm, max_position_absolute, clip_epsilon_high. All wired into
PPOConfig construction. CUDA cleanup with tensor readback sync.

Phase 2: Symlog value transform (DreamerV3) — sign(x)*ln(|x|+1) for
compressing large financial returns while preserving sign. 13 tests.

Phase 4: Adaptive entropy coefficient (SAC-style) — learnable log(alpha)
auto-tuned via dual gradient descent toward target entropy. 9 tests.

Phase 5: Percentile advantage scaling — EMA-tracked P5/P95 for robust
normalization of heavy-tailed financial returns. 11 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:23:57 +01:00
jgrusewski
6d0182d77e feat(hyperopt): make TPE the default optimizer
Change --optimizer default from "pso" to "tpe" since TPE has better
sample efficiency in 25D spaces. Fix clippy let_underscore_must_use
on CUDA sync tensor readback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:50:02 +01:00
jgrusewski
c29fafbbb6 feat(hyperopt): reduce DQN search space from 45D to 25D
Fix 20 exotic parameters to validated defaults, keeping only the 25
that genuinely affect trading performance. This makes both PSO and
TPE dramatically more effective at exploring the parameter space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:42:37 +01:00
jgrusewski
903e690e9a feat(hyperopt): wire TPE optimizer into pipeline with --optimizer flag
Add optimize_with_tpe() function that uses Tree-Parzen Estimator for
Bayesian hyperparameter optimization. Add --optimizer=tpe CLI flag
to hyperopt_baseline_rl binary (default: pso for backward compat).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:40:03 +01:00
jgrusewski
012bf3dc00 fix(hyperopt): replace sleep-based CUDA cleanup with synchronize
Force CUDA synchronization between trials by creating a tiny tensor and
reading it back (forces cudaDeviceSynchronize). This ensures GPU memory
from dropped models is actually freed before the next trial starts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:35:41 +01:00
jgrusewski
10163bb167 fix(hyperopt): narrow max_position [1,4] and add 20% drawdown circuit breaker
Tighten max_position_absolute search range from [4,8] to [1,4] to
prevent catastrophic leverage during hyperopt. Add drawdown circuit
breaker to PortfolioTracker that force-closes positions at >20%
drawdown and refuses new trades while flat.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:30:29 +01:00
jgrusewski
6c8fc7b1c1 feat(hyperopt): implement TPE (Tree-Parzen Estimator) optimizer with KDE and LHS
Add a standalone TPE optimizer for Bayesian hyperparameter optimization that
replaces PSO by modeling good/bad trial distributions with per-dimension
kernel density estimates. Includes Latin Hypercube Sampling for initial
exploration, Silverman bandwidth selection, log-sum-exp numerical stability,
and JSON-based trial persistence. 15 tests covering splits, bounds, KDE
density, convergence on 1D/2D objectives, and history serialization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:29:33 +01:00
jgrusewski
25f8d514b1 perf(ml): batched GPU inference for hyperopt backtest evaluation
Replace per-bar GPU forward passes with chunked batch inference (1024 bars
per chunk). This reduces ~90K individual CUDA kernel launches to ~88 batched
forward passes — ~1000× fewer GPU round-trips.

Changes:
- Add DQN::batch_greedy_actions(&self) for immutable batched forward+argmax
- Add RegimeConditionalDQN::batch_greedy_actions with per-regime-head batching
- Add DQNTrainer::convert_to_state_vec (CPU-only, skips GPU tensor allocation)
- Add PortfolioTracker::set_position_direct for backtest state sync
- Rewrite hyperopt backtest loop: chunked batching with portfolio state
  updates between chunks (1024-bar granularity ≈ 17h of 1-min data)

Portfolio features (last 3 of 54 dims) are refreshed between chunks via
set_portfolio_for_backtest(), keeping position/PnL/exposure accurate at
chunk boundaries while batching inference within each chunk.

2640 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:37:12 +01:00
jgrusewski
a7b7796147 fix(ml): correct PSO auto-scaling with empirical VRAM estimates
Split model overhead into two constants: MODEL_OVERHEAD_MB (pure
model weights for batch-size capping) and TRIAL_VRAM_MB (total
per-trial VRAM for concurrent hyperopt planning). DQN trials
empirically consume ~7 GB each on L40S (model + GPU replay buffer +
experience collector + CUDA allocations + fragmentation), not the
200 MB previously estimated. This caused plan_hyperopt to compute
128 concurrent trials instead of the actual 5, inflating PSO
particles from 20→128 and total trials from 20→384 via .max()
instead of .min(), guaranteeing a 4h timeout kill.

Fix auto-scaling to: (1) match particles to GPU concurrency for
maximum hardware utilization on any node, (2) cap particles at
max_trials to never inflate the trial budget, (3) never auto-inflate
the total trial count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:34:05 +01:00
jgrusewski
864cf8690d fix(dqn,ppo): prevent 45-action collapse with entropy, exploration, and monitoring fixes
DQN: wire SAC-style entropy regularizer into TD loss, fix diversity
penalty normalization from ln(3) to ln(45), add epsilon floor (0.05)
for noisy nets, enable sigma scheduling (0.8→0.4 with 0.3 floor),
add UCB count-based exploration bonus, expand hyperopt search space
from 43D to 45D.

PPO: replace fabricated entropy metric (value_loss×0.5) with real
Shannon entropy from action distribution, fix EntropyRegularizer
normalization from ln(3) to ln(45), implement Monte Carlo entropy
estimate for flow policy (was returning zeros), fix hyperopt VRAM
bound from 3 to 45 actions.

Monitoring: add normalized action entropy and diversity Prometheus
gauges, add exploration diagnostics logging per epoch.

2503 ml tests pass, 277 common tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:04:22 +01:00
jgrusewski
c64022f819 fix(hyperopt): remove double win_rate multiplication in DQN trial summary
PerformanceMetrics::from_trades() already returns win_rate as percentage
(e.g. 57.78), but TRIAL_SUMMARY and backtest details log lines multiplied
by 100 again, producing values like 5778%. Remove the extra * 100.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:47:35 +01:00
jgrusewski
148d4cbb43 feat(ml): ungate GPU experience collector + async PER pre-sampling
Enable curiosity module by default (weight 0.0→0.1) to satisfy the
three-way gate (dueling + target + curiosity) that was blocking the
GPU experience collector CUDA kernel. This eliminates the 30-40% CPU
experience collection phase that was the main GPU idle bottleneck.

Additional changes:
- BatchSample API: train_step/compute_gradients now accept
  Option<BatchSample> instead of Option<Vec<Experience>>, preserving
  PER importance-sampling weights and indices through pre-sampling.
- Async PER pre-sampling: train_step_single_batch and
  train_step_with_accumulation now pre-sample from the replay buffer
  using a READ lock before acquiring the WRITE lock for GPU training.
  This separates CPU sampling (~250μs) from GPU forward/backward (~3ms).
- Delete dead EpochPrefetcher: the binary (train_baseline_rl.rs) already
  implements fold prefetching with background thread + mpsc channel +
  GPU double-buffering, making the trainer's EpochPrefetcher redundant.
- Hyperopt DQN bounds: curiosity_weight min 0.0→0.01 so PSO can never
  fully disable curiosity (which would re-gate the GPU collector).

10 files changed, -195 net lines. 2497 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:14:14 +01:00
jgrusewski
129c2f19b8 fix(ml): mamba2 hyperopt tensors created on GPU instead of CPU
Replace &Device::Cpu with &self.device for input and target tensor
creation in Mamba2 hyperopt adapter. Avoids unnecessary CPU→GPU
transfer during hyperparameter optimization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:03:10 +01:00
jgrusewski
9c3383420b fix(ml): update remaining PPO num_actions: 3 → 45 in hyperopt and benchmark
Hyperopt adapter and PPO benchmark still had num_actions: 3, which would
produce misconfigured PPO models when used with the 45-action FactoredAction
sampling path. Found by spec compliance review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:41:23 +01:00
jgrusewski
512535076f feat(ml): wire PPO to 45 factored actions via FactoredAction
sample_action(), act(), act_with_log_prob(), greedy_action() now return
FactoredAction instead of TradingAction. Fixes the architectural disconnect
where num_actions=45 output neurons were sampled through a 3-action bottleneck.

TrajectoryStep.action and TrajectoryBatch.actions now use FactoredAction.
Added FactoredAction::from_legacy() for backward compatibility in tests.

Updated all PPO consumers: trainers/ppo.rs, hyperopt/adapters/ppo.rs,
validation/ppo_adapter.rs, benchmark/ppo_benchmark.rs.

2487 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:41:23 +01:00
jgrusewski
3d679e824f feat(ml): GPU saturation final — DQN PER deferral, PPO trajectory batching, Liquid/TGGN sync reduction, DoubleBuffer wiring
- DQN PER: defer td_errors to_vec1() after loss.to_scalar() — piggyback on
  existing pipeline flush instead of forcing premature GPU→CPU stall
- PPO trajectories: capacity-hint Vec allocations, extend_flat_states methods,
  states_flat field on TrajectoryBatch for zero-copy GPU upload
- TGGN validate(): batch N per-sample losses on GPU → single to_scalar() sync
  (was N GPU→CPU syncs)
- Liquid backward(): batch grad-norm per-param sqr().sum_all() on GPU → single
  to_scalar() sync (was N GPU→CPU syncs per optimizer step)
- Liquid validate(): same N→1 GPU sync reduction as TGGN
- DQN trainer: restore EpochPrefetcher/DoubleBufferedLoader API (wrongly deleted)
- train_baseline_rl: wire DoubleBuffer GPU pre-upload — after CPU prefetch
  completes, immediately upload next fold to GPU via DqnGpuData::upload() so
  next fold starts with data already resident on GPU

2478 tests pass, 0 clippy warnings, 0 compile errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:41:23 +01:00
jgrusewski
2aedc2ae1a feat(ml): comprehensive GPU saturation audit — 58 fixes across all 10 models
Phase 1 — Fix broken models (P0):
- Diffusion: wire optimizer_step to actually apply gradients (was no-op)
- TLOB: connect forward pass to projection layers (was Tensor::zeros)
- Mamba2: F64→F32 migration across 5 files (~30x faster on L40S tensor cores)

Phase 2 — Eliminate hot-path GPU sync stalls:
- Mamba2: keep dt on GPU in discretize_ssm (4 functions, no CPU round-trip)
- TFT: gate attention weight logging to eval only (8 syncs/forward eliminated)
- Mamba2: defer loss scalar after backward (pipeline stall removed)
- Mamba2: delete dead gradient clipping (4N wasted GPU syncs removed)

Phase 3 — Enable BF16 for supervised models:
- Flip mixed_precision defaults to true in 4 config locations
- Fix cuda_layer_norm to support BF16/F16 via F32 intermediate

Phase 4 — Raise hyperopt bounds for datacenter GPUs:
- 7 adapters with VRAM-aware tiers (TFT, Liquid, TGGN, KAN, xLSTM,
  Diffusion, TLOB) — L40S gets full hidden_dim range
- Fix L40S tier boundary (was excluded at <48000, now >=40000)

Phase 5 — Update memory estimates:
- 10 param_count estimates updated (DQN 200K→12M, TFT 2M→50M, etc.)
- Fix power-of-two rounding (was wasting up to 49% of budget)
- Correct MODEL_OVERHEAD_MB in DQN/PPO/TFT adapters

Phase 6 — Fix per-epoch CPU bottlenecks:
- PPO: deduplicate double advantage normalization (correctness fix)
- PPO: GPU tensor reward normalization + explained variance
- Fuse per-parameter grad norm to single GPU sync (xLSTM, KAN, TGGN)

Phase 7 — Data pipeline:
- GpuBufferPool: use from_slice (eliminate staging buffer copy)

Phase 8 — Correctness:
- TFT: remove broken .detach() in forward_checkpointed (restore gradients)
- Update stale RTX 3050 Ti doc references

33 files changed, 2451 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:41:23 +01:00
jgrusewski
d63190b9b9 feat(ml): GPU full saturation — remove artificial caps and enable VRAM-aware scaling
Phase 1: Remove artificial caps
- TFT benchmark: VRAM-scaled batch sizes (4/16/32/64) replacing hardcoded max_batch=4
- Liquid CUDA: VRAM-aware config defaults (batch 256-2048, pool 10% VRAM)
- DQN trainer: remove double-clamp between AutoBatchSizer and HardwareBudget

Phase 2: Mixed precision in training
- DQN agent: add BF16/FP16 dtype casting in forward_with_gradients and
  forward_without_gradients (training was bypassing forward_mixed)

Phase 3: Reduce CPU round-trips
- DQN trainer: flat buffer select_actions_batch (eliminate Vec<Vec<f32>>)
- DQN trainer: early-skip experience extraction (avoid .to_vec() on invalid)
- EpochPrefetcher: AtomicBool is_ready() so callers can detect completion

Phase 4: Adaptive scaling
- HardwareBudget: tiered safety factor (0.70-0.85 by GPU size)
- AutoBatchSizer: VRAM-proportional batch_overhead_mb (1.5% instead of fixed 250MB)

2451 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:42:38 +01:00
jgrusewski
56374a43ac feat(ml): VRAM-aware hidden dimension scaling for datacenter GPUs
DQN and PPO trainers now resolve hidden_dim_base from GPU VRAM when no
explicit override is given, so L4/L40S/H100 GPUs use proportionally
larger networks instead of being stuck at RTX 3050 Ti defaults (256).

- Add resolve_hidden_dim_base() tiered lookup (256/512/768/1024 by VRAM)
- Add network_param_count() for accurate model size estimation
- DQN: pre-compute hidden dims, use real param count for batch sizing
- PPO: add hidden_dim_base field, VRAM resolution in PpoTrainer::new()
- PPO hyperopt: raise hidden_dim_base ceiling from 2048 to 4096
- TFT hyperopt: expand hidden_sizes from [128,256,512] to 5 tiers
- Update stale estimates (DQN 50K→200K, PPO 100K→400K params)
- Fix pre-existing clippy lints in prefetch.rs and ppo.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:02:56 +01:00
jgrusewski
632c780c00 feat(ml): GPU max performance phase 2 — C51 re-enabled, buffer pooling, kernel sizing
Phase 2 GPU optimizations for L4→H100 scaling:

- Re-enable C51 distributional RL (BUG #36 scatter_add gradient flow VERIFIED)
- Add GpuBufferPool for zero-alloc walk-forward fold transitions
- Add DoubleBufferedLoader for CUDA stream overlap during fold swaps
- Add EpochPrefetcher for background data preparation (overlaps I/O with GPU)
- Add optimal_launch_dims() for dynamic CUDA kernel block/grid sizing (32→256)
- Wire real INT8 quantization into QuantizedTFT via quantize_varmap_parallel()
- Wire DoubleBufferedLoader + EpochPrefetcher into DQN trainer
- Wire optimal_launch_dims into DQN + PPO GPU experience collectors
- Fix flaky hot_swap latency test (100μs→2ms threshold for debug builds)
- Fix missing mixed_precision field in trading_service PPOConfig

Full Rainbow DQN now enabled by default (all 6 components + IQN + CQL).
2,437 ml tests pass, 0 failures. Workspace compiles clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:25:36 +01:00