lowkey_backtest/metrics.py

54 lines
1.7 KiB
Python

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,
)