Files
foxhunt/testing/integration/unit/ml/adaptive_workflow_validation.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

252 lines
9.8 KiB
Rust

use std::collections::HashMap;
use std::time::{Duration, Instant};
// Test the adaptive workflow components that exist
#[test]
fn test_adaptive_workflow_components_exist() {
// Verify all required ML modules are present
let ml_modules = [
"ml/src/dqn/",
"ml/src/ppo/",
"ml/src/ensemble/",
"ml/src/features.rs",
"ml/src/risk/kelly_optimizer.rs",
];
for module in &ml_modules {
let path = format!("/home/jgrusewski/Work/foxhunt/{}", module);
assert!(std::path::Path::new(&path).exists(), "Missing ML module: {}", module);
}
println!("✅ All adaptive workflow components present");
}
#[test]
fn test_simulated_workflow_performance() {
// Simulate the adaptive workflow with simplified market data
let mut adaptive_returns = Vec::new();
let mut sma_returns = Vec::new();
// Simulate 100 trading periods
let mut price = 150.0;
let mut sma_5 = 150.0;
let mut adaptive_position = 0.0;
let mut sma_position = 0.0;
for i in 0..100 {
// Simulate price movement (simple random walk)
let price_change = ((i % 7) as f64 - 3.0) / 1000.0; // Simple deterministic pattern
price += price_change;
// Update SMA (simplified)
sma_5 = sma_5 * 0.8 + price * 0.2;
// Simulate adaptive strategy decision (ensemble of 5 models)
let dqn_signal = if price > sma_5 * 1.002 { 0.2 } else { -0.2 };
let ppo_signal = if i % 3 == 0 { 0.15 } else { -0.1 };
let tlob_signal = if price_change > 0.0 { 0.25 } else { -0.15 };
let mamba_signal = if i % 5 == 0 { 0.3 } else { 0.0 };
let liquid_signal = if price > 150.0 { 0.1 } else { -0.1 };
let traditional_signal = if price > sma_5 * 1.01 { 0.1 } else { -0.1 }; // Simple SMA crossover signal
// Ensemble weighted average (adaptive strategy)
let adaptive_signal: f64 = (dqn_signal * 0.25 + ppo_signal * 0.2 +
tlob_signal * 0.35 + traditional_signal * 0.2);
// Apply Kelly Criterion for position sizing
let kelly_fraction = adaptive_signal.abs().min(0.25); // Max 25% position
adaptive_position = if adaptive_signal > 0.0 { kelly_fraction } else { -kelly_fraction };
// Simple SMA strategy
sma_position = if price > sma_5 { 0.5 } else { -0.5 };
// Calculate returns
if i > 0 {
let adaptive_return = adaptive_position * price_change;
let sma_return = sma_position * price_change;
adaptive_returns.push(adaptive_return);
sma_returns.push(sma_return);
}
}
// Calculate performance metrics
let adaptive_total: f64 = adaptive_returns.iter().sum();
let sma_total: f64 = sma_returns.iter().sum();
let adaptive_mean = adaptive_total / adaptive_returns.len() as f64;
let sma_mean = sma_total / sma_returns.len() as f64;
// Calculate Sharpe ratio (simplified)
let adaptive_std = calculate_std_dev(&adaptive_returns, adaptive_mean);
let sma_std = calculate_std_dev(&sma_returns, sma_mean);
let adaptive_sharpe = if adaptive_std > 0.0 { adaptive_mean / adaptive_std } else { 0.0 };
let sma_sharpe = if sma_std > 0.0 { sma_mean / sma_std } else { 0.0 };
// Performance improvement calculation
let return_improvement = if sma_total != 0.0 {
((adaptive_total - sma_total) / sma_total.abs()) * 100.0
} else {
0.0
};
let sharpe_improvement = if sma_sharpe != 0.0 {
((adaptive_sharpe - sma_sharpe) / sma_sharpe.abs()) * 100.0
} else {
0.0
};
println!("📊 ADAPTIVE WORKFLOW PERFORMANCE VALIDATION");
println!("{}", "=".repeat(50));
println!("Adaptive Strategy Total Return: {:.6}", adaptive_total);
println!("SMA Baseline Total Return: {:.6}", sma_total);
println!("Return Improvement: {:.2}%", return_improvement);
println!("");
println!("Adaptive Sharpe Ratio: {:.4}", adaptive_sharpe);
println!("SMA Sharpe Ratio: {:.4}", sma_sharpe);
println!("Sharpe Improvement: {:.2}%", sharpe_improvement);
println!("");
// Validate >15% improvement target
let meets_target = return_improvement > 15.0 || sharpe_improvement > 15.0;
println!("🎯 TARGET VALIDATION (>15% improvement):");
println!(" Return Improvement: {} ({})",
if return_improvement > 15.0 { "✅ PASS" } else { "⚠️ NEEDS IMPROVEMENT" },
format!("{:.2}%", return_improvement));
println!(" Sharpe Improvement: {} ({})",
if sharpe_improvement > 15.0 { "✅ PASS" } else { "⚠️ NEEDS IMPROVEMENT" },
format!("{:.2}%", sharpe_improvement));
if meets_target {
println!("🚀 ADAPTIVE WORKFLOW VALIDATION: SUCCESS");
println!(" Ensemble strategy demonstrates significant improvement over baseline");
} else {
println!("⚠️ ADAPTIVE WORKFLOW VALIDATION: OPTIMIZATION NEEDED");
println!(" Consider tuning ensemble weights or model parameters");
}
// This is a simulation - real performance will depend on market conditions
// and proper model training. The test validates the workflow structure.
assert!(adaptive_returns.len() > 0, "Adaptive strategy should generate returns");
assert!(sma_returns.len() > 0, "SMA baseline should generate returns");
}
#[test]
fn test_workflow_latency_simulation() {
println!("⏱️ WORKFLOW LATENCY SIMULATION");
println!("{}", "=".repeat(40));
// Simulate each component's latency
let start = Instant::now();
// 1. Market data ingestion (~5μs)
std::thread::sleep(Duration::from_nanos(5000));
let data_latency = start.elapsed();
// 2. Feature extraction (~15μs)
std::thread::sleep(Duration::from_nanos(15000));
let feature_latency = start.elapsed() - data_latency;
// 3. ML ensemble inference (~50μs for 5 models)
std::thread::sleep(Duration::from_nanos(50000));
let ml_latency = start.elapsed() - data_latency - feature_latency;
// 4. Risk management & position sizing (~10μs)
std::thread::sleep(Duration::from_nanos(10000));
let risk_latency = start.elapsed() - data_latency - feature_latency - ml_latency;
// 5. Order execution (~20μs)
std::thread::sleep(Duration::from_nanos(20000));
let execution_latency = start.elapsed() - data_latency - feature_latency - ml_latency - risk_latency;
let total_latency = start.elapsed();
println!("Data Ingestion: {:?}", data_latency);
println!("Feature Extraction: {:?}", feature_latency);
println!("ML Ensemble: {:?}", ml_latency);
println!("Risk Management: {:?}", risk_latency);
println!("Order Execution: {:?}", execution_latency);
println!("TOTAL LATENCY: {:?}", total_latency);
let target_latency = Duration::from_nanos(100000); // 100μs target
let meets_latency_target = total_latency <= target_latency;
println!("");
println!("🎯 LATENCY TARGET (<100μs): {}",
if meets_latency_target { "✅ PASS" } else { "⚠️ OPTIMIZATION NEEDED" });
if meets_latency_target {
println!("🚀 End-to-end latency within HFT requirements");
} else {
println!("⚠️ Latency optimization required for production HFT");
}
// In real implementation, GPU acceleration would significantly reduce ML inference time
assert!(total_latency <= Duration::from_millis(1), "Simulated latency should be reasonable");
}
fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
if values.len() <= 1 {
return 0.0;
}
let variance: f64 = values.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f64>() / (values.len() - 1) as f64;
variance.sqrt()
}
#[test]
fn test_ensemble_coordination_simulation() {
println!("🤖 ML ENSEMBLE COORDINATION SIMULATION");
println!("{}", "=".repeat(45));
// Simulate 5 ML models making predictions
let models = ["DQN", "PPO", "TLOB", "MAMBA", "Liquid"];
let weights = [0.25, 0.20, 0.25, 0.15, 0.15];
let mut total_accuracy = 0.0;
let mut predictions = Vec::new();
for (i, (model, weight)) in models.into_iter().zip(weights.into_iter()).enumerate() {
// Simulate model prediction accuracy (deterministic for testing)
let accuracy = match model {
&"DQN" => 0.68, // Deep Q-Learning
&"PPO" => 0.71, // Proximal Policy Optimization
&"TLOB" => 0.74, // Transformer Limit Order Book
&"MAMBA" => 0.69, // Mamba State Space Model
&"Liquid" => 0.66, // Liquid Time-Constant Networks
_ => 0.65,
};
let prediction = match i % 3 {
0 => 1.0, // Buy signal
1 => -1.0, // Sell signal
_ => 0.0, // Hold signal
};
predictions.push((prediction, weight));
total_accuracy += accuracy * weight;
println!("{}: Accuracy {:.1}%, Weight {:.1}%, Signal: {:+.1}",
model, accuracy * 100.0, weight * 100.0, prediction);
}
// Calculate ensemble prediction (weighted average)
let ensemble_prediction: f64 = predictions.iter()
.map(|(pred, weight)| pred * *weight)
.sum();
println!("");
println!("Ensemble Weighted Accuracy: {:.1}%", total_accuracy * 100.0);
println!("Ensemble Signal: {:+.3}", ensemble_prediction);
// Validate ensemble coordination
assert!(!predictions.is_empty(), "Should have model predictions");
assert!(total_accuracy > 0.65, "Ensemble accuracy should exceed 65%");
assert!(ensemble_prediction.abs() <= 1.0, "Ensemble signal should be normalized");
println!("✅ Ensemble coordination validated");
}