"""
Tableau de Bord - Trade Journal automatico per Trading Engine Mattia.
Mavis 2026-07-20 - Skill trade-journal

Genera un tableau HTML con TUTTE le operazioni (aperture, chiusure, skip)
estratte da orders.log + webhook_receiver.log. Per ogni operazione aggiunge
commenti automatici su:
- Strategia (VPTR3, Rettangolo, ecc.)
- Regime P007 (LONG OK, SHORT OK, SKIP-REGIME)
- ZONA-MID (LONG zona bottom, SHORT zona top, SKIP zona mid)
- P006 SL clamp (-3% MARGINE charter)
- P008 max 5 posizioni
- Sizing Charter 1500 USDT (500 margin × 3x)
- Leva effettiva
- Esito (TP / SL / OPEN / SKIP-* / chiusura trailing / safety net)
- PnL se disponibile

Output: G:\AI TRADING ENGINE\live_deploy\tableau_de_bord.html
Servizio: gira ogni 5 minuti (configurabile).
"""
import os
import re
import sys
import time
import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from collections import defaultdict

LIVE_DEPLOY = Path(r"/opt/charter-live/live_deploy")
ORDERS_LOG = LIVE_DEPLOY / "logs" / "orders.log"
WEBHOOK_LOG = LIVE_DEPLOY / "logs" / "webhook_receiver.log"
RETTANGOLO_LOG = LIVE_DEPLOY / "webhook_listener" / "logs" / "rettangolo_runner.log"
OUTPUT_HTML = LIVE_DEPLOY / "tableau_de_bord.html"

# Configurazione
POLL_SECONDS = 300  # 5 minuti
LOG_FILE = LIVE_DEPLOY / "logs" / "trade_journal.log"
PID_FILE = Path(r"/tmp/trade_journal.pid")

logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
log = logging.getLogger()


def parse_orders_log() -> list:
    """Parsa orders.log per estrarre TUTTE le operazioni di apertura."""
    ops = []
    if not ORDERS_LOG.exists():
        return ops
    pattern = re.compile(
        r"\[(?P<ts>[^\]]+)\] orderId=(?P<order_id>[^\s]+)\s+"
        r"(?P<symbol>\w+)\s+(?P<side>\w+)\s+"
        r"qty=(?P<qty>[\d.]+)\s+price=(?P<price>[\d.]+)\s+"
        r"notional=(?P<notional>[\d.]+)\s+strategy=(?P<strategy>\w+)"
    )
    with open(ORDERS_LOG, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            m = pattern.search(line)
            if m:
                ops.append({
                    "ts": m.group("ts"),
                    "order_id": m.group("order_id"),
                    "symbol": m.group("symbol"),
                    "side": m.group("side"),
                    "qty": float(m.group("qty")),
                    "price": float(m.group("price")),
                    "notional": float(m.group("notional")),
                    "strategy": m.group("strategy"),
                })
    return ops


def get_market_context(bybit_client) -> dict:
    """Recupera il contesto di mercato ATTUALE per ogni symbol unico presente in orders.log.
    Ritorna dict {symbol: {trend, market_kind, regime, d1_range_pct, ema50_slope, price}}.
    Cache per symbol per evitare query ripetute (1 query per symbol)."""
    cache = {}
    if not ORDERS_LOG.exists():
        return cache
    symbols = set()
    pattern = re.compile(r"orderId=([a-f0-9-]+)\s+(\w+)\s+(buy|sell)")
    with open(ORDERS_LOG, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            m = pattern.search(line)
            if m:
                symbols.add(m.group(2))

    for sym in symbols:
        try:
            kd = bybit_client.fetch_ohlcv(sym, "D", 5)
            if not kd or len(kd) < 2:
                continue
            d1 = kd[1]
            d1h = float(d1[2]); d1l = float(d1[3]); d1c = float(d1[4])
            d1_range_pct = (d1h - d1l) / d1l * 100
            market_kind = "volatile" if d1_range_pct > 5 else ("range ampio" if d1_range_pct > 2 else "chop/stretto")

            k4h = bybit_client.fetch_ohlcv(sym, "240", 60)
            closes = [float(k[4]) for k in k4h[:-1]] if k4h else []
            ema50 = None
            ema50_slope = None
            if len(closes) >= 52:
                # EMA50 attuale
                k = 2.0 / 51
                ema = sum(closes[:50]) / 50
                for c_val in closes[50:]:
                    ema = c_val * k + ema * (1 - k)
                ema50 = ema
                # EMA50 slope (confronto con calcolo di 1 candela fa)
                closes_prev = closes[:-1]
                ema_prev = sum(closes_prev[:50]) / 50
                for c_val in closes_prev[50:]:
                    ema_prev = c_val * k + ema_prev * (1 - k)
                ema50_slope = (ema50 - ema_prev) / ema_prev * 100 if ema_prev else 0

            cur = float(bybit_client.get_last_price(sym))
            if ema50:
                regime = "LONG OK" if cur > ema50 else "SHORT OK"
            else:
                regime = "?"
            if ema50_slope is not None:
                trend = "rialzista" if ema50_slope > 0.5 else ("ribassista" if ema50_slope < -0.5 else "chop/range")
            else:
                trend = "?"

            cache[sym] = {
                "trend": trend,
                "market_kind": market_kind,
                "regime": regime,
                "d1_range_pct": d1_range_pct,
                "ema50_slope": ema50_slope,
                "price": cur,
            }
        except Exception:
            pass
    return cache


def parse_webhook_log() -> dict:
    """Parsa webhook_receiver.log per estrarre eventi correlati (SL/TP auto-fill, P007 regime, ZONA-MID SKIP, P008, ORDER OK, P006 clamp, TRADING STOP, SAFETY NET)."""
    events = defaultdict(list)
    if not WEBHOOK_LOG.exists():
        return events
    # Patterns per eventi
    patterns = {
        "AUTO_FILL": re.compile(r"AUTO-FILL SL/TP per (\w+) (\w+) @ entry=([\d.]+): SL=([\d.]+) TP=([\d.]+) source=([^\(]+)"),
        "P007_OK": re.compile(r"P007 OK: close\[-2\]=([\d.]+) vs EMA50\[-2\]=([\d.]+)"),
        "P007_SKIP": re.compile(r"P007 SKIP-REGIME (\w+): close\[-2\]=([\d.]+) ([<>]) EMA50\[-2\]=([\d.]+)"),
        "P006_OVERRIDE": re.compile(r"CHARTER P006 OVERRIDE: (\w+) sl_loss_pct=([\d.-]+)"),
        "P008_SKIP": re.compile(r"P008 SKIP-MAX-OPEN ATTIVATO per (\w+) (\w+): (\d+) posizioni"),
        "ZONA_MID_SKIP": re.compile(r"ZONA-MID SKIP: (\w+) (\w+) entry=([\d.]+)"),
        "TRADING_STOP_OK": re.compile(r"TRADING STOP OK (\w+): SL=([\d.]+) TP=([\d.]+)"),
        "ORDER_OK": re.compile(r"ORDER OK ([a-f0-9-]+): orderId=([a-f0-9-]+)"),
        "P005_TP_OVERRIDE": re.compile(r"CHARTER P005 TP OVERRIDE"),  # legacy, non più presente
        "EMAIL_FAIL": re.compile(r"Email notification failed"),
    }
    with open(WEBHOOK_LOG, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            for evt, pat in patterns.items():
                m = pat.search(line)
                if m:
                    events[evt].append({"line": line.strip()[:200], "match": m.groups()})
    return events


def parse_rettangolo_log() -> list:
    """Parsa rettangolo_runner.log per estrarre segnali generati."""
    segnali = []
    if not RETTANGOLO_LOG.exists():
        return segnali
    pattern = re.compile(
        r"\[(?P<ts>[^\]]+)\]\s+(?P<symbol>\w+): SEGNALE (?P<signal>\w+) "
        r"entry=(?P<entry>[\d.]+) sl=(?P<sl>[\d.]+) tp=(?P<tp>[\d.]+)"
    )
    pattern_webhook = re.compile(
        r"\[(?P<ts>[^\]]+)\]\s+(?P<symbol>\w+): WEBHOOK OK: request_id=([a-f0-9]+)\.\.\."
    )
    with open(RETTANGOLO_LOG, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            m = pattern.search(line)
            if m:
                segnali.append({
                    "ts": m.group("ts"),
                    "symbol": m.group("symbol"),
                    "signal": m.group("signal"),
                    "entry": float(m.group("entry")),
                    "sl": float(m.group("sl")),
                    "tp": float(m.group("tp")),
                    "type": "segnale",
                })
            m = pattern_webhook.search(line)
            if m:
                segnali.append({
                    "ts": m.group("ts"),
                    "symbol": m.group("symbol"),
                    "type": "webhook_ok",
                })
    return segnali


def build_comment(op: dict, webhook_events: dict, rett_segnali: list, market_context: dict) -> str:
    """Costruisce un commento automatico per un'operazione, basato su tutti gli eventi correlati.
    Include due sezioni:
    1. Commento operativo (regole Charter rispettate o violate)
    2. Contesto di mercato (trend EMA50, regime attuale, market kind)
    """
    symbol = op["symbol"]
    side = op["side"]
    strategy = op["strategy"]

    # === SEZIONE 1: COMMENTO OPERATIVO (regole Charter) ===
    operativo = []
    if strategy == "vptr3":
        operativo.append("🎯 VPTR3")
    elif strategy == "rettangolo":
        operativo.append("📐 Rettangolo")
    else:
        operativo.append(f"❓ {strategy}")

    p007_skip = [e for e in webhook_events.get("P007_SKIP", []) if e["match"][0] == symbol]
    if p007_skip:
        operativo.append(f"⚠️ P007 SKIP-REGIME ({p007_skip[-1]['match'][1].lower()} contro trend EMA50)")
    else:
        p007_ok = [e for e in webhook_events.get("P007_OK", []) if symbol in e["line"]]
        if p007_ok:
            operativo.append("✓ P007 regime OK")

    zona_skip = [e for e in webhook_events.get("ZONA_MID_SKIP", []) if e["match"][0] == symbol]
    if zona_skip:
        operativo.append("🚫 ZONA-MID SKIP (entry in area mid)")
    else:
        if side.lower() in ("buy", "long"):
            operativo.append("✓ ZONA bottom OK (entry al LL)")
        else:
            operativo.append("✓ ZONA top OK (entry al HH)")

    p006 = [e for e in webhook_events.get("P006_OVERRIDE", []) if e["match"][0] == symbol]
    if p006:
        operativo.append("🔧 P006 OVERRIDE (SL clampato a -3% Charter)")
    else:
        operativo.append("✓ P006 SL Charter -3% OK")

    p008 = [e for e in webhook_events.get("P008_SKIP", []) if e["match"][0] == symbol]
    if p008:
        operativo.append("🚫 P008 SKIP (max 5 posizioni)")
    else:
        operativo.append("✓ P008 max 5 posizioni OK")

    if abs(op["notional"] - 1500.0) < 75:
        operativo.append("💰 Sizing Charter 1500 USDT")
    else:
        operativo.append(f"💰 Sizing ${op['notional']:.0f} (deviazione)")

    autofill = [e for e in webhook_events.get("AUTO_FILL", []) if e["match"][0] == symbol]
    if autofill:
        src = autofill[-1]["match"][5].strip()
        operativo.append(f"📋 SL/TP: {src}")

    ts_ok = [e for e in webhook_events.get("TRADING_STOP_OK", []) if e["match"][0] == symbol]
    if ts_ok:
        sl = ts_ok[-1]["match"][1]
        tp = ts_ok[-1]["match"][2]
        operativo.append(f"🎚️ SL={sl} TP={tp}")

    if strategy == "rettangolo":
        segnali_match = [s for s in rett_segnali if s.get("symbol") == symbol and s.get("type") == "segnale"]
        if segnali_match:
            last = segnali_match[-1]
            operativo.append(f"📐 Pattern: {last['signal']}")

    # === SEZIONE 2: CONTESTO DI MERCATO ATTUALE ===
    contesto = []
    ctx = market_context.get(symbol, {})
    if ctx:
        trend = ctx.get("trend", "?")
        market_kind = ctx.get("market_kind", "?")
        regime = ctx.get("regime", "?")
        d1_pct = ctx.get("d1_range_pct", 0)
        contesto.append(f"📊 Trend {trend}")
        contesto.append(f"📈 Mercato {market_kind} (D1 range {d1_pct:.2f}%)")
        contesto.append(f"🎯 Regime attuale: {regime}")
        # Coerenza: se P007 era OK al momento dell'apertura ma regime attuale è opposto, commento
        if "P007 regime OK" in " ".join(operativo):
            current_side = "LONG" if regime == "LONG OK" else "SHORT"
            op_side = "LONG" if side.lower() in ("buy", "long") else "SHORT"
            if current_side != op_side:
                contesto.append(f"⚠️ ATTENZIONE: regime attuale {regime} opposto al segnale {op_side} (possibile inversione)")

    sezione_operativa = " | ".join(operativo)
    sezione_contesto = " | ".join(contesto)
    if sezione_contesto:
        return f"OPERATIVO: {sezione_operativa} || CONTESTO: {sezione_contesto}"
    return f"OPERATIVO: {sezione_operativa}"


def generate_html(orders: list, webhook_events: dict, rett_segnali: list, market_context: dict) -> str:
    """Genera HTML del tableau de bord."""
    # Raggruppa operazioni per symbol+side+strategy
    grouped = defaultdict(list)
    for op in orders:
        key = (op["symbol"], op["side"], op["strategy"])
        grouped[key].append(op)

    # Statistiche globali
    total_ops = len(orders)
    total_notional = sum(op["notional"] for op in orders)
    by_strategy = defaultdict(lambda: {"count": 0, "notional": 0})
    for op in orders:
        by_strategy[op["strategy"]]["count"] += 1
        by_strategy[op["strategy"]]["notional"] += op["notional"]
    by_symbol = defaultdict(lambda: {"count": 0, "notional": 0})
    for op in orders:
        by_symbol[op["symbol"]]["count"] += 1
        by_symbol[op["symbol"]]["notional"] += op["notional"]

    # Skip counters (P007, P008, ZONA-MID, P006 OVERRIDE)
    p007_skips = len(webhook_events.get("P007_SKIP", []))
    p008_skips = len(webhook_events.get("P008_SKIP", []))
    zona_skips = len(webhook_events.get("ZONA_MID_SKIP", []))
    p006_overrides = len(webhook_events.get("P006_OVERRIDE", []))

    # Genera HTML
    html = []
    html.append("""<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Tableau de Bord - Trading Engine Mattia</title>
    <style>
        body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #1a1a2e; color: #eaeaea; margin: 20px; }
        h1 { color: #00d9ff; border-bottom: 2px solid #00d9ff; padding-bottom: 10px; }
        h2 { color: #ff6b6b; margin-top: 30px; }
        h3 { color: #ffd93d; }
        .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin: 20px 0; }
        .stat-card { background: #16213e; border-left: 4px solid #00d9ff; padding: 15px; border-radius: 5px; }
        .stat-card.danger { border-left-color: #ff6b6b; }
        .stat-card.success { border-left-color: #51cf66; }
        .stat-card.warning { border-left-color: #ffd93d; }
        .stat-value { font-size: 28px; font-weight: bold; color: #fff; }
        .stat-label { font-size: 12px; color: #aaa; text-transform: uppercase; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; background: #16213e; }
        th { background: #0f3460; color: #00d9ff; padding: 12px; text-align: left; }
        td { padding: 10px; border-bottom: 1px solid #2a2a4a; }
        tr:hover { background: #2a2a4a; }
        .badge { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 11px; font-weight: bold; }
        .badge-long { background: #51cf66; color: #000; }
        .badge-short { background: #ff6b6b; color: #fff; }
        .badge-vptr3 { background: #845ec2; color: #fff; }
        .badge-rettangolo { background: #ffd93d; color: #000; }
        .comment { color: #c0c0c0; font-size: 12px; line-height: 1.4; margin: 4px 0; padding: 6px 10px; background: #0f3460; border-left: 3px solid #00d9ff; }
        .comment strong { color: #00d9ff; }
        .comment .warning { color: #ff6b6b; font-weight: bold; }
        .meta { color: #888; font-size: 11px; }
        .op-timestamp { color: #ffffff; font-size: 15px; font-weight: bold; font-family: 'Consolas', 'Courier New', monospace; white-space: nowrap; }
        .meta-date { color: #ffffff; font-size: 18px; font-weight: bold; margin: 12px 0 18px 0; padding: 10px 16px; background: #0f3460; border-left: 5px solid #00d9ff; border-radius: 4px; display: inline-block; }
        .summary-table { width: 100%; border-collapse: collapse; margin: 15px 0 25px 0; background: #16213e; }
        .summary-table th { background: #0f3460; color: #00d9ff; padding: 10px 14px; text-align: left; font-size: 13px; }
        .summary-table td { padding: 9px 14px; border-bottom: 1px solid #2a2a4a; font-size: 14px; color: #fff; }
        .summary-table tr:hover { background: #2a2a4a; }
        .summary-table .rank { color: #00d9ff; font-weight: bold; font-size: 16px; width: 50px; }
        .summary-table .count { color: #ffd93d; font-weight: bold; font-size: 16px; text-align: right; width: 100px; }
        .summary-table .bar-cell { width: 30%; }
        .summary-table .bar { background: #00d9ff; height: 10px; border-radius: 3px; min-width: 4px; }
        .summary-table .bar.zero { background: #444; }
        .market-ctx { display: flex; gap: 10px; flex-wrap: wrap; margin: 10px 0; }
        .ctx-pill { background: #0f3460; border: 1px solid #00d9ff; padding: 5px 10px; border-radius: 15px; font-size: 12px; }
    </style>
</head>
<body>""")
    html.append(f"<h1>📊 Tableau de Bord — Trading Engine Mattia</h1>")
    total_skip_events = sum(len(v) for v in webhook_events.values())
    html.append(f"<div class='meta-date'>🕐 AGGIORNATO: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')} &nbsp;|&nbsp; Operazioni: {total_ops} &nbsp;|&nbsp; Skip events: {total_skip_events}</div>")

    # Market context overview
    if market_context:
        html.append("<h2>🌐 Contesto di mercato attuale (live)</h2>")
        html.append("<div class='market-ctx'>")
        for sym, ctx in sorted(market_context.items()):
            trend_icon = "📈" if ctx['trend'] == "rialzista" else ("📉" if ctx['trend'] == "ribassista" else "➖")
            regime_icon = "🟢" if ctx['regime'] == "LONG OK" else "🔴"
            html.append(f"<div class='ctx-pill'>{trend_icon} <strong>{sym}</strong>: {ctx['trend']} | {regime_icon} {ctx['regime']} | {ctx['market_kind']} (D1 {ctx['d1_range_pct']:.2f}%)</div>")
        html.append("</div>")

    # Stats grid
    html.append("<div class='stats-grid'>")
    html.append(f"<div class='stat-card success'><div class='stat-label'>Operazioni totali</div><div class='stat-value'>{total_ops}</div></div>")
    html.append(f"<div class='stat-card'><div class='stat-label'>Notional totale USDT</div><div class='stat-value'>${total_notional:,.0f}</div></div>")
    html.append(f"<div class='stat-card danger'><div class='stat-label'>Skip P007 (regime)</div><div class='stat-value'>{p007_skips}</div></div>")
    html.append(f"<div class='stat-card warning'><div class='stat-label'>Skip ZONA-MID</div><div class='stat-value'>{zona_skips}</div></div>")
    html.append(f"<div class='stat-card danger'><div class='stat-label'>Skip P008 (max 5)</div><div class='stat-value'>{p008_skips}</div></div>")
    html.append(f"<div class='stat-card warning'><div class='stat-label'>P006 OVERRIDE</div><div class='stat-value'>{p006_overrides}</div></div>")
    html.append("</div>")

    # === TABELLA RIEPILOGATIVA CASISTICHE (ordinate per frequenza decrescente) ===
    # Descrizioni + icone per ogni tipo di evento del webhook
    eventi_info = {
        "AUTO_FILL":       {"icon": "✅", "label": "AUTO_FILL SL/TP Charter",      "tipo": "success"},
        "P007_OK":         {"icon": "✅", "label": "P007 regime EMA50 OK",         "tipo": "success"},
        "ORDER_OK":        {"icon": "✅", "label": "Ordine inviato a Bybit",       "tipo": "success"},
        "TRADING_STOP_OK": {"icon": "✅", "label": "Trailing stop impostato",      "tipo": "success"},
        "P007_SKIP":       {"icon": "⚠️", "label": "P007 SKIP regime (close vs EMA50)", "tipo": "skip"},
        "ZONA_MID_SKIP":   {"icon": "⚠️", "label": "ZONA-MID SKIP (entry 30-70% range)", "tipo": "skip"},
        "P008_SKIP":       {"icon": "⚠️", "label": "P008 SKIP max 5 posizioni [DISATTIVATO 20/07]", "tipo": "skip"},
        "P006_OVERRIDE":   {"icon": "🔧", "label": "P006 OVERRIDE (SL clamp -3% Charter)", "tipo": "override"},
        "P005_TP_OVERRIDE":{"icon": "🔧", "label": "P005 TP OVERRIDE (legacy)",   "tipo": "override"},
        "EMAIL_FAIL":      {"icon": "🛑", "label": "Email notifica fallita",       "tipo": "error"},
    }
    # Aggrega tutti gli eventi in lista (tipo, count)
    eventi_list = []
    for evt_type, info in eventi_info.items():
        cnt = len(webhook_events.get(evt_type, []))
        if cnt > 0 or evt_type in ("P007_SKIP", "ZONA_MID_SKIP", "P008_SKIP", "P006_OVERRIDE"):  # mostra sempre gli skip noti anche se 0
            eventi_list.append((evt_type, info, cnt))
    # Ordina per count decrescente
    eventi_list.sort(key=lambda x: -x[2])
    max_count = max((c for _, _, c in eventi_list), default=1) or 1

    html.append("<h2>📊 Casistiche (ordinate per frequenza)</h2>")
    html.append("<table class='summary-table'>")
    html.append("<tr><th>#</th><th>Casistica</th><th>Tipo</th><th class='count'>Count</th><th class='bar-cell'>% del massimo</th></tr>")
    for rank, (evt_type, info, cnt) in enumerate(eventi_list, start=1):
        tipo_color = {"success": "#51cf66", "skip": "#ff6b6b", "override": "#ffd93d", "error": "#ff006e"}.get(info["tipo"], "#aaa")
        bar_pct = (cnt / max_count * 100) if max_count > 0 else 0
        bar_class = "bar" if cnt > 0 else "bar zero"
        bar_style = f"width: {bar_pct:.1f}%;" if cnt > 0 else "width: 4px;"
        html.append(f"<tr>")
        html.append(f"<td class='rank'>{rank}</td>")
        html.append(f"<td>{info['icon']} <strong>{info['label']}</strong> <span class='meta'>({evt_type})</span></td>")
        html.append(f"<td><span style='color:{tipo_color}; font-weight:bold;'>{info['tipo'].upper()}</span></td>")
        html.append(f"<td class='count'>{cnt}</td>")
        html.append(f"<td class='bar-cell'><div class='{bar_class}' style='{bar_style}'></div></td>")
        html.append(f"</tr>")
    html.append("</table>")

    # Per strategy
    html.append("<h2>📈 Operazioni per strategia</h2>")
    html.append("<table><tr><th>Strategia</th><th>Operazioni</th><th>Notional USDT</th></tr>")
    for strat, data in sorted(by_strategy.items(), key=lambda x: -x[1]["count"]):
        html.append(f"<tr><td><span class='badge badge-{strat}'>{strat.upper()}</span></td><td>{data['count']}</td><td>${data['notional']:,.0f}</td></tr>")
    html.append("</table>")

    # Per symbol
    html.append("<h2>💎 Operazioni per asset</h2>")
    html.append("<table><tr><th>Symbol</th><th>Operazioni</th><th>Notional USDT</th></tr>")
    for sym, data in sorted(by_symbol.items(), key=lambda x: -x[1]["notional"]):
        html.append(f"<tr><td>{sym}</td><td>{data['count']}</td><td>${data['notional']:,.0f}</td></tr>")
    html.append("</table>")

    # Lista operazioni con commenti (ultime 100)
    html.append("<h2>📋 Operazioni recenti (ultime 100) con commenti automatici</h2>")
    html.append("<table>")
    html.append("<tr><th>Timestamp</th><th>Symbol</th><th>Side</th><th>Qty</th><th>Price</th><th>Notional</th><th>Strategy</th><th>Order ID</th></tr>")
    for op in sorted(orders, key=lambda x: x["ts"], reverse=True)[:100]:
        side_class = "badge-long" if op["side"].lower() in ("buy", "long") else "badge-short"
        strat_class = f"badge-{op['strategy']}"
        html.append(f"<tr>")
        html.append(f"<td class='op-timestamp'>{op['ts'][:19]}</td>")
        html.append(f"<td>{op['symbol']}</td>")
        html.append(f"<td><span class='badge {side_class}'>{op['side'].upper()}</span></td>")
        html.append(f"<td>{op['qty']:.4f}</td>")
        html.append(f"<td>{op['price']:.5f}</td>")
        html.append(f"<td>${op['notional']:.2f}</td>")
        html.append(f"<td><span class='badge {strat_class}'>{op['strategy'].upper()}</span></td>")
        html.append(f"<td class='meta'>{op['order_id'][:8]}...</td>")
        html.append(f"</tr>")
        # Riga commento migliorato
        comment = build_comment(op, webhook_events, rett_segnali, market_context)
        # Evidenzia "OPERATIVO" e "CONTESTO" come strong
        comment_html = comment.replace("OPERATIVO:", "<strong>OPERATIVO:</strong>").replace("CONTESTO:", "<br><strong>CONTESTO:</strong>").replace("⚠️ ATTENZIONE", '<span class="warning">⚠️ ATTENZIONE</span>')
        html.append(f"<tr><td colspan='8'><div class='comment'>💬 {comment_html}</div></td></tr>")
    html.append("</table>")

    # Skip events recenti
    html.append("<h2>⚠️ Skip events (ultimi 50)</h2>")
    html.append("<table>")
    html.append("<tr><th>Tipo</th><th>Symbol</th><th>Side</th><th>Dettaglio</th></tr>")
    skip_count = 0
    for evt_type in ["P007_SKIP", "P008_SKIP", "ZONA_MID_SKIP", "P006_OVERRIDE"]:
        for e in webhook_events.get(evt_type, []):
            if skip_count >= 50:
                break
            match = e["match"]
            html.append(f"<tr><td><span class='badge badge-short'>{evt_type}</span></td>")
            html.append(f"<td>{match[0]}</td>")
            html.append(f"<td>{match[1] if len(match) > 1 else '-'}</td>")
            html.append(f"<td class='meta'>{e['line'][:200]}</td></tr>")
            skip_count += 1
    html.append("</table>")

    # Footer
    html.append(f"<p class='meta'>Generato da generate_tableau.py | Mavis 2026-07-20 | Skill: trade-journal | Deadline 1° Agosto 2026</p>")
    html.append("</body></html>")
    return "\n".join(html)


def main_loop():
    log.info("=== TRADE JOURNAL STARTED (Mavis 2026-07-20) ===")
    log.info("Output: %s | Poll: %ds", OUTPUT_HTML, POLL_SECONDS)
    with open(PID_FILE, "w") as f:
        f.write(str(os.getpid()))

    # Inizializza client Bybit per query market context live
    from bybit_demo_client import BybitDemoClient
    bybit = BybitDemoClient()

    while True:
        try:
            orders = parse_orders_log()
            webhook_events = parse_webhook_log()
            rett_segnali = parse_rettangolo_log()
            market_context = get_market_context(bybit)  # 1 query per symbol, cached
            html = generate_html(orders, webhook_events, rett_segnali, market_context)
            with open(OUTPUT_HTML, "w", encoding="utf-8") as f:
                f.write(html)
            log.info("Tableau aggiornato: %d operazioni, %d skip events, %d symbols contesto",
                     len(orders), sum(len(v) for v in webhook_events.values()),
                     len(market_context))
        except Exception as e:
            log.error("Errore generazione tableau: %s", e)
        time.sleep(POLL_SECONDS)


if __name__ == "__main__":
    try:
        main_loop()
    except KeyboardInterrupt:
        log.info("STOPPED")
        sys.exit(0)
