update 03-22 09:28

This commit is contained in:
2026-03-22 09:28:14 +09:00
commit 7f45211276
43 changed files with 9373 additions and 0 deletions

116
src/utils/metrics.py Normal file
View 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),
}