""" Performance Metrics and Analytics Calculates various performance metrics for strategies. """ from typing import Dict, List import numpy as np import pandas as pd class PerformanceMetrics: """Calculate performance metrics from backtest results""" @staticmethod def calculate_sharpe_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: """ Calculate Sharpe ratio. Args: returns: List of daily returns risk_free_rate: Annual risk-free rate Returns: Sharpe ratio """ if not returns: return 0.0 returns_array = np.array(returns) excess_returns = returns_array - (risk_free_rate / 365) if returns_array.std() == 0: return 0.0 sharpe = np.sqrt(365) * excess_returns.mean() / returns_array.std() return sharpe @staticmethod def calculate_sortino_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: """ Calculate Sortino ratio (downside deviation only). Args: returns: List of daily returns risk_free_rate: Annual risk-free rate Returns: Sortino ratio """ if not returns: return 0.0 returns_array = np.array(returns) excess_returns = returns_array - (risk_free_rate / 365) # Calculate downside deviation downside_returns = excess_returns[excess_returns < 0] if len(downside_returns) == 0: return 0.0 downside_std = np.std(downside_returns) if downside_std == 0: return 0.0 sortino = np.sqrt(365) * excess_returns.mean() / downside_std return sortino @staticmethod def calculate_max_drawdown(equity_curve: List[float]) -> Dict[str, float]: """ Calculate maximum drawdown. Args: equity_curve: List of equity values over time Returns: Dictionary with max_drawdown, max_drawdown_percent, and drawdown_duration """ if not equity_curve: return {'max_drawdown': 0.0, 'max_drawdown_percent': 0.0, 'drawdown_duration': 0} equity_array = np.array(equity_curve) peak = np.maximum.accumulate(equity_array) drawdown = peak - equity_array drawdown_percent = (drawdown / peak) * 100 max_dd = float(np.max(drawdown)) max_dd_percent = float(np.max(drawdown_percent)) # Calculate drawdown duration in_drawdown = drawdown > 0 if np.any(in_drawdown): # Count consecutive periods in drawdown durations = [] current_duration = 0 for in_dd in in_drawdown: if in_dd: current_duration += 1 else: if current_duration > 0: durations.append(current_duration) current_duration = 0 if current_duration > 0: durations.append(current_duration) max_duration = max(durations) if durations else 0 else: max_duration = 0 return { 'max_drawdown': max_dd, 'max_drawdown_percent': max_dd_percent, 'drawdown_duration': max_duration } @staticmethod def calculate_calmar_ratio(total_return: float, max_drawdown_percent: float) -> float: """ Calculate Calmar ratio (return / max drawdown). Args: total_return: Total return percentage max_drawdown_percent: Maximum drawdown percentage Returns: Calmar ratio """ if max_drawdown_percent == 0: return 0.0 return total_return / max_drawdown_percent @staticmethod def calculate_profit_factor(total_profit: float, total_loss: float) -> float: """ Calculate profit factor. Args: total_profit: Total profit total_loss: Total loss (absolute value) Returns: Profit factor """ if total_loss == 0: return 0.0 if total_profit == 0 else float('inf') return abs(total_profit / total_loss) @staticmethod def calculate_expectancy(win_rate: float, avg_win: float, avg_loss: float) -> float: """ Calculate expectancy per trade. Args: win_rate: Win rate (0-1) avg_win: Average winning trade avg_loss: Average losing trade (absolute value) Returns: Expectancy """ return (win_rate * avg_win) - ((1 - win_rate) * avg_loss) @staticmethod def generate_report(backtest_results: Dict) -> str: """ Generate formatted performance report. Args: backtest_results: Results dictionary from backtest Returns: Formatted report string """ equity_curve = [point['equity'] for point in backtest_results.get('equity_curve', [])] daily_returns = backtest_results.get('daily_returns', []) # Calculate additional metrics sharpe = PerformanceMetrics.calculate_sharpe_ratio(daily_returns) sortino = PerformanceMetrics.calculate_sortino_ratio(daily_returns) dd_metrics = PerformanceMetrics.calculate_max_drawdown(equity_curve) report = f""" {'='*70} POLYMARKET BACKTEST REPORT {'='*70} Strategy: {backtest_results.get('strategy', 'Unknown')} Period: {backtest_results.get('start_date')} to {backtest_results.get('end_date')} INITIAL METRICS: Initial Balance: ${backtest_results.get('initial_balance', 0):,.2f} Final Equity: ${backtest_results.get('final_equity', 0):,.2f} Total Return: {backtest_results.get('total_return', 0):.2f}% TRADE STATISTICS: Total Trades: {backtest_results.get('total_trades', 0)} Winning Trades: {backtest_results.get('winning_trades', 0)} Losing Trades: {backtest_results.get('losing_trades', 0)} Win Rate: {backtest_results.get('win_rate', 0):.2f}% PROFITABILITY: Total Profit: ${backtest_results.get('total_profit', 0):,.2f} Total Loss: ${backtest_results.get('total_loss', 0):,.2f} Net Profit: ${backtest_results.get('net_profit', 0):,.2f} Profit Factor: {backtest_results.get('profit_factor', 0):.2f} RISK METRICS: Maximum Drawdown: {dd_metrics['max_drawdown_percent']:.2f}% Drawdown Duration: {dd_metrics['drawdown_duration']} periods Sharpe Ratio: {sharpe:.2f} Sortino Ratio: {sortino:.2f} {'='*70} """ return report