""" 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, }