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)