131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
"""
|
|
Metrics Tracker — Computes trading performance metrics.
|
|
|
|
Key metrics tracked:
|
|
- P&L (cumulative and daily)
|
|
- Sharpe Ratio (annualized)
|
|
- Win Rate
|
|
- Calibration Score (how accurate our probability estimates are)
|
|
- Max Drawdown
|
|
- Average Edge realized
|
|
"""
|
|
import logging
|
|
import math
|
|
from datetime import datetime, UTC
|
|
from typing import Optional
|
|
|
|
from bot.executor.paper import Trade
|
|
from bot.data.db import Database
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class MetricsTracker:
|
|
def __init__(self, db: Database) -> None:
|
|
self._db = db
|
|
self._trades: list[Trade] = []
|
|
self._daily_returns: list[float] = []
|
|
|
|
async def record_trade(self, trade: Trade) -> None:
|
|
self._trades.append(trade)
|
|
await self._db.save_trade(trade)
|
|
log.info("Trade recorded. Total trades: %d", len(self._trades))
|
|
|
|
async def update_daily_summary(self) -> None:
|
|
"""Compute and store daily metrics snapshot."""
|
|
if not self._trades:
|
|
return
|
|
|
|
metrics = self.compute_metrics()
|
|
await self._db.save_daily_metrics(metrics)
|
|
|
|
log.info(
|
|
"Daily metrics | Trades: %d | P&L: $%.2f | Win: %.1f%% | Sharpe: %.2f",
|
|
metrics["total_trades"],
|
|
metrics["total_pnl"],
|
|
metrics["win_rate"] * 100,
|
|
metrics["sharpe_ratio"],
|
|
)
|
|
|
|
def compute_metrics(self) -> dict:
|
|
if not self._trades:
|
|
return self._empty_metrics()
|
|
|
|
trades = self._trades
|
|
n = len(trades)
|
|
|
|
# Total cost deployed
|
|
total_deployed = sum(t.net_cost for t in trades)
|
|
total_fees = sum(t.fee_usdc for t in trades)
|
|
|
|
# Win rate (trades where we had positive edge — in paper mode we estimate)
|
|
# A trade "wins" if entry_price < 0.5 (buying undervalued token)
|
|
wins = sum(1 for t in trades if t.entry_price < 0.5)
|
|
win_rate = wins / n if n > 0 else 0
|
|
|
|
# Estimated P&L (paper — based on edge captured)
|
|
# Edge = (estimated_prob - entry_price) * shares
|
|
total_pnl = sum(
|
|
(0.5 - t.entry_price) * t.shares - t.fee_usdc
|
|
for t in trades
|
|
)
|
|
|
|
# Average edge per trade
|
|
avg_edge = total_pnl / total_deployed if total_deployed > 0 else 0
|
|
|
|
# Sharpe ratio (simplified — daily returns not yet available in paper mode)
|
|
# Will improve once markets resolve and we have actual returns
|
|
sharpe = self._compute_sharpe()
|
|
|
|
# Calibration score (Brier score based)
|
|
# Perfect calibration = 1.0, random = 0.0
|
|
calibration = 1 - (2 * abs(avg_edge)) # Simplified until markets resolve
|
|
|
|
return {
|
|
"timestamp": datetime.now(UTC),
|
|
"total_trades": n,
|
|
"total_deployed": total_deployed,
|
|
"total_fees": total_fees,
|
|
"total_pnl": total_pnl,
|
|
"win_rate": win_rate,
|
|
"avg_edge": avg_edge,
|
|
"sharpe_ratio": sharpe,
|
|
"calibration_score": max(0, min(1, calibration)),
|
|
"paper_mode": True,
|
|
}
|
|
|
|
def _compute_sharpe(self) -> float:
|
|
"""Annualized Sharpe ratio from daily returns."""
|
|
if len(self._daily_returns) < 2:
|
|
return 0.0
|
|
mean_r = sum(self._daily_returns) / len(self._daily_returns)
|
|
variance = sum((r - mean_r) ** 2 for r in self._daily_returns) / len(self._daily_returns)
|
|
std_r = math.sqrt(variance) if variance > 0 else 1e-9
|
|
return (mean_r / std_r) * math.sqrt(365) # Annualize
|
|
|
|
def check_promotion_thresholds(self) -> tuple[bool, dict]:
|
|
"""Check if metrics qualify for real money trading."""
|
|
metrics = self.compute_metrics()
|
|
checks = {
|
|
"sharpe_ratio": (metrics["sharpe_ratio"], 0.5, metrics["sharpe_ratio"] >= 0.5),
|
|
"win_rate": (metrics["win_rate"], 0.52, metrics["win_rate"] >= 0.52),
|
|
"calibration_score": (metrics["calibration_score"], 0.7, metrics["calibration_score"] >= 0.7),
|
|
"min_trades": (metrics["total_trades"], 50, metrics["total_trades"] >= 50),
|
|
}
|
|
all_pass = all(v[2] for v in checks.values())
|
|
return all_pass, checks
|
|
|
|
def _empty_metrics(self) -> dict:
|
|
return {
|
|
"timestamp": datetime.now(UTC),
|
|
"total_trades": 0,
|
|
"total_deployed": 0,
|
|
"total_fees": 0,
|
|
"total_pnl": 0,
|
|
"win_rate": 0,
|
|
"avg_edge": 0,
|
|
"sharpe_ratio": 0,
|
|
"calibration_score": 0,
|
|
"paper_mode": True,
|
|
}
|