#![warn(missing_docs)] #![warn(clippy::all)] #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] #![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_lines)] //! Historical Market Replay System for Backtesting //! //! This crate provides a comprehensive backtesting framework for trading strategies //! with tick-by-tick historical market data replay, strategy execution, and performance analytics. //! //! # Features //! //! - **Market Data Replay**: Replay historical market data with configurable speed and filtering //! - **Strategy Testing**: Execute trading strategies against historical data with realistic execution //! - **Performance Analytics**: Comprehensive performance metrics including risk, return, and drawdown analysis //! - **Tick-by-tick Precision**: Support for high-frequency tick-level backtesting //! - **Multiple Data Sources**: CSV, Parquet, and database support for historical data //! - **Risk Management**: Built-in position sizing, stop losses, and risk controls //! //! # Quick Start //! //! ```rust,no_run //! use backtesting::{BacktestEngine, BacktestConfig, replay_engine::ReplayConfig}; //! use chrono::Utc; //! use common::types::Order; use common::types::Position; use common::types::Execution; use common::types::Symbol; use common::types::Price; use common::types::Quantity; use common::error::CommonError; use common::error::CommonResult; use common::types::HftTimestamp; use common::types::OrderId; use common::types::TradeId; // // #[tokio::main] // async fn main() -> anyhow::Result<()> { // let config = BacktestConfig { // initial_capital: Decimal::from(100000), // replay_config: ReplayConfig { // start_time: Utc::now() - chrono::Duration::days(30), // end_time: Utc::now(), // tick_by_tick: true, // ..Default::default() // }, // ..Default::default() // }; // // let mut engine = BacktestEngine::new(config).await?; // let results = engine.run().await?; // // info!("Total Return: {:.2}%", results.strategy_result.total_return * Decimal::from(100)); // info!("Sharpe Ratio: {:.2}", results.strategy_result.sharpe_ratio); // info!("Max Drawdown: {:.2}%", results.strategy_result.max_drawdown * Decimal::from(100)); // // Ok(()) // } /// ``` // Re-export std modules that might be shadowed by local crate names use std as stdlib; use std::{collections::HashMap, sync::Arc, time::Instant}; use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use tokio::sync::{mpsc, RwLock}; use tracing::{error, info, warn}; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; // mod types; // Removed - using core::prelude types instead pub mod metrics; pub mod replay_engine; pub mod strategy_tester; pub mod strategy_runner; // Import OrderSide from common types use trading_engine::types::events::MarketEvent; /// Main backtesting engine configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestConfig { /// Initial capital for backtesting pub initial_capital: Decimal, /// Market data replay configuration pub replay_config: ReplayConfig, /// Strategy configuration pub strategy_config: StrategyConfig, /// Risk-free rate for Sharpe ratio calculation pub risk_free_rate: Decimal, /// Enable detailed logging pub enable_logging: bool, /// Performance snapshot interval (seconds) pub snapshot_interval: u64, /// Maximum memory usage (bytes) pub max_memory_usage: usize, } impl Default for BacktestConfig { fn default() -> Self { Self { initial_capital: Decimal::from(100000), replay_config: ReplayConfig::default(), strategy_config: StrategyConfig::default(), risk_free_rate: Decimal::new(2, 2), // 2% annually enable_logging: true, snapshot_interval: 3600, // 1 hour max_memory_usage: 1024 * 1024 * 1024, // 1GB } } } /// Main backtesting engine that orchestrates market replay and strategy execution pub struct BacktestEngine { /// Configuration config: BacktestConfig, /// Market data replay engine market_replay: Arc, /// Strategy being tested strategy: Option>, /// Strategy tester strategy_tester: Option, /// Performance metrics calculator metrics_calculator: Arc>, /// Current execution state state: Arc>, /// Performance monitoring performance_monitor: Arc, } /// Current state of backtesting engine #[derive(Debug, Clone)] pub struct BacktestState { /// Is backtest running pub is_running: bool, /// Is backtest paused pub is_paused: bool, /// Backtest start time (wall clock) pub start_time: Option, /// Current simulation time pub current_sim_time: Option>, /// Events processed pub events_processed: u64, /// Current portfolio value pub portfolio_value: Decimal, /// Total trades executed pub trades_executed: u64, /// Last performance snapshot time pub last_snapshot: Option>, } impl Default for BacktestState { fn default() -> Self { Self { is_running: false, is_paused: false, start_time: None, current_sim_time: None, events_processed: 0, portfolio_value: Decimal::ZERO, trades_executed: 0, last_snapshot: None, } } } /// Performance monitoring for the backtesting engine #[derive(Debug)] pub struct PerformanceMonitor { /// Memory usage tracking memory_usage: Arc, /// CPU usage tracking cpu_usage: Arc, /// Event processing rate events_per_second: Arc, /// Last performance check last_check: Arc>>, } impl Default for PerformanceMonitor { fn default() -> Self { Self { memory_usage: Arc::new(std::sync::atomic::AtomicUsize::new(0)), cpu_usage: Arc::new(std::sync::atomic::AtomicU64::new(0)), events_per_second: Arc::new(std::sync::atomic::AtomicU64::new(0)), last_check: Arc::new(RwLock::new(None)), } } } impl BacktestEngine { /// Create a new backtesting engine pub async fn new(config: BacktestConfig) -> Result { info!( "Initializing backtesting engine with initial capital: {}", config.initial_capital ); let market_replay = Arc::new(MarketReplay::new(config.replay_config.clone())); let metrics_calculator = Arc::new(RwLock::new(MetricsCalculator::new(config.risk_free_rate))); let performance_monitor = Arc::new(PerformanceMonitor::default()); Ok(Self { config, market_replay, strategy: None, strategy_tester: None, metrics_calculator, state: Arc::new(RwLock::new(BacktestState::default())), performance_monitor, }) } /// Set the trading strategy to test pub async fn set_strategy(&mut self, strategy: Box) -> Result<()> { info!("Setting strategy: {}", strategy.name()); let strategy_tester = StrategyTester::new( strategy, self.config.strategy_config.clone(), Arc::clone(&self.market_replay), self.config.initial_capital, ); self.strategy_tester = Some(strategy_tester); Ok(()) } /// Run the complete backtesting process pub async fn run(&mut self) -> Result { if self.strategy_tester.is_none() { return Err(anyhow::anyhow!( "No strategy set. Call set_strategy() first." )); } info!("Starting backtesting run"); // Update state { let mut state = self.state.write().await; state.is_running = true; state.start_time = Some(Instant::now()); state.current_sim_time = Some(self.config.replay_config.start_time); } // Start performance monitoring let monitor_handle = self.start_performance_monitoring().await; // Run the strategy test let strategy_result = match self.strategy_tester.as_mut() { Some(tester) => tester.run_test().await?, None => return Err(anyhow::anyhow!("Strategy tester not initialized")), }; // Calculate comprehensive analytics let analytics = { let calculator = self.metrics_calculator.read().await; calculator.calculate_analytics()? }; // Stop performance monitoring monitor_handle.abort(); // Update final state { let mut state = self.state.write().await; state.is_running = false; state.portfolio_value = strategy_result.final_value; state.trades_executed = strategy_result.total_trades; } let backtest_result = BacktestResult { strategy_result, analytics, execution_stats: self.get_execution_stats().await, config: self.config.clone(), }; info!( "Backtesting completed. Total return: {:.2}%, Sharpe ratio: {:.2}", backtest_result.strategy_result.total_return * Decimal::from(100), backtest_result.strategy_result.sharpe_ratio ); Ok(backtest_result) } /// Run backtesting with real-time monitoring pub async fn run_with_monitoring( &mut self, ) -> Result<(BacktestResult, mpsc::UnboundedReceiver)> { let (update_sender, update_receiver) = mpsc::unbounded_channel(); // Clone necessary data for monitoring task let state = Arc::clone(&self.state); let performance_monitor = Arc::clone(&self.performance_monitor); // Start monitoring task let monitoring_handle = tokio::spawn(async move { let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1)); loop { interval.tick().await; let current_state = state.read().await.clone(); if !current_state.is_running { break; } let update = MonitoringUpdate { timestamp: Utc::now(), events_processed: current_state.events_processed, portfolio_value: current_state.portfolio_value, trades_executed: current_state.trades_executed, memory_usage: performance_monitor .memory_usage .load(std::sync::atomic::Ordering::Relaxed), events_per_second: performance_monitor .events_per_second .load(std::sync::atomic::Ordering::Relaxed), current_sim_time: current_state.current_sim_time, }; if update_sender.send(update).is_err() { break; // Receiver dropped } } }); // Run the backtest let result = self.run().await; // Clean up monitoring monitoring_handle.abort(); match result { Ok(backtest_result) => Ok((backtest_result, update_receiver)), Err(e) => Err(e), } } /// Pause the backtesting process pub async fn pause(&self) -> Result<()> { { let mut state = self.state.write().await; state.is_paused = true; } self.market_replay.pause().await; info!("Backtesting paused"); Ok(()) } /// Resume the backtesting process pub async fn resume(&self) -> Result<()> { { let mut state = self.state.write().await; state.is_paused = false; } self.market_replay.resume().await; info!("Backtesting resumed"); Ok(()) } /// Stop the backtesting process pub async fn stop(&self) -> Result<()> { { let mut state = self.state.write().await; state.is_running = false; state.is_paused = false; } self.market_replay.stop().await; info!("Backtesting stopped"); Ok(()) } /// Get current backtesting state pub async fn get_state(&self) -> BacktestState { self.state.read().await.clone() } /// Get current performance metrics pub async fn get_current_analytics(&self) -> Result { let calculator = self.metrics_calculator.read().await; calculator.calculate_analytics() } /// Add market data for replay pub async fn add_market_data(&self, _symbol: Symbol, _data: Vec) -> Result<()> { // This would integrate with the market replay engine to add data // Implementation depends on the specific data loading mechanism warn!("add_market_data not yet implemented - use ReplayConfig data sources instead"); Ok(()) } /// Run backtesting with adaptive strategy using real ML models pub async fn run_with_adaptive_strategy( &mut self, adaptive_config: AdaptiveStrategyConfig, ) -> Result { info!("Starting backtesting with adaptive ML strategy"); // Create adaptive strategy let adaptive_strategy = Box::new(create_adaptive_strategy_with_config(adaptive_config)); // Set the strategy self.set_strategy(adaptive_strategy).await?; // Run the backtest let result = self.run().await?; info!( "Adaptive ML backtesting completed. Models used: {:?}", result.config.strategy_config ); Ok(result) } /// Run backtesting with parallel model evaluation pub async fn run_with_parallel_models( &mut self, model_names: Vec, ) -> Result> { info!( "Starting parallel backtesting with {} models", model_names.len() ); let mut results = Vec::new(); for model_name in model_names { info!("Running backtest with model: {}", model_name); let adaptive_config = AdaptiveStrategyConfig { active_models: vec![model_name.clone()], ..AdaptiveStrategyConfig::default() }; // Clone the engine configuration for each model test let mut model_engine = BacktestEngine::new(self.config.clone()).await?; let result = model_engine .run_with_adaptive_strategy(adaptive_config) .await?; results.push(result); } info!( "Parallel model backtesting completed for {} models", results.len() ); Ok(results) } /// Run ensemble backtesting comparing individual models vs ensemble pub async fn run_ensemble_comparison( &mut self, model_names: Vec, ) -> Result { info!( "Starting ensemble comparison with {} models", model_names.len() ); // Test individual models let individual_results = self.run_with_parallel_models(model_names.clone()).await?; // Test ensemble let ensemble_config = AdaptiveStrategyConfig { active_models: model_names.clone(), ..AdaptiveStrategyConfig::default() }; let mut ensemble_engine = BacktestEngine::new(self.config.clone()).await?; let ensemble_result = ensemble_engine .run_with_adaptive_strategy(ensemble_config) .await?; // Calculate comparison metrics before moving individual_results let comparison_metrics = self .calculate_comparison_metrics(&individual_results, &ensemble_result) .await; let comparison = EnsembleComparisonResult { individual_results, ensemble_result, models_tested: model_names, comparison_metrics, }; info!( "Ensemble comparison completed. Ensemble Sharpe: {:.3}, Best Individual: {:.3}", comparison.ensemble_result.strategy_result.sharpe_ratio, comparison.comparison_metrics.best_individual_sharpe ); Ok(comparison) } /// Start performance monitoring task async fn start_performance_monitoring(&self) -> tokio::task::JoinHandle<()> { let performance_monitor = Arc::clone(&self.performance_monitor); tokio::spawn(async move { let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(5)); loop { interval.tick().await; // Update memory usage if let Ok(info) = sys_info::mem_info() { let used_memory = (info.total - info.free) * 1024; // Convert to bytes performance_monitor .memory_usage .store(used_memory as usize, std::sync::atomic::Ordering::Relaxed); } // Update last check time { let mut last_check = performance_monitor.last_check.write().await; *last_check = Some(Instant::now()); } } }) } /// Calculate comparison metrics between individual models and ensemble async fn calculate_comparison_metrics( &self, individual_results: &[BacktestResult], ensemble_result: &BacktestResult, ) -> ComparisonMetrics { let individual_sharpes: Vec = individual_results .iter() .map(|r| r.strategy_result.sharpe_ratio) .collect(); let best_individual_sharpe = individual_sharpes .iter() .max() .copied() .unwrap_or(Decimal::ZERO); let avg_individual_sharpe = if !individual_sharpes.is_empty() { individual_sharpes.iter().sum::() / Decimal::from(individual_sharpes.len()) } else { Decimal::ZERO }; let ensemble_sharpe = ensemble_result.strategy_result.sharpe_ratio; ComparisonMetrics { best_individual_sharpe, avg_individual_sharpe, ensemble_sharpe, ensemble_improvement: ensemble_sharpe - best_individual_sharpe, diversification_benefit: ensemble_sharpe - avg_individual_sharpe, } } /// Get execution statistics async fn get_execution_stats(&self) -> ExecutionStats { let state = self.state.read().await; let wall_time = state .start_time .map(|start| start.elapsed()) .unwrap_or_default(); ExecutionStats { wall_time_seconds: wall_time.as_secs(), events_processed: state.events_processed, trades_executed: state.trades_executed, memory_peak_mb: self .performance_monitor .memory_usage .load(std::sync::atomic::Ordering::Relaxed) / (1024 * 1024), events_per_second: if wall_time.as_secs() > 0 { state.events_processed / wall_time.as_secs() } else { 0 }, } } } /// Complete backtesting result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BacktestResult { /// Strategy execution results pub strategy_result: StrategyResult, /// Comprehensive performance analytics pub analytics: PerformanceAnalytics, /// Execution statistics pub execution_stats: ExecutionStats, /// Configuration used pub config: BacktestConfig, } /// Execution performance statistics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExecutionStats { /// Wall clock time in seconds pub wall_time_seconds: u64, /// Total events processed pub events_processed: u64, /// Total trades executed pub trades_executed: u64, /// Peak memory usage in MB pub memory_peak_mb: usize, /// Average events per second pub events_per_second: u64, } /// Real-time monitoring update #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MonitoringUpdate { /// Update timestamp pub timestamp: DateTime, /// Events processed so far pub events_processed: u64, /// Current portfolio value pub portfolio_value: Decimal, /// Trades executed so far pub trades_executed: u64, /// Current memory usage in bytes pub memory_usage: usize, /// Current events per second pub events_per_second: u64, /// Current simulation time pub current_sim_time: Option>, } /// Result of ensemble vs individual model comparison #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EnsembleComparisonResult { /// Results from individual model backtests pub individual_results: Vec, /// Result from ensemble backtest pub ensemble_result: BacktestResult, /// Names of models tested pub models_tested: Vec, /// Comparison metrics pub comparison_metrics: ComparisonMetrics, } /// Metrics comparing ensemble vs individual model performance #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ComparisonMetrics { /// Best individual model Sharpe ratio pub best_individual_sharpe: Decimal, /// Average individual model Sharpe ratio pub avg_individual_sharpe: Decimal, /// Ensemble Sharpe ratio pub ensemble_sharpe: Decimal, /// Improvement of ensemble over best individual pub ensemble_improvement: Decimal, /// Diversification benefit (ensemble vs average) pub diversification_benefit: Decimal, } // Re-export commonly used types // Note: DateTime, Utc, and Decimal are already imported above, no need to re-export #[cfg(test)] mod tests { use super::*; use std::io::Write; use tempfile::NamedTempFile; #[tokio::test] async fn test_backtest_engine_creation() { let config = BacktestConfig::default(); let engine = BacktestEngine::new(config).await; assert!( engine.is_ok(), "BacktestEngine creation should not fail in test: {:?}", engine.err() ); let engine = engine.unwrap(); let state = engine.get_state().await; assert!(!state.is_running); assert!(!state.is_paused); } #[tokio::test] async fn test_backtest_config_default() { let config = BacktestConfig::default(); assert_eq!(config.initial_capital, Decimal::from(100000)); assert_eq!(config.risk_free_rate, Decimal::new(2, 2)); assert!(config.enable_logging); } // Real Mean Reversion Strategy for Testing struct MeanReversionStrategy { lookback_period: usize, price_history: Vec, position_size: Decimal, entry_threshold: Decimal, exit_threshold: Decimal, current_position: Option, position_side: Option, trades_executed: usize, total_pnl: Decimal, max_drawdown: Decimal, peak_value: Decimal, initial_capital: Decimal, } impl MeanReversionStrategy { fn new() -> Self { Self { lookback_period: 20, price_history: Vec::new(), position_size: dec!(0.02), // 2% position size entry_threshold: dec!(2.0), // 2 standard deviations exit_threshold: dec!(0.5), // 0.5 standard deviations current_position: None, position_side: None, trades_executed: 0, total_pnl: Decimal::ZERO, max_drawdown: Decimal::ZERO, peak_value: Decimal::ZERO, initial_capital: Decimal::ZERO, } } fn calculate_z_score(&self, current_price: Decimal) -> Option { if self.price_history.len() < self.lookback_period { return None; } let recent_prices = &self.price_history[self.price_history.len() - self.lookback_period..]; let mean = recent_prices.iter().sum::() / Decimal::from(recent_prices.len()); let variance = recent_prices .iter() .map(|price| { let diff = *price - mean; diff * diff }) .sum::() / Decimal::from(recent_prices.len()); // Calculate standard deviation using f64 for sqrt operation let variance_f64 = variance.to_f64().unwrap_or(0.0); let std_dev = Decimal::try_from(variance_f64.sqrt()).unwrap_or(Decimal::ZERO); if std_dev > Decimal::ZERO { Some((current_price - mean) / std_dev) } else { None } } fn should_enter_long(&self, z_score: Decimal) -> bool { z_score < -self.entry_threshold && self.current_position.is_none() } fn should_enter_short(&self, z_score: Decimal) -> bool { z_score > self.entry_threshold && self.current_position.is_none() } fn should_exit_position(&self, z_score: Decimal) -> bool { if let Some(ref _position) = self.current_position { if let Some(ref side) = self.position_side { match side { OrderSide::Buy => z_score > -self.exit_threshold, // Long position OrderSide::Sell => z_score < self.exit_threshold, // Short position } } else { false } } else { false } } } #[async_trait::async_trait(?Send)] impl Strategy for MeanReversionStrategy { fn name(&self) -> &str { "mean_reversion_strategy" } async fn initialize( &mut self, initial_capital: Decimal, _config: StrategyConfig, ) -> Result<()> { self.initial_capital = initial_capital; self.peak_value = initial_capital; info!( "Mean Reversion Strategy initialized with capital: {}", initial_capital ); Ok(()) } async fn on_market_event( &mut self, event: &MarketEvent, context: &StrategyContext, ) -> Result> { let mut signals = Vec::new(); if let MarketEvent::Trade { symbol, price, .. } = event { self.price_history.push((*price).into()); // Keep only recent price history if self.price_history.len() > self.lookback_period * 2 { self.price_history.drain(0..self.lookback_period); } if let Some(z_score) = self.calculate_z_score((*price).into()) { // Generate trading signals based on mean reversion logic if self.should_enter_long(z_score) { let price_decimal: Decimal = (*price).into(); let quantity = (context.account_balance * self.position_size / price_decimal) .round_dp(0); let mut metadata = HashMap::new(); metadata .insert("strategy".to_string(), serde_json::json!("mean_reversion")); metadata.insert("z_score".to_string(), serde_json::json!(z_score)); metadata.insert("signal_type".to_string(), serde_json::json!("enter_long")); signals.push(TradingSignal { symbol: symbol.clone(), signal_type: SignalType::Buy, quantity: Quantity::from_f64(quantity.to_f64().unwrap_or(0.0)) .unwrap_or(Quantity::ZERO), target_price: Some(*price), stop_loss: None, take_profit: None, confidence: dec!(0.8), metadata, }); } else if self.should_enter_short(z_score) { let price_decimal: Decimal = (*price).into(); let quantity = (context.account_balance * self.position_size / price_decimal) .round_dp(0); let mut metadata = HashMap::new(); metadata .insert("strategy".to_string(), serde_json::json!("mean_reversion")); metadata.insert("z_score".to_string(), serde_json::json!(z_score)); metadata .insert("signal_type".to_string(), serde_json::json!("enter_short")); signals.push(TradingSignal { symbol: symbol.clone(), signal_type: SignalType::Sell, quantity: Quantity::from_f64(quantity.to_f64().unwrap_or(0.0)) .unwrap_or(Quantity::ZERO), target_price: Some(*price), stop_loss: None, take_profit: None, confidence: dec!(0.8), metadata, }); } else if self.should_exit_position(z_score) { if let Some(ref position) = self.current_position { if let Some(ref side) = self.position_side { let exit_signal_type = match side { OrderSide::Buy => SignalType::Sell, // Exit long position OrderSide::Sell => SignalType::Cover, // Exit short position }; let mut metadata = HashMap::new(); metadata.insert( "strategy".to_string(), serde_json::json!("mean_reversion"), ); metadata.insert("z_score".to_string(), serde_json::json!(z_score)); metadata.insert( "signal_type".to_string(), serde_json::json!("exit_position"), ); signals.push(TradingSignal { symbol: symbol.clone(), signal_type: exit_signal_type, quantity: Quantity::from_f64(position.quantity.to_f64()) .unwrap_or(Quantity::ZERO), target_price: Some(*price), stop_loss: None, take_profit: None, confidence: dec!(0.8), metadata, }); } } } } } Ok(signals) } async fn on_order_update( &mut self, order: &Order, _context: &StrategyContext, ) -> anyhow::Result<()> { if order.status == OrderStatus::Filled { self.trades_executed += 1; let display_price = order .average_price .unwrap_or(order.price.unwrap_or(Price::ZERO)); info!( "Order filled: {} {} @ {}", order.side, order.quantity, display_price ); } Ok(()) } async fn on_position_update( &mut self, position: &Position, context: &StrategyContext, ) -> anyhow::Result<()> { self.current_position = Some(position.clone()); // Determine position side based on quantity sign if position.quantity.to_f64() > 0.0 { self.position_side = Some(OrderSide::Buy); // Long position } else if position.quantity.to_f64() < 0.0 { self.position_side = Some(OrderSide::Sell); // Short position } else { self.position_side = None; // No position } // Update P&L tracking let current_value = context.account_balance; if current_value > self.peak_value { self.peak_value = current_value; } let current_drawdown = (self.peak_value - current_value) / self.peak_value; if current_drawdown > self.max_drawdown { self.max_drawdown = current_drawdown; } self.total_pnl = current_value - self.initial_capital; Ok(()) } async fn finalize(&mut self, context: &StrategyContext) -> Result { let total_return = if self.initial_capital > Decimal::ZERO { self.total_pnl / self.initial_capital } else { Decimal::ZERO }; let annualized_return = total_return; // Simplified for test let win_rate = if self.trades_executed > 0 { // Simplified calculation - in reality would track individual trade outcomes if self.total_pnl > Decimal::ZERO { dec!(0.6) } else { dec!(0.4) } } else { Decimal::ZERO }; let avg_trade_return = if self.trades_executed > 0 { self.total_pnl / Decimal::from(self.trades_executed) } else { Decimal::ZERO }; let sharpe_ratio = if self.max_drawdown > Decimal::ZERO { annualized_return / self.max_drawdown // Simplified Sharpe calculation } else { Decimal::ZERO }; Ok(StrategyResult { strategy_name: "mean_reversion_strategy".to_string(), total_return, annualized_return, max_drawdown: self.max_drawdown, sharpe_ratio, total_trades: self.trades_executed as u64, win_rate, avg_trade_return, final_value: context.account_balance, trades: vec![], // Would be populated with actual trade records performance_timeline: vec![], // Would be populated with performance snapshots }) } async fn get_state(&self) -> Result { Ok(serde_json::json!({ "name": "mean_reversion_strategy", "lookback_period": self.lookback_period, "position_size": self.position_size, "entry_threshold": self.entry_threshold, "exit_threshold": self.exit_threshold, "trades_executed": self.trades_executed, "total_pnl": self.total_pnl, "max_drawdown": self.max_drawdown, "current_position": self.current_position })) } } #[tokio::test] async fn test_strategy_setting() { let config = BacktestConfig::default(); let engine_result = BacktestEngine::new(config).await; assert!( engine_result.is_ok(), "BacktestEngine creation should not fail in test: {:?}", engine_result.err() ); let mut engine = engine_result.unwrap(); let strategy = Box::new(MeanReversionStrategy::new()); let result = engine.set_strategy(strategy).await; assert!( result.is_ok(), "Strategy setting should not fail in test: {:?}", result.err() ); assert!(engine.strategy_tester.is_some()); } }