hyperliquid_backtest/
paper_trading.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4use rand::distributions::Distribution;
5use rand_distr::Normal;
6use rand::thread_rng;
7use chrono::{DateTime, FixedOffset, Utc};
8use log::{info, warn, error};
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use uuid::Uuid;
12
13use crate::trading_mode::SlippageConfig;
14use crate::unified_data::{
15    Position, OrderRequest, OrderResult, MarketData, 
16    OrderSide, OrderType, OrderStatus,
17    TradingStrategy
18};
19use crate::real_time_data_stream::RealTimeDataStream;
20
21/// Error types specific to paper trading operations
22#[derive(Debug, Error)]
23pub enum PaperTradingError {
24    /// Error when market data is not available
25    #[error("Market data not available for {0}")]
26    MarketDataNotAvailable(String),
27    
28    /// Error when order execution fails
29    #[error("Order execution failed: {0}")]
30    OrderExecutionFailed(String),
31    
32    /// Error when position is not found
33    #[error("Position not found for {0}")]
34    PositionNotFound(String),
35    
36    /// Error when insufficient balance
37    #[error("Insufficient balance: required {required}, available {available}")]
38    InsufficientBalance {
39        required: f64,
40        available: f64,
41    },
42    
43    /// Error when real-time data stream fails
44    #[error("Real-time data stream error: {0}")]
45    RealTimeDataError(String),
46    
47    /// Error when strategy execution fails
48    #[error("Strategy execution error: {0}")]
49    StrategyError(String),
50}
51
52/// Represents a simulated order in the paper trading system
53#[derive(Debug, Clone)]
54pub struct SimulatedOrder {
55    /// Order ID
56    pub order_id: String,
57    
58    /// Original order request
59    pub request: OrderRequest,
60    
61    /// Order result
62    pub result: OrderResult,
63    
64    /// Creation timestamp
65    pub created_at: DateTime<FixedOffset>,
66    
67    /// Last update timestamp
68    pub updated_at: DateTime<FixedOffset>,
69    
70    /// Execution delay in milliseconds
71    pub execution_delay_ms: u64,
72    
73    /// Slippage applied (percentage)
74    pub slippage_pct: f64,
75}
76
77/// Paper trading engine for simulating trading with real-time data
78pub struct PaperTradingEngine {
79    /// Simulated account balance
80    simulated_balance: f64,
81    
82    /// Simulated positions
83    simulated_positions: HashMap<String, Position>,
84    
85    /// Order history
86    order_history: Vec<SimulatedOrder>,
87    
88    /// Active orders
89    active_orders: HashMap<String, SimulatedOrder>,
90    
91    /// Real-time data stream
92    real_time_data: Option<Arc<Mutex<RealTimeDataStream>>>,
93    
94    /// Latest market data
95    market_data_cache: HashMap<String, MarketData>,
96    
97    /// Slippage model configuration
98    slippage_config: SlippageConfig,
99    
100    /// Trading fees (maker and taker)
101    maker_fee: f64,
102    taker_fee: f64,
103    
104    /// Performance metrics
105    metrics: PaperTradingMetrics,
106    
107    /// Trade log
108    trade_log: Vec<TradeLogEntry>,
109    
110    /// Is simulation running
111    is_running: bool,
112    
113    /// Last update timestamp
114    last_update: DateTime<FixedOffset>,
115}
116
117/// Performance metrics for paper trading
118#[derive(Debug, Clone)]
119pub struct PaperTradingMetrics {
120    /// Initial balance
121    pub initial_balance: f64,
122    
123    /// Current balance
124    pub current_balance: f64,
125    
126    /// Realized profit and loss
127    pub realized_pnl: f64,
128    
129    /// Unrealized profit and loss
130    pub unrealized_pnl: f64,
131    
132    /// Funding profit and loss
133    pub funding_pnl: f64,
134    
135    /// Total fees paid
136    pub total_fees: f64,
137    
138    /// Number of trades
139    pub trade_count: usize,
140    
141    /// Number of winning trades
142    pub winning_trades: usize,
143    
144    /// Number of losing trades
145    pub losing_trades: usize,
146    
147    /// Maximum drawdown
148    pub max_drawdown: f64,
149    
150    /// Maximum drawdown percentage
151    pub max_drawdown_pct: f64,
152    
153    /// Peak balance
154    pub peak_balance: f64,
155    
156    /// Start time
157    pub start_time: DateTime<FixedOffset>,
158    
159    /// Last update time
160    pub last_update: DateTime<FixedOffset>,
161}
162
163/// Trade log entry
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct TradeLogEntry {
166    /// Entry ID
167    pub id: String,
168    
169    /// Symbol
170    pub symbol: String,
171    
172    /// Side (buy/sell)
173    pub side: OrderSide,
174    
175    /// Quantity
176    pub quantity: f64,
177    
178    /// Price
179    pub price: f64,
180    
181    /// Timestamp
182    pub timestamp: DateTime<FixedOffset>,
183    
184    /// Fees paid
185    pub fees: f64,
186    
187    /// Order type
188    pub order_type: OrderType,
189    
190    /// Related order ID
191    pub order_id: String,
192    
193    /// Profit and loss for this trade
194    pub pnl: Option<f64>,
195    
196    /// Additional metadata
197    pub metadata: HashMap<String, String>,
198}
199
200impl PaperTradingEngine {
201    /// Create a new paper trading engine with the specified initial balance and slippage configuration
202    pub fn new(initial_balance: f64, slippage_config: SlippageConfig) -> Self {
203        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
204        
205        let metrics = PaperTradingMetrics {
206            initial_balance,
207            current_balance: initial_balance,
208            realized_pnl: 0.0,
209            unrealized_pnl: 0.0,
210            funding_pnl: 0.0,
211            total_fees: 0.0,
212            trade_count: 0,
213            winning_trades: 0,
214            losing_trades: 0,
215            max_drawdown: 0.0,
216            max_drawdown_pct: 0.0,
217            peak_balance: initial_balance,
218            start_time: now,
219            last_update: now,
220        };
221        
222        Self {
223            simulated_balance: initial_balance,
224            simulated_positions: HashMap::new(),
225            order_history: Vec::new(),
226            active_orders: HashMap::new(),
227            real_time_data: None,
228            market_data_cache: HashMap::new(),
229            slippage_config,
230            maker_fee: 0.0002, // 0.02% maker fee
231            taker_fee: 0.0005, // 0.05% taker fee
232            metrics,
233            trade_log: Vec::new(),
234            is_running: false,
235            last_update: now,
236        }
237    }
238    
239    /// Set the real-time data stream
240    pub fn set_real_time_data(&mut self, data_stream: Arc<Mutex<RealTimeDataStream>>) {
241        self.real_time_data = Some(data_stream);
242    }
243    
244    /// Set the fee rates
245    pub fn set_fees(&mut self, maker_fee: f64, taker_fee: f64) {
246        self.maker_fee = maker_fee;
247        self.taker_fee = taker_fee;
248    }
249    
250    /// Get the current simulated balance
251    pub fn get_balance(&self) -> f64 {
252        self.simulated_balance
253    }
254    
255    /// Get the current positions
256    pub fn get_positions(&self) -> &HashMap<String, Position> {
257        &self.simulated_positions
258    }
259    
260    /// Get the order history
261    pub fn get_order_history(&self) -> &Vec<SimulatedOrder> {
262        &self.order_history
263    }
264    
265    /// Get the active orders
266    pub fn get_active_orders(&self) -> &HashMap<String, SimulatedOrder> {
267        &self.active_orders
268    }
269    
270    /// Get the trade log
271    pub fn get_trade_log(&self) -> &Vec<TradeLogEntry> {
272        &self.trade_log
273    }
274    
275    /// Get the performance metrics
276    pub fn get_metrics(&self) -> &PaperTradingMetrics {
277        &self.metrics
278    }
279    
280    /// Get the portfolio value (balance + position values)
281    pub fn get_portfolio_value(&self) -> f64 {
282        let position_value = self.simulated_positions.values()
283            .map(|p| p.notional_value())
284            .sum::<f64>();
285        
286        self.simulated_balance + position_value
287    }
288    
289    /// Update market data
290    pub fn update_market_data(&mut self, data: MarketData) -> Result<(), PaperTradingError> {
291        // Update the market data cache
292        self.market_data_cache.insert(data.symbol.clone(), data.clone());
293        
294        // Update position prices if we have a position in this symbol
295        if let Some(position) = self.simulated_positions.get_mut(&data.symbol) {
296            position.update_price(data.price);
297        }
298        
299        // Process any active orders that might be affected by this price update
300        self.process_active_orders(&data)?;
301        
302        // Update metrics
303        self.update_metrics();
304        
305        Ok(())
306    }
307    
308    /// Execute an order
309    pub async fn execute_order(&mut self, order: OrderRequest) -> Result<OrderResult, PaperTradingError> {
310        // Validate the order
311        if let Err(err) = order.validate() {
312            return Err(PaperTradingError::OrderExecutionFailed(err));
313        }
314        
315        // Get the latest market data for this symbol
316        let market_data = self.get_market_data(&order.symbol)?;
317        
318        // Generate a unique order ID
319        let order_id = Uuid::new_v4().to_string();
320        
321        // Create the initial order result
322        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
323        let mut order_result = OrderResult::new(
324            &order_id,
325            &order.symbol,
326            order.side,
327            order.order_type,
328            order.quantity,
329            now,
330        );
331        order_result.status = OrderStatus::Submitted;
332        
333        // For market orders, execute immediately with simulated slippage
334        if order.order_type == OrderType::Market {
335            // Calculate execution price with slippage
336            let execution_price = self.calculate_execution_price(&order, &market_data);
337            
338            // Calculate fees
339            let fee_rate = self.taker_fee; // Market orders are taker orders
340            let fee_amount = order.quantity * execution_price * fee_rate;
341            
342            // Update the order result
343            order_result.status = OrderStatus::Filled;
344            order_result.filled_quantity = order.quantity;
345            order_result.average_price = Some(execution_price);
346            order_result.fees = Some(fee_amount);
347            
348            // Update positions and balance
349            self.update_position_and_balance(&order, execution_price, fee_amount)?;
350            
351            // Add to order history
352            let simulated_order = SimulatedOrder {
353                order_id: order_id.clone(),
354                request: order.clone(),
355                result: order_result.clone(),
356                created_at: now,
357                updated_at: now,
358                execution_delay_ms: self.slippage_config.simulated_latency_ms,
359                slippage_pct: (execution_price - market_data.price) / market_data.price * 100.0,
360            };
361            self.order_history.push(simulated_order);
362            
363            // Add to trade log
364            self.add_trade_log_entry(&order, &order_result);
365            
366            // Update metrics
367            self.update_metrics();
368            
369            return Ok(order_result);
370        } else {
371            // For limit orders, add to active orders
372            let simulated_order = SimulatedOrder {
373                order_id: order_id.clone(),
374                request: order.clone(),
375                result: order_result.clone(),
376                created_at: now,
377                updated_at: now,
378                execution_delay_ms: self.slippage_config.simulated_latency_ms,
379                slippage_pct: 0.0, // Will be calculated on execution
380            };
381            
382            self.active_orders.insert(order_id.clone(), simulated_order);
383            
384            return Ok(order_result);
385        }
386    }
387    
388    /// Cancel an order
389    pub fn cancel_order(&mut self, order_id: &str) -> Result<OrderResult, PaperTradingError> {
390        // Check if the order exists
391        if let Some(mut order) = self.active_orders.remove(order_id) {
392            // Update the order result
393            let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
394            order.result.status = OrderStatus::Cancelled;
395            order.updated_at = now;
396            
397            // Add to order history
398            self.order_history.push(order.clone());
399            
400            return Ok(order.result);
401        } else {
402            return Err(PaperTradingError::OrderExecutionFailed(
403                format!("Order not found: {}", order_id)
404            ));
405        }
406    }
407    
408    /// Start the paper trading simulation with the given strategy
409    pub async fn start_simulation(&mut self, strategy: Box<dyn TradingStrategy>) -> Result<(), PaperTradingError> {
410        if self.real_time_data.is_none() {
411            return Err(PaperTradingError::RealTimeDataError(
412                "Real-time data stream not set".to_string()
413            ));
414        }
415        
416        self.is_running = true;
417        info!("Starting paper trading simulation with strategy: {}", strategy.name());
418        
419        // Main simulation loop
420        while self.is_running {
421            // Process market data updates
422            self.process_market_data_updates(strategy.as_ref()).await?;
423            
424            // Simulate some delay to avoid CPU spinning
425            tokio::time::sleep(Duration::from_millis(100)).await;
426        }
427        
428        info!("Paper trading simulation stopped");
429        Ok(())
430    }
431    
432    /// Stop the paper trading simulation
433    pub fn stop_simulation(&mut self) {
434        self.is_running = false;
435        info!("Stopping paper trading simulation");
436    }
437    
438    /// Apply a funding payment
439    pub fn apply_funding_payment(&mut self, symbol: &str, payment: f64) -> Result<(), PaperTradingError> {
440        if let Some(position) = self.simulated_positions.get_mut(symbol) {
441            position.apply_funding_payment(payment);
442            self.metrics.funding_pnl += payment;
443            Ok(())
444        } else {
445            Err(PaperTradingError::PositionNotFound(symbol.to_string()))
446        }
447    }
448    
449    /// Get the current market data for a symbol
450    fn get_market_data(&self, symbol: &str) -> Result<MarketData, PaperTradingError> {
451        if let Some(data) = self.market_data_cache.get(symbol) {
452            Ok(data.clone())
453        } else {
454            Err(PaperTradingError::MarketDataNotAvailable(symbol.to_string()))
455        }
456    }
457    
458    /// Calculate the execution price with slippage
459    fn calculate_execution_price(&self, order: &OrderRequest, market_data: &MarketData) -> f64 {
460        let base_price = match order.side {
461            OrderSide::Buy => market_data.ask,  // Buy at ask price
462            OrderSide::Sell => market_data.bid, // Sell at bid price
463        };
464        
465        // Calculate slippage based on configuration
466        let mut slippage_pct = self.slippage_config.base_slippage_pct;
467        
468        // Add volume-based slippage
469        let volume_impact = order.quantity / market_data.volume * self.slippage_config.volume_impact_factor;
470        slippage_pct += volume_impact;
471        
472        // Add random slippage component
473        let mut rng = thread_rng();
474        let normal = Normal::new(0.0, self.slippage_config.random_slippage_max_pct / 2.0).unwrap();
475        let random_slippage = normal.sample(&mut rng);
476        slippage_pct += random_slippage;
477        
478        // Cap slippage at maximum
479        slippage_pct = slippage_pct.min(0.01); // Cap at 1% max slippage
480        
481        // Apply slippage to price
482        let slippage_factor = match order.side {
483            OrderSide::Buy => 1.0 + slippage_pct,  // Higher price for buys
484            OrderSide::Sell => 1.0 - slippage_pct, // Lower price for sells
485        };
486        
487        base_price * slippage_factor
488    }
489    
490    /// Update position and balance after an order execution
491    fn update_position_and_balance(&mut self, order: &OrderRequest, execution_price: f64, fee_amount: f64) -> Result<(), PaperTradingError> {
492        let symbol = &order.symbol;
493        let quantity = order.quantity;
494        let side = order.side;
495        
496        // Calculate the order cost
497        let order_cost = quantity * execution_price;
498        
499        // Check if we have enough balance for a buy order
500        if side == OrderSide::Buy && order_cost + fee_amount > self.simulated_balance {
501            return Err(PaperTradingError::InsufficientBalance {
502                required: order_cost + fee_amount,
503                available: self.simulated_balance,
504            });
505        }
506        
507        // Update the position
508        let position = self.simulated_positions.entry(symbol.clone())
509            .or_insert_with(|| {
510                let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
511                Position::new(symbol, 0.0, 0.0, execution_price, now)
512            });
513        
514        // Calculate the position change
515        let position_change = match side {
516            OrderSide::Buy => quantity,
517            OrderSide::Sell => -quantity,
518        };
519        
520        // If this is a closing trade, calculate PnL
521        let mut realized_pnl = 0.0;
522        if (position.size > 0.0 && position_change < 0.0) || (position.size < 0.0 && position_change > 0.0) {
523            // Closing trade (partial or full)
524            let closing_size = position_change.abs().min(position.size.abs());
525            realized_pnl = closing_size * (execution_price - position.entry_price) * position.size.signum();
526            
527            // Update metrics
528            self.metrics.realized_pnl += realized_pnl;
529            self.metrics.trade_count += 1;
530            if realized_pnl > 0.0 {
531                self.metrics.winning_trades += 1;
532            } else if realized_pnl < 0.0 {
533                self.metrics.losing_trades += 1;
534            }
535        }
536        
537        // Update position size and entry price
538        if position.size + position_change == 0.0 {
539            // Position closed completely
540            position.size = 0.0;
541            position.entry_price = 0.0;
542        } else if position.size * (position.size + position_change) < 0.0 {
543            // Position flipped from long to short or vice versa
544            position.size = position_change;
545            position.entry_price = execution_price;
546        } else if position.size == 0.0 {
547            // New position
548            position.size = position_change;
549            position.entry_price = execution_price;
550        } else {
551            // Position increased or partially decreased
552            let old_notional = position.size.abs() * position.entry_price;
553            let new_notional = position_change.abs() * execution_price;
554            let total_size = position.size + position_change;
555            
556            if total_size != 0.0 {
557                position.entry_price = (old_notional + new_notional) / total_size.abs();
558            }
559            position.size = total_size;
560        }
561        
562        // Update current price
563        position.current_price = execution_price;
564        
565        // Update balance
566        match side {
567            OrderSide::Buy => {
568                self.simulated_balance -= order_cost + fee_amount;
569            },
570            OrderSide::Sell => {
571                self.simulated_balance += order_cost - fee_amount;
572                self.simulated_balance += realized_pnl;
573            },
574        }
575        
576        // Update metrics
577        self.metrics.total_fees += fee_amount;
578        self.update_metrics();
579        
580        Ok(())
581    }
582    
583    /// Process active orders based on new market data
584    fn process_active_orders(&mut self, market_data: &MarketData) -> Result<(), PaperTradingError> {
585        let symbol = &market_data.symbol;
586        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
587        
588        // Collect orders that need to be processed
589        let orders_to_process: Vec<String> = self.active_orders.iter()
590            .filter(|(_, order)| order.request.symbol == *symbol)
591            .map(|(id, _)| id.clone())
592            .collect();
593        
594        for order_id in orders_to_process {
595            if let Some(mut order) = self.active_orders.remove(&order_id) {
596                let request = &order.request;
597                
598                // Check if the order should be executed based on price
599                let should_execute = match (request.order_type, request.side) {
600                    (OrderType::Limit, OrderSide::Buy) => {
601                        // Buy limit: execute if ask price <= limit price
602                        if let Some(limit_price) = request.price {
603                            market_data.ask <= limit_price
604                        } else {
605                            false
606                        }
607                    },
608                    (OrderType::Limit, OrderSide::Sell) => {
609                        // Sell limit: execute if bid price >= limit price
610                        if let Some(limit_price) = request.price {
611                            market_data.bid >= limit_price
612                        } else {
613                            false
614                        }
615                    },
616                    (OrderType::StopMarket, OrderSide::Buy) => {
617                        // Buy stop: execute if price >= stop price
618                        if let Some(stop_price) = request.stop_price {
619                            market_data.price >= stop_price
620                        } else {
621                            false
622                        }
623                    },
624                    (OrderType::StopMarket, OrderSide::Sell) => {
625                        // Sell stop: execute if price <= stop price
626                        if let Some(stop_price) = request.stop_price {
627                            market_data.price <= stop_price
628                        } else {
629                            false
630                        }
631                    },
632                    _ => false, // Other order types not implemented yet
633                };
634                
635                if should_execute {
636                    // Calculate execution price
637                    let execution_price = match request.order_type {
638                        OrderType::Limit => request.price.unwrap_or(market_data.price),
639                        _ => self.calculate_execution_price(request, market_data),
640                    };
641                    
642                    // Calculate fees
643                    let fee_rate = match request.order_type {
644                        OrderType::Limit => self.maker_fee, // Limit orders are maker orders
645                        _ => self.taker_fee,                // Other orders are taker orders
646                    };
647                    let fee_amount = request.quantity * execution_price * fee_rate;
648                    
649                    // Update the order result
650                    order.result.status = OrderStatus::Filled;
651                    order.result.filled_quantity = request.quantity;
652                    order.result.average_price = Some(execution_price);
653                    order.result.fees = Some(fee_amount);
654                    order.updated_at = now;
655                    
656                    // Update positions and balance
657                    if let Err(err) = self.update_position_and_balance(request, execution_price, fee_amount) {
658                        // If there's an error (e.g., insufficient balance), reject the order
659                        order.result.status = OrderStatus::Rejected;
660                        order.result.error = Some(err.to_string());
661                    }
662                    
663                    // Add to order history
664                    self.order_history.push(order.clone());
665                    
666                    // Add to trade log if executed
667                    if order.result.status == OrderStatus::Filled {
668                        self.add_trade_log_entry(request, &order.result);
669                    }
670                } else {
671                    // Order not executed yet, put it back in active orders
672                    self.active_orders.insert(order_id, order);
673                }
674            }
675        }
676        
677        Ok(())
678    }
679    
680    /// Process market data updates and execute strategy
681    async fn process_market_data_updates(&mut self, strategy: &dyn TradingStrategy) -> Result<(), PaperTradingError> {
682        // Get the latest market data from the real-time stream
683        if let Some(data_stream) = &self.real_time_data {
684            if let Ok(stream) = data_stream.lock() {
685                // This is a placeholder since we don't have the actual RealTimeDataStream implementation
686                // In a real implementation, we would get the latest data from the stream
687                // For now, we'll just use the cached data
688            }
689        }
690        
691        // Collect market data to avoid borrowing issues
692        let market_data_vec: Vec<_> = self.market_data_cache.values().cloned().collect();
693        
694        // Process each market data update
695        for market_data in market_data_vec {
696            // Execute strategy on market data (placeholder - would need mutable strategy)
697            // match strategy.on_market_data(&market_data) {
698            match Ok(vec![]) as Result<Vec<OrderRequest>, String> {
699                Ok(order_requests) => {
700                    // Execute any orders generated by the strategy
701                    for order_request in order_requests {
702                        match self.execute_order(order_request).await {
703                            Ok(_) => {},
704                            Err(err) => {
705                                warn!("Failed to execute order: {}", err);
706                            }
707                        }
708                    }
709                },
710                Err(err) => {
711                    return Err(PaperTradingError::StrategyError(err));
712                }
713            }
714        }
715        
716        Ok(())
717    }
718    
719    /// Add a trade log entry
720    fn add_trade_log_entry(&mut self, order: &OrderRequest, result: &OrderResult) {
721        if result.status != OrderStatus::Filled || result.average_price.is_none() {
722            return;
723        }
724        
725        let entry = TradeLogEntry {
726            id: Uuid::new_v4().to_string(),
727            symbol: order.symbol.clone(),
728            side: order.side,
729            quantity: result.filled_quantity,
730            price: result.average_price.unwrap(),
731            timestamp: result.timestamp,
732            fees: result.fees.unwrap_or(0.0),
733            order_type: order.order_type,
734            order_id: result.order_id.clone(),
735            pnl: None, // Will be calculated later for closing trades
736            metadata: HashMap::new(),
737        };
738        
739        self.trade_log.push(entry);
740    }
741    
742    /// Update performance metrics
743    fn update_metrics(&mut self) {
744        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
745        
746        // Calculate unrealized PnL
747        let unrealized_pnl = self.simulated_positions.values()
748            .map(|p| p.unrealized_pnl)
749            .sum::<f64>();
750        
751        // Calculate funding PnL
752        let funding_pnl = self.simulated_positions.values()
753            .map(|p| p.funding_pnl)
754            .sum::<f64>();
755        
756        // Update metrics
757        self.metrics.current_balance = self.simulated_balance;
758        self.metrics.unrealized_pnl = unrealized_pnl;
759        self.metrics.funding_pnl = funding_pnl;
760        self.metrics.last_update = now;
761        
762        // Update peak balance and drawdown
763        let total_equity = self.simulated_balance + unrealized_pnl + funding_pnl;
764        if total_equity > self.metrics.peak_balance {
765            self.metrics.peak_balance = total_equity;
766        } else {
767            let drawdown = self.metrics.peak_balance - total_equity;
768            let drawdown_pct = if self.metrics.peak_balance > 0.0 {
769                drawdown / self.metrics.peak_balance * 100.0
770            } else {
771                0.0
772            };
773            
774            if drawdown > self.metrics.max_drawdown {
775                self.metrics.max_drawdown = drawdown;
776                self.metrics.max_drawdown_pct = drawdown_pct;
777            }
778        }
779    }
780    
781    /// Generate a performance report
782    pub fn generate_report(&self) -> PaperTradingReport {
783        let now = Utc::now().with_timezone(&FixedOffset::east_opt(0).unwrap());
784        let duration = now.signed_duration_since(self.metrics.start_time);
785        let duration_days = duration.num_milliseconds() as f64 / (1000.0 * 60.0 * 60.0 * 24.0);
786        
787        let total_equity = self.simulated_balance + self.metrics.unrealized_pnl + self.metrics.funding_pnl;
788        let total_return = total_equity - self.metrics.initial_balance;
789        let total_return_pct = if self.metrics.initial_balance > 0.0 {
790            total_return / self.metrics.initial_balance * 100.0
791        } else {
792            0.0
793        };
794        
795        let annualized_return = if duration_days > 0.0 {
796            (total_return_pct / 100.0 + 1.0).powf(365.0 / duration_days) - 1.0
797        } else {
798            0.0
799        } * 100.0;
800        
801        let win_rate = if self.metrics.trade_count > 0 {
802            self.metrics.winning_trades as f64 / self.metrics.trade_count as f64 * 100.0
803        } else {
804            0.0
805        };
806        
807        PaperTradingReport {
808            initial_balance: self.metrics.initial_balance,
809            current_balance: self.simulated_balance,
810            unrealized_pnl: self.metrics.unrealized_pnl,
811            realized_pnl: self.metrics.realized_pnl,
812            funding_pnl: self.metrics.funding_pnl,
813            total_pnl: self.metrics.realized_pnl + self.metrics.unrealized_pnl + self.metrics.funding_pnl,
814            total_fees: self.metrics.total_fees,
815            total_equity,
816            total_return,
817            total_return_pct,
818            annualized_return,
819            trade_count: self.metrics.trade_count,
820            winning_trades: self.metrics.winning_trades,
821            losing_trades: self.metrics.losing_trades,
822            win_rate,
823            max_drawdown: self.metrics.max_drawdown,
824            max_drawdown_pct: self.metrics.max_drawdown_pct,
825            start_time: self.metrics.start_time,
826            end_time: now,
827            duration_days,
828        }
829    }
830}
831
832/// Paper trading performance report
833#[derive(Debug, Clone)]
834pub struct PaperTradingReport {
835    /// Initial balance
836    pub initial_balance: f64,
837    
838    /// Current balance
839    pub current_balance: f64,
840    
841    /// Unrealized profit and loss
842    pub unrealized_pnl: f64,
843    
844    /// Realized profit and loss
845    pub realized_pnl: f64,
846    
847    /// Funding profit and loss
848    pub funding_pnl: f64,
849    
850    /// Total profit and loss
851    pub total_pnl: f64,
852    
853    /// Total fees paid
854    pub total_fees: f64,
855    
856    /// Total equity (balance + unrealized PnL)
857    pub total_equity: f64,
858    
859    /// Total return (absolute)
860    pub total_return: f64,
861    
862    /// Total return (percentage)
863    pub total_return_pct: f64,
864    
865    /// Annualized return (percentage)
866    pub annualized_return: f64,
867    
868    /// Number of trades
869    pub trade_count: usize,
870    
871    /// Number of winning trades
872    pub winning_trades: usize,
873    
874    /// Number of losing trades
875    pub losing_trades: usize,
876    
877    /// Win rate (percentage)
878    pub win_rate: f64,
879    
880    /// Maximum drawdown
881    pub max_drawdown: f64,
882    
883    /// Maximum drawdown percentage
884    pub max_drawdown_pct: f64,
885    
886    /// Start time
887    pub start_time: DateTime<FixedOffset>,
888    
889    /// End time
890    pub end_time: DateTime<FixedOffset>,
891    
892    /// Duration in days
893    pub duration_days: f64,
894}
895
896impl std::fmt::Display for PaperTradingReport {
897    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
898        writeln!(f, "=== Paper Trading Performance Report ===")?;
899        writeln!(f, "Period: {} to {}", self.start_time, self.end_time)?;
900        writeln!(f, "Duration: {:.2} days", self.duration_days)?;
901        writeln!(f, "")?;
902        writeln!(f, "Initial Balance: ${:.2}", self.initial_balance)?;
903        writeln!(f, "Current Balance: ${:.2}", self.current_balance)?;
904        writeln!(f, "Unrealized P&L: ${:.2}", self.unrealized_pnl)?;
905        writeln!(f, "Realized P&L: ${:.2}", self.realized_pnl)?;
906        writeln!(f, "Funding P&L: ${:.2}", self.funding_pnl)?;
907        writeln!(f, "Total P&L: ${:.2}", self.total_pnl)?;
908        writeln!(f, "Total Fees: ${:.2}", self.total_fees)?;
909        writeln!(f, "")?;
910        writeln!(f, "Total Equity: ${:.2}", self.total_equity)?;
911        writeln!(f, "Total Return: ${:.2} ({:.2}%)", self.total_return, self.total_return_pct)?;
912        writeln!(f, "Annualized Return: {:.2}%", self.annualized_return)?;
913        writeln!(f, "")?;
914        writeln!(f, "Trade Count: {}", self.trade_count)?;
915        writeln!(f, "Winning Trades: {} ({:.2}%)", self.winning_trades, self.win_rate)?;
916        writeln!(f, "Losing Trades: {}", self.losing_trades)?;
917        writeln!(f, "Maximum Drawdown: ${:.2} ({:.2}%)", self.max_drawdown, self.max_drawdown_pct)?;
918        
919        Ok(())
920    }
921}
922
923#[cfg(test)]
924mod tests {
925    use super::*;
926    use std::sync::Arc;
927    
928    // Mock TradingStrategy for testing
929    struct MockStrategy {
930        name: String,
931        signals: HashMap<String, Signal>,
932    }
933    
934    impl TradingStrategy for MockStrategy {
935        fn name(&self) -> &str {
936            &self.name
937        }
938        
939        fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String> {
940            // Simple strategy: buy when price increases, sell when price decreases
941            let symbol = &data.symbol;
942            let signal = self.signals.get(symbol);
943            
944            match signal {
945                Some(signal) => {
946                    match signal.direction {
947                        SignalDirection::Buy => {
948                            Ok(vec![OrderRequest::market(symbol, OrderSide::Buy, 1.0)])
949                        },
950                        SignalDirection::Sell => {
951                            Ok(vec![OrderRequest::market(symbol, OrderSide::Sell, 1.0)])
952                        },
953                        _ => Ok(vec![]),
954                    }
955                },
956                None => Ok(vec![]),
957            }
958        }
959        
960        fn on_order_fill(&mut self, _fill: &OrderResult) -> Result<(), String> {
961            Ok(())
962        }
963        
964        fn on_funding_payment(&mut self, _payment: &FundingPayment) -> Result<(), String> {
965            Ok(())
966        }
967        
968        fn get_current_signals(&self) -> HashMap<String, Signal> {
969            self.signals.clone()
970        }
971    }
972    
973    #[test]
974    fn test_paper_trading_engine_creation() {
975        let slippage_config = SlippageConfig::default();
976        let engine = PaperTradingEngine::new(10000.0, slippage_config);
977        
978        assert_eq!(engine.simulated_balance, 10000.0);
979        assert!(engine.simulated_positions.is_empty());
980        assert!(engine.order_history.is_empty());
981        assert!(engine.active_orders.is_empty());
982    }
983    
984    #[test]
985    fn test_market_data_update() {
986        let slippage_config = SlippageConfig::default();
987        let mut engine = PaperTradingEngine::new(10000.0, slippage_config);
988        
989        let now = Utc::now().with_timezone(&FixedOffset::east(0));
990        let market_data = MarketData::new("BTC", 50000.0, 49990.0, 50010.0, 100.0, now);
991        
992        // Add a position first
993        let position = Position::new("BTC", 1.0, 49000.0, 49000.0, now);
994        engine.simulated_positions.insert("BTC".to_string(), position);
995        
996        // Update market data
997        engine.update_market_data(market_data).unwrap();
998        
999        // Check that position price was updated
1000        let updated_position = engine.simulated_positions.get("BTC").unwrap();
1001        assert_eq!(updated_position.current_price, 50000.0);
1002        assert_eq!(updated_position.unrealized_pnl, 1000.0); // 1 BTC * (50000 - 49000)
1003    }
1004    
1005    #[test]
1006    fn test_market_order_execution() {
1007        let slippage_config = SlippageConfig {
1008            base_slippage_pct: 0.0, // No slippage for testing
1009            volume_impact_factor: 0.0,
1010            volatility_impact_factor: 0.0,
1011            random_slippage_max_pct: 0.0,
1012            simulated_latency_ms: 0,
1013            use_order_book: false,
1014            max_slippage_pct: 0.0,
1015        };
1016        
1017        let mut engine = PaperTradingEngine::new(10000.0, slippage_config);
1018        
1019        let now = Utc::now().with_timezone(&FixedOffset::east(0));
1020        let market_data = MarketData::new("BTC", 50000.0, 49990.0, 50010.0, 100.0, now);
1021        
1022        // Add market data
1023        engine.market_data_cache.insert("BTC".to_string(), market_data);
1024        
1025        // Create a market buy order
1026        let order = OrderRequest::market("BTC", OrderSide::Buy, 0.1);
1027        
1028        // Execute the order
1029        let rt = tokio::runtime::Runtime::new().unwrap();
1030        let result = rt.block_on(engine.execute_order(order)).unwrap();
1031        
1032        // Check the result
1033        assert_eq!(result.status, OrderStatus::Filled);
1034        assert_eq!(result.filled_quantity, 0.1);
1035        assert!(result.average_price.is_some());
1036        assert!(result.fees.is_some());
1037        
1038        // Check the position
1039        let position = engine.simulated_positions.get("BTC").unwrap();
1040        assert_eq!(position.size, 0.1);
1041        assert_eq!(position.entry_price, 50010.0); // Buy at ask price
1042        
1043        // Check the balance (10000 - (0.1 * 50010) - fees)
1044        let fees = 0.1 * 50010.0 * engine.taker_fee;
1045        assert_eq!(engine.simulated_balance, 10000.0 - (0.1 * 50010.0) - fees);
1046    }
1047    
1048    #[test]
1049    fn test_limit_order_execution() {
1050        let slippage_config = SlippageConfig::default();
1051        let mut engine = PaperTradingEngine::new(10000.0, slippage_config);
1052        
1053        let now = Utc::now().with_timezone(&FixedOffset::east(0));
1054        let market_data = MarketData::new("BTC", 50000.0, 49990.0, 50010.0, 100.0, now);
1055        
1056        // Add market data
1057        engine.market_data_cache.insert("BTC".to_string(), market_data.clone());
1058        
1059        // Create a limit buy order below current ask price
1060        let limit_price = 49980.0;
1061        let order = OrderRequest::limit("BTC", OrderSide::Buy, 0.1, limit_price);
1062        
1063        // Execute the order (should be added to active orders)
1064        let rt = tokio::runtime::Runtime::new().unwrap();
1065        let result = rt.block_on(engine.execute_order(order)).unwrap();
1066        
1067        // Check the result
1068        assert_eq!(result.status, OrderStatus::Submitted);
1069        assert_eq!(engine.active_orders.len(), 1);
1070        
1071        // Update market data with lower ask price that triggers the limit order
1072        let new_market_data = MarketData::new("BTC", 49970.0, 49960.0, 49980.0, 100.0, now);
1073        engine.update_market_data(new_market_data).unwrap();
1074        
1075        // Check that the order was executed
1076        assert_eq!(engine.active_orders.len(), 0);
1077        assert_eq!(engine.order_history.len(), 1);
1078        
1079        // Check the position
1080        let position = engine.simulated_positions.get("BTC").unwrap();
1081        assert_eq!(position.size, 0.1);
1082        assert_eq!(position.entry_price, limit_price);
1083    }
1084    
1085    #[test]
1086    fn test_position_tracking() {
1087        let slippage_config = SlippageConfig {
1088            base_slippage_pct: 0.0, // No slippage for testing
1089            volume_impact_factor: 0.0,
1090            volatility_impact_factor: 0.0,
1091            random_slippage_max_pct: 0.0,
1092            simulated_latency_ms: 0,
1093            use_order_book: false,
1094            max_slippage_pct: 0.0,
1095        };
1096        
1097        let mut engine = PaperTradingEngine::new(10000.0, slippage_config);
1098        
1099        let now = Utc::now().with_timezone(&FixedOffset::east(0));
1100        let market_data = MarketData::new("BTC", 50000.0, 49990.0, 50010.0, 100.0, now);
1101        
1102        // Add market data
1103        engine.market_data_cache.insert("BTC".to_string(), market_data);
1104        
1105        // Create and execute a market buy order
1106        let buy_order = OrderRequest::market("BTC", OrderSide::Buy, 0.1);
1107        let rt = tokio::runtime::Runtime::new().unwrap();
1108        rt.block_on(engine.execute_order(buy_order)).unwrap();
1109        
1110        // Check the position
1111        let position = engine.simulated_positions.get("BTC").unwrap();
1112        assert_eq!(position.size, 0.1);
1113        
1114        // Create and execute a market sell order (partial close)
1115        let sell_order = OrderRequest::market("BTC", OrderSide::Sell, 0.05);
1116        rt.block_on(engine.execute_order(sell_order)).unwrap();
1117        
1118        // Check the position
1119        let position = engine.simulated_positions.get("BTC").unwrap();
1120        assert_eq!(position.size, 0.05);
1121        
1122        // Create and execute another market sell order (full close)
1123        let sell_order = OrderRequest::market("BTC", OrderSide::Sell, 0.05);
1124        rt.block_on(engine.execute_order(sell_order)).unwrap();
1125        
1126        // Check the position
1127        let position = engine.simulated_positions.get("BTC").unwrap();
1128        assert_eq!(position.size, 0.0);
1129    }
1130    
1131    #[test]
1132    fn test_performance_metrics() {
1133        let slippage_config = SlippageConfig {
1134            base_slippage_pct: 0.0, // No slippage for testing
1135            volume_impact_factor: 0.0,
1136            volatility_impact_factor: 0.0,
1137            random_slippage_max_pct: 0.0,
1138            simulated_latency_ms: 0,
1139            use_order_book: false,
1140            max_slippage_pct: 0.0,
1141        };
1142        
1143        let mut engine = PaperTradingEngine::new(10000.0, slippage_config);
1144        
1145        let now = Utc::now().with_timezone(&FixedOffset::east(0));
1146        
1147        // Add market data
1148        let market_data = MarketData::new("BTC", 50000.0, 49990.0, 50010.0, 100.0, now);
1149        engine.market_data_cache.insert("BTC".to_string(), market_data);
1150        
1151        // Execute a buy order
1152        let buy_order = OrderRequest::market("BTC", OrderSide::Buy, 0.1);
1153        let rt = tokio::runtime::Runtime::new().unwrap();
1154        rt.block_on(engine.execute_order(buy_order)).unwrap();
1155        
1156        // Update market data with higher price
1157        let new_market_data = MarketData::new("BTC", 51000.0, 50990.0, 51010.0, 100.0, now);
1158        engine.update_market_data(new_market_data).unwrap();
1159        
1160        // Execute a sell order
1161        let sell_order = OrderRequest::market("BTC", OrderSide::Sell, 0.1);
1162        rt.block_on(engine.execute_order(sell_order)).unwrap();
1163        
1164        // Check metrics
1165        let metrics = engine.get_metrics();
1166        assert!(metrics.realized_pnl > 0.0); // Should have made profit
1167        assert_eq!(metrics.trade_count, 1);
1168        assert_eq!(metrics.winning_trades, 1);
1169        
1170        // Generate report
1171        let report = engine.generate_report();
1172        assert!(report.total_return > 0.0);
1173        assert!(report.total_return_pct > 0.0);
1174        assert!(report.win_rate > 0.0);
1175    }
1176}