//! Strategy testing framework for backtesting //! //! Provides infrastructure for executing trading strategies against historical data, //! managing positions, tracking performance, and handling risk management. // Import everything async_trait needs - use fully qualified paths to avoid shadowing use std::{ collections::{HashMap, VecDeque}, sync::Arc, }; use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use common::Order; use common::OrderId; use common::OrderSide; use common::OrderStatus; use common::OrderType; use common::Position; use common::Price; use common::Quantity; use common::Symbol; use common::TimeInForce; use dashmap::DashMap; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_json; use tokio::sync::RwLock; use tracing::{error, info}; use trading_engine::types::events::MarketEvent; use uuid::Uuid; // TECHNICAL DEBT ELIMINATED - Use String and DateTime directly use crate::replay_engine::{MarketReplay, ReplayEvent}; /// Trading strategy trait that backtesting strategies must implement #[async_trait(?Send)] pub trait Strategy: Send + Sync { /// Strategy name for identification fn name(&self) -> &str; /// Initialize strategy with initial capital and configuration async fn initialize(&mut self, initial_capital: Decimal, config: StrategyConfig) -> Result<()>; /// Process market event and generate trading signals async fn on_market_event( &mut self, event: &MarketEvent, context: &StrategyContext, ) -> Result>; /// Handle order execution updates async fn on_order_update(&mut self, order: &Order, context: &StrategyContext) -> Result<()>; /// Handle position updates async fn on_position_update( &mut self, position: &Position, context: &StrategyContext, ) -> Result<()>; /// Strategy cleanup and final calculations async fn finalize(&mut self, context: &StrategyContext) -> Result; /// Get current strategy state for debugging async fn get_state(&self) -> Result; } /// Strategy configuration parameters #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StrategyConfig { /// Maximum position size per symbol pub max_position_size: Decimal, /// Risk per trade as percentage of capital pub risk_per_trade: Decimal, /// Maximum number of open positions pub max_open_positions: u32, /// Stop loss percentage pub stop_loss_pct: Option, /// Take profit percentage pub take_profit_pct: Option, /// Strategy-specific parameters pub parameters: HashMap, /// Enable position sizing pub position_sizing_enabled: bool, /// Commission rate per trade pub commission_rate: Decimal, /// Slippage factor pub slippage_factor: Decimal, } impl Default for StrategyConfig { fn default() -> Self { Self { max_position_size: Decimal::from(100000), risk_per_trade: Decimal::new(2, 2), // 2% max_open_positions: 10, stop_loss_pct: Some(Decimal::new(5, 2)), // 5% take_profit_pct: Some(Decimal::new(10, 2)), // 10% parameters: HashMap::new(), position_sizing_enabled: true, commission_rate: Decimal::new(1, 4), // 0.01% slippage_factor: Decimal::new(5, 5), // 0.005% } } } /// Context provided to strategy during execution #[derive(Debug, Clone)] pub struct StrategyContext { /// Current timestamp pub current_time: DateTime, /// Current account balance pub account_balance: Decimal, /// Available buying power pub buying_power: Decimal, /// Current positions pub positions: HashMap, /// Open orders pub open_orders: HashMap, /// Current market prices pub market_prices: HashMap, /// Performance metrics pub performance: PerformanceMetrics, } /// Trading signal generated by strategy #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TradingSignal { /// Symbol to trade pub symbol: Symbol, /// Signal type pub signal_type: SignalType, /// Suggested quantity pub quantity: Quantity, /// Target price (if limit order) pub target_price: Option, /// Stop loss price pub stop_loss: Option, /// Take profit price pub take_profit: Option, /// Signal confidence (0.0 - 1.0) pub confidence: Decimal, /// Additional metadata pub metadata: HashMap, } /// Types of trading signals #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SignalType { Buy, Sell, Short, Cover, CloseLong, CloseShort, CloseAll, } /// Strategy execution result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StrategyResult { /// Strategy name pub strategy_name: String, /// Total return pub total_return: Decimal, /// Annualized return pub annualized_return: Decimal, /// Maximum drawdown pub max_drawdown: Decimal, /// Sharpe ratio pub sharpe_ratio: Decimal, /// Number of trades pub total_trades: u64, /// Win rate pub win_rate: Decimal, /// Average trade return pub avg_trade_return: Decimal, /// Final portfolio value pub final_value: Decimal, /// Detailed trade history pub trades: Vec, /// Performance timeline pub performance_timeline: Vec, } /// Individual trade record #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TradeRecord { /// Trade ID pub trade_id: String, /// Symbol traded pub symbol: Symbol, /// Trade side pub side: OrderSide, /// Entry price pub entry_price: Price, /// Exit price pub exit_price: Price, /// Quantity traded pub quantity: Quantity, /// Entry timestamp pub entry_time: DateTime, /// Exit timestamp pub exit_time: DateTime, /// Profit/loss pub pnl: Decimal, /// Return percentage pub return_pct: Decimal, /// Commission paid pub commission: Decimal, } /// Performance snapshot at a point in time #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PerformanceSnapshot { /// Timestamp pub timestamp: DateTime, /// Portfolio value pub portfolio_value: Decimal, /// Cash balance pub cash_balance: Decimal, /// Unrealized PnL pub unrealized_pnl: Decimal, /// Realized PnL pub realized_pnl: Decimal, /// Number of open positions pub open_positions: u32, /// Current drawdown pub drawdown: Decimal, } /// Performance metrics tracked during execution #[derive(Debug, Clone, Default)] pub struct PerformanceMetrics { /// Total realized PnL pub total_realized_pnl: Decimal, /// Total unrealized PnL pub total_unrealized_pnl: Decimal, /// Peak portfolio value pub peak_value: Decimal, /// Current drawdown pub current_drawdown: Decimal, /// Maximum drawdown pub max_drawdown: Decimal, /// Total trades executed pub total_trades: u64, /// Winning trades pub winning_trades: u64, /// Total commission paid pub total_commission: Decimal, /// Returns history pub daily_returns: VecDeque, } /// Strategy tester engine pub struct StrategyTester { /// Strategy being tested strategy: Box, /// Strategy configuration config: StrategyConfig, /// Market replay engine market_replay: Arc, /// Current account state account: Arc>, /// Order management system order_manager: Arc, /// Position tracker position_tracker: Arc, /// Performance tracker performance_tracker: Arc>, /// Current market data market_data: Arc>, } /// Account state for backtesting #[derive(Debug, Clone)] pub struct Account { /// Initial capital pub initial_capital: Decimal, /// Current cash balance pub cash_balance: Decimal, /// Total portfolio value pub portfolio_value: Decimal, /// Account creation time pub created_at: DateTime, /// Last update time pub last_updated: DateTime, } /// Order management for backtesting pub struct OrderManager { /// Open orders orders: DashMap, /// Order history #[allow(dead_code)] order_history: RwLock>, /// Next order ID next_order_id: std::sync::atomic::AtomicU64, } /// Position tracking for backtesting pub struct PositionTracker { /// Current positions positions: DashMap, /// Position history #[allow(dead_code)] position_history: RwLock>, /// Trade records #[allow(dead_code)] trade_records: RwLock>, } /// Performance tracking pub struct PerformanceTracker { /// Performance metrics metrics: PerformanceMetrics, /// Performance snapshots snapshots: Vec, /// Last snapshot time last_snapshot: Option>, } impl StrategyTester { /// Create new strategy tester pub fn new( strategy: Box, config: StrategyConfig, market_replay: Arc, initial_capital: Decimal, ) -> Self { let account = Account { initial_capital, cash_balance: initial_capital, portfolio_value: initial_capital, created_at: Utc::now(), last_updated: Utc::now(), }; Self { strategy, config, market_replay, account: Arc::new(RwLock::new(account)), order_manager: Arc::new(OrderManager::new()), position_tracker: Arc::new(PositionTracker::new()), performance_tracker: Arc::new(RwLock::new(PerformanceTracker::new())), market_data: Arc::new(DashMap::new()), } } /// Run the strategy test pub async fn run_test(&mut self) -> Result { info!("Starting strategy test for: {}", self.strategy.name()); // Initialize strategy self.strategy .initialize( self.account.read().await.initial_capital, self.config.clone(), ) .await?; // Get market data receiver let mut event_receiver = self .market_replay .take_receiver() .await .context("Failed to get market data receiver")?; // Start market replay let replay_handle = { let replay = Arc::clone(&self.market_replay); tokio::spawn(async move { if let Err(e) = replay.start_replay().await { error!("Market replay failed: {}", e); } }) }; // Process market events while let Some(replay_event) = event_receiver.recv().await { if let Err(e) = self.process_market_event(replay_event).await { error!("Failed to process market event: {}", e); } } // Wait for replay to complete replay_handle.await?; // Finalize strategy and generate results let context = self.build_strategy_context().await?; let result = self.strategy.finalize(&context).await?; info!( "Strategy test completed. Total return: {:.2}%", result.total_return * Decimal::from(100) ); Ok(result) } /// Process a single market event async fn process_market_event(&mut self, replay_event: ReplayEvent) -> Result<()> { let event = &replay_event.event; // Update market data self.update_market_data(event).await; // Update account and positions with current market prices self.update_valuations().await?; // Process pending orders self.process_pending_orders().await?; // Build strategy context let context = self.build_strategy_context().await?; // Get trading signals from strategy let signals = self.strategy.on_market_event(event, &context).await?; // Execute trading signals for signal in signals { self.execute_trading_signal(signal).await?; } // Take performance snapshot periodically self.take_performance_snapshot(&context).await?; Ok(()) } /// Update market data cache async fn update_market_data(&self, event: &MarketEvent) { let symbol = match event { MarketEvent::Trade { symbol, .. } => symbol, MarketEvent::Quote { symbol, .. } => symbol, MarketEvent::OrderBookUpdate { symbol, .. } => symbol, MarketEvent::Bar { symbol, .. } => symbol, MarketEvent::OrderBook { symbol, .. } => symbol, MarketEvent::Sentiment { .. } => return, // Skip sentiment events for now MarketEvent::Control { .. } => return, // Skip control events for now }; self.market_data.insert(symbol.clone(), event.clone()); } /// Update portfolio valuations async fn update_valuations(&self) -> Result<()> { let mut account = self.account.write().await; let positions = self.position_tracker.get_all_positions().await; let mut total_value = account.cash_balance; for (symbol, position) in positions { if let Some(market_event) = self.market_data.get(&symbol) { let current_price = self.extract_price_from_event(&market_event)?; let position_decimal = position.quantity; let price_decimal = current_price.to_decimal().unwrap_or(Decimal::ZERO); let position_value = position_decimal * price_decimal; if position_decimal >= Decimal::ZERO { total_value += position_value; } else { // Short position total_value -= position_value; } } } account.portfolio_value = total_value; account.last_updated = Utc::now(); Ok(()) } /// Process pending orders for execution async fn process_pending_orders(&mut self) -> Result<()> { let pending_orders = self.order_manager.get_pending_orders().await; for order in pending_orders { // Extract the current price first to avoid borrowing conflicts let current_price = if let Some(market_event) = self.market_data.get(&order.symbol) { self.extract_price_from_event(&market_event)? } else { continue; // Skip this order if no market data available }; // Now check if we should execute and execute if needed if self.should_execute_order(&order, current_price) { self.execute_order(order).await?; } } Ok(()) } /// Check if order should be executed fn should_execute_order(&self, order: &Order, current_price: Price) -> bool { match order.order_type { OrderType::Market => true, OrderType::Iceberg => { // For backtesting, treat Iceberg orders as market orders true }, OrderType::Limit => { if let Some(order_price) = order.price { match order.side { OrderSide::Buy => current_price <= order_price, OrderSide::Sell => current_price >= order_price, } } else { false } }, OrderType::Stop => { if let Some(order_price) = order.price { match order.side { OrderSide::Buy => current_price >= order_price, OrderSide::Sell => current_price <= order_price, } } else { false } }, OrderType::StopLimit => { // Simplified logic - would need stop price tracking if let Some(order_price) = order.price { match order.side { OrderSide::Buy => current_price >= order_price, OrderSide::Sell => current_price <= order_price, } } else { false } }, OrderType::TrailingStop => { // For backtesting, treat as stop order if let Some(order_price) = order.price { match order.side { OrderSide::Buy => current_price >= order_price, OrderSide::Sell => current_price <= order_price, } } else { false } }, OrderType::Hidden => { // For backtesting, treat as market order true }, _ => { // Default case for any other order types false }, } } /// Execute an order async fn execute_order(&mut self, mut order: Order) -> Result<()> { let current_price = if let Some(market_event) = self.market_data.get(&order.symbol) { self.extract_price_from_event(&market_event)? } else { return Err(anyhow::anyhow!( "No market data for symbol: {}", order.symbol )); }; // Apply slippage let execution_price = self.apply_slippage(current_price, &order); // Calculate commission let commission = self.calculate_commission(&order, execution_price); // Update order order.status = OrderStatus::Filled; order.filled_quantity = order.quantity; order.average_price = Some(execution_price); // Update account let mut account = self.account.write().await; let trade_value = order.quantity.to_decimal().unwrap_or(Decimal::ZERO) * execution_price.to_decimal().unwrap_or(Decimal::ZERO); match order.side { OrderSide::Buy => { account.cash_balance -= trade_value + commission; }, OrderSide::Sell => { account.cash_balance += trade_value - commission; }, } // Update positions self.position_tracker .update_position(&order.symbol, &order, execution_price) .await?; // Record trade self.position_tracker .record_trade(&order, execution_price, commission) .await; // Notify strategy of order update let context = self.build_strategy_context().await?; self.strategy.on_order_update(&order, &context).await?; info!( "Executed order: {:?} {} {} @ {} (commission: {})", order.side, order.quantity, order.symbol, execution_price, commission ); Ok(()) } /// Execute a trading signal async fn execute_trading_signal(&mut self, signal: TradingSignal) -> Result<()> { let order = self.convert_signal_to_order(signal).await?; self.order_manager.place_order(order).await?; Ok(()) } /// Convert trading signal to order async fn convert_signal_to_order(&self, signal: TradingSignal) -> Result { let order_id = self.order_manager.generate_order_id(); let (side, order_type, price) = match signal.signal_type { SignalType::Buy => (OrderSide::Buy, OrderType::Market, Price::zero()), SignalType::Sell => (OrderSide::Sell, OrderType::Market, Price::zero()), SignalType::Short => (OrderSide::Sell, OrderType::Market, Price::zero()), SignalType::Cover => (OrderSide::Buy, OrderType::Market, Price::zero()), _ => { return Err(anyhow::anyhow!( "Unsupported signal type: {:?}", signal.signal_type )) }, }; Ok(Order { id: order_id.clone(), client_order_id: Some(format!("client_{}", Uuid::new_v4())), broker_order_id: None, account_id: Some("default".to_string()), symbol: signal.symbol, side, order_type, status: OrderStatus::Pending, time_in_force: TimeInForce::Day, quantity: signal.quantity, price: Some(price), stop_price: None, filled_quantity: Quantity::zero(), remaining_quantity: signal.quantity, average_price: None, avg_fill_price: None, parent_id: None, execution_algorithm: None, execution_params: serde_json::json!({}), stop_loss: None, take_profit: None, created_at: common::HftTimestamp::now_or_zero(), updated_at: None, expires_at: None, metadata: serde_json::json!({}), }) } /// Apply slippage to execution price fn apply_slippage(&self, price: Price, order: &Order) -> Price { let slippage = price.to_decimal().unwrap_or(Decimal::ZERO) * self.config.slippage_factor; match order.side { OrderSide::Buy => Price::from_f64( (price.to_decimal().unwrap_or(Decimal::ZERO) + slippage) .try_into() .unwrap_or(0.0), ) .unwrap_or(Price::zero()), OrderSide::Sell => Price::from_f64( (price.to_decimal().unwrap_or(Decimal::ZERO) - slippage) .try_into() .unwrap_or(0.0), ) .unwrap_or(Price::zero()), } } /// Calculate commission for trade fn calculate_commission(&self, order: &Order, price: Price) -> Decimal { let trade_value = order.quantity.to_decimal().unwrap_or(Decimal::ZERO) * price.to_decimal().unwrap_or(Decimal::ZERO); trade_value * self.config.commission_rate } /// Extract price from market event fn extract_price_from_event(&self, event: &MarketEvent) -> Result { match event { MarketEvent::Trade { price, .. } => Ok(*price), MarketEvent::Quote { bid_price, ask_price, .. } => { let avg_price = (bid_price.to_decimal().unwrap_or(Decimal::ZERO) + ask_price.to_decimal().unwrap_or(Decimal::ZERO)) / Decimal::from(2); Ok(Price::from_f64(avg_price.try_into().unwrap_or(0.0)).unwrap_or(Price::ZERO)) }, MarketEvent::Bar { close, .. } => Ok(*close), _ => Err(anyhow::anyhow!( "Cannot extract price from event: {:?}", event )), } } /// Build strategy context async fn build_strategy_context(&self) -> Result { let account = self.account.read().await; let positions = self.position_tracker.get_all_positions().await; let open_orders = self.order_manager.get_all_orders().await; let performance = self.performance_tracker.read().await.metrics.clone(); let mut market_prices = HashMap::new(); for item in self.market_data.iter() { let symbol = item.key(); let event = item.value(); if let Ok(price) = self.extract_price_from_event(event) { market_prices.insert(symbol.clone(), price); } } Ok(StrategyContext { current_time: Utc::now(), account_balance: account.cash_balance, buying_power: account.cash_balance, // Simplified positions, open_orders, market_prices, performance, }) } /// Take performance snapshot async fn take_performance_snapshot(&self, context: &StrategyContext) -> Result<()> { let mut tracker = self.performance_tracker.write().await; let snapshot = PerformanceSnapshot { timestamp: context.current_time, portfolio_value: context.account_balance, cash_balance: context.account_balance, unrealized_pnl: context.performance.total_unrealized_pnl, realized_pnl: context.performance.total_realized_pnl, open_positions: context.positions.len() as u32, drawdown: context.performance.current_drawdown, }; tracker.snapshots.push(snapshot); tracker.last_snapshot = Some(context.current_time); Ok(()) } } impl OrderManager { pub fn new() -> Self { Self { orders: DashMap::new(), order_history: RwLock::new(Vec::new()), next_order_id: std::sync::atomic::AtomicU64::new(1), } } pub fn generate_order_id(&self) -> OrderId { format!( "order_{}", self.next_order_id .fetch_add(1, std::sync::atomic::Ordering::Relaxed) ) .into() } pub async fn place_order(&self, order: Order) -> Result<()> { let order_id = order.id.clone(); self.orders.insert(order_id, order); Ok(()) } pub async fn get_pending_orders(&self) -> Vec { self.orders .iter() .filter(|entry| entry.value().status == OrderStatus::Pending) .map(|entry| entry.value().clone()) .collect() } pub async fn get_all_orders(&self) -> HashMap { self.orders .iter() .map(|entry| (entry.key().clone(), entry.value().clone())) .collect() } } impl PositionTracker { pub fn new() -> Self { Self { positions: DashMap::new(), position_history: RwLock::new(Vec::new()), trade_records: RwLock::new(Vec::new()), } } pub async fn get_all_positions(&self) -> HashMap { self.positions .iter() .map(|entry| (entry.key().clone(), entry.value().clone())) .collect() } pub async fn update_position( &self, _symbol: &Symbol, _order: &Order, _execution_price: Price, ) -> Result<()> { // Position update logic would be implemented here // This is a simplified version Ok(()) } pub async fn record_trade( &self, _order: &Order, _execution_price: Price, _commission: Decimal, ) { // Trade recording logic would be implemented here } } impl PerformanceTracker { pub fn new() -> Self { Self { metrics: PerformanceMetrics::default(), snapshots: Vec::new(), last_snapshot: None, } } } #[cfg(test)] mod tests { use super::*; struct TestStrategy { name: String, } #[async_trait(?Send)] impl Strategy for TestStrategy { fn name(&self) -> &str { &self.name } async fn initialize( &mut self, _initial_capital: Decimal, _config: StrategyConfig, ) -> Result<()> { Ok(()) } async fn on_market_event( &mut self, _event: &MarketEvent, _context: &StrategyContext, ) -> Result> { Ok(vec![]) } async fn on_order_update( &mut self, _order: &Order, _context: &StrategyContext, ) -> Result<()> { Ok(()) } async fn on_position_update( &mut self, _position: &Position, _context: &StrategyContext, ) -> Result<()> { Ok(()) } async fn finalize(&mut self, _context: &StrategyContext) -> Result { Ok(StrategyResult { strategy_name: self.name.clone(), total_return: Decimal::ZERO, annualized_return: Decimal::ZERO, max_drawdown: Decimal::ZERO, sharpe_ratio: Decimal::ZERO, total_trades: 0, win_rate: Decimal::ZERO, avg_trade_return: Decimal::ZERO, final_value: Decimal::from(100000), trades: vec![], performance_timeline: vec![], }) } async fn get_state(&self) -> Result { Ok(serde_json::json!({"name": self.name})) } } #[tokio::test] async fn test_strategy_tester_creation() { use crate::replay_engine::{MarketReplay, ReplayConfig}; let strategy = Box::new(TestStrategy { name: "test_strategy".to_string(), }); let config = StrategyConfig::default(); let replay_config = ReplayConfig::default(); let market_replay = Arc::new(MarketReplay::new(replay_config)); let initial_capital = Decimal::from(100000); let tester = StrategyTester::new(strategy, config, market_replay, initial_capital); assert_eq!(tester.strategy.name(), "test_strategy"); } }