7.8 KiB
Full-Stack ML Integration Design
Date: 2026-02-21 Goal: Take the ML codebase from "4 models that train independently" to "paper-trading pipeline producing simulated P&L from a 4-model ensemble."
Architecture Overview
Market Data (Databento MBP10/OHLCV)
│
▼
┌──────────────────┐
│ Feature Extraction│ ← 51-dim FeatureVector (43 market + 8 OFI)
└────────┬─────────┘
│
▼
┌──────────────────────────────────────────┐
│ Ensemble Coordinator │
│ ┌─────┐ ┌─────┐ ┌────────┐ ┌─────┐ │
│ │ DQN │ │ PPO │ │ Mamba2 │ │ TFT │ │
│ └──┬──┘ └──┬──┘ └───┬────┘ └──┬──┘ │
│ └───┬───┘ │ │ │
│ └────────┬───┘─────────┘ │
│ Weighted Vote │
│ direction: f64, confidence: f64 │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────┐
│ TradeSignal │ ← action + size + confidence
└────────┬─────────┘
│
▼
┌──────────────────┐
│ PaperBroker │ ← simulates fills against real prices
│ PnL Tracker │ ← tracks cumulative returns, drawdown, Sharpe
└──────────────────┘
Phase 1: Codebase Cleanup
Problem: 6+ test files use stale imports (foxhunt_ml::, WorkingDQN), several modules have unstaged changes, and cargo test -p ml fails to compile all integration tests.
Actions:
- Delete or fix stale test files:
tft_int8_integration_test.rs,dqn_full_gradient_flow_integration_test.rs,debug_target_network_init.rs,epsilon_greedy_softmax_test.rs,dqn_target_update_frequency_bug9_test.rs,trending_test.rs - Commit unstaged Mamba2 changes (bilinear discretization,
loss.rs,mod.rs) - Commit unstaged ensemble changes (
adapters/module with DQN + PPO adapters) - Verify
SQLX_OFFLINE=true cargo test -p ml --libcompiles cleanly - Verify
SQLX_OFFLINE=true cargo test -p ml --testscompiles cleanly (integration tests)
Success criteria: Zero compilation errors across all test targets.
Phase 2: Complete Ensemble Inference Adapters
Problem: Only DQN and PPO have ModelInferenceAdapter implementations. Mamba2 and TFT are missing.
Existing trait (ml/src/ensemble/inference_adapter.rs):
pub trait ModelInferenceAdapter: Send + Sync {
fn predict(&self, features: &FeatureVector) -> MLResult<EnsemblePrediction>;
fn is_ready(&self) -> bool;
fn model_name(&self) -> &str;
}
Mamba2 Inference Adapter
- Input: Mamba2 is a sequence model — needs a sliding window of recent FeatureVectors, not just the current bar. The adapter holds an internal ring buffer of the last N feature vectors (configurable, default 50).
- Forward pass: Stack the window into a
[seq_len, feature_dim]tensor, run throughMamba2SSM::forward(), take the last timestep prediction. - Output: The final prediction is a scalar (next-bar return prediction). Convert to directional signal:
direction = prediction.signum(),confidence = prediction.abs().min(1.0). - Checkpoint loading: Mamba2 checkpoint save/load works (safetensors format via
VarMap).
TFT Inference Adapter
- Challenge: TFT checkpoint save is stubbed (saves empty placeholder). The model weights cannot be restored from disk.
- Solution: Two options:
- (A) Fix TFT to expose its VarMap for proper checkpoint save/load (requires TFT refactoring)
- (B) Use TFT as an in-memory model only — train it, keep it alive, inference from the same instance
- Recommendation: Option B for now — skip checkpoint loading, construct TFT from config and train before use. Defer VarMap refactoring.
- Input: TFT needs
past_features: [past_len, feat_dim]andknown_futures: [future_len, known_dim]. The adapter handles windowing and known-future construction (time features: hour, day-of-week, etc.). - Output: Quantile predictions → median as point estimate → directional signal.
Ensemble Coordinator
- Wire all 4 adapters into the existing
EnsembleCoordinatororWeightedVotingsystem. - Graceful degradation: if a model's
is_ready()returns false, skip it and re-weight the remaining models. - Default equal weights (0.25 each), with API to set custom weights.
Success criteria: EnsembleCoordinator::predict(features) returns a weighted consensus from all available models.
Phase 3: Production Hyperopt Campaign
Problem: Models use default/conservative hyperparameters. No systematic search has been run on the full dataset.
Components:
- Hyperopt runner script — CLI entrypoint that configures and runs hyperopt:
- Input: model type (DQN/PPO), dataset path, trial budget, early stopping strategy (SHA/Hyperband), GPU memory limit
- Output: trial history CSV, best params JSON, best checkpoint .safetensors
- Results persistence — save to
ml/hyperopt_results/{model}/{timestamp}/ - Automated validation — after hyperopt, take best params → full train →
ValidationHarness→ verdict - OOM guard — clamp batch size based on available VRAM (RTX 3050 Ti = 4GB, ~230 max batch size for DQN)
Campaign plan:
- DQN QR-DQN on 361-file 6E.FUT dataset, 50 trials, SHA with η=3
- PPO on same dataset, 30 trials, Hyperband (max_resource=81, η=3)
- Validate top-3 param sets through harness, pick the one with best DSR
Success criteria: At least one model achieves ValidationVerdict::Pass (DSR p < 0.05, PBO < 0.25).
Phase 4: Paper Trading Pipeline
Problem: trading_engine has no dependency on ml. There's no path from market data to ML-driven orders.
MLSignalService
- Lives in
trading_engine/src/ml_signal.rs - Subscribes to market data feed (Databento or replayed historical data)
- Extracts 51-dim FeatureVector per bar (43 market features + 8 OFI from MBP10)
- Calls
EnsembleCoordinator::predict(features)→EnsemblePrediction - Converts to
TradeSignal { action: Buy/Sell/Hold, size: f64, confidence: f64 } - Emits signals to the order management system
PaperBrokerAdapter
- Implements the existing broker adapter trait but simulates fills
- Fill price = current market price + simulated slippage
- Tracks simulated positions, P&L, commissions
- No real network calls
PaperPnLTracker
- Tracks cumulative returns, max drawdown, rolling Sharpe (252-bar window)
- Periodic reporting to stdout/log
- Optional: write results to CSV for post-analysis
Integration Test
- Replay 1 week of historical 6E.FUT OHLCV data through the full pipeline
- Verify: signals generated, positions opened/closed, P&L computed
- Assert no panics, all values finite
Success criteria: Full pipeline runs on replayed data, produces finite P&L and Sharpe.
Constraints
- GPU: RTX 3050 Ti, 4GB VRAM — batch sizes must be bounded
- Concurrent sessions: Other Claude sessions may be active — use worktrees for isolation
- Clippy denials:
unwrap_used,expect_used,panic,indexing_slicing— all code must use safe patterns - SQLX_OFFLINE=true: Required for all cargo commands
Non-Goals (YAGNI)
- Live broker integration (paper-trading only for now)
- Multi-asset support (6E.FUT only)
- Web dashboard or UI
- Cloud deployment
- Model versioning / MLOps