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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
fxt 2.0 overhaul: consolidated proto/, absorbed monitoring into API Gateway,
new 15-command CLI with gRPC, MCP server mode, TUI cockpit framework.
159 files changed, net -11,835 lines.
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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>