"""Metrics collector for the performance dashboard.""" from __future__ import annotations import time from collections import deque from dataclasses import dataclass, field from typing import Optional @dataclass class TradeMetric: """Single trade metric for time-series tracking.""" timestamp: float asset: str direction: str timeframe: str entry_price: float size: int edge: float pnl: Optional[float] = None won: Optional[bool] = None class MetricsCollector: """Collects and aggregates trading metrics for the dashboard. Maintains rolling windows and aggregated stats for real-time display. """ def __init__(self, max_history: int = 10000) -> None: self._trades: deque[TradeMetric] = deque(maxlen=max_history) self._pnl_series: deque[tuple[float, float]] = deque(maxlen=max_history) self._start_time = time.time() # Running aggregates self.total_volume: float = 0.0 self.total_fees: float = 0.0 def record_trade(self, metric: TradeMetric) -> None: self._trades.append(metric) self.total_volume += metric.entry_price * metric.size def record_pnl(self, pnl: float) -> None: self._pnl_series.append((time.time(), pnl)) def record_fee(self, fee: float) -> None: self.total_fees += fee # ------------------------------------------------------------------ # Aggregations # ------------------------------------------------------------------ def get_recent_trades(self, n: int = 50) -> list[TradeMetric]: return list(self._trades)[-n:] def get_pnl_series(self) -> list[tuple[float, float]]: return list(self._pnl_series) def get_hourly_stats(self) -> dict: """Aggregate stats for the last hour.""" cutoff = time.time() - 3600 recent = [t for t in self._trades if t.timestamp > cutoff] wins = sum(1 for t in recent if t.won is True) losses = sum(1 for t in recent if t.won is False) total_pnl = sum(t.pnl for t in recent if t.pnl is not None) return { "trades": len(recent), "wins": wins, "losses": losses, "win_rate": wins / (wins + losses) * 100 if (wins + losses) > 0 else 0, "pnl": round(total_pnl, 2), } def get_asset_breakdown(self) -> dict[str, dict]: """PnL and trade count per asset.""" breakdown: dict[str, dict] = {} for t in self._trades: if t.asset not in breakdown: breakdown[t.asset] = {"trades": 0, "wins": 0, "pnl": 0.0} breakdown[t.asset]["trades"] += 1 if t.won is True: breakdown[t.asset]["wins"] += 1 if t.pnl is not None: breakdown[t.asset]["pnl"] += t.pnl for asset in breakdown: total = breakdown[asset]["trades"] wins = breakdown[asset]["wins"] breakdown[asset]["win_rate"] = round(wins / total * 100, 1) if total > 0 else 0 breakdown[asset]["pnl"] = round(breakdown[asset]["pnl"], 2) return breakdown def get_uptime(self) -> float: """Return uptime in seconds.""" return time.time() - self._start_time def get_summary(self) -> dict: total_trades = len(self._trades) wins = sum(1 for t in self._trades if t.won is True) losses = sum(1 for t in self._trades if t.won is False) total_pnl = sum(t.pnl for t in self._trades if t.pnl is not None) return { "total_trades": total_trades, "wins": wins, "losses": losses, "win_rate": round(wins / (wins + losses) * 100, 1) if (wins + losses) > 0 else 0, "total_pnl": round(total_pnl, 2), "total_volume": round(self.total_volume, 2), "total_fees": round(self.total_fees, 2), "uptime_hours": round(self.get_uptime() / 3600, 2), }