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>
593 lines
21 KiB
Rust
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),
|
|
})
|
|
}
|
|
} |