//! ML Model Integration and Accuracy Validation Tests //! //! Comprehensive test suite for all ML models in the Foxhunt HFT system. //! Tests model loading, inference accuracy, performance, and integration. use std::time::{Duration, Instant}; use tokio::time::timeout; // Note: ML models not yet available - commenting out for compilation // use ml::{ // MLModel, ModelRegistry, TLOBTransformer, MAMBAModel, // LiquidNeuralNetwork, TemporalFusionTransformer, DQNAgent, PPOAgent // }; // use risk::{RiskEngine, PositionTracker}; // Mock ML types for testing pub struct MockMLModel; pub struct MockModelRegistry; pub struct MockTLOBTransformer; pub struct MockMAMBAModel; pub struct MockLiquidNeuralNetwork; pub struct MockTemporalFusionTransformer; pub struct MockDQNAgent; pub struct MockPPOAgent; pub struct MockRiskEngine; pub struct MockPositionTracker; // Simple test configuration for this file #[derive(Debug, Clone)] struct UnifiedTestConfig { initial_capital: common::prelude::Decimal, enable_logging: bool, } fn create_test_config() -> UnifiedTestConfig { UnifiedTestConfig { initial_capital: common::prelude::Decimal::from(100000), enable_logging: false, } } /// Configuration for ML model testing #[derive(Debug, Clone)] pub struct MLTestConfig { pub inference_timeout: Duration, pub accuracy_threshold: f64, pub max_inference_latency: Duration, pub model_warmup_iterations: usize, pub test_batch_size: usize, pub performance_iterations: usize, } impl Default for MLTestConfig { fn default() -> Self { Self { inference_timeout: Duration::from_millis(100), accuracy_threshold: 0.75, // 75% accuracy minimum max_inference_latency: Duration::from_micros(50), model_warmup_iterations: 10, test_batch_size: 100, performance_iterations: 1000, } } } /// ML model accuracy measurement #[derive(Debug, Clone)] pub struct AccuracyMeasurement { pub model_name: String, pub correct_predictions: usize, pub total_predictions: usize, pub accuracy_percentage: f64, pub precision: f64, pub recall: f64, pub f1_score: f64, } impl AccuracyMeasurement { pub fn new(model_name: String) -> Self { Self { model_name, correct_predictions: 0, total_predictions: 0, accuracy_percentage: 0.0, precision: 0.0, recall: 0.0, f1_score: 0.0, } } pub fn calculate_metrics(&mut self, true_positives: usize, false_positives: usize, false_negatives: usize) { self.accuracy_percentage = if self.total_predictions > 0 { (self.correct_predictions as f64 / self.total_predictions as f64) * 100.0 } else { 0.0 }; self.precision = if true_positives + false_positives > 0 { true_positives as f64 / (true_positives + false_positives) as f64 } else { 0.0 }; self.recall = if true_positives + false_negatives > 0 { true_positives as f64 / (true_positives + false_negatives) as f64 } else { 0.0 }; self.f1_score = if self.precision + self.recall > 0.0 { 2.0 * (self.precision * self.recall) / (self.precision + self.recall) } else { 0.0 }; } } /// ML inference performance measurement #[derive(Debug, Clone)] pub struct InferencePerformance { pub model_name: String, pub avg_inference_time: Duration, pub p50_latency: Duration, pub p95_latency: Duration, pub p99_latency: Duration, pub throughput_per_second: f64, pub memory_usage_mb: f64, } /// ML model test suite pub struct MLModelTestSuite { config: MLTestConfig, model_registry: ModelRegistry, test_data: Vec, expected_signals: Vec, } impl MLModelTestSuite { pub async fn new(config: MLTestConfig) -> Result> { let model_registry = ModelRegistry::new().await?; let (test_data, expected_signals) = Self::generate_test_dataset(config.test_batch_size).await?; Ok(Self { config, model_registry, test_data, expected_signals, }) } async fn generate_test_dataset( batch_size: usize ) -> Result<(Vec, Vec), Box> { let mut test_data = Vec::with_capacity(batch_size); let mut expected_signals = Vec::with_capacity(batch_size); for i in 0..batch_size { let timestamp = std::time::SystemTime::now(); let price = Price::new(100 + (i as f64 * 0.01))?; let volume = Quantity::new(1000 + i as i64)?; let snapshot = MarketSnapshot { symbol: Symbol::new("EURUSD")?, timestamp, bid: price, ask: price + Price::new(0.0001)?, volume, spread: Price::new(0.0001)?, }; // Generate expected signal based on simple pattern let signal_strength = if i % 4 == 0 { 0.8 } else { 0.3 }; let direction = if i % 2 == 0 { SignalDirection::Buy } else { SignalDirection::Sell }; let signal = TradingSignal { symbol: snapshot.symbol.clone(), direction, strength: signal_strength, timestamp, confidence: 0.75, source: "test_data".to_string(), }; test_data.push(snapshot); expected_signals.push(signal); } Ok((test_data, expected_signals)) } pub async fn test_model_accuracy( &self, model: &T, model_name: &str ) -> Result> { let mut measurement = AccuracyMeasurement::new(model_name.to_string()); let mut true_positives = 0; let mut false_positives = 0; let mut false_negatives = 0; for (i, (snapshot, expected)) in self.test_data.into_iter().zip(self.expected_signals.into_iter()).enumerate() { let inference_future = model.predict(snapshot); let result = timeout(self.config.inference_timeout, inference_future).await; match result { Ok(Ok(prediction)) => { measurement.total_predictions += 1; // Compare prediction with expected signal let prediction_correct = self.evaluate_prediction(&prediction, expected); if prediction_correct { measurement.correct_predictions += 1; true_positives += 1; } else { if prediction.direction == SignalDirection::Buy || prediction.direction == SignalDirection::Sell { false_positives += 1; } else { false_negatives += 1; } } } Ok(Err(e)) => { eprintln!("Model prediction error for sample {}: {}", i, e); false_negatives += 1; } Err(_) => { eprintln!("Model prediction timeout for sample {}", i); false_negatives += 1; } } } measurement.calculate_metrics(true_positives, false_positives, false_negatives); Ok(measurement) } fn evaluate_prediction(&self, prediction: &TradingSignal, expected: &TradingSignal) -> bool { // Check if direction matches and confidence is reasonable prediction.direction == expected.direction && prediction.confidence >= 0.5 && (prediction.strength - expected.strength).abs() <= 0.3 } pub async fn test_inference_performance( &self, model: &T, model_name: &str ) -> Result> { // Warmup for _ in 0..self.config.model_warmup_iterations { if let Some(sample) = self.test_data.first() { let _ = model.predict(sample).await; } } let mut latencies = Vec::with_capacity(self.config.performance_iterations); let start_time = Instant::now(); let mut successful_inferences = 0; for i in 0..self.config.performance_iterations { let sample_idx = i % self.test_data.len(); let sample = &self.test_data[sample_idx]; let inference_start = Instant::now(); let result = model.predict(sample).await; let inference_duration = inference_start.elapsed(); if result.is_ok() { latencies.push(inference_duration); successful_inferences += 1; } // Validate latency requirement assert!( inference_duration <= self.config.max_inference_latency, "Model {} inference latency {}μs exceeds maximum {}μs", model_name, inference_duration.as_micros(), self.config.max_inference_latency.as_micros() ); } let total_duration = start_time.elapsed(); latencies.sort(); let avg_inference_time = if !latencies.is_empty() { latencies.iter().sum::() / latencies.len() as u32 } else { Duration::ZERO }; let p50_latency = latencies.get(latencies.len() * 50 / 100).copied().unwrap_or(Duration::ZERO); let p95_latency = latencies.get(latencies.len() * 95 / 100).copied().unwrap_or(Duration::ZERO); let p99_latency = latencies.get(latencies.len() * 99 / 100).copied().unwrap_or(Duration::ZERO); let throughput_per_second = if total_duration.as_secs_f64() > 0.0 { successful_inferences as f64 / total_duration.as_secs_f64() } else { 0.0 }; Ok(InferencePerformance { model_name: model_name.to_string(), avg_inference_time, p50_latency, p95_latency, p99_latency, throughput_per_second, memory_usage_mb: Self::estimate_memory_usage(), }) } fn estimate_memory_usage() -> f64 { // Simplified memory usage estimation // In real implementation, this would use system monitoring 64.0 // MB estimate } } #[tokio::test] async fn test_tlob_transformer_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let tlob_model = TLOBTransformer::new().await?; // Test accuracy let accuracy = test_suite.test_model_accuracy(&tlob_model, "TLOB").await?; assert!( accuracy.accuracy_percentage >= config.accuracy_threshold * 100.0, "TLOB accuracy {}% below threshold {}%", accuracy.accuracy_percentage, config.accuracy_threshold * 100.0 ); // Test performance let performance = test_suite.test_inference_performance(&tlob_model, "TLOB").await?; assert!( performance.avg_inference_time <= config.max_inference_latency, "TLOB average inference time {}μs exceeds limit {}μs", performance.avg_inference_time.as_micros(), config.max_inference_latency.as_micros() ); println!("✅ TLOB Transformer: {:.2}% accuracy, {:.2}μs avg latency", accuracy.accuracy_percentage, performance.avg_inference_time.as_micros()); Ok(()) } #[tokio::test] async fn test_mamba_model_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let mamba_model = MAMBAModel::new().await?; // Test accuracy let accuracy = test_suite.test_model_accuracy(&mamba_model, "MAMBA").await?; assert!( accuracy.accuracy_percentage >= config.accuracy_threshold * 100.0, "MAMBA accuracy {}% below threshold {}%", accuracy.accuracy_percentage, config.accuracy_threshold * 100.0 ); // Test performance let performance = test_suite.test_inference_performance(&mamba_model, "MAMBA").await?; assert!( performance.throughput_per_second >= 100.0, "MAMBA throughput {:.2} inferences/sec too low", performance.throughput_per_second ); println!("✅ MAMBA Model: {:.2}% accuracy, {:.2} inferences/sec", accuracy.accuracy_percentage, performance.throughput_per_second); Ok(()) } #[tokio::test] async fn test_liquid_neural_network_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let liquid_model = LiquidNeuralNetwork::new().await?; // Test accuracy and adaptability let accuracy = test_suite.test_model_accuracy(&liquid_model, "Liquid").await?; assert!( accuracy.f1_score >= 0.7, "Liquid Neural Network F1 score {:.3} below threshold 0.7", accuracy.f1_score ); // Test performance let performance = test_suite.test_inference_performance(&liquid_model, "Liquid").await?; assert!( performance.p99_latency <= Duration::from_micros(100), "Liquid NN P99 latency {}μs exceeds 100μs", performance.p99_latency.as_micros() ); println!("✅ Liquid Neural Network: F1={:.3}, P99={}μs", accuracy.f1_score, performance.p99_latency.as_micros()); Ok(()) } #[tokio::test] async fn test_temporal_fusion_transformer_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let tft_model = TemporalFusionTransformer::new().await?; // Test accuracy let accuracy = test_suite.test_model_accuracy(&tft_model, "TFT").await?; assert!( accuracy.precision >= 0.75 && accuracy.recall >= 0.75, "TFT precision={:.3} or recall={:.3} below 0.75", accuracy.precision, accuracy.recall ); // Test performance let performance = test_suite.test_inference_performance(&tft_model, "TFT").await?; assert!( performance.memory_usage_mb <= 128.0, "TFT memory usage {:.1}MB exceeds 128MB limit", performance.memory_usage_mb ); println!("✅ Temporal Fusion Transformer: P={:.3}, R={:.3}, Mem={:.1}MB", accuracy.precision, accuracy.recall, performance.memory_usage_mb); Ok(()) } #[tokio::test] async fn test_dqn_agent_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let dqn_agent = DQNAgent::new().await?; // Test decision-making accuracy let accuracy = test_suite.test_model_accuracy(&dqn_agent, "DQN").await?; assert!( accuracy.accuracy_percentage >= 70.0, "DQN agent accuracy {}% below 70%", accuracy.accuracy_percentage ); // Test performance and action selection speed let performance = test_suite.test_inference_performance(&dqn_agent, "DQN").await?; assert!( performance.avg_inference_time <= Duration::from_micros(25), "DQN action selection {}μs exceeds 25μs limit", performance.avg_inference_time.as_micros() ); println!("✅ DQN Agent: {:.2}% accuracy, {}μs action selection", accuracy.accuracy_percentage, performance.avg_inference_time.as_micros()); Ok(()) } #[tokio::test] async fn test_ppo_agent_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let ppo_agent = PPOAgent::new().await?; // Test policy optimization let accuracy = test_suite.test_model_accuracy(&ppo_agent, "PPO").await?; assert!( accuracy.accuracy_percentage >= 75.0, "PPO agent accuracy {}% below 75%", accuracy.accuracy_percentage ); // Test performance let performance = test_suite.test_inference_performance(&ppo_agent, "PPO").await?; assert!( performance.throughput_per_second >= 50.0, "PPO throughput {:.2} decisions/sec too low", performance.throughput_per_second ); println!("✅ PPO Agent: {:.2}% accuracy, {:.2} decisions/sec", accuracy.accuracy_percentage, performance.throughput_per_second); Ok(()) } #[tokio::test] async fn test_model_registry_integration() -> Result<(), Box> { let config = MLTestConfig::default(); let mut registry = ModelRegistry::new().await?; // Test model registration and loading let model_names = vec!["TLOB", "MAMBA", "Liquid", "TFT", "DQN", "PPO"]; for model_name in &model_names { let model_exists = registry.has_model(model_name).await?; assert!(model_exists, "Model {} not found in registry", model_name); let model = registry.get_model(model_name).await?; assert!(model.is_some(), "Failed to load model {}", model_name); } // Test model ensemble prediction let test_suite = MLModelTestSuite::new(config.clone()).await?; if let Some(sample) = test_suite.test_data.first() { let ensemble_prediction = registry.predict_ensemble(sample).await?; assert!( ensemble_prediction.confidence >= 0.5, "Ensemble prediction confidence {:.3} too low", ensemble_prediction.confidence ); } println!("✅ Model Registry: {} models loaded, ensemble prediction working", model_names.len()); Ok(()) } #[tokio::test] async fn test_ml_risk_integration() -> Result<(), Box> { let config = create_test_config(); let ml_config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(ml_config.clone()).await?; // Create integrated ML-Risk system let mut risk_engine = RiskEngine::new(config.risk.clone()).await?; let tlob_model = TLOBTransformer::new().await?; // Test ML signal integration with risk management if let Some(sample) = test_suite.test_data.first() { let ml_signal = tlob_model.predict(sample).await?; // Validate ML signal passes risk checks let risk_result = risk_engine.validate_signal(&ml_signal).await?; assert!(risk_result.is_valid, "ML signal failed risk validation: {:?}", risk_result.reason); // Test signal strength adjustment based on risk let adjusted_signal = risk_engine.adjust_signal_strength(&ml_signal).await?; assert!( adjusted_signal.strength <= ml_signal.strength, "Risk adjustment should not increase signal strength" ); } println!("✅ ML-Risk Integration: Signal validation and adjustment working"); Ok(()) } #[tokio::test] async fn test_concurrent_ml_inference() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let tlob_model = std::sync::Arc::new(TLOBTransformer::new().await?); let mamba_model = std::sync::Arc::new(MAMBAModel::new().await?); // Test concurrent inference from multiple models let mut tasks = vec![]; let num_concurrent = 10; for i in 0..num_concurrent { let tlob = tlob_model.clone(); let mamba = mamba_model.clone(); let sample = test_suite.test_data[i % test_suite.test_data.len()].clone(); tasks.push(tokio::spawn(async move { let start = Instant::now(); let (tlob_result, mamba_result) = tokio::join!( tlob.predict(&sample), mamba.predict(&sample) ); let duration = start.elapsed(); (tlob_result, mamba_result, duration) })); } // Wait for all concurrent inferences let results = futures::future::join_all(tasks).await; for (i, result) in results.into_iter().enumerate() { let (tlob_result, mamba_result, duration) = result?; assert!(tlob_result.is_ok(), "TLOB concurrent inference {} failed", i); assert!(mamba_result.is_ok(), "MAMBA concurrent inference {} failed", i); assert!( duration <= Duration::from_millis(100), "Concurrent inference {} took {}ms", i, duration.as_millis() ); } println!("✅ Concurrent ML Inference: {} parallel inferences completed", num_concurrent); Ok(()) } #[tokio::test] async fn test_ml_model_memory_safety() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; // Test memory allocation patterns let tlob_model = TLOBTransformer::new().await?; // Run many inferences to test memory stability let iterations = 1000; let initial_memory = Self::estimate_memory_usage(); for i in 0..iterations { let sample_idx = i % test_suite.test_data.len(); let sample = &test_suite.test_data[sample_idx]; let result = tlob_model.predict(sample).await; assert!(result.is_ok(), "Memory safety test failed at iteration {}", i); // Check for memory leaks every 100 iterations if i % 100 == 0 { let current_memory = Self::estimate_memory_usage(); assert!( current_memory <= initial_memory * 1.5, "Potential memory leak detected: {}MB -> {}MB at iteration {}", initial_memory, current_memory, i ); } } println!("✅ ML Memory Safety: {} inferences completed without memory issues", iterations); Ok(()) } #[tokio::test] async fn test_comprehensive_ml_validation() -> Result<(), Box> { let config = MLTestConfig::default(); let test_suite = MLModelTestSuite::new(config.clone()).await?; let models = vec![ ("TLOB", Box::new(TLOBTransformer::new().await?) as Box), ("MAMBA", Box::new(MAMBAModel::new().await?) as Box), ("Liquid", Box::new(LiquidNeuralNetwork::new().await?) as Box), ]; let mut total_accuracy = 0.0; let mut total_throughput = 0.0; for (name, model) in &models { // Test accuracy let accuracy = test_suite.test_model_accuracy(model.as_ref(), name).await?; assert!( accuracy.accuracy_percentage >= config.accuracy_threshold * 100.0, "{} accuracy {}% below threshold", name, accuracy.accuracy_percentage ); // Test performance let performance = test_suite.test_inference_performance(model.as_ref(), name).await?; assert!( performance.avg_inference_time <= config.max_inference_latency, "{} latency {}μs exceeds limit", name, performance.avg_inference_time.as_micros() ); total_accuracy += accuracy.accuracy_percentage; total_throughput += performance.throughput_per_second; println!("✅ {}: {:.2}% accuracy, {:.2} inf/sec, {}μs avg", name, accuracy.accuracy_percentage, performance.throughput_per_second, performance.avg_inference_time.as_micros()); } let avg_accuracy = total_accuracy / models.len() as f64; let total_system_throughput = total_throughput; assert!( avg_accuracy >= 75.0, "Overall ML system accuracy {:.2}% below 75%", avg_accuracy ); assert!( total_system_throughput >= 200.0, "Total ML system throughput {:.2} inf/sec below 200", total_system_throughput ); println!("🎯 COMPREHENSIVE ML VALIDATION PASSED"); println!(" Average Accuracy: {:.2}%", avg_accuracy); println!(" Total Throughput: {:.2} inferences/sec", total_system_throughput); println!(" All {} models meet production requirements", models.len()); Ok(()) }