Files
profitable-expert-advisor/polymarket/analytics/metrics.py
T

217 lines
6.7 KiB
Python
Raw Normal View History

2026-02-13 08:03:25 +01:00
"""
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