from __future__ import annotations from dataclasses import dataclass import numpy as np import pandas as pd @dataclass class Perf: total_return: float max_drawdown: float sharpe_ratio: float win_rate: float num_trades: int final_equity: float initial_equity: float num_stop_losses: int total_fees: float total_slippage_usd: float avg_slippage_bps: float def compute_metrics(equity_curve: pd.Series, trades: list[dict]) -> Perf: ret = equity_curve.pct_change().fillna(0.0) total_return = equity_curve.iat[-1] / equity_curve.iat[0] - 1.0 cummax = equity_curve.cummax() dd = (equity_curve / cummax - 1.0).min() max_drawdown = dd if ret.std(ddof=0) > 0: sharpe = (ret.mean() / ret.std(ddof=0)) * np.sqrt(252 * 24 * 60) # minute bars -> annualized else: sharpe = 0.0 closes = [t for t in trades if t.get("side") == "SELL"] wins = [t for t in closes if t.get("pnl", 0.0) > 0] win_rate = (len(wins) / len(closes)) if closes else 0.0 fees = sum(t.get("fee", 0.0) for t in trades) slip = sum(t.get("slippage", 0.0) for t in trades) slippage_bps = [t.get("slippage_bps", 0.0) for t in trades if "slippage_bps" in t] return Perf( total_return=total_return, max_drawdown=max_drawdown, sharpe_ratio=sharpe, win_rate=win_rate, num_trades=len(closes), final_equity=float(equity_curve.iat[-1]), initial_equity=float(equity_curve.iat[0]), num_stop_losses=sum(1 for t in closes if t.get("reason") == "stop"), total_fees=fees, total_slippage_usd=slip, avg_slippage_bps=float(np.mean(slippage_bps)) if slippage_bps else 0.0, )