a6905f2691
Bug: win_rate() only checked pnl_net/pnl_gross/pnl fields, but Kalman backtests save trades with net_pnl/gross_pnl (underscore-first). Result: all 4 Kalman assets showed 0% win on 27-35 trades. After fix: BTC: 0% → 45% (16/35) ETH: 0% → 47% (16/34) HYPE: 0% → 51% (14/27) VVV: 0% → 57% (19/33) Also corrected paper trader coin assignments for Mean Reversion and Momentum Breakout (was BTC, should be ETH).
47 lines
1.3 KiB
Python
47 lines
1.3 KiB
Python
"""
|
|
Performance metrics.
|
|
|
|
Sharpe ratio, Sortino ratio, max drawdown, win rate.
|
|
Standard toolbox for evaluating a trading strategy.
|
|
"""
|
|
import numpy as np
|
|
|
|
|
|
def sharpe(returns: list[float], rf: float = 0.0, periods: int = 365) -> float:
|
|
if len(returns) < 2:
|
|
return 0.0
|
|
excess = np.mean(returns) - rf
|
|
std = np.std(returns, ddof=1)
|
|
return (excess / std) * np.sqrt(periods) if std > 0 else 0.0
|
|
|
|
|
|
def sortino(returns: list[float], rf: float = 0.0, periods: int = 365) -> float:
|
|
if len(returns) < 2:
|
|
return 0.0
|
|
excess = np.mean(returns) - rf
|
|
downside = [r for r in returns if r < 0]
|
|
d_std = np.std(downside, ddof=1) if downside else 0.0
|
|
return (excess / d_std) * np.sqrt(periods) if d_std > 0 else 0.0
|
|
|
|
|
|
def max_drawdown(equity: list[float]) -> float:
|
|
if not equity:
|
|
return 0.0
|
|
peak = equity[0]
|
|
worst = 0.0
|
|
for v in equity:
|
|
if v > peak:
|
|
peak = v
|
|
dd = (peak - v) / peak if peak > 0 else 0.0
|
|
worst = max(worst, dd)
|
|
return worst
|
|
|
|
|
|
def win_rate(trades: list[dict]) -> float:
|
|
if not trades:
|
|
return 0.0
|
|
tp = sum(1 for t in trades if (
|
|
(t.get("pnl_net") or t.get("net_pnl") or t.get("pnl_gross") or t.get("gross_pnl") or t.get("pnl", 0)) > 0
|
|
))
|
|
return tp / len(trades)
|