doulfa commited on
Commit
de0dde6
Β·
verified Β·
1 Parent(s): 08547ef

Add monitor module

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