Commit Graph

887 Commits

Author SHA1 Message Date
jgrusewski
71bc2e7d65 fix(ml): correct DQNConfig defaults — widen v_min/v_max, reduce cql_alpha, disable IQN
- v_min/v_max: -2/+2 → -10/+10 (C51 needs room to separate 45 actions)
- entropy_coefficient: 0.01 → 0.05 (5x stronger anti-collapse)
- cql_alpha: 1.0 → 0.1 (was adding ~3.8 loss penalty per step)
- use_iqn: true → false (IQN trains q_network but inference uses dist_dueling)
- Added get_count_bonuses() method for batch action selection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:58:40 +01:00
jgrusewski
5e54f2b269 refactor(ml): consolidate duplicate Rainbow DQN agent implementations
Delete stub rainbow_agent.rs (fake select_action, hardcoded train loss)
and promote rainbow_agent_impl.rs to rainbow_agent.rs. Unify on the
real 8-field RainbowAgentMetrics from rainbow_config.rs, replacing the
4-field stub version (epsilon→exploration_rate, average_loss→current_loss).

5 files changed: -667/+447 lines, 2698+32 tests pass, 0 clippy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:27:42 +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
2f75071551 fix(ml): keep DQN and PPO training pipelines in BF16 on Ampere+ GPUs
On Ampere+ GPUs the training dtype is BF16.  Network forward passes
with BF16 inputs return BF16 tensors, but all loss-path arithmetic
(rewards, dones, gamma, PER weights, Huber constants, atoms, clip
epsilon) was created as F32 from Tensor::from_vec.  Candle forbids
mixed-dtype binary ops, causing "dtype mismatch in {mul,sub,add}"
on every training step.

DQN fixes (7 mismatch sites):
- Cast current_q_values to F32 immediately after forward pass
- Cast all target network outputs (standard, dueling, C51, IQN) to F32
- Keep rewards/dones/gamma/weights/atoms/half/delta as F32 (remove
  .to_dtype(training_dtype) casts — now use DType::F32 sentinel)
- Entropy regularization and CQL paths now match (F32 + F32)

PPO fixes (3 mismatch sites):
- Use DType::F32 for clip epsilon one_tensor (was training_dtype BF16)
- Cast critic.forward() output to F32 in both MLP and LSTM value loss
- Cast target_returns to F32 for symlog/normalization path

Infra: revert runner-h100 from SXM2 (zero Scaleway quota) back to
ci-training-h100 PCIe pool.

2704 ml lib tests pass, 260 PPO tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:52:56 +01:00
jgrusewski
dd10497cfd fix(ml): cast Bellman target to F32 before TD error sub on BF16 GPUs
BUG #41 kept forward pass in F32 for autograd, but the target-side
tensors (reward, gamma, done, next_q) were cast to BF16 via `dtype`.
The `state_action_values.sub(&target_q_values)` then hit F32-vs-BF16
mismatch on Ampere+ GPUs, causing every training step to fail silently.

Fix: `.to_dtype(state_action_values.dtype())` on the detached target.
Safe because target is detached (no autograd graph to break).

Also: H100 runner → SXM2 pool, GPU availability checker script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:29:15 +01:00
jgrusewski
2e40a19d6f fix(ml): keep PPO and ensemble pipelines in BF16 on Ampere+ GPUs
Same class of bug as the DQN fix (4c88498b): network outputs were
being cast to F32 mid-pipeline, defeating tensor-core acceleration
on H100/L40S. Now the full training loop stays in training_dtype()
(BF16 on CUDA Ampere+, F32 on CPU), with F32 casts only at scalar
extraction boundaries (to_scalar, to_vec1).

Files fixed:
- ppo.rs: Actor/Critic forward, act_with_log_prob, compute_losses,
  update_mlp, LSTM recurrent loop, predict method
- lstm_networks.rs: removed F32 output casts from both networks
- continuous_ppo.rs: one_tensor + scalar extractions
- hidden_state_manager.rs: zeros/ones use training_dtype()
- flow_policy/mod.rs: log_det accumulators + dummy log_std
- ensemble/adapters/ppo.rs + dqn.rs: F32 cast at extraction

2704 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:51:23 +01:00
jgrusewski
4c88498b8b fix(ml): keep DQN training pipeline in BF16 on Ampere+ GPUs
Remove premature F32 output casts from network forward passes
(Sequential, NetworkLayers, DistributionalDueling) that were negating
tensor-core acceleration. Instead, cast F32 input tensors (rewards,
dones, gamma, weights, atoms) to training_dtype() at the boundary
and only escape to F32 at scalar extraction points (loss value,
TD errors, logging vectors).

465 DQN tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:28:06 +01:00
jgrusewski
39848a926b fix(eval): read use_noisy_nets from hyperopt params in evaluate_baseline
NoisyLinear layers have different parameter names (sigma_weight/sigma_bias)
than standard Linear layers. Hardcoding use_noisy_nets=false caused
checkpoint loading to silently fail when the training run used noisy nets,
resulting in empty folds in the eval report.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:07:51 +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
1ebbfea8f6 fix(eval): match all architecture params from hyperopt in evaluate_baseline
num_atoms, dueling_hidden_dim, v_min/v_max, gamma were using defaults
instead of hyperopt values — causing tensor shape mismatch on checkpoint
load (e.g. output layer 45×200=9000 vs 45×51=2295). Also fixed use_iqn
to read from use_qr_dqn key (matching trainer's field mapping).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:19:49 +01:00
jgrusewski
d3c834eeb7 fix: pass reward-shaping params from hyperopt to walk-forward DQN
Hyperopt found hold_penalty_weight=1.818, curiosity_weight=0.402, etc.
but walk-forward training used tiny defaults (0.01, 0.1), causing the
agent to learn "holding is optimal" → 0 trades across all 50 epochs.

Now passes: hold_penalty_weight, max_position_absolute, huber_delta,
entropy_coefficient, curiosity_weight, weight_decay, kelly_fractional,
kelly_max_fraction from hyperopt JSON to DQNHyperparameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 06:24:26 +01:00
jgrusewski
1d25d96f7e fix: walk-forward training passes hyperopt params, evaluator falls back to epoch checkpoints
Walk-forward DQN training hardcoded epsilon_start=1.0 with noisy_nets=true,
forcing pure random exploration for all 50 epochs (0 trades, 0 Sharpe).
Now reads epsilon, PER, dueling, distributional, noisy nets, and QR-DQN
params from the hyperopt JSON. When noisy_nets=true, defaults epsilon to
0.05 instead of 1.0.

Evaluator now falls back to highest dqn_fold{N}_epoch{E}.safetensors when
_best.safetensors doesn't exist (early stopping saves epoch checkpoints).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 04:03:21 +01:00
jgrusewski
74eba7920e fix(dqn): real confidence from softmax Q-values, read lock for concurrent inference
Added select_action_inference(&self) — pure greedy forward pass with
no exploration mutations (epsilon, noisy nets, count bonus, step counter
are all skipped). Returns (FactoredAction, f32) where confidence is
softmax probability of the selected action, clamped [0.5, 0.95].

Also added select_action_with_confidence(&mut self) for training path
with the same confidence computation.

Production wrapper now uses read lock (was write lock), enabling
concurrent predictions. Confidence is real softmax probability
instead of hardcoded 0.85.

323 trading_service tests, 15 DQN core tests, 0 clippy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:43:48 +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
042c38fed2 fix(dqn,ppo): verification swarm round 2 — gamma^n, checkpoint config, circuit breaker
- IQN + C51 Bellman bootstrap: gamma → gamma^n for n-step returns
- DQNConfig::from_safetensors_file(): reconstruct config from checkpoint
  metadata instead of hardcoding dims/Rainbow flags in enhanced_ml.rs
- PPO update_value_only(): add circuit breaker guard (consistency with update())
- PPO eval: tensor-core alignment on hidden dims (must match PpoTrainer)
- enhanced_ml.rs: checkpoint-driven config loading with fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:40:08 +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
fc0754c63f fix(dqn): production hardening — LR scheduler, urgency costs, F32 traps, atomic IO
Wire LR scheduler to actually update the Adam optimizer (was logged but
never applied). Add update_learning_rate to DQN, RegimeConditionalDQN,
and DQNAgentType so decay_factor propagates through all agent variants.

Fix train/eval parity: CLI defaults 51→54 features, 3→45 actions;
DQN eval always uses 3-layer hidden_dims; PPO eval uses 5-layer value
network matching trainer. enhanced_ml.rs hardcoded config updated from
state_dim=16/num_actions=3 to 54/45.

Fix Candle F32/BF16 traps: replace `Tensor * 0.5` (f64 literal) with
broadcast_mul(Tensor::full(0.5_f32)) in quantile_regression.rs (2x),
dqn.rs Huber loss, and IQN gamma multiplication. Prevents panics on
Ampere+ BF16 GPUs.

Add urgency_weight() multiplier to training cost model in reward.rs
and portfolio_tracker.rs — urgency dimension (Patient/Normal/Aggressive)
now affects learned value function, matching evaluate_baseline.rs.

Fix equity tracking: additive (equity += ret) → multiplicative
(equity *= 1.0 + ret) in compute_metrics. Fix total return calc.

Fix PER beta annealing: epochs*70 → epochs*1000 (~1015 actual steps
per epoch for 130k bars / batch 128).

Fix silent target network freeze: mutex lock failure now propagates
error instead of silently skipping weight update.

Make save_checkpoint atomic (write .tmp then rename). Make NormStats
write atomic with error logging instead of silent discard.

Convert EnsembleConfig::new assert! → Result<Self, MLError> with
14 call site updates. Fix hyperopt result serialization to warn
instead of silently dropping to Value::Null.

Fix clippy MSRV mismatch: clippy.toml 1.75 → 1.85 matching Cargo.toml.

2732 tests pass, 0 clippy warnings across workspace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 23:04:53 +01:00
jgrusewski
d289b2f264 fix(dqn): close checkpoint validation gaps, atomic NormStats, clippy cleanup
- DQNAgent::load_from_safetensors: validate architecture hash before
  loading weights (was bypassing validation entirely - P0 production gap)
- DQNEnsemble::save_to_directory: embed architecture metadata via
  safetensors::serialize_to_file instead of bare VarMap::save (save/load
  round-trip was guaranteed to fail)
- architecture_hash: hash hidden_dims.len() before values to prevent
  theoretical collision between different-length configs
- train_baseline_rl: NormStats write now atomic (write .tmp then rename)
  with cleanup guard on rename failure; serialization error is now loud
  (error! + return None) instead of silently discarded
- train_baseline_rl: checkpoint rename failure cleans up orphaned .tmp
- hyperopt_baseline_rl: fix clippy single_match_else (match on equality
  check -> if/else)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:31:20 +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
6770e28ca9 feat(dqn): per-sample quantile Huber loss for PER IS-weight correction
Add quantile_huber_loss_per_sample() that preserves the batch dimension
(mean over quantiles only) so PER importance-sampling weights can be
applied per-sample before final batch reduction. Mirrors the existing
quantile_huber_loss() but returns [batch] instead of scalar.

Two tests verify shape correctness, non-negativity, consistency with
the scalar variant (mean of per-sample == scalar loss), and IS weight
application behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:07:50 +01:00
jgrusewski
ba841f7c8b feat(dqn): checkpoint architecture validation via safetensors metadata
Embed DQNConfig architecture hash (SHA-256 of state_dim, num_actions,
hidden_dims, dueling/distributional/noisy/IQN flags) in safetensors
file header on save. Validate hash on load to catch shape mismatches
before touching the VarMap - prevents silent corruption from loading
checkpoints trained with different network architectures.

All 5 save paths (trainable_adapter, trainer serialize_model,
DQNAgentType::save_checkpoint, RegimeConditionalDQN per-head) now
embed metadata. All 3 load paths (DQN::load_from_safetensors,
trainable_adapter::load_checkpoint, ensemble adapter) validate.

No backward compatibility: checkpoints without metadata are rejected
with a clear error message to re-train.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:06:56 +01:00
jgrusewski
2f60e0cda0 fix(eval): train/eval parity - position tracking, cost differentiation, feature alignment
- Wire position-delta cost model: costs only apply on position changes,
  proportional to delta (not flat per-bar)
- Differentiate order types: Market=15bps, LimitMaker=5bps, IoC=10bps
- Apply urgency weight: Patient=0.5x, Normal=1.0x, Aggressive=1.5x
- Append 3 portfolio features (position, unrealized PnL, drawdown) to match
  training's 54-dim state vector (was 51, causing shape mismatch on load)
- Fix default num_actions 3->45 to match training's factored action space
- Add --bars-per-year CLI flag for per-symbol annualization
  (ES/NQ=347760, 6E=345000, ZN=105840)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:43:01 +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
4ecb20ab25 fix(dqn): wire hyperopt weight_decay through trainer to agent
The DQNConfig construction in trainer.rs hardcoded weight_decay to 1e-4,
ignoring the hyperopt-tuned value in DQNHyperparameters. This meant PSO
optimization of weight_decay (search space index 17, range [1e-5, 1e-3])
had no effect on actual training runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:13:24 +01:00
jgrusewski
e338eda7ac fix(dqn): wire weight_decay to Adam optimizer
DQNConfig.weight_decay (default 1e-4, hyperopt range [1e-5, 1e-3]) was
silently ignored — ParamsAdam always received weight_decay: None. Now
conditionally passes Decay::DecoupledWeightDecay when weight_decay > 0,
enabling L2 regularization that hyperopt has been tuning for.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:07:40 +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
2ac4525399 feat(ppo): enable DAPO asymmetric clipping by default (clip_epsilon_high=0.28)
Clip range [1-0.2, 1+0.28] = [0.8, 1.28] allows larger policy updates
toward profitable actions while being conservative about penalizing
exploration. ByteDance DAPO 2025 — 50% faster convergence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:25:04 +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
7e2545bf92 fix(ml): use training_dtype in curiosity module instead of hardcoded F32
Replace DType::F32 with training_dtype(&device) for action one-hot
encoding and state embedding conversion. On BF16 devices, model
weights are BF16 but inputs were F32, causing dtype mismatch errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:39:04 +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
1dac9b0556 fix(ml): cast NoisyNet noise tensors to training dtype (BF16)
sample_noise(), sigma init, disable_noise(), and epsilon buffers all
used hardcoded DType::F32. When weights are BF16 on Ampere+ CUDA,
this caused dtype mismatch in mul/add operations during forward pass.

Fix: use training_dtype(device) for all noise-related tensors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:26:50 +01:00
jgrusewski
4f6e893d2b Merge branch 'worktree-bf16-training' 2026-03-03 17:49:35 +01:00
jgrusewski
864808e056 fix(ml): add ensure_training_dtype boundary casts to all model forward methods
After converting all VarBuilder sites from F32 to training_dtype (BF16 on
Ampere+ GPUs), input tensors from callers remain F32, causing dtype
mismatches in matmul/add/mul operations. This commit adds systematic
boundary casts across all 10 model architectures:

- Input boundary: ensure_training_dtype() at each model forward() entry
- Output boundary: to_dtype(F32) at each model forward() exit
- Internal intermediates: hidden state init, gradient extraction,
  positional encodings, causal masks, SSM state matrices, B-spline
  basis values all cast to match computation dtype
- Ensemble adapters: ensure_training_dtype after Tensor::from_vec

36 files, +293/-72 lines. 2640 tests pass, 0 failures, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 17:42:41 +01:00
jgrusewski
dc8ee1f630 feat(ml): BF16 VarBuilder for all 8 supervised models
Replace DType::F32 with training_dtype(&device) in VarBuilder::from_varmap
calls across TFT, Mamba2, Liquid/CfC, KAN, xLSTM, Diffusion, TGGN, TLOB.
61 sites changed across 25 files (production constructors + test helpers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:23:03 +01:00
jgrusewski
6febee532f feat(ml): BF16 VarBuilder for all DQN networks and layers
Replace DType::F32 with training_dtype(&device) in all VarBuilder::from_varmap
calls across 16 DQN files (~55 call sites). This enables automatic BF16 weight
initialization on Ampere+ GPUs while keeping F32 on CPU and older hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:20:15 +01:00
jgrusewski
392655d5fb fix: add GetEpochHistory to api_gateway proxy + clippy fixes
- Implement GetEpochHistory forwarding in MonitoringServiceProxy
- Fix clippy integer suffix style (0usize → 0_usize)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:20:11 +01:00
jgrusewski
4e0225e090 feat(ml): BF16 VarBuilder, checkpoints, and training tensors for PPO
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:16:07 +01:00
jgrusewski
d0dd3af2f1 feat(ml): BF16 CUDA pipeline (replay buffer, weights, data upload)
- gpu_replay_buffer: allocate states/next_states with training_dtype(),
  cast incoming batches at ingestion boundary
- gpu_weights: cast BF16 model weights to F32 before extraction for
  CUDA f32 kernels in both extract_one() and sync_one()
- mod.rs: cast DqnGpuData, GpuBufferPool, PpoGpuData uploads to
  training_dtype(); cast portfolio tensors to match features dtype;
  cast bar_target_values readback to F32

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:14:31 +01:00
jgrusewski
4accdded0f feat(ml): BF16 training tensors in DQN loss and trainer
Cast state tensors to BF16 (via training_dtype()) at all DQN network
input sites: compute_loss_internal states/next_states, select_actions_batch,
curiosity module state/next_state, validation batch, and Q-value logging
batch. Non-network tensors (actions, rewards, dones, weights) are left
as F32 since they participate in loss math, not forward passes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:14:09 +01:00
jgrusewski
45487206bd fix(ml): allow BF16/F16 in Mamba2 scalar_tensor helper
Previously scalar_tensor() rejected BF16 and F16 with an error,
blocking BF16 training for Mamba2. Now BF16/F16 scalars are created
as F32 then cast to the target dtype, matching Candle's internal
precision requirements while supporting half-precision training.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:10:25 +01:00
jgrusewski
868daff02a feat(ml): add training_dtype() for dynamic BF16 detection
Add detect_from_gpu_name_auto() which uses cached nvidia-smi GPU
capabilities to auto-detect mixed precision config, and training_dtype()
which returns BF16 for Ampere+ CUDA GPUs and F32 for everything else.
This is the single entry point for "what dtype should weights use?"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:09:56 +01:00
jgrusewski
26c7c3b5b8 feat(ml): push epoch financial metrics from PPO trainer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:54:19 +01:00