File size: 6,781 Bytes
de0dde6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | """
Module de monitoring: logs, mΓ©triques, alertes, dashboard.
"""
import json
import logging
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional
logger = logging.getLogger("polybot.monitor")
@dataclass
class MetricSnapshot:
timestamp: float
balance_usd: float
total_exposure: float
total_pnl: float
daily_pnl: float
num_positions: int
num_trades: int
max_drawdown: float
arb_opportunities: int = 0
value_bet_signals: int = 0
lf_signals: int = 0
@dataclass
class AlertEvent:
timestamp: float
level: str # "INFO", "WARN", "ERROR"
title: str
message: str
strategy: str = ""
metadata: dict = field(default_factory=dict)
class BotMonitor:
"""
Moniteur du bot avec historique des métriques et système d'alertes.
"""
def __init__(self):
self.metrics_history: list[MetricSnapshot] = []
self.alerts: list[AlertEvent] = []
self.start_time: float = time.time()
self._daily_reset_time: float = time.time()
def record_metrics(self, snapshot: MetricSnapshot):
"""Enregistre un snapshot de mΓ©triques."""
self.metrics_history.append(snapshot)
logger.info(
f"π Balance: ${snapshot.balance_usd:.2f} | "
f"PnL: ${snapshot.total_pnl:+.2f} | "
f"Positions: {snapshot.num_positions} | "
f"Trades: {snapshot.num_trades} | "
f"Drawdown: {snapshot.max_drawdown*100:.2f}%"
)
def alert(self, level: str, title: str, message: str,
strategy: str = "", metadata: dict = None):
"""Enregistre et logue une alerte."""
event = AlertEvent(
timestamp=time.time(),
level=level,
title=title,
message=message,
strategy=strategy,
metadata=metadata or {},
)
self.alerts.append(event)
log_func = {
"INFO": logger.info,
"WARN": logger.warning,
"ERROR": logger.error,
}.get(level, logger.info)
log_func(f"π¨ [{level}] {title}: {message}")
def check_risk_alerts(self, portfolio_summary: dict, config):
"""VΓ©rifie les conditions d'alerte basΓ©es sur le portefeuille."""
# Alerte perte journalière
daily_pnl = portfolio_summary.get("daily_pnl", 0)
if daily_pnl < -config.max_daily_loss_usd * 0.5:
self.alert(
"WARN",
"Daily Loss Warning",
f"Daily PnL: ${daily_pnl:.2f} β approaching daily limit of ${config.max_daily_loss_usd:.2f}. "
f"Consider reducing position sizes.",
metadata={"daily_pnl": daily_pnl}
)
if daily_pnl < -config.max_daily_loss_usd:
self.alert(
"ERROR",
"Daily Loss Limit Reached",
f"Daily PnL: ${daily_pnl:.2f} β HALTING trading. "
f"All new positions blocked until daily reset.",
metadata={"daily_pnl": daily_pnl}
)
# Alerte drawdown
dd_str = portfolio_summary.get("max_drawdown", "0%")
dd = float(dd_str.replace("%", "")) / 100
if dd > 0.10:
self.alert(
"WARN",
"High Drawdown",
f"Max drawdown: {dd*100:.2f}% β consider reducing exposure. "
f"Reduce position sizes by 50% if drawdown exceeds 15%.",
metadata={"max_drawdown": dd}
)
def get_performance_report(self) -> dict:
"""Génère un rapport de performance complet."""
if not self.metrics_history:
return {"status": "no data"}
latest = self.metrics_history[-1]
first = self.metrics_history[0]
uptime = time.time() - self.start_time
hours = uptime / 3600
return {
"uptime_hours": round(hours, 2),
"starting_balance": round(first.balance_usd, 2),
"current_balance": round(latest.balance_usd, 2),
"total_pnl": round(latest.total_pnl, 2),
"return_pct": round(latest.total_pnl / first.balance_usd * 100, 2) if first.balance_usd > 0 else 0,
"max_drawdown_pct": round(latest.max_drawdown * 100, 2),
"total_trades": latest.num_trades,
"active_positions": latest.num_positions,
"pnl_per_hour": round(latest.total_pnl / hours, 2) if hours > 0 else 0,
"total_alerts": len(self.alerts),
"error_alerts": sum(1 for a in self.alerts if a.level == "ERROR"),
"warn_alerts": sum(1 for a in self.alerts if a.level == "WARN"),
"snapshots_recorded": len(self.metrics_history),
}
def export_metrics(self, filepath: str = "bot_metrics.json"):
"""Exporte les mΓ©triques en JSON."""
data = {
"performance": self.get_performance_report(),
"alerts": [asdict(a) for a in self.alerts[-50:]],
"metrics_history": [asdict(m) for m in self.metrics_history[-100:]],
}
with open(filepath, "w") as f:
json.dump(data, f, indent=2)
logger.info(f"Metrics exported to {filepath}")
def print_dashboard(self):
"""Affiche un dashboard compact dans les logs."""
report = self.get_performance_report()
if report.get("status") == "no data":
logger.info("π No metrics data yet")
return
dashboard = f"""
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β POLYMARKET BOT DASHBOARD β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Uptime: {report['uptime_hours']:.1f}h
β Balance: ${report['current_balance']:,.2f} (start: ${report['starting_balance']:,.2f})
β Total PnL: ${report['total_pnl']:+,.2f} ({report['return_pct']:+.2f}%)
β PnL/Hour: ${report['pnl_per_hour']:+,.2f}
β Max Drawdown: {report['max_drawdown_pct']:.2f}%
β Trades: {report['total_trades']}
β Positions: {report['active_positions']}
β Alerts: {report['total_alerts']} (β οΈ{report['warn_alerts']} β{report['error_alerts']})
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ"""
logger.info(dashboard)
|