Files
foxhunt/tests/integration/backtesting_tests.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

593 lines
21 KiB
Rust

//! Comprehensive Backtesting Workflow Integration Tests
//!
//! This module provides complete end-to-end backtesting workflow testing covering:
//! - Backtesting engine initialization and configuration
//! - Historical data loading and validation
//! - Strategy execution and ML model integration
//! - Performance analytics and result storage
//! - Multi-timeframe and multi-strategy testing
//! - Resource management and memory efficiency
#![allow(unused_crate_dependencies)]
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use uuid::Uuid;
// use trading_engine::prelude::*; // REMOVED - prelude does not exist
// use risk::prelude::*; // REMOVED - prelude does not exist
use ml::prelude::*;
use data::prelude::*;
/// Comprehensive backtesting test suite
pub struct BacktestingTestSuite {
backtest_engine: Arc<BacktestEngine>,
data_manager: Arc<DataManager>,
strategy_manager: Arc<StrategyManager>,
performance_analyzer: Arc<PerformanceAnalyzer>,
test_config: BacktestingTestConfig,
}
/// Configuration for backtesting tests
#[derive(Debug, Clone)]
pub struct BacktestingTestConfig {
pub test_start_date: chrono::DateTime<chrono::Utc>,
pub test_end_date: chrono::DateTime<chrono::Utc>,
pub test_symbols: Vec<String>,
pub initial_capital: Decimal,
pub max_backtest_duration_seconds: u64,
pub min_trades_per_day: usize,
pub max_drawdown_percent: f64,
pub min_sharpe_ratio: f64,
}
impl Default for BacktestingTestConfig {
fn default() -> Self {
Self {
test_start_date: chrono::Utc::now() - chrono::Duration::days(30),
test_end_date: chrono::Utc::now() - chrono::Duration::days(1),
test_symbols: vec!["EURUSD".to_string(), "GBPUSD".to_string(), "USDJPY".to_string()],
initial_capital: Decimal::new(100_000, 0), // $100,000
max_backtest_duration_seconds: 300, // 5 minutes max
min_trades_per_day: 10,
max_drawdown_percent: 20.0, // 20% max drawdown
min_sharpe_ratio: 1.0, // Minimum Sharpe ratio
}
}
}
/// Backtesting execution result
#[derive(Debug, Clone)]
pub struct BacktestResult {
pub backtest_id: Uuid,
pub strategy_name: String,
pub execution_time: Duration,
pub total_trades: usize,
pub winning_trades: usize,
pub losing_trades: usize,
pub total_pnl: Decimal,
pub max_drawdown: f64,
pub sharpe_ratio: f64,
pub sortino_ratio: f64,
pub win_rate: f64,
pub avg_trade_duration: Duration,
pub memory_usage_mb: f64,
pub cpu_usage_percent: f64,
}
impl BacktestingTestSuite {
/// Create new backtesting test suite
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let backtest_engine = Arc::new(BacktestEngine::new().await?);
let data_manager = Arc::new(DataManager::new().await?);
let strategy_manager = Arc::new(StrategyManager::new().await?);
let performance_analyzer = Arc::new(PerformanceAnalyzer::new());
let test_config = BacktestingTestConfig::default();
Ok(Self {
backtest_engine,
data_manager,
strategy_manager,
performance_analyzer,
test_config,
})
}
/// Test complete backtesting workflow
#[tokio::test]
pub async fn test_complete_backtesting_workflow() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test multiple strategies
let strategies = vec![
"MovingAverageCrossover",
"MeanReversion",
"MLTrendFollowing",
"RiskParity",
];
for strategy_name in strategies {
let result = suite.test_strategy_backtest(strategy_name.to_string()).await?;
// Validate results
suite.validate_backtest_result(&result).await?;
println!("Strategy: {} - PnL: ${:.2}, Sharpe: {:.2}, Drawdown: {:.1}%",
result.strategy_name, result.total_pnl, result.sharpe_ratio, result.max_drawdown);
}
Ok(())
}
/// Test backtesting performance and scalability
#[tokio::test]
pub async fn test_backtesting_performance() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test with different data sizes
let test_cases = vec![
(7, "1 week"), // 7 days
(30, "1 month"), // 30 days
(90, "3 months"), // 90 days
(180, "6 months"), // 180 days
];
for (days, description) in test_cases {
let config = BacktestingTestConfig {
test_start_date: chrono::Utc::now() - chrono::Duration::days(days),
test_end_date: chrono::Utc::now() - chrono::Duration::days(1),
..suite.test_config.clone()
};
let start_time = Instant::now();
let result = suite.run_performance_test(&config).await?;
let execution_time = start_time.elapsed();
// Validate performance requirements
assert!(
execution_time.as_secs() <= config.max_backtest_duration_seconds,
"Backtest duration {}s exceeds limit {}s for {}",
execution_time.as_secs(), config.max_backtest_duration_seconds, description
);
// Memory usage should be reasonable
assert!(
result.memory_usage_mb < 1000.0, // Less than 1GB
"Memory usage {:.1}MB too high for {}",
result.memory_usage_mb, description
);
println!("Performance Test {}: {:.1}s, Memory: {:.1}MB, CPU: {:.1}%",
description, execution_time.as_secs_f64(),
result.memory_usage_mb, result.cpu_usage_percent);
}
Ok(())
}
/// Test ML model integration in backtesting
#[tokio::test]
pub async fn test_ml_model_integration() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test different ML models
let ml_models = vec![
"TFT", // Temporal Fusion Transformer
"MAMBA", // MAMBA-2 State Space Model
"LSTM", // LSTM Neural Network
"Transformer", // Transformer model
"DQN", // Deep Q-Network
"PPO", // Proximal Policy Optimization
];
for model_name in ml_models {
let result = suite.test_ml_model_backtest(model_name.to_string()).await?;
// ML models should show some predictive capability
assert!(
result.sharpe_ratio > 0.5, // Modest requirement for ML models
"ML model {} Sharpe ratio {:.2} too low",
model_name, result.sharpe_ratio
);
// Should generate reasonable number of trades
assert!(
result.total_trades > 50, // At least 50 trades in test period
"ML model {} generated only {} trades",
model_name, result.total_trades
);
println!("ML Model {}: {} trades, Sharpe: {:.2}, PnL: ${:.2}",
model_name, result.total_trades, result.sharpe_ratio, result.total_pnl);
}
Ok(())
}
/// Test multi-asset backtesting
#[tokio::test]
pub async fn test_multi_asset_backtesting() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test portfolio strategies across multiple assets
let portfolio_config = BacktestingTestConfig {
test_symbols: vec![
"EURUSD".to_string(), "GBPUSD".to_string(), "USDJPY".to_string(),
"AUDUSD".to_string(), "NZDUSD".to_string(), "USDCAD".to_string(),
"USDCHF".to_string(), "EURGBP".to_string(), "EURJPY".to_string(),
],
..suite.test_config.clone()
};
let result = suite.run_portfolio_backtest(&portfolio_config).await?;
// Portfolio should show diversification benefits
assert!(
result.sharpe_ratio >= suite.test_config.min_sharpe_ratio,
"Portfolio Sharpe ratio {:.2} below minimum {:.2}",
result.sharpe_ratio, suite.test_config.min_sharpe_ratio
);
// Drawdown should be controlled
assert!(
result.max_drawdown <= suite.test_config.max_drawdown_percent,
"Portfolio drawdown {:.1}% exceeds limit {:.1}%",
result.max_drawdown, suite.test_config.max_drawdown_percent
);
// Should trade across multiple symbols
assert!(
result.total_trades > portfolio_config.test_symbols.len() * 10,
"Portfolio generated only {} trades across {} symbols",
result.total_trades, portfolio_config.test_symbols.len()
);
Ok(())
}
/// Test risk management integration
#[tokio::test]
pub async fn test_risk_management_integration() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test with aggressive risk settings
let high_risk_config = BacktestingTestConfig {
max_drawdown_percent: 50.0, // Allow higher drawdown for testing
..suite.test_config.clone()
};
let result = suite.test_risk_managed_backtest(&high_risk_config).await?;
// Risk management should prevent excessive losses
assert!(
result.max_drawdown <= high_risk_config.max_drawdown_percent,
"Risk management failed: drawdown {:.1}% exceeded limit {:.1}%",
result.max_drawdown, high_risk_config.max_drawdown_percent
);
// Should generate trades but with controlled risk
assert!(
result.total_trades > 0,
"Risk management too restrictive: no trades generated"
);
Ok(())
}
/// Test backtesting data integrity
#[tokio::test]
pub async fn test_data_integrity() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
// Test data loading and validation
for symbol in &suite.test_config.test_symbols {
let data = suite.data_manager.load_historical_data(
symbol.clone(),
suite.test_config.test_start_date,
suite.test_config.test_end_date,
).await?;
// Validate data quality
assert!(!data.is_empty(), "No data loaded for symbol {}", symbol);
// Check for gaps in data
suite.validate_data_continuity(&data, symbol).await?;
// Check data ranges
suite.validate_data_ranges(&data, symbol).await?;
}
Ok(())
}
/// Test concurrent backtesting
#[tokio::test]
pub async fn test_concurrent_backtesting() -> Result<(), Box<dyn std::error::Error>> {
let suite = Self::new().await?;
let strategies = vec!["Strategy1", "Strategy2", "Strategy3", "Strategy4"];
let mut tasks = Vec::new();
// Run multiple backtests concurrently
for strategy in strategies {
let suite_clone = suite.clone();
let strategy_name = strategy.to_string();
let task = tokio::spawn(async move {
suite_clone.test_strategy_backtest(strategy_name).await
});
tasks.push(task);
}
// Wait for all backtests to complete
let results = futures::future::try_join_all(tasks).await?;
// All backtests should complete successfully
for result in results {
let backtest_result = result?;
assert!(backtest_result.total_trades > 0, "Concurrent backtest failed to generate trades");
}
Ok(())
}
/// Helper method to test a single strategy backtest
async fn test_strategy_backtest(&self, strategy_name: String) -> Result<BacktestResult, Box<dyn std::error::Error>> {
let start_time = Instant::now();
let backtest_id = Uuid::new_v4();
// Initialize strategy
let strategy = self.strategy_manager.create_strategy(&strategy_name).await?;
// Load historical data
let mut all_data = HashMap::new();
for symbol in &self.test_config.test_symbols {
let data = self.data_manager.load_historical_data(
symbol.clone(),
self.test_config.test_start_date,
self.test_config.test_end_date,
).await?;
all_data.insert(symbol.clone(), data);
}
// Run backtest
let backtest_config = BacktestConfig {
initial_capital: self.test_config.initial_capital,
start_date: self.test_config.test_start_date,
end_date: self.test_config.test_end_date,
symbols: self.test_config.test_symbols.clone(),
};
let trades = self.backtest_engine.run_backtest(strategy, &all_data, &backtest_config).await?;
let execution_time = start_time.elapsed();
// Calculate performance metrics
let performance = self.performance_analyzer.analyze_trades(&trades).await?;
Ok(BacktestResult {
backtest_id,
strategy_name,
execution_time,
total_trades: trades.len(),
winning_trades: trades.iter().filter(|t| t.pnl > Decimal::ZERO).count(),
losing_trades: trades.iter().filter(|t| t.pnl < Decimal::ZERO).count(),
total_pnl: trades.iter().map(|t| t.pnl).sum(),
max_drawdown: performance.max_drawdown,
sharpe_ratio: performance.sharpe_ratio,
sortino_ratio: performance.sortino_ratio,
win_rate: performance.win_rate,
avg_trade_duration: performance.avg_trade_duration,
memory_usage_mb: self.get_memory_usage(),
cpu_usage_percent: self.get_cpu_usage(),
})
}
/// Validate backtest result against requirements
async fn validate_backtest_result(&self, result: &BacktestResult) -> Result<(), Box<dyn std::error::Error>> {
// Execution time validation
assert!(
result.execution_time.as_secs() <= self.test_config.max_backtest_duration_seconds,
"Backtest execution time {}s exceeds limit {}s",
result.execution_time.as_secs(), self.test_config.max_backtest_duration_seconds
);
// Trade count validation
let test_days = (self.test_config.test_end_date - self.test_config.test_start_date).num_days() as usize;
let min_total_trades = test_days * self.test_config.min_trades_per_day;
assert!(
result.total_trades >= min_total_trades,
"Strategy {} generated {} trades, expected at least {}",
result.strategy_name, result.total_trades, min_total_trades
);
// Risk validation
assert!(
result.max_drawdown <= self.test_config.max_drawdown_percent,
"Strategy {} drawdown {:.1}% exceeds limit {:.1}%",
result.strategy_name, result.max_drawdown, self.test_config.max_drawdown_percent
);
Ok(())
}
// Additional helper methods...
async fn test_ml_model_backtest(&self, model_name: String) -> Result<BacktestResult, Box<dyn std::error::Error>> {
// Implementation for ML-specific backtesting
self.test_strategy_backtest(format!("ML_{}", model_name)).await
}
async fn run_performance_test(&self, config: &BacktestingTestConfig) -> Result<BacktestResult, Box<dyn std::error::Error>> {
// Performance-focused test implementation
self.test_strategy_backtest("PerformanceTest".to_string()).await
}
async fn run_portfolio_backtest(&self, config: &BacktestingTestConfig) -> Result<BacktestResult, Box<dyn std::error::Error>> {
// Portfolio backtesting implementation
self.test_strategy_backtest("Portfolio".to_string()).await
}
async fn test_risk_managed_backtest(&self, config: &BacktestingTestConfig) -> Result<BacktestResult, Box<dyn std::error::Error>> {
// Risk management focused test
self.test_strategy_backtest("RiskManaged".to_string()).await
}
async fn validate_data_continuity(&self, data: &[MarketTick], symbol: &str) -> Result<(), Box<dyn std::error::Error>> {
// Validate data has no significant gaps
if data.len() < 2 { return Ok(()); }
for window in data.windows(2) {
let time_gap = window[1].timestamp.signed_duration_since(window[0].timestamp);
assert!(
time_gap.num_minutes() <= 5, // No gaps larger than 5 minutes
"Data gap of {} minutes found in {} data",
time_gap.num_minutes(), symbol
);
}
Ok(())
}
async fn validate_data_ranges(&self, data: &[MarketTick], symbol: &str) -> Result<(), Box<dyn std::error::Error>> {
// Validate price and volume ranges are reasonable
for tick in data {
assert!(tick.bid > Decimal::ZERO, "Invalid bid price for {}", symbol);
assert!(tick.ask > tick.bid, "Ask <= bid for {}", symbol);
assert!(tick.volume >= Decimal::ZERO, "Negative volume for {}", symbol);
}
Ok(())
}
fn get_memory_usage(&self) -> f64 {
// Mock memory usage calculation
250.5 // MB
}
fn get_cpu_usage(&self) -> f64 {
// Mock CPU usage calculation
15.3 // Percent
}
}
// Clone implementation for test suite
impl Clone for BacktestingTestSuite {
fn clone(&self) -> Self {
Self {
backtest_engine: Arc::clone(&self.backtest_engine),
data_manager: Arc::clone(&self.data_manager),
strategy_manager: Arc::clone(&self.strategy_manager),
performance_analyzer: Arc::clone(&self.performance_analyzer),
test_config: self.test_config.clone(),
}
}
}
// Mock implementations for testing
pub struct BacktestEngine;
pub struct DataManager;
pub struct StrategyManager;
pub struct PerformanceAnalyzer;
pub struct Strategy;
pub struct BacktestConfig {
pub initial_capital: Decimal,
pub start_date: chrono::DateTime<chrono::Utc>,
pub end_date: chrono::DateTime<chrono::Utc>,
pub symbols: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MarketTick {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub symbol: String,
pub bid: Decimal,
pub ask: Decimal,
pub volume: Decimal,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "database", derive(sqlx::FromRow))]
pub struct Trade {
pub id: Uuid,
pub symbol: String,
pub quantity: Decimal,
pub price: Decimal,
pub pnl: Decimal,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub max_drawdown: f64,
pub sharpe_ratio: f64,
pub sortino_ratio: f64,
pub win_rate: f64,
pub avg_trade_duration: Duration,
}
impl BacktestEngine {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> { Ok(Self) }
pub async fn run_backtest(
&self,
_strategy: Strategy,
_data: &HashMap<String, Vec<MarketTick>>,
_config: &BacktestConfig
) -> Result<Vec<Trade>, Box<dyn std::error::Error>> {
// Mock backtest execution
Ok(vec![
Trade {
id: Uuid::new_v4(),
symbol: "EURUSD".to_string(),
quantity: Decimal::new(10000, 0),
price: Decimal::new(11000, 4),
pnl: Decimal::new(150, 0),
timestamp: chrono::Utc::now(),
}
])
}
}
impl DataManager {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> { Ok(Self) }
pub async fn load_historical_data(
&self,
_symbol: String,
_start: chrono::DateTime<chrono::Utc>,
_end: chrono::DateTime<chrono::Utc>
) -> Result<Vec<MarketTick>, Box<dyn std::error::Error>> {
// Mock data loading
Ok(vec![
MarketTick {
timestamp: chrono::Utc::now(),
symbol: "EURUSD".to_string(),
bid: Decimal::new(10995, 4),
ask: Decimal::new(11005, 4),
volume: Decimal::new(1000000, 0),
}
])
}
}
impl StrategyManager {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> { Ok(Self) }
pub async fn create_strategy(&self, _name: &str) -> Result<Strategy, Box<dyn std::error::Error>> {
Ok(Strategy)
}
}
impl PerformanceAnalyzer {
pub fn new() -> Self { Self }
pub async fn analyze_trades(&self, _trades: &[Trade]) -> Result<PerformanceMetrics, Box<dyn std::error::Error>> {
Ok(PerformanceMetrics {
max_drawdown: 5.2,
sharpe_ratio: 1.8,
sortino_ratio: 2.1,
win_rate: 0.65,
avg_trade_duration: Duration::from_secs(3600),
})
}
}