update 03-22 09:28
This commit is contained in:
116
src/utils/metrics.py
Normal file
116
src/utils/metrics.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""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),
|
||||
}
|
||||
Reference in New Issue
Block a user