//! Backtesting Flow Integration Tests //! //! Comprehensive integration tests for TLI Client ↔ Backtesting Service communication. //! Tests historical data replay, strategy execution, performance analytics, and //! ML model integration with real-time monitoring. use std::collections::HashMap; use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; use std::time::{Duration, Instant}; use tokio::sync::{mpsc, RwLock}; use tokio::time::timeout; use uuid::Uuid; use serde_json::json; use chrono::{DateTime, Utc, Duration as ChronoDuration}; use core::types::prelude::*; use tli::prelude::*; use backtesting::*; use ml::*; use crate::fixtures::*; use crate::mocks::*; /// Backtesting flow integration test suite pub struct BacktestingFlowTests { /// TLI client suite for testing client_suite: TliClientSuite, /// Mock backtesting service mock_backtesting_service: MockBacktestingService, /// Historical data provider test_data_provider: TestDataProvider, /// ML model testing infrastructure ml_test_infrastructure: MLTestInfrastructure, /// Performance metrics collector metrics: Arc, /// Test configuration config: IntegrationTestConfig, } /// Performance metrics for backtesting operations #[derive(Debug, Default)] pub struct BacktestingMetrics { /// Backtest initialization latency (nanoseconds) pub initialization_latencies: RwLock>, /// Data processing throughput (events per second) pub data_processing_throughput: RwLock>, /// ML inference latencies (nanoseconds) pub ml_inference_latencies: RwLock>, /// Strategy execution latencies (nanoseconds) pub strategy_execution_latencies: RwLock>, /// Memory usage during backtesting (MB) pub memory_usage_samples: RwLock>, /// Total events processed pub total_events_processed: AtomicU64, /// Error counter pub error_count: AtomicU64, } impl BacktestingFlowTests { /// Create new backtesting flow test suite pub async fn new(config: IntegrationTestConfig) -> TliResult { // Initialize test data provider with historical market data let test_data_provider = TestDataProvider::new().await?; // Initialize ML testing infrastructure let ml_test_infrastructure = MLTestInfrastructure::new().await?; // Initialize mock backtesting service let mock_backtesting_service = MockBacktestingService::new().await?; // Create TLI client suite with backtesting endpoints let client_suite = TliClientBuilder::new() .with_service_endpoint( "backtesting_service".to_string(), format!("http://localhost:{}", mock_backtesting_service.port()) ) .with_backtesting_config(BacktestingClientConfig { timeout_ms: config.request_timeout_ms, max_retry_attempts: config.max_retry_attempts, stream_buffer_size: config.stream_buffer_size, enable_real_time_monitoring: true, ..Default::default() }) .build() .await?; Ok(Self { client_suite, mock_backtesting_service, test_data_provider, ml_test_infrastructure, metrics: Arc::new(BacktestingMetrics::default()), config, }) } /// Test basic backtest initialization and execution pub async fn test_basic_backtest_execution(&self) -> TliResult { let mut test_result = TestResult::new("basic_backtest_execution"); let start_time = Instant::now(); // Create backtest configuration let backtest_config = CreateBacktestRequest { name: "Integration Test Basic Strategy".to_string(), strategy_type: "mean_reversion".to_string(), symbol: "AAPL".to_string(), start_date: (Utc::now() - ChronoDuration::days(30)).timestamp(), end_date: Utc::now().timestamp(), initial_capital: 100_000.0, parameters: json!({ "lookback_period": 20, "entry_threshold": 2.0, "exit_threshold": 0.5, "position_size": 0.02 }), enable_real_time_monitoring: true, }; // Configure mock response with realistic backtest results self.mock_backtesting_service.configure_backtest_response( &backtest_config.name, BacktestResult { backtest_id: format!("bt_{}", Uuid::new_v4()), strategy_name: backtest_config.strategy_type.clone(), total_return: 0.0847, // 8.47% return annualized_return: 0.1124, // 11.24% annualized max_drawdown: -0.0423, // -4.23% max drawdown sharpe_ratio: 1.45, total_trades: 127, win_rate: 0.61, avg_trade_return: 0.0012, final_value: 108_470.0, execution_time_ms: 2340, events_processed: 876_543, } ).await; // Measure initialization latency let init_start = Instant::now(); let create_response = match self.client_suite.backtesting_client { Some(ref client) => { timeout( Duration::from_millis(self.config.request_timeout_ms), client.create_backtest(backtest_config.clone()) ).await } None => { test_result.add_error("Backtesting client not available".to_string()); return Ok(test_result); } }; let init_latency = init_start.elapsed().as_nanos() as u64; self.metrics.initialization_latencies.write().await.push(init_latency); // Validate backtest creation let backtest_id = match create_response { Ok(Ok(resp)) => { test_result.add_assertion("Backtest created successfully", resp.success); test_result.add_assertion("Backtest ID provided", !resp.backtest_id.is_empty()); test_result.add_assertion( "Initialization latency acceptable", init_latency < self.config.max_init_latency_ns ); resp.backtest_id } Ok(Err(e)) => { test_result.add_error(format!("Backtest creation failed: {:?}", e)); return Ok(test_result); } Err(_) => { test_result.add_error("Backtest creation timeout".to_string()); return Ok(test_result); } }; // Start backtest execution if let Some(ref client) = self.client_suite.backtesting_client { let start_response = client.start_backtest(StartBacktestRequest { backtest_id: backtest_id.clone(), enable_monitoring: true, }).await?; test_result.add_assertion("Backtest started successfully", start_response.success); // Monitor backtest progress let mut progress_updates = 0; let monitor_duration = Duration::from_secs(10); let monitor_start = Instant::now(); let progress_stream = client.subscribe_to_backtest_progress(&backtest_id).await?; while monitor_start.elapsed() < monitor_duration { if let Ok(update) = timeout(Duration::from_secs(1), progress_stream.recv()).await { if let Some(progress) = update { progress_updates += 1; // Record performance metrics if let Some(throughput) = progress.events_per_second { self.metrics.data_processing_throughput.write().await.push(throughput); } if let Some(memory_mb) = progress.memory_usage_mb { self.metrics.memory_usage_samples.write().await.push(memory_mb); } // Check if backtest completed if progress.status == BacktestStatus::Completed { break; } } } } test_result.add_assertion("Received progress updates", progress_updates > 0); // Get final results let results_response = client.get_backtest_results(&backtest_id).await?; if let Some(results) = results_response.results { test_result.add_assertion("Results contain performance metrics", results.sharpe_ratio > 0.0); test_result.add_assertion("Total trades executed", results.total_trades > 0); test_result.add_assertion("Events processed", results.events_processed > 0); test_result.metadata.insert("final_return".to_string(), json!(results.total_return)); test_result.metadata.insert("sharpe_ratio".to_string(), json!(results.sharpe_ratio)); test_result.metadata.insert("total_trades".to_string(), json!(results.total_trades)); self.metrics.total_events_processed.store(results.events_processed, Ordering::Relaxed); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test ML-integrated backtesting with multiple models pub async fn test_ml_integrated_backtesting(&self) -> TliResult { let mut test_result = TestResult::new("ml_integrated_backtesting"); let start_time = Instant::now(); // Test multiple ML models let test_models = vec!["TLOB", "MAMBA", "TFT", "DQN"]; for model_name in test_models { let backtest_config = CreateBacktestRequest { name: format!("ML Integration Test - {}", model_name), strategy_type: "adaptive_ml".to_string(), symbol: "AAPL".to_string(), start_date: (Utc::now() - ChronoDuration::days(7)).timestamp(), end_date: Utc::now().timestamp(), initial_capital: 50_000.0, parameters: json!({ "model_type": model_name, "inference_interval_ms": 100, "retraining_interval_hours": 4, "feature_window": 50, "confidence_threshold": 0.7 }), enable_real_time_monitoring: true, }; // Configure model-specific responses let expected_performance = match model_name { "TLOB" => (0.0623, 1.34, 89), // return, sharpe, trades "MAMBA" => (0.0791, 1.52, 76), "TFT" => (0.0534, 1.21, 102), "DQN" => (0.0445, 1.08, 134), _ => (0.05, 1.0, 100), }; self.mock_backtesting_service.configure_ml_backtest_response( &backtest_config.name, model_name, expected_performance.0, expected_performance.1, expected_performance.2, ).await; // Execute ML backtest if let Some(ref client) = self.client_suite.backtesting_client { let ml_start = Instant::now(); let create_response = client.create_backtest(backtest_config.clone()).await?; test_result.add_assertion( &format!("{} backtest created", model_name), create_response.success ); if create_response.success { let start_response = client.start_backtest(StartBacktestRequest { backtest_id: create_response.backtest_id.clone(), enable_monitoring: true, }).await?; test_result.add_assertion( &format!("{} backtest started", model_name), start_response.success ); // Monitor ML inference performance let inference_stream = client.subscribe_to_ml_metrics(&create_response.backtest_id).await?; let mut inference_samples = 0; // Collect inference metrics for 5 seconds let inference_start = Instant::now(); while inference_start.elapsed() < Duration::from_secs(5) && inference_samples < 10 { if let Ok(Some(metrics)) = timeout(Duration::from_millis(500), inference_stream.recv()).await { inference_samples += 1; if let Some(inference_latency) = metrics.inference_latency_ns { self.metrics.ml_inference_latencies.write().await.push(inference_latency); test_result.add_assertion( &format!("{} inference latency acceptable", model_name), inference_latency < self.config.max_ml_inference_latency_ns ); } } } test_result.add_assertion( &format!("{} inference samples collected", model_name), inference_samples > 0 ); } let ml_latency = ml_start.elapsed().as_nanos() as u64; test_result.metadata.insert( format!("{}_execution_latency_ns", model_name), json!(ml_latency) ); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test ensemble backtesting with model comparison pub async fn test_ensemble_backtesting(&self) -> TliResult { let mut test_result = TestResult::new("ensemble_backtesting"); let start_time = Instant::now(); let ensemble_config = CreateBacktestRequest { name: "Ensemble Strategy Integration Test".to_string(), strategy_type: "ensemble_ml".to_string(), symbol: "AAPL".to_string(), start_date: (Utc::now() - ChronoDuration::days(14)).timestamp(), end_date: Utc::now().timestamp(), initial_capital: 100_000.0, parameters: json!({ "models": ["TLOB", "MAMBA", "TFT"], "ensemble_method": "weighted_average", "model_weights": [0.4, 0.35, 0.25], "rebalance_interval_hours": 6, "confidence_threshold": 0.65 }), enable_real_time_monitoring: true, }; // Configure ensemble response with expected improved performance self.mock_backtesting_service.configure_ensemble_response( &ensemble_config.name, EnsembleResults { ensemble_return: 0.0934, // Better than individual models ensemble_sharpe: 1.67, // Higher Sharpe ratio individual_returns: vec![0.0623, 0.0791, 0.0534], individual_sharpes: vec![1.34, 1.52, 1.21], diversification_benefit: 0.0143, model_weights_final: vec![0.42, 0.38, 0.20], rebalance_count: 28, } ).await; if let Some(ref client) = self.client_suite.backtesting_client { let ensemble_start = Instant::now(); // Create ensemble backtest let create_response = client.create_backtest(ensemble_config.clone()).await?; test_result.add_assertion("Ensemble backtest created", create_response.success); if create_response.success { // Start ensemble execution let start_response = client.start_backtest(StartBacktestRequest { backtest_id: create_response.backtest_id.clone(), enable_monitoring: true, }).await?; test_result.add_assertion("Ensemble backtest started", start_response.success); // Monitor ensemble metrics let ensemble_stream = client.subscribe_to_ensemble_metrics(&create_response.backtest_id).await?; let mut ensemble_updates = 0; let mut weight_updates = Vec::new(); // Collect ensemble metrics let monitor_start = Instant::now(); while monitor_start.elapsed() < Duration::from_secs(8) && ensemble_updates < 15 { if let Ok(Some(metrics)) = timeout(Duration::from_millis(500), ensemble_stream.recv()).await { ensemble_updates += 1; if let Some(weights) = metrics.current_weights { weight_updates.push(weights); } if let Some(diversification) = metrics.diversification_benefit { test_result.add_assertion( "Positive diversification benefit", diversification > 0.0 ); } } } test_result.add_assertion("Ensemble updates received", ensemble_updates > 0); test_result.add_assertion("Weight rebalancing observed", weight_updates.len() > 1); // Get final ensemble results let results = client.get_ensemble_comparison(&create_response.backtest_id).await?; if let Some(comparison) = results.comparison { test_result.add_assertion( "Ensemble outperformed individual models", comparison.ensemble_improvement > 0.0 ); test_result.metadata.insert("ensemble_improvement".to_string(), json!(comparison.ensemble_improvement)); test_result.metadata.insert("diversification_benefit".to_string(), json!(comparison.diversification_benefit)); } } let ensemble_latency = ensemble_start.elapsed().as_nanos() as u64; test_result.metadata.insert("ensemble_execution_latency_ns".to_string(), json!(ensemble_latency)); } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test parallel backtesting execution for throughput validation pub async fn test_parallel_backtesting_throughput(&self) -> TliResult { let mut test_result = TestResult::new("parallel_backtesting_throughput"); let start_time = Instant::now(); let num_parallel_backtests = self.config.parallel_backtest_count; let mut handles = Vec::new(); // Create multiple backtests in parallel for i in 0..num_parallel_backtests { let client = match self.client_suite.backtesting_client { Some(ref c) => c.clone(), None => { test_result.add_error("Backtesting client not available".to_string()); return Ok(test_result); } }; let backtest_config = CreateBacktestRequest { name: format!("Parallel Backtest {}", i), strategy_type: "simple_ma".to_string(), symbol: if i % 2 == 0 { "AAPL" } else { "MSFT" }.to_string(), start_date: (Utc::now() - ChronoDuration::days(7)).timestamp(), end_date: Utc::now().timestamp(), initial_capital: 25_000.0, parameters: json!({ "fast_period": 10 + (i % 5), "slow_period": 20 + (i % 10), "position_size": 0.1 }), enable_real_time_monitoring: false, // Disable for throughput test }; let metrics = Arc::clone(&self.metrics); let handle = tokio::spawn(async move { let start = Instant::now(); // Create and execute backtest let create_result = client.create_backtest(backtest_config).await; let execution_latency = start.elapsed().as_nanos() as u64; match create_result { Ok(response) if response.success => { // Start the backtest let start_result = client.start_backtest(StartBacktestRequest { backtest_id: response.backtest_id.clone(), enable_monitoring: false, }).await; match start_result { Ok(start_resp) if start_resp.success => { metrics.initialization_latencies.write().await.push(execution_latency); true } _ => false } } _ => { metrics.error_count.fetch_add(1, Ordering::Relaxed); false } } }); handles.push(handle); } // Wait for all backtests to complete let mut successful_backtests = 0; for handle in handles { if let Ok(success) = handle.await { if success { successful_backtests += 1; } } } let total_time = start_time.elapsed(); let throughput = successful_backtests as f64 / total_time.as_secs_f64(); // Validate throughput requirements test_result.add_assertion( "Minimum successful backtests", successful_backtests >= (num_parallel_backtests * 8 / 10) // 80% success rate ); test_result.add_assertion( "Throughput meets requirements", throughput >= self.config.min_backtest_throughput ); test_result.metadata.insert("throughput_backtests_per_sec".to_string(), json!(throughput)); test_result.metadata.insert("successful_backtests".to_string(), json!(successful_backtests)); test_result.metadata.insert("total_backtests".to_string(), json!(num_parallel_backtests)); test_result.execution_time = total_time; test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test real-time backtest monitoring and control pub async fn test_real_time_monitoring_and_control(&self) -> TliResult { let mut test_result = TestResult::new("real_time_monitoring_control"); let start_time = Instant::now(); let backtest_config = CreateBacktestRequest { name: "Real-time Monitoring Test".to_string(), strategy_type: "monitoring_test".to_string(), symbol: "AAPL".to_string(), start_date: (Utc::now() - ChronoDuration::days(30)).timestamp(), end_date: Utc::now().timestamp(), initial_capital: 100_000.0, parameters: json!({ "slow_execution": true, // Force slow execution for control testing "report_interval_ms": 1000 }), enable_real_time_monitoring: true, }; if let Some(ref client) = self.client_suite.backtesting_client { // Create backtest let create_response = client.create_backtest(backtest_config.clone()).await?; test_result.add_assertion("Monitoring test backtest created", create_response.success); if create_response.success { let backtest_id = create_response.backtest_id; // Start backtest let start_response = client.start_backtest(StartBacktestRequest { backtest_id: backtest_id.clone(), enable_monitoring: true, }).await?; test_result.add_assertion("Monitoring test started", start_response.success); // Monitor for 3 seconds, then pause let progress_stream = client.subscribe_to_backtest_progress(&backtest_id).await?; let mut progress_count = 0; let monitor_start = Instant::now(); while monitor_start.elapsed() < Duration::from_secs(3) { if let Ok(Some(_progress)) = timeout(Duration::from_millis(500), progress_stream.recv()).await { progress_count += 1; } } test_result.add_assertion("Progress updates received", progress_count > 0); // Test pause functionality let pause_response = client.pause_backtest(&backtest_id).await?; test_result.add_assertion("Backtest paused successfully", pause_response.success); // Verify paused state tokio::time::sleep(Duration::from_millis(100)).await; let status = client.get_backtest_status(&backtest_id).await?; test_result.add_assertion("Status shows paused", status.status == BacktestStatus::Paused); // Test resume functionality let resume_response = client.resume_backtest(&backtest_id).await?; test_result.add_assertion("Backtest resumed successfully", resume_response.success); // Monitor resumed execution for 2 seconds let resume_start = Instant::now(); let mut resume_progress_count = 0; while resume_start.elapsed() < Duration::from_secs(2) { if let Ok(Some(_progress)) = timeout(Duration::from_millis(500), progress_stream.recv()).await { resume_progress_count += 1; } } test_result.add_assertion("Progress after resume", resume_progress_count > 0); // Test stop functionality let stop_response = client.stop_backtest(&backtest_id).await?; test_result.add_assertion("Backtest stopped successfully", stop_response.success); // Verify stopped state tokio::time::sleep(Duration::from_millis(100)).await; let final_status = client.get_backtest_status(&backtest_id).await?; test_result.add_assertion("Status shows stopped", final_status.status == BacktestStatus::Stopped || final_status.status == BacktestStatus::Completed); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Run complete backtesting flow test suite pub async fn run_complete_suite(&self) -> TliResult { let mut suite = TestSuite::new("backtesting_flow_integration"); let suite_start = Instant::now(); // Run all test cases let tests = vec![ self.test_basic_backtest_execution().await?, self.test_ml_integrated_backtesting().await?, self.test_ensemble_backtesting().await?, self.test_parallel_backtesting_throughput().await?, self.test_real_time_monitoring_and_control().await?, ]; for test in tests { suite.add_test_result(test); } // Generate performance summary let metrics = self.generate_performance_summary().await; suite.metadata.insert("backtesting_metrics".to_string(), json!(metrics)); suite.execution_time = suite_start.elapsed(); suite.set_passed(suite.passed_tests >= suite.total_tests * 80 / 100); // 80% pass rate Ok(suite) } /// Generate comprehensive performance summary async fn generate_performance_summary(&self) -> serde_json::Value { let init_latencies = self.metrics.initialization_latencies.read().await; let ml_latencies = self.metrics.ml_inference_latencies.read().await; let throughput_samples = self.metrics.data_processing_throughput.read().await; let memory_samples = self.metrics.memory_usage_samples.read().await; let init_stats = calculate_latency_stats(&init_latencies); let ml_stats = calculate_latency_stats(&ml_latencies); let avg_throughput = if !throughput_samples.is_empty() { throughput_samples.iter().sum::() / throughput_samples.len() as f64 } else { 0.0 }; let avg_memory = if !memory_samples.is_empty() { memory_samples.iter().sum::() / memory_samples.len() as u64 } else { 0 }; json!({ "initialization": { "count": init_latencies.len(), "avg_ns": init_stats.avg, "p95_ns": init_stats.p95, "p99_ns": init_stats.p99, "max_ns": init_stats.max }, "ml_inference": { "count": ml_latencies.len(), "avg_ns": ml_stats.avg, "p95_ns": ml_stats.p95, "p99_ns": ml_stats.p99, "max_ns": ml_stats.max }, "data_processing": { "avg_throughput_eps": avg_throughput, "samples": throughput_samples.len() }, "memory_usage": { "avg_mb": avg_memory, "samples": memory_samples.len() }, "total_events_processed": self.metrics.total_events_processed.load(Ordering::Relaxed), "error_count": self.metrics.error_count.load(Ordering::Relaxed) }) } } /// Calculate latency statistics from measurements fn calculate_latency_stats(latencies: &[u64]) -> LatencyStats { if latencies.is_empty() { return LatencyStats::default(); } let mut sorted = latencies.to_vec(); sorted.sort_unstable(); let len = sorted.len(); let avg = sorted.iter().sum::() / len as u64; let p95 = sorted[len * 95 / 100]; let p99 = sorted[len * 99 / 100]; let max = sorted[len - 1]; LatencyStats { avg, p95, p99, max } } /// Latency statistics structure #[derive(Debug, Default)] struct LatencyStats { avg: u64, p95: u64, p99: u64, max: u64, } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_backtesting_flow_integration() { let config = IntegrationTestConfig::default(); let tests = BacktestingFlowTests::new(config).await.unwrap(); let results = tests.run_complete_suite().await.unwrap(); println!("Backtesting Flow Integration Test Results:"); println!("Passed: {}/{}", results.passed_tests, results.total_tests); println!("Execution time: {:?}", results.execution_time); // Print performance metrics if let Some(metrics) = results.metadata.get("backtesting_metrics") { println!("Backtesting Metrics: {}", serde_json::to_string_pretty(metrics).unwrap()); } assert!(results.passed, "Backtesting flow integration tests should pass"); } }