import numpy as np import pandas as pd def total_return(equity: pd.Series) -> float: return equity.iloc[-1] / equity.iloc[0] - 1 def annualized_return(equity: pd.Series, periods: int = 252) -> float: n = len(equity) return (1 + total_return(equity)) ** (periods / n) - 1 def annualized_volatility(returns: pd.Series, periods: int = 252) -> float: return returns.std() * np.sqrt(periods) def sharpe_ratio(returns: pd.Series, risk_free_rate: float = 0.0, periods: int = 252) -> float: excess = returns - risk_free_rate / periods std = excess.std() if std == 0: return 0.0 return (excess.mean() / std) * np.sqrt(periods) def sortino_ratio(returns: pd.Series, risk_free_rate: float = 0.0, periods: int = 252) -> float: excess = returns - risk_free_rate / periods downside_std = excess[excess < 0].std() if downside_std == 0: return 0.0 return (excess.mean() / downside_std) * np.sqrt(periods) def max_drawdown(equity: pd.Series) -> float: rolling_peak = equity.cummax() drawdown = (equity - rolling_peak) / rolling_peak return drawdown.min() def calmar_ratio(equity: pd.Series, periods: int = 252) -> float: ann_ret = annualized_return(equity, periods) mdd = abs(max_drawdown(equity)) if mdd == 0: return np.inf return ann_ret / mdd def win_rate(returns: pd.Series) -> float: active = returns[returns != 0] if len(active) == 0: return 0.0 return (active > 0).sum() / len(active) def summary(equity: pd.Series, name: str = "Strategy") -> dict: returns = equity.pct_change().dropna() metrics = { "Total Return": f"{total_return(equity):.2%}", "Annualized Return": f"{annualized_return(equity):.2%}", "Annualized Volatility": f"{annualized_volatility(returns):.2%}", "Sharpe Ratio": f"{sharpe_ratio(returns):.2f}", "Sortino Ratio": f"{sortino_ratio(returns):.2f}", "Max Drawdown": f"{max_drawdown(equity):.2%}", "Calmar Ratio": f"{calmar_ratio(equity):.2f}", "Win Rate": f"{win_rate(returns):.2%}", } print(f"\n{'='*45}") print(f" {name}") print(f"{'='*45}") for k, v in metrics.items(): print(f" {k:<26} {v:>10}") return metrics