Files
foxhunt/docs/CHECKPOINT_SELECTION_FRAMEWORK.md
jgrusewski 650b3894c6 🚀 Wave 160 Phase 5: Complete ML Ensemble + Production Deployment (27 Agents)
## Executive Summary
Deployed 27 parallel agents: all 6 models operational, ensemble working, adaptive
strategy integrated, hyperparameter tuning automated, TFT fixed, critical blocker
resolved (DbnSequenceLoader 99.85% memory reduction 40.6GB→61MB).

## Critical Fixes
- Agent 85: DbnSequenceLoader memory fix (UNBLOCKED all ML training)
- Agent 79: TFT 5 critical bugs fixed
- Agent 86: Adaptive strategy integration (regime-aware ensemble)
- Agent 88: Liquid NN API fix (14 compilation errors)
- Agent 89: Paper trading deployment (LIVE, 3-model ensemble)

## Infrastructure
- Database: 2,127 writes/sec (212% of target)
- Memory: DQN 192MB, PPO 288MB, TFT 384MB (all within targets)
- Ensemble: Sharpe 10.68, latency 35μs, throughput >20K/sec
- Monitoring: 22 alerts, PagerDuty integration

## Files: 193 changed, +70,250 insertions, -414 deletions

🤖 Generated with Claude Code - Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 18:41:48 +02:00

990 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Checkpoint Selection Framework
**Author**: Agent Analysis
**Date**: 2025-10-14
**Status**: Design Specification
**Related**: Wave 160 ML Training Infrastructure, GPU Training Benchmark System
---
## Executive Summary
Production-ready framework for systematic ML checkpoint selection and ensemble composition in the Foxhunt HFT system. Addresses the critical problem of selecting high-performing checkpoints from 100+ epoch training runs and composing optimal ensemble models for trading signal generation.
**Key Metrics**:
- **Target Trade Frequency**: 2-5% of bars (vs. current 0.01% baseline)
- **Minimum Sharpe Ratio**: >1.0 (annualized, risk-adjusted)
- **Minimum Win Rate**: >50% (statistically significant)
- **Maximum Drawdown**: <20% (capital preservation)
- **Selection Latency**: <100ms (backtest evaluation per checkpoint)
---
## 1. Problem Analysis
### 1.1 Current Training Pipeline
```
DQN/PPO Training (100 epochs, RTX 3050 Ti)
Checkpoint saves every 10 epochs → MinIO/S3
Result: 10 checkpoints × 2 models = 20+ checkpoints
Problem: Which checkpoints to deploy?
```
### 1.2 What Makes a "Good" Checkpoint?
#### Primary Quality Metrics
1. **Trade Frequency** (Weight: 0.2)
- **Target**: 2-5% of total bars (400-1000 trades per 20K bar dataset)
- **Current Baseline**: 0.01% (2 trades per 20K bars) ❌ TOO LOW
- **Why it matters**: Undertrading indicates overly conservative model (low PnL potential)
- **Data source**: `TradeStatistics.total_trades` from backtesting metrics
2. **Sharpe Ratio** (Weight: 0.4)
- **Target**: >1.0 (annualized risk-adjusted returns)
- **World-class**: >2.0 (elite quant funds)
- **Why it matters**: Risk-adjusted profitability (primary alpha measure)
- **Data source**: `RiskMetrics.sharpe_ratio` from backtesting
- **Formula**: `(mean_return - risk_free_rate) / std_dev * sqrt(252)`
3. **Win Rate** (Weight: 0.3)
- **Target**: >50% (statistically significant edge)
- **Elite**: >55% (top decile quant strategies)
- **Why it matters**: Indicates model prediction accuracy
- **Data source**: `TradeStatistics.win_rate`
- **Formula**: `winning_trades / total_trades`
4. **Maximum Drawdown** (Weight: 0.1)
- **Target**: <20% (capital preservation)
- **Excellent**: <10% (institutional grade)
- **Why it matters**: Risk of ruin, investor confidence
- **Data source**: `DrawdownMetrics.max_drawdown`
- **Formula**: `max((peak - trough) / peak)`
#### Secondary Quality Metrics (Tiebreakers)
- **Profit Factor**: `gross_profit / gross_loss` (target >1.5)
- **Sortino Ratio**: Downside-focused risk measure (target >1.5)
- **Calmar Ratio**: Return / max_drawdown (target >1.0)
- **Average Trade Duration**: Alignment with HFT strategy (minutes to hours)
- **Convergence Stability**: Low variance in last 20% of training epochs
### 1.3 Current Infrastructure (Available)
From codebase analysis:
1. **Checkpoint System** (`ml/src/checkpoint/mod.rs`):
- ✅ Metadata storage (`CheckpointMetadata` with metrics HashMap)
- ✅ Version management (semantic versioning)
- ✅ Storage backends (FileSystem, S3/MinIO)
- ✅ Compression (LZ4/Zstd)
- ✅ Model-specific implementations (DQN, PPO, MAMBA-2, TFT)
2. **Backtesting Engine** (`backtesting/src/metrics.rs`):
- ✅ Comprehensive metrics (`PerformanceAnalytics` struct)
- ✅ Sharpe ratio calculation (`calculate_sharpe_ratio`)
- ✅ Drawdown analysis (`DrawdownMetrics`)
- ✅ Trade statistics (`TradeStatistics` with win rate, profit factor)
- ✅ Time-based analysis (monthly/yearly performance)
3. **Ensemble System** (`ml/src/ensemble/model.rs`):
- ✅ Model registration (`register_model`)
- ✅ Signal aggregation (weighted average, majority vote, adaptive)
- ✅ Health monitoring (`health_check`, `HealthInfo`)
- ⚠️ Missing: Checkpoint selection logic
- ⚠️ Missing: Performance-based model weighting
---
## 2. Checkpoint Selection Algorithm
### 2.1 Scoring Function
```rust
/// Composite score for checkpoint quality (0.0 to 100.0)
///
/// Weighted combination of 4 primary metrics:
/// - Sharpe ratio: 40% (risk-adjusted returns)
/// - Win rate: 30% (prediction accuracy)
/// - Trade frequency: 20% (activity level)
/// - Drawdown: 10% (risk management)
pub fn calculate_checkpoint_score(metrics: &PerformanceAnalytics) -> f64 {
const SHARPE_WEIGHT: f64 = 0.4;
const WIN_RATE_WEIGHT: f64 = 0.3;
const TRADE_FREQ_WEIGHT: f64 = 0.2;
const DRAWDOWN_WEIGHT: f64 = 0.1;
// Component scores (normalized 0-100)
let sharpe_score = normalize_sharpe_ratio(
metrics.risk.sharpe_ratio.to_f64()
);
let win_rate_score = normalize_win_rate(
metrics.trade_stats.win_rate.to_f64()
);
let trade_freq_score = normalize_trade_frequency(
metrics.trade_stats.total_trades,
metrics.time_analysis.total_days
);
let drawdown_score = normalize_drawdown(
metrics.drawdown.max_drawdown.to_f64()
);
// Weighted composite
let composite_score =
SHARPE_WEIGHT * sharpe_score +
WIN_RATE_WEIGHT * win_rate_score +
TRADE_FREQ_WEIGHT * trade_freq_score +
DRAWDOWN_WEIGHT * drawdown_score;
composite_score
}
```
### 2.2 Normalization Functions
#### Sharpe Ratio (Target: >1.0, World-class: >2.0)
```rust
fn normalize_sharpe_ratio(sharpe: f64) -> f64 {
// Piecewise linear mapping
if sharpe < 0.0 {
0.0 // Negative Sharpe = 0 score
} else if sharpe < 1.0 {
sharpe * 50.0 // 0-1.0 → 0-50 points
} else if sharpe < 2.0 {
50.0 + (sharpe - 1.0) * 30.0 // 1.0-2.0 → 50-80 points
} else {
80.0 + (sharpe - 2.0).min(1.0) * 20.0 // 2.0-3.0 → 80-100 points
}
}
```
#### Win Rate (Target: >50%, Elite: >55%)
```rust
fn normalize_win_rate(win_rate: f64) -> f64 {
// Below 50% = losing strategy
if win_rate < 0.5 {
win_rate * 100.0 // 0-0.5 → 0-50 points (harsh penalty)
} else if win_rate < 0.55 {
50.0 + (win_rate - 0.5) * 600.0 // 0.5-0.55 → 50-80 points
} else {
80.0 + (win_rate - 0.55).min(0.05) * 400.0 // 0.55-0.6 → 80-100 points
}
}
```
#### Trade Frequency (Target: 2-5% of bars)
```rust
fn normalize_trade_frequency(total_trades: u64, total_days: i64) -> f64 {
// Assume ~20 bars per day (1-hour bars for ES futures)
let estimated_bars = total_days * 20;
let trade_freq_pct = (total_trades as f64 / estimated_bars as f64) * 100.0;
// Scoring curve (bell-shaped around 3.5%)
if trade_freq_pct < 0.1 {
0.0 // Severe undertrading (<0.1%)
} else if trade_freq_pct < 2.0 {
trade_freq_pct * 25.0 // 0.1-2.0% → 2.5-50 points
} else if trade_freq_pct <= 5.0 {
50.0 + (trade_freq_pct - 2.0) / 3.0 * 50.0 // 2-5% → 50-100 points (optimal)
} else {
100.0 - (trade_freq_pct - 5.0).min(5.0) * 10.0 // >5% → penalize overtrading
}
}
```
#### Maximum Drawdown (Target: <20%, Elite: <10%)
```rust
fn normalize_drawdown(max_drawdown_pct: f64) -> f64 {
// Lower drawdown = higher score (inverted metric)
if max_drawdown_pct < 0.05 {
100.0 // <5% drawdown = perfect score
} else if max_drawdown_pct < 0.10 {
100.0 - (max_drawdown_pct - 0.05) * 400.0 // 5-10% → 100-80 points
} else if max_drawdown_pct < 0.20 {
80.0 - (max_drawdown_pct - 0.10) * 400.0 // 10-20% → 80-40 points
} else if max_drawdown_pct < 0.30 {
40.0 - (max_drawdown_pct - 0.20) * 200.0 // 20-30% → 40-20 points
} else {
(0.5 - max_drawdown_pct).max(0.0) * 40.0 // >30% → 0-20 points
}
}
```
### 2.3 Statistical Significance Filter
Before scoring, filter out checkpoints with insufficient data:
```rust
fn is_statistically_significant(metrics: &PerformanceAnalytics) -> bool {
// Minimum sample size requirements
const MIN_TRADES: u64 = 30; // Statistical power for 50% win rate test
const MIN_DAYS: i64 = 30; // 1 month minimum backtest period
metrics.trade_stats.total_trades >= MIN_TRADES &&
metrics.time_analysis.total_days >= MIN_DAYS
}
```
---
## 3. Automatic Checkpoint Ranking
### 3.1 Ranking Pipeline
```
Checkpoint Discovery (MinIO/S3 scan)
Load Metadata (CheckpointMetadata for each checkpoint)
Backtest Evaluation (parallel, GPU-accelerated)
Statistical Filter (min 30 trades, 30 days)
Score Calculation (composite metric 0-100)
Ranking (descending by score)
Top-K Selection (top 5 DQN + top 5 PPO)
Ensemble Composition
```
### 3.2 Implementation Structure
```rust
// New module: ml/src/checkpoint/selection.rs
pub struct CheckpointSelector {
backtesting_engine: Arc<BacktestingEngine>,
checkpoint_manager: Arc<CheckpointManager>,
config: SelectionConfig,
}
pub struct SelectionConfig {
/// Minimum trades for statistical significance
pub min_trades: u64,
/// Minimum backtest period (days)
pub min_days: i64,
/// Number of top checkpoints to select per model
pub top_k: usize,
/// Parallel backtest workers
pub num_workers: usize,
/// DBN data source for backtesting
pub dbn_data_dir: PathBuf,
}
pub struct RankedCheckpoint {
pub checkpoint_id: String,
pub checkpoint_path: String,
pub model_type: ModelType,
pub epoch: u64,
pub score: f64,
pub metrics: PerformanceAnalytics,
pub rank: usize, // 1 = best
}
impl CheckpointSelector {
/// Discover and rank all checkpoints for a model
pub async fn rank_checkpoints(
&self,
model_type: ModelType,
) -> Result<Vec<RankedCheckpoint>, MLError> {
// 1. Discover checkpoints from storage
let checkpoints = self.checkpoint_manager
.list_checkpoints(model_type, "")
.await;
// 2. Parallel backtest evaluation
let mut evaluated_checkpoints = Vec::new();
for checkpoint in checkpoints {
let metrics = self.backtest_checkpoint(&checkpoint).await?;
// 3. Statistical significance filter
if !is_statistically_significant(&metrics) {
continue;
}
// 4. Score calculation
let score = calculate_checkpoint_score(&metrics);
evaluated_checkpoints.push((checkpoint, metrics, score));
}
// 5. Sort by score (descending)
evaluated_checkpoints.sort_by(|a, b|
b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal)
);
// 6. Assign ranks
let ranked: Vec<RankedCheckpoint> = evaluated_checkpoints
.into_iter()
.enumerate()
.map(|(rank, (checkpoint, metrics, score))| RankedCheckpoint {
checkpoint_id: checkpoint.checkpoint_id.clone(),
checkpoint_path: checkpoint.generate_filename(),
model_type: checkpoint.model_type,
epoch: checkpoint.epoch.unwrap_or(0),
score,
metrics,
rank: rank + 1,
})
.collect();
Ok(ranked)
}
/// Select top-K checkpoints for production ensemble
pub async fn select_top_k(
&self,
model_type: ModelType,
k: usize,
) -> Result<Vec<RankedCheckpoint>, MLError> {
let ranked = self.rank_checkpoints(model_type).await?;
Ok(ranked.into_iter().take(k).collect())
}
/// Backtest a single checkpoint
async fn backtest_checkpoint(
&self,
checkpoint: &CheckpointMetadata,
) -> Result<PerformanceAnalytics, MLError> {
// Load model from checkpoint
let model = self.load_model(checkpoint).await?;
// Run backtest on DBN data
let metrics = self.backtesting_engine
.run_backtest(
model,
&self.config.dbn_data_dir,
BacktestConfig::default(),
)
.await?;
Ok(metrics)
}
}
```
### 3.3 Performance Optimization
#### Parallel Backtesting
```rust
use tokio::task::JoinSet;
pub async fn rank_checkpoints_parallel(
&self,
model_type: ModelType,
) -> Result<Vec<RankedCheckpoint>, MLError> {
let checkpoints = self.checkpoint_manager
.list_checkpoints(model_type, "")
.await;
// Spawn parallel backtest tasks
let mut join_set = JoinSet::new();
for checkpoint in checkpoints {
let selector_clone = self.clone();
join_set.spawn(async move {
let metrics = selector_clone.backtest_checkpoint(&checkpoint).await?;
let score = calculate_checkpoint_score(&metrics);
Ok::<_, MLError>((checkpoint, metrics, score))
});
}
// Collect results
let mut evaluated = Vec::new();
while let Some(result) = join_set.join_next().await {
if let Ok(Ok((checkpoint, metrics, score))) = result {
if is_statistically_significant(&metrics) {
evaluated.push((checkpoint, metrics, score));
}
}
}
// Sort and rank
evaluated.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
Ok(self.to_ranked_checkpoints(evaluated))
}
```
#### Caching Strategy
```rust
use std::collections::HashMap;
use std::sync::RwLock;
pub struct CheckpointCache {
// Cache: (checkpoint_id, dbn_data_hash) -> PerformanceAnalytics
cache: Arc<RwLock<HashMap<(String, String), PerformanceAnalytics>>>,
}
impl CheckpointCache {
pub fn get(
&self,
checkpoint_id: &str,
data_hash: &str,
) -> Option<PerformanceAnalytics> {
self.cache
.read()
.ok()?
.get(&(checkpoint_id.to_string(), data_hash.to_string()))
.cloned()
}
pub fn insert(
&self,
checkpoint_id: String,
data_hash: String,
metrics: PerformanceAnalytics,
) {
if let Ok(mut cache) = self.cache.write() {
cache.insert((checkpoint_id, data_hash), metrics);
}
}
}
```
---
## 4. Ensemble Composition
### 4.1 Composition Strategy
**Principle**: Combine top-performing checkpoints from multiple models to diversify prediction strategies.
```
Top 5 DQN Checkpoints (reinforcement learning)
+
Top 5 PPO Checkpoints (policy optimization)
+
Optional: Top 3 MAMBA-2 (state space models)
+
Optional: Top 2 TFT (transformers)
=
15-model Ensemble (diverse, robust)
```
### 4.2 Model Weighting Strategies
#### Strategy 1: Score-Based Weighting
```rust
pub fn calculate_score_based_weights(
checkpoints: &[RankedCheckpoint],
) -> Vec<f64> {
let total_score: f64 = checkpoints.iter().map(|c| c.score).sum();
checkpoints
.iter()
.map(|c| c.score / total_score)
.collect()
}
```
#### Strategy 2: Inverse-Rank Weighting
```rust
pub fn calculate_inverse_rank_weights(
checkpoints: &[RankedCheckpoint],
) -> Vec<f64> {
// Higher-ranked models get higher weights
let weights: Vec<f64> = checkpoints
.iter()
.map(|c| 1.0 / c.rank as f64)
.collect();
let total: f64 = weights.iter().sum();
weights.into_iter().map(|w| w / total).collect()
}
```
#### Strategy 3: Equal Weighting (Baseline)
```rust
pub fn calculate_equal_weights(
checkpoints: &[RankedCheckpoint],
) -> Vec<f64> {
vec![1.0 / checkpoints.len() as f64; checkpoints.len()]
}
```
#### Strategy 4: Sharpe-Optimized Weighting (Advanced)
```rust
/// Mean-variance optimization for ensemble weights
/// Maximizes Sharpe ratio of ensemble predictions
pub fn calculate_sharpe_optimized_weights(
checkpoints: &[RankedCheckpoint],
correlation_matrix: &[Vec<f64>],
) -> Vec<f64> {
// Requires covariance matrix of model returns
// Use quadratic programming to solve:
// max: (w^T * μ - r_f) / sqrt(w^T * Σ * w)
// s.t.: sum(w) = 1, w >= 0
// Placeholder: Use score-based for now
calculate_score_based_weights(checkpoints)
}
```
### 4.3 Ensemble Integration
```rust
// Update ml/src/ensemble/model.rs
impl EnsembleModel {
/// Register top-K checkpoints as ensemble members
pub async fn register_top_checkpoints(
&self,
selector: &CheckpointSelector,
model_types: Vec<ModelType>,
k_per_model: usize,
) -> Result<(), MLError> {
for model_type in model_types {
let top_k = selector.select_top_k(model_type, k_per_model).await?;
for ranked in top_k {
self.register_model(
&ranked.checkpoint_id,
&format!("{:?}", ranked.model_type),
&format!("epoch_{}", ranked.epoch),
vec!["price".to_string(), "volume".to_string()],
100, // Expected latency
).await?;
}
}
Ok(())
}
/// Set model weights based on checkpoint scores
pub fn set_model_weights(
&self,
weights: HashMap<String, f64>,
) -> Result<(), MLError> {
// Store weights for signal aggregation
// (Implementation TBD in aggregation methods)
Ok(())
}
}
```
---
## 5. Periodic Re-evaluation
### 5.1 Re-evaluation Strategy
**Frequency**: Weekly (every Sunday 00:00 UTC)
**Triggers**:
1. New training epochs completed (automatic)
2. Performance degradation detected (>10% Sharpe drop)
3. Market regime change (volatility spike, correlation breakdown)
4. Manual trigger (operator command)
### 5.2 Implementation
```rust
pub struct RevaluationScheduler {
selector: Arc<CheckpointSelector>,
ensemble: Arc<EnsembleModel>,
schedule: RevaluationSchedule,
}
pub struct RevaluationSchedule {
/// Cron expression (e.g., "0 0 * * SUN")
pub cron: String,
/// Minimum Sharpe ratio before triggering re-evaluation
pub min_sharpe_threshold: f64,
/// Performance window (days)
pub performance_window_days: i64,
}
impl RevaluationScheduler {
pub async fn run_periodic_reevaluation(&self) -> Result<(), MLError> {
loop {
// Wait for next scheduled time
self.wait_for_next_schedule().await;
// Re-rank all checkpoints
let dqn_ranked = self.selector
.rank_checkpoints(ModelType::DQN)
.await?;
let ppo_ranked = self.selector
.rank_checkpoints(ModelType::PPO)
.await?;
// Detect if top performers changed
let current_top_dqn = self.ensemble.get_active_models(ModelType::DQN);
let new_top_dqn = dqn_ranked.iter().take(5).collect::<Vec<_>>();
if self.should_update_ensemble(&current_top_dqn, &new_top_dqn) {
// Unregister old models
for model_id in current_top_dqn {
self.ensemble.unregister_model(&model_id).await?;
}
// Register new top performers
for ranked in new_top_dqn {
self.ensemble.register_model(
&ranked.checkpoint_id,
&format!("{:?}", ranked.model_type),
&format!("epoch_{}", ranked.epoch),
vec![],
100,
).await?;
}
info!("Ensemble updated with new top performers");
}
}
}
fn should_update_ensemble(
&self,
current: &[String],
new: &[&RankedCheckpoint],
) -> bool {
// Update if any of top 3 changed
let current_top3: HashSet<_> = current.iter().take(3).collect();
let new_top3: HashSet<_> = new.iter().take(3)
.map(|r| &r.checkpoint_id)
.collect();
current_top3 != new_top3
}
}
```
### 5.3 Performance Monitoring
```rust
pub struct EnsemblePerformanceMonitor {
ensemble: Arc<EnsembleModel>,
metrics_history: Arc<RwLock<Vec<EnsembleMetrics>>>,
}
impl EnsemblePerformanceMonitor {
/// Track ensemble Sharpe ratio in production
pub async fn monitor_sharpe_ratio(&self) -> Result<f64, MLError> {
let recent_metrics = self.get_recent_metrics(30)?; // 30-day window
let sharpe = self.calculate_sharpe_from_signals(&recent_metrics);
// Alert if Sharpe drops below threshold
if sharpe < self.config.min_sharpe_threshold {
self.trigger_reevaluation().await?;
}
Ok(sharpe)
}
/// Trigger immediate re-evaluation
async fn trigger_reevaluation(&self) -> Result<(), MLError> {
warn!("Performance degradation detected, triggering re-evaluation");
// Send notification to RevaluationScheduler
Ok(())
}
}
```
---
## 6. Production Integration
### 6.1 TLI Commands (User Interface)
```bash
# Rank all checkpoints for a model
tli checkpoint rank --model DQN --output ranked_dqn.json
# Select top-K checkpoints
tli checkpoint select --model DQN --top 5
# View checkpoint scores
tli checkpoint list --model DQN --sort score --limit 10
# Deploy ensemble with top performers
tli ensemble deploy --models DQN:5,PPO:5,MAMBA2:3
# Check ensemble health
tli ensemble health
# Force re-evaluation
tli ensemble reevaluate
```
### 6.2 ML Training Service Integration
Update `services/ml_training_service/src/main.rs`:
```rust
// New gRPC method
rpc RankCheckpoints(RankCheckpointsRequest) returns (RankCheckpointsResponse);
message RankCheckpointsRequest {
ModelType model_type = 1;
uint32 top_k = 2; // Optional: limit results
}
message RankCheckpointsResponse {
repeated RankedCheckpoint checkpoints = 1;
}
message RankedCheckpoint {
string checkpoint_id = 1;
string checkpoint_path = 2;
uint64 epoch = 3;
double score = 4;
uint32 rank = 5;
CheckpointMetrics metrics = 6;
}
message CheckpointMetrics {
double sharpe_ratio = 1;
double win_rate = 2;
double max_drawdown = 3;
uint64 total_trades = 4;
double trade_frequency_pct = 5;
}
```
### 6.3 CI/CD Pipeline Integration
```yaml
# .github/workflows/ml_checkpoint_selection.yml
name: ML Checkpoint Selection
on:
schedule:
- cron: '0 0 * * SUN' # Weekly on Sunday
workflow_dispatch: # Manual trigger
jobs:
reevaluate_checkpoints:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Rust
uses: actions-rs/toolchain@v1
- name: Download DBN test data
run: aws s3 sync s3://foxhunt-ml-data/dbn/ test_data/
- name: Run checkpoint ranking
run: cargo run -p ml --example checkpoint_ranking --release
- name: Update ensemble configuration
run: |
tli ensemble deploy \
--models DQN:5,PPO:5 \
--config production
- name: Validate ensemble health
run: tli ensemble health --fail-on-degraded
```
---
## 7. Testing Strategy
### 7.1 Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_sharpe_ratio() {
assert_eq!(normalize_sharpe_ratio(0.0), 0.0);
assert_eq!(normalize_sharpe_ratio(1.0), 50.0);
assert_eq!(normalize_sharpe_ratio(2.0), 80.0);
assert!(normalize_sharpe_ratio(3.0) >= 90.0);
}
#[test]
fn test_normalize_win_rate() {
assert!(normalize_win_rate(0.45) < 50.0);
assert_eq!(normalize_win_rate(0.50), 50.0);
assert!(normalize_win_rate(0.55) >= 80.0);
}
#[test]
fn test_calculate_checkpoint_score() {
let metrics = create_test_metrics(
1.5, // sharpe
0.55, // win_rate
500, // trades
0.15, // drawdown
);
let score = calculate_checkpoint_score(&metrics);
assert!(score > 70.0); // Good checkpoint
}
#[test]
fn test_statistical_significance_filter() {
let metrics = create_test_metrics_with_trades(10); // Too few trades
assert!(!is_statistically_significant(&metrics));
let metrics = create_test_metrics_with_trades(50);
assert!(is_statistically_significant(&metrics));
}
}
```
### 7.2 Integration Tests
```rust
#[tokio::test]
async fn test_checkpoint_selection_end_to_end() {
// 1. Setup test environment
let temp_dir = tempfile::tempdir().unwrap();
let checkpoint_manager = create_test_checkpoint_manager(&temp_dir);
let backtesting_engine = create_test_backtesting_engine();
let selector = CheckpointSelector::new(
backtesting_engine,
checkpoint_manager,
SelectionConfig::default(),
);
// 2. Create synthetic checkpoints
create_test_checkpoints(&checkpoint_manager, 10).await;
// 3. Rank checkpoints
let ranked = selector.rank_checkpoints(ModelType::DQN).await.unwrap();
// 4. Assertions
assert_eq!(ranked.len(), 10);
assert!(ranked[0].score >= ranked[1].score); // Descending order
assert!(ranked[0].metrics.risk.sharpe_ratio > Decimal::ZERO);
}
#[tokio::test]
async fn test_ensemble_composition() {
let ensemble = create_test_ensemble();
let selector = create_test_selector();
// Register top 5 DQN + top 5 PPO
ensemble.register_top_checkpoints(
&selector,
vec![ModelType::DQN, ModelType::PPO],
5,
).await.unwrap();
// Verify 10 models registered
let models = ensemble.list_models();
assert_eq!(models.len(), 10);
}
```
### 7.3 Performance Benchmarks
```rust
#[tokio::test]
async fn benchmark_checkpoint_ranking_latency() {
let selector = create_test_selector();
let start = std::time::Instant::now();
let _ranked = selector.rank_checkpoints(ModelType::DQN).await.unwrap();
let duration = start.elapsed();
// Target: <10ms per checkpoint (excluding backtest)
assert!(duration.as_millis() < 100);
}
#[tokio::test]
async fn benchmark_parallel_backtesting() {
let selector = create_test_selector_with_workers(4);
let start = std::time::Instant::now();
let _ranked = selector.rank_checkpoints_parallel(ModelType::DQN).await.unwrap();
let duration = start.elapsed();
// Target: 4x speedup with 4 workers
// (Actual backtest time depends on DBN data size)
}
```
---
## 8. Deployment Checklist
### Phase 1: Development (Week 1-2)
- [ ] Implement scoring functions (`checkpoint/selection.rs`)
- [ ] Add backtesting integration
- [ ] Write unit tests (100% coverage for scoring logic)
- [ ] Test with synthetic checkpoints
### Phase 2: Integration (Week 3)
- [ ] Integrate with checkpoint manager
- [ ] Add parallel backtesting support
- [ ] Implement caching layer
- [ ] Integration tests with real DBN data
### Phase 3: Production (Week 4)
- [ ] Add TLI commands (`tli checkpoint rank/select`)
- [ ] Implement re-evaluation scheduler
- [ ] Add ensemble composition logic
- [ ] Performance monitoring dashboard
### Phase 4: Validation (Week 5-6)
- [ ] Backtest ensemble on 90-day ES/NQ data
- [ ] Compare against individual models
- [ ] Measure Sharpe ratio improvement
- [ ] Validate trade frequency targets (2-5%)
### Phase 5: Deployment (Week 7)
- [ ] Deploy to production ML Training Service
- [ ] Enable weekly re-evaluation cron job
- [ ] Monitor ensemble health metrics
- [ ] A/B test against baseline strategy
---
## 9. Success Metrics
### Immediate (Week 1-4)
- ✅ Checkpoint ranking completes in <100ms per checkpoint
- ✅ Statistical significance filter removes <20% false positives
- ✅ Score function correlates >0.8 with manual expert ranking
### Short-term (Month 1-2)
- ✅ Ensemble Sharpe ratio >1.5 (vs. 1.0 baseline)
- ✅ Trade frequency in 2-5% range (vs. 0.01% baseline)
- ✅ Win rate >52% (statistically significant)
- ✅ Max drawdown <15%
### Long-term (Month 3-6)
- ✅ Ensemble outperforms best individual model by >20%
- ✅ Re-evaluation detects performance degradation <7 days
- ✅ Production uptime >99.5% (ensemble health monitoring)
- ✅ User adoption: 100% of ML training jobs use checkpoint selection
---
## 10. References
### Codebase Files
- `/home/jgrusewski/Work/foxhunt/ml/src/checkpoint/mod.rs` - Checkpoint infrastructure
- `/home/jgrusewski/Work/foxhunt/backtesting/src/metrics.rs` - Performance analytics (1664 lines)
- `/home/jgrusewski/Work/foxhunt/ml/src/ensemble/model.rs` - Ensemble management
- `/home/jgrusewski/Work/foxhunt/ml/src/trainers/dqn.rs` - DQN training pipeline
### Related Documentation
- `CLAUDE.md` - System architecture (ML training status)
- `GPU_TRAINING_BENCHMARK.md` - Wave 152 benchmark system
- `ML_TRAINING_ROADMAP.md` - 4-6 week training plan
- `TESTING_PLAN.md` - ML testing strategy
### Academic References
- Sharpe Ratio: "The Sharpe Ratio" (Sharpe, 1994)
- Ensemble Methods: "Ensemble Machine Learning" (Zhang & Ma, 2012)
- Quantitative Trading: "Advances in Financial Machine Learning" (López de Prado, 2018)
---
**Status**: Design Complete - Ready for Implementation
**Next Action**: Implement `ml/src/checkpoint/selection.rs` (Phase 1)
**Estimated Effort**: 7 weeks (1 developer, full-time)
**Priority**: HIGH (blocks production ML deployment)