""" Paper Trading Performance Dashboard Real-time monitoring and analytics """ import json import os from datetime import datetime from typing import Dict, List, Optional import pandas as pd from dataclasses import asdict class PaperTradingDashboard: """ Performance dashboard for paper trading. Features: - Real-time metrics display - Performance analytics - Trade history analysis - Risk metrics """ def __init__(self, engine): """ Initialize dashboard. Args: engine: PaperTradingEngine instance """ self.engine = engine def print_live_status(self): """Print real-time trading status.""" portfolio_value = self.engine.get_portfolio_value() total_return = (portfolio_value - self.engine.initial_capital) / self.engine.initial_capital print("\n" + "="*80) print(f"{'PAPER TRADING DASHBOARD':^80}") print("="*80) # Portfolio Overview print("\nšŸ“Š PORTFOLIO OVERVIEW") print(f" Portfolio Value: ${portfolio_value:,.2f}") print(f" Initial Capital: ${self.engine.initial_capital:,.2f}") print(f" Cash: ${self.engine.cash:,.2f}") print(f" Total Return: {total_return:+.2%}") print(f" Daily P&L: {self.engine.daily_pnl:+.2%}") # Positions print("\nšŸ“ˆ OPEN POSITIONS") if self.engine.positions: for symbol, pos in self.engine.positions.items(): price = self.engine.current_prices.get(symbol, pos.current_price) pos.current_price = price pos.unrealized_pnl = (price - pos.entry_price) * pos.amount pos.unrealized_pnl_pct = (price / pos.entry_price - 1) emoji = "🟢" if pos.unrealized_pnl >= 0 else "šŸ”“" print(f" {emoji} {symbol:12s} | Amount: {pos.amount:.6f} | Entry: ${pos.entry_price:,.2f} | Current: ${price:,.2f} | P&L: {pos.unrealized_pnl_pct:+.2%} (${pos.unrealized_pnl:+,.2f})") else: print(" No open positions") # Recent Orders print("\nšŸ“‹ RECENT ORDERS (Last 5)") recent_orders = self.engine.orders[-5:] if len(self.engine.orders) >= 5 else self.engine.orders if recent_orders: for order in reversed(recent_orders): emoji = "🟢" if order.side.value == "buy" else "šŸ”“" time_str = order.timestamp.strftime("%H:%M:%S") print(f" {emoji} [{time_str}] {order.side.value.upper():4s} {order.amount:.6f} {order.symbol:12s} @ ${order.price:,.2f}") if order.reason: print(f" └─ {order.reason}") else: print(" No orders yet") # Statistics print("\nšŸ“Š STATISTICS") print(f" Total Orders: {len(self.engine.orders)}") print(f" Buy Orders: {sum(1 for o in self.engine.orders if o.side.value == 'buy')}") print(f" Sell Orders: {sum(1 for o in self.engine.orders if o.side.value == 'sell')}") print(f" Updates: {len(self.engine.portfolio_value_history)}") # Risk Metrics print("\nāš ļø RISK METRICS") print(f" Max Position Size: {self.engine.max_position_size:.1%}") print(f" Max Daily Loss: {self.engine.max_daily_loss:.1%}") print(f" Stop Loss: {self.engine.stop_loss_pct:.1%}") print(f" Take Profit: {self.engine.take_profit_pct:.1%}") print("\n" + "="*80 + "\n") def get_performance_metrics(self) -> Dict: """Calculate comprehensive performance metrics.""" if not self.engine.portfolio_value_history: return {} # Get portfolio values values = [h['portfolio_value'] for h in self.engine.portfolio_value_history] # Calculate returns returns = [(values[i] - values[i-1]) / values[i-1] for i in range(1, len(values))] # Current metrics current_value = values[-1] total_return = (current_value - self.engine.initial_capital) / self.engine.initial_capital # Calculate max drawdown peak = self.engine.initial_capital max_dd = 0 for value in values: if value > peak: peak = value dd = (value - peak) / peak if dd < max_dd: max_dd = dd # Win rate winning_trades = 0 losing_trades = 0 total_profit = 0 total_loss = 0 for order in self.engine.orders: if order.side.value == "sell" and "P&L:" in order.reason: # Extract P&L from reason pnl_str = order.reason.split("P&L: $")[1].split(" ")[0].replace(",", "") pnl = float(pnl_str) if pnl > 0: winning_trades += 1 total_profit += pnl else: losing_trades += 1 total_loss += abs(pnl) total_trades = winning_trades + losing_trades win_rate = winning_trades / total_trades if total_trades > 0 else 0 # Average win/loss avg_win = total_profit / winning_trades if winning_trades > 0 else 0 avg_loss = total_loss / losing_trades if losing_trades > 0 else 0 profit_factor = total_profit / total_loss if total_loss > 0 else float('inf') # Sharpe ratio (annualized, simplified) if returns: import numpy as np avg_return = np.mean(returns) std_return = np.std(returns) sharpe = (avg_return / std_return * (252 ** 0.5)) if std_return > 0 else 0 else: sharpe = 0 return { 'current_value': current_value, 'initial_capital': self.engine.initial_capital, 'total_return': total_return, 'total_return_pct': total_return * 100, 'max_drawdown': max_dd, 'max_drawdown_pct': max_dd * 100, 'sharpe_ratio': sharpe, 'total_trades': total_trades, 'winning_trades': winning_trades, 'losing_trades': losing_trades, 'win_rate': win_rate, 'win_rate_pct': win_rate * 100, 'total_profit': total_profit, 'total_loss': total_loss, 'avg_win': avg_win, 'avg_loss': avg_loss, 'profit_factor': profit_factor, 'num_updates': len(self.engine.portfolio_value_history), } def print_performance_report(self): """Print comprehensive performance report.""" metrics = self.get_performance_metrics() if not metrics: print("No performance data yet.") return print("\n" + "="*80) print(f"{'PERFORMANCE REPORT':^80}") print("="*80) # Returns print("\nšŸ’° RETURNS") print(f" Current Value: ${metrics['current_value']:,.2f}") print(f" Initial Capital: ${metrics['initial_capital']:,.2f}") print(f" Total Return: {metrics['total_return']:+.2%} (${metrics['current_value'] - metrics['initial_capital']:+,.2f})") print(f" Max Drawdown: {metrics['max_drawdown']:.2%}") print(f" Sharpe Ratio: {metrics['sharpe_ratio']:.2f}") # Trading Stats print("\nšŸ“Š TRADING STATISTICS") print(f" Total Trades: {metrics['total_trades']}") print(f" Winning Trades: {metrics['winning_trades']} ({metrics['win_rate_pct']:.1f}%)") print(f" Losing Trades: {metrics['losing_trades']} ({100 - metrics['win_rate_pct']:.1f}%)") print(f" Win Rate: {metrics['win_rate_pct']:.1f}%") # P&L print("\nšŸ’µ PROFIT & LOSS") print(f" Total Profit: ${metrics['total_profit']:,.2f}") print(f" Total Loss: ${metrics['total_loss']:,.2f}") print(f" Net P&L: ${metrics['total_profit'] - metrics['total_loss']:+,.2f}") print(f" Avg Win: ${metrics['avg_win']:,.2f}") print(f" Avg Loss: ${metrics['avg_loss']:,.2f}") print(f" Profit Factor: {metrics['profit_factor']:.2f}") # Data print("\nšŸ“ˆ DATA COVERAGE") print(f" Total Updates: {metrics['num_updates']}") print(f" Update Interval: {self.engine.update_interval}s") duration_seconds = metrics['num_updates'] * self.engine.update_interval hours = duration_seconds // 3600 minutes = (duration_seconds % 3600) // 60 print(f" Trading Duration: {hours}h {minutes}m") print("\n" + "="*80 + "\n") def export_to_csv(self, filename: Optional[str] = None): """Export trading data to CSV.""" if not filename: filename = f"paper_trading_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" # Prepare data data = [] for order in self.engine.orders: data.append({ 'timestamp': order.timestamp, 'order_id': order.order_id, 'symbol': order.symbol, 'side': order.side.value, 'amount': order.amount, 'price': order.price, 'status': order.status.value, 'reason': order.reason, }) if data: df = pd.DataFrame(data) filepath = os.path.join(self.engine.data_dir, filename) df.to_csv(filepath, index=False) print(f"āœ“ Exported {len(data)} orders to: {filepath}") else: print("No orders to export") def export_portfolio_history(self, filename: Optional[str] = None): """Export portfolio value history to CSV.""" if not filename: filename = f"portfolio_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" if self.engine.portfolio_value_history: df = pd.DataFrame(self.engine.portfolio_value_history) filepath = os.path.join(self.engine.data_dir, filename) df.to_csv(filepath, index=False) print(f"āœ“ Exported portfolio history to: {filepath}") else: print("No portfolio history to export") def generate_html_report(self, filename: Optional[str] = None): """Generate HTML dashboard report.""" if not filename: filename = f"dashboard_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" metrics = self.get_performance_metrics() if not metrics: print("No performance data yet.") return html = f""" Paper Trading Dashboard

šŸ“Š Paper Trading Dashboard

Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

Portfolio Overview

${metrics['current_value']:,.2f}
Current Value
{metrics['total_return']:+.2%}
Total Return
{metrics['sharpe_ratio']:.2f}
Sharpe Ratio

Trading Statistics

{metrics['total_trades']}
Total Trades
{metrics['win_rate_pct']:.1f}%
Win Rate
{metrics['profit_factor']:.2f}
Profit Factor

Risk Metrics

Metric Value
Max Drawdown {metrics['max_drawdown']:.2%}
Average Win ${metrics['avg_win']:,.2f}
Average Loss ${metrics['avg_loss']:,.2f}

Recent Orders

""" for order in reversed(self.engine.orders[-10:]): side_class = "positive" if order.side.value == "buy" else "negative" html += f""" """ html += """
Time Symbol Side Amount Price Reason
{order.timestamp.strftime('%H:%M:%S')} {order.symbol} {order.side.value.upper()} {order.amount:.6f} ${order.price:,.2f} {order.reason}
""" filepath = os.path.join(self.engine.data_dir, filename) with open(filepath, 'w') as f: f.write(html) print(f"āœ“ Generated HTML dashboard: {filepath}") return filepath