Commit Graph

307 Commits

Author SHA1 Message Date
jgrusewski
ece9ae11d2 feat(ml): re-enable hyperopt action counting (fixes 62% Sharpe degradation) 2026-02-21 21:20:45 +01:00
jgrusewski
4a8513f813 safety(trading-engine): replace Prometheus static panic with abort fallback 2026-02-21 21:06:33 +01:00
jgrusewski
786029539d fix(ml): remove mock prediction fallback from ensemble coordinator
Production trading must never rely on simulated predictions. The ensemble
coordinator now requires real model adapters and returns errors when none
are registered or all fail inference, instead of silently falling back to
mock/simulated predictions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:23:42 +01:00
jgrusewski
0e1c8be186 feat(ml): wire TFT safetensors checkpoint save/load
Replace empty placeholder safetensors file with actual model weight
serialization using the VarMap, matching the DQN trainable adapter pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:23:40 +01:00
jgrusewski
2fa459cf08 fix(ml): replace unwrap() with ok_or/? in DQN IQN paths
Replace 6 unwrap() calls with safe error handling in DQN IQN code:
- Production: 3 unwrap() on iqn_network/iqn_target_network replaced with
  ok_or_else returning MLError::ModelError for clear diagnostics
- Tests: 2 result.unwrap() replaced with ?, 2 DQN::new().unwrap() replaced
  with ? after changing test signatures to return anyhow::Result<()>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:12:16 +01:00
jgrusewski
6cdffda475 fix(ml): silence unsafe-code warnings on inference adapter Send/Sync impls
Add #[allow(unsafe_code)] on each unsafe impl Send/Sync for the four
inference adapters (DQN, PPO, TFT, Mamba2). The SAFETY comments explain
why these are sound — Mutex provides exclusive access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 18:39:05 +01:00
jgrusewski
07ab29b8b3 Merge branch 'worktree-full-stack-ml-integration' 2026-02-21 14:56:36 +01:00
jgrusewski
ed320c936b feat(paper_trading): add PaperBroker and PnLTracker for simulated trading
PaperBroker: simulated fills with configurable slippage and commission.
PnLTracker: rolling Sharpe ratio (252-day window), max drawdown, cumulative return.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:26:00 +01:00
jgrusewski
dc3952ce22 feat(ensemble): add TradeSignal type with Buy/Sell/Hold action mapping
TradeSignal converts ensemble predictions to actionable trade signals:
direction > 0.1 → Buy, < -0.1 → Sell, else Hold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:25:02 +01:00
jgrusewski
b529c0b1db feat(hyperopt): add campaign runner with results persistence
CampaignResults with serde serialization. run_campaign() orchestrates
ArgminOptimizer for DQN hyperopt, saves best_params.json and
campaign_summary.json to timestamped results directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:16:02 +01:00
jgrusewski
5dddf365e5 feat(hyperopt): add CampaignConfig with DQN/PPO defaults and GPU limits
Campaign configuration for systematic multi-trial optimization:
- DQN: 50 trials, SHA η=3, 81 max epochs
- PPO: 30 trials, Hyperband, 81 max epochs
- Batch size clamped at 230 (RTX 3050 Ti 4GB VRAM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:09:59 +01:00
jgrusewski
3bf3a2a0bb feat(ensemble): add InferenceEnsemble coordinator with confidence-weighted aggregation
Lightweight synchronous coordinator that wraps N ModelInferenceAdapter
instances. Aggregates predictions via confidence-weighted voting with
graceful degradation for unready or failing models.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:59:18 +01:00
jgrusewski
89079a72c9 test(ensemble): add checkpoint round-trip and full integration tests
- DQN adapter: save/load/predict round-trip validation
- Coordinator: full ensemble with real DQN adapter integration test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:42:32 +01:00
jgrusewski
c300fa0551 feat(ensemble): integrate real inference adapters into coordinator
Replace mock predictions with real model inference when adapters are
registered. Falls back to mock predictions for models without adapters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:35:28 +01:00
jgrusewski
c402da4c8b feat(ensemble): add TFT inference adapter with sequence buffering
Implements TftInferenceAdapter that wraps the Temporal Fusion Transformer
for ensemble prediction. Unlike single-step models (DQN, PPO), TFT requires
a sequence of observations before running inference. The adapter maintains
an internal VecDeque buffer that collects sequence_length feature vectors,
then constructs static/historical/future tensors for the TFT forward pass.
Median quantile is mapped to directional signal via smooth saturating
function; IQR provides confidence estimation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:27:11 +01:00
jgrusewski
37d617fb6a feat(mamba): add directional MSE loss and fix ZOH->bilinear discretization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:17:31 +01:00
jgrusewski
93447ec1de feat(ensemble): add DQN and PPO inference adapters
Implement ModelInferenceAdapter for DQN (Q-value argmax -> direction + softmax confidence)
and PPO (probability-weighted action values -> direction + max prob confidence) with
zero-padding for mismatched feature dimensions and 6 passing unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:13:33 +01:00
jgrusewski
7ecd10e793 Merge branch 'feature/codebase-cleanup' 2026-02-21 13:07:29 +01:00
jgrusewski
20cbe7a7fa feat(ensemble): add ModelInferenceAdapter trait and types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:02:42 +01:00
jgrusewski
66c2ff7095 feat(hyperopt): add ObjectiveMode and two-phase optimization orchestration
Add ObjectiveMode enum (EpisodeReward/Sharpe) to DQNTrainer and a
TwoPhaseObjective trait + optimize_two_phase() method to ArgminOptimizer.
Phase A optimizes episode reward for fast convergence, Phase B (pending
model Clone support) refines with Sharpe ratio. This keeps the static
extract_objective trait method untouched by separating objective switching
into instance-level state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:50:17 +01:00
jgrusewski
689231d6cb feat(data): integrate MBP10/OFI features into training data pipeline
Add load_ofi_features() helper to both DQN and PPO trainer adapters
that loads MBP10 snapshots from a sibling mbp10/ directory, computes
8-slot OFI feature vectors via OFICalculator, and overlays them onto
positions 43-50 of the training feature arrays. Gracefully falls back
to zero-padded features when MBP10 data is not available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:40:41 +01:00
jgrusewski
11d486021f feat(dqn): wire QR-DQN QuantileNetwork through hyperopt to training loop
Add use_qr_dqn, num_quantiles, qr_kappa fields to DQNHyperparameters
and pass them from DQNParams (hyperopt) through to DQNConfig (agent),
replacing hardcoded IQN values in the trainer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:33:21 +01:00
jgrusewski
e29b6ac307 feat(hyperopt): add QR-DQN parameters to DQN hyperopt search space (40D->42D)
Add num_quantiles and qr_kappa to the continuous search space for
Quantile Regression DQN, replacing the disabled C51 distributional RL
(BUG #36). QR-DQN is enabled by default with 32 quantiles and kappa=1.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:26:38 +01:00
jgrusewski
fb8ad23803 feat(data): recursive DBN file discovery for multi-symbol dataset loading
Replace flat read_dir() with recursive collect_dbn_files_recursive() in
both DQN and PPO hyperopt adapters so .dbn files inside symbol
subdirectories (6E.FUT/, ES.FUT/, NQ.FUT/, ZN.FUT/) are discovered
automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:19:34 +01:00
jgrusewski
0029887479 feat(hyperopt): implement Hyperband early stopping with rung-based pruning
Replaces the TODO stub with a working Hyperband implementation that
applies Successive Halving pruning only at rung epochs (max_resource/eta^k).
Between rungs, trials always continue. Adds two tests verifying pruning
at rung epochs and non-pruning between rungs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:10:35 +01:00
jgrusewski
f107204fe0 feat(risk,trading,ml): add RiskEnforcer, pipeline traits, and DriftResponder
- RiskEnforcer: drawdown-to-action orchestrator with audit log and recovery (risk)
- PipelineMessage + PipelineStage: typed trading pipeline contract layer (trading_service)
- DriftResponder: maps drift detection scores to risk response recommendations (ml)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:05:00 +01:00
jgrusewski
41afb20b37 feat(hyperopt): implement Successive Halving early stopping strategy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:54:17 +01:00
jgrusewski
4ff4205a4b feat(validation,risk): add noise injection, sensitivity analysis, risk enforcement, and correlation monitor
- NoiseInjector: Gaussian noise and feature dropout for robustness testing (ml)
- SensitivityAnalyzer: hyperparameter fragility detection with perturbation analysis (ml)
- RiskAction trait + enforcement module: pluggable risk response actions with severity levels (risk)
- CorrelationMonitor: rolling cross-asset correlation with effective exposure limits (risk)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:43:50 +01:00
jgrusewski
ff6ad2d039 fix(test): use median of 21 runs for inference latency check
Single-sample latency measurement was flaky under concurrent test load.
Using median eliminates outlier sensitivity (127μs median vs 1154μs spike).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:22:36 +01:00
jgrusewski
77dbd0e96a feat(hyperopt): implement DBN file loading and fix NaN objective handling
- Implement load_from_dbn() for both PPO and DQN hyperopt adapters
  using dbn::DbnDecoder (same pattern as RealDataLoader)
- Fix PPO feature extraction: [f64;51] → [f32;54] with zero-padded
  portfolio state (was silently dropping all samples due to len==54 check)
- Add NaN/Inf → 1e6 penalty in optimizer for non-finite objectives
- Fix partial_cmp().unwrap() panic when comparing NaN objectives
- Add ensemble real-model validation test (DQN + PPO trained on
  real 6E.FUT data, predictions aggregated through ensemble)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 10:57:23 +01:00
jgrusewski
4b51c300ad feat(validation): add PPO adapter for validation harness (MLP + LSTM)
Create PpoStrategy and PpoLstmStrategy implementing ValidatableStrategy
to run PPO through walk-forward validation with DSR, PBO, and permutation
tests. Both variants validated on real 6E.FUT data (29,937 bars, 15 folds).

Key implementation detail: LSTM hidden states are detached from the
computation graph after each step to prevent stack overflow from
unbounded graph growth across 30k+ sequential forward passes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 10:23:48 +01:00
jgrusewski
0fd7277455 feat(hyperopt): enable ContinuousPPO hyperopt adapter
Fix ParameterSpace trait mismatch by encoding integer/categorical params
(batch_size, num_epochs, learnable_std) in continuous space. Update
ContinuousPolicyConfig → FlowPolicyConfig, fix GAEConfig missing field,
and add explicit f64 type annotations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 01:10:07 +01:00
jgrusewski
5935907cd7 feat(ppo): gradient accumulation, clip-higher, and WorkingPPO→PPO rename
- Add accumulation_steps config to PPOConfig with gradient accumulation
  in update_mlp() using existing accumulate_grads/scale_grads utilities
- Add clip_epsilon_high: Option<f32> for asymmetric PPO clipping to
  prevent entropy collapse during long training
- Rename WorkingPPO → PPO for consistency with DQN naming convention
- Add pub type WorkingPPO = PPO for backward compatibility
- Fix PPOConfig struct literals in trading_service and hyperopt adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 01:00:54 +01:00
jgrusewski
b80135dd6d feat(ppo): add gradient clipping and LSTM checkpoint loading
Apply max_grad_norm clipping in update_mlp() and update_lstm() by splitting
backward_step into backward + norm computation + conditional scaling.
Add from_varbuilder() to LSTMPolicyNetwork and LSTMValueNetwork for
checkpoint deserialization. Remove TODO early-return error in load_checkpoint()
and add LSTM branching for actor/critic loading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 00:45:32 +01:00
jgrusewski
6f6a6a9972 fix(ml): fix PPO flow policy shape dimensions and clean up tests
- Remove unnecessary unsqueeze(1) in FlowPolicy log_prob and entropy
  (shapes should be [batch_size], not [batch_size, 1])
- Update flow_policy tests to expect correct [batch_size] shape
- Simplify kelly position sizing test with helper function
- Clean up example files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:52:12 +01:00
jgrusewski
f8be49ed3a fix(ml): convert SIGN_DIAG println to debug! tracing macro
The Bug #42 sign inversion diagnostic was logging thousands of lines to
stdout on every training run. Now gated behind RUST_LOG=debug level.
Removed unused expected_position variable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:41:51 +01:00
jgrusewski
878510ba6d feat(ml): add gradient accumulation support for RegimeConditionalDQN
The default DQN agent (RegimeConditionalDQN with 3 regime heads) was
silently failing all gradient accumulation calls because only the
Standard DQN variant was supported. All training steps errored out and
loss was recorded as 0.0.

Changes:
- Add compute_gradients() to RegimeConditionalDQN: classifies batch by
  regime, routes sub-batches to heads, merges gradient stores (no key
  collisions since heads have independent parameters)
- Add apply_accumulated_gradients(): applies to heads with initialized
  optimizers, skips uninitialized heads (no training data for that regime)
- Add optimizer_vars(): returns combined vars from all heads
- Update DQNAgentType dispatch to delegate to RegimeConditional
- Re-export GradientResult from dqn module
- Fix batch size validation tests for AutoBatchSizer clamping behavior

Verified: convergence test shows 4.8% relative difference between
accumulated (16x4) and direct (64x1) training paths (threshold: 30%).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:02:47 +01:00
jgrusewski
55c8190262 fix(test): update batch size tests to match AutoBatchSizer clamping behavior
DQNTrainer::new() now clamps oversized batch sizes to the safe GPU limit
instead of rejecting them. Updated test_batch_size_validation and
test_gpu_batch_limit_230_enforced to assert is_ok() with clamping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:36:51 +01:00
jgrusewski
59de49c6a4 feat(ml): rewrite train_step_with_accumulation with true gradient accumulation
Replace the previous implementation that ran N independent optimizer steps
with true gradient accumulation: compute gradients for each mini-batch,
accumulate them across steps, average, then apply a single optimizer step.
This simulates training with effective batch size of accumulation_steps *
batch_size while keeping memory usage at batch_size. Also adds PER priority
updates from accumulated TD errors and replay buffer stepping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:22:04 +01:00
jgrusewski
73455274e9 feat(ml): add gradient accumulation dispatch to DQNAgentType
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:18:25 +01:00
jgrusewski
b4bee985b1 feat(ml): add compute_gradients and apply_accumulated_gradients to DQN
Extract target network update logic into private update_target_networks()
helper to avoid duplication between train_step() and the new gradient
accumulation methods. Add three new public methods:

- compute_gradients(): forward+backward without optimizer step
- apply_accumulated_gradients(): apply pre-computed grads + target update
- optimizer_vars(): expose optimizer variables for accumulation utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:16:09 +01:00
jgrusewski
4f9092c0ac refactor(ml): extract compute_loss_internal from DQN train_step
Split the ~700-line train_step into two methods to prepare for gradient
accumulation support. compute_loss_internal handles batch sampling,
tensor creation, forward pass (IQN/C51/standard), entropy and CQL
regularization, returning a ComputeLossResult with the loss tensor still
in the computation graph. train_step now calls compute_loss_internal,
then performs backward pass, optimizer step, and all bookkeeping
(PER updates, diagnostics, target network updates). No behavioral
changes -- identical results to the original monolithic method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:12:58 +01:00
jgrusewski
ad97d5753d feat(ml): add gradient accumulation utility functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:04:13 +01:00
jgrusewski
03600a685a feat(ml): add backward_and_clip and apply_grads to Adam optimizer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:02:55 +01:00
jgrusewski
7f1aa971a7 fix(ml): propagate reduced batch size to agent during OOM recovery
The OOM recovery loop halves `current_batch_size` on each retry, but
`train_step_single_batch()` and `train_step_with_accumulation()` both
called `agent.train_step(None)`, which makes the agent sample from its
replay buffer at the original `config.batch_size` -- ignoring the
reduction entirely. This meant OOM retries would always fail with the
same allocation size.

Fix: when `current_batch_size < hyperparams.batch_size`, manually
sample from the replay buffer at the reduced size and pass the explicit
batch via `agent.train_step(Some(experiences))`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:22:24 +01:00
jgrusewski
78aadf4302 fix(ml): clean up gradient accumulation documentation and use current_batch_size
Replace misleading doc comments and TODOs that described "true gradient
accumulation" with accurate documentation. The method runs N sequential
mini-batch training steps (each with its own optimizer.step()), not
gradient accumulation in the strict sense. Also switch debug log
messages from self.hyperparams.batch_size to self.current_batch_size
so they reflect the dynamically-adjusted batch size after any OOM
recovery halving.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:11:49 +01:00
jgrusewski
98b523580d feat(ml): add OOM recovery loop to DQN train_step with batch size halving
Wraps the train_step() dispatch in a retry loop (up to 3 attempts) that
detects CUDA OOM errors via string matching on the anyhow error chain and
halves the tracked batch size on each retry. Adds a current_batch_size
field to DQNTrainer for monitoring the effective batch size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:09:21 +01:00
jgrusewski
d077f459b8 feat(ml): wire AutoBatchSizer into DQNTrainer::new() for dynamic GPU batch sizing
Replace the static MAX_BATCH_SIZE=230 hard-coded cap with dynamic GPU memory
detection via AutoBatchSizer. When nvidia-smi is available, the trainer queries
actual free GPU memory and calculates an optimal batch size. When unavailable
(CI environments), it falls back to the previous static cap of 230.

Behavioral change: instead of returning an error when batch_size exceeds the
limit, the trainer now clamps it down to the safe maximum. This is more
user-friendly and enables OOM recovery in a later task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 21:05:01 +01:00
jgrusewski
4cff7a56a7 test(ml): add DQN training smoke test with 6 core assertions
Assertions verified on real 6E.FUT data:
1. All 20 epochs complete (no premature early stopping)
2. Loss decreases >5% (gradient flow works) — actual: 22.4%
3. All per-epoch losses are finite (no NaN/Inf)
4. Q-value divergence (model develops action preferences)
5. Checkpoint round-trip (save/load weight integrity)
6. Epsilon decayed below 0.5 (exploration schedule ran)

Also fixes divide-by-zero in triple_barrier.rs:103 when
entry_price_cents is zero (guard in both barrier tracker
and trainer caller).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:17:34 +01:00
jgrusewski
50a0d98a5a feat(ml): add loss_history and agent accessors to DQNTrainer
Add public accessor methods for smoke test verification:
- loss_history() — per-epoch training loss
- val_loss_history() — per-epoch validation loss
- get_agent_epsilon() — current epsilon from DQN agent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:07:46 +01:00