"""
square_monitor.py - BOZZA per fork parallelo a rettangolo_monitor.py.

Usa SquareStrategy (mean-reversion del Quadrato, 4 fix applicati: no-trade zone,
pattern 2 candele, time filter 30min, ATR filter 0.3%).

Differenze vs rettangolo_monitor:
- Importa SquareStrategy invece di compute_signal/scan_signals
- State machine (IDLE -> FIRST_TEST -> RETEST -> IN_TRADE) per symbol
- Visualizza: range ieri (box_top/bottom/mid), no-trade zone centrale,
  eventuale signal live, livelli SL/TP se in_trade
- Output: monitor_square.html (separato da monitor.html)
- Symbol list: SOLO symbol con strategy=square in rettangolo_assets.csv

Auto-refresh: rigenera ogni 60s (cambia LOOP_INTERVAL_SEC per test).

WORKFLOW:
1. Carica symbol da rettangolo_assets.csv con filter strategy=square
2. Per ogni symbol:
   a. Fetch klines daily (ultimi 3 giorni) + intraday 30m (ultimi 2 giorni)
   b. Identifica prev_daily (ieri)
   c. Inizializza SquareStrategy, update_box(prev_daily.high, prev_daily.low, date)
   d. Per ogni candela intraday (ieri+oggi in ordine), chiama bot.on_new_candle(Candle(...))
   e. Visualizza con plotly: candele + box (top/bot/mid) + no-trade zone + signal live
3. Salva monitor_square.html
4. Sleep 60s, rigenera

PATTERN: questo e' un fork parallelo (Opzione B). Alternativa Opzione A:
modificare rettangolo_monitor.py riga 21 per dispatcher dinamico su colonna
strategy. Opzione A raccomandata da Mavis ma richiede refactor di
rettangolo_monitor.py (no tocco file progetto da parte mia).
"""
import sys
import time
import urllib.request
import urllib.parse
import json
import argparse
import threading
from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo

import plotly.graph_objects as go
from plotly.subplots import make_subplots
from flask import Flask, jsonify, send_from_directory

# === IMPORTS STRATEGIA ===
# Aggiungi live_deploy al path PRIMA di importare
_LIVE_DEPLOY = Path(r"G:\AI TRADING ENGINE\live_deploy")
if str(_LIVE_DEPLOY) not in sys.path:
    sys.path.insert(0, str(_LIVE_DEPLOY))

from square_strategy import SquareStrategy, Candle  # noqa: E402
from rettangolo_config import load_assets  # noqa: E402

# === CONFIG ===
LOCAL_TZ = ZoneInfo("Europe/Rome")
OUT_HTML = _LIVE_DEPLOY / "bt_rettangolo" / "monitor_square.html"
LOOP_INTERVAL_SEC = 60  # rigenera ogni 60s
INTRADAY_TIMEFRAME = "30"  # 30 min
INTRADAY_DAYS = 2
DAILY_DAYS = 5

# London 9:00 Europe/London (BST = UTC+1 estate, GMT = UTC+0 inverno, auto con zoneinfo).
# SESSION_TZ_OFFSET mantenuto per retro-compatibilita' ma il calcolo usa ZoneInfo.
SESSION_TZ_OFFSET = 1  # legacy, usato solo da get_session_open_unix_legacy


def fetch_klines(symbol, interval, days):
    """Fetch candele da Bybit V5 public API (no auth richiesta per kline)."""
    end_ms = int(time.time() * 1000)
    start_ms = end_ms - days * 24 * 3600 * 1000
    params = {
        "category": "linear",
        "symbol": symbol,
        "interval": interval,
        "start": start_ms,
        "end": end_ms,
        "limit": 200,
    }
    qs = urllib.parse.urlencode(params)
    url = f"https://api.bybit.com/v5/market/kline?{qs}"
    with urllib.request.urlopen(url, timeout=15) as r:
        data = json.loads(r.read().decode("utf-8"))
    rows = data.get("result", {}).get("list", [])
    rows.sort(key=lambda x: int(x[0]))
    klines = []
    for row in rows:
        klines.append({
            "ts": int(row[0]),
            "date": datetime.fromtimestamp(int(row[0]) / 1000, tz=LOCAL_TZ).strftime("%Y-%m-%d %H:%M"),
            "open": float(row[1]),
            "high": float(row[2]),
            "low": float(row[3]),
            "close": float(row[4]),
            "volume": float(row[5]),
        })
    return klines


def dict_to_candle(d):
    """Converte dict (output fetch_klines) in Candle (dataclass per SquareStrategy)."""
    return Candle(
        timestamp=d["ts"] // 1000,  # Candle vuole unix seconds, Bybit da unix ms
        open=d["open"],
        high=d["high"],
        low=d["low"],
        close=d["close"],
        volume=d["volume"],
    )


def get_session_open_unix(cur_day_ts_ms: int) -> int:
    """Calcola unix timestamp del session open (9:00 Europe/London del giorno corrente).
    cur_day_ts_ms: timestamp daily corrente (usato per estrarre YYYY-MM-DD).
    Usa zoneinfo Europe/London per gestire BST (estate, UTC+1) e GMT (inverno, UTC+0) automaticamente.
    FIX 2026-07-17 (Verifier): precedente hardcoded SESSION_TZ_OFFSET=1 non gestiva DST.
    """
    from zoneinfo import ZoneInfo
    cur_day_date = datetime.fromtimestamp(cur_day_ts_ms / 1000, tz=timezone.utc)
    # 9:00 Europe/London (gestisce BST/GMT automaticamente)
    london_tz = ZoneInfo("Europe/London")
    session_dt = cur_day_date.replace(hour=9, minute=0, second=0, microsecond=0, tzinfo=london_tz)
    return int(session_dt.timestamp())


def run_square_for_symbol(symbol, daily_klines, intraday_klines):
    """Esegue SquareStrategy su un symbol e ritorna: bot (con state aggiornato),
    eventuale signal live, lista candele filtrate (ieri+oggi per sessione London 9-9).

    FIX 2026-07-17 (Verifier, da Mattia): il BOX deve essere calcolato sulla sessione
    London 9-9 (24h custom), NON sulla Bybit daily K-line (00-24 UTC). Quindi:
    - Filtro candele: 9:00 London ieri -> 9:00 London oggi
    - Box custom: high = max(high) delle candele filtrate, low = min(low) delle candele filtrate
    - Etichette: LOCAL_TZ (Europe/Rome) = 9:00 London = 10:00 Italy in estate
    """
    if len(daily_klines) < 2 or len(intraday_klines) < 5:
        return None, None, []

    prev_daily = daily_klines[-2]  # Bybit daily K-line ieri (riferimento, non usato per box)
    cur_day = daily_klines[-1]

    # Calcola finestra sessione London 9-9
    session_open_unix_oggi = get_session_open_unix(cur_day["ts"])
    session_open_unix_ieri = session_open_unix_oggi - 86400  # 24h prima
    session_start_ms = session_open_unix_ieri * 1000
    session_end_ms = session_open_unix_oggi * 1000
    filtered = [k for k in intraday_klines if session_start_ms <= k["ts"] <= session_end_ms]
    if len(filtered) < 3:
        # Fallback: ultime 48 candele 30m (24h)
        filtered = intraday_klines[-48:]

    # Box CUSTOM da sessione London 9-9 (NON Bybit daily K-line)
    if filtered:
        custom_high = max(k["high"] for k in filtered)
        custom_low = min(k["low"] for k in filtered)
        custom_date = datetime.fromtimestamp(session_open_unix_ieri, tz=LOCAL_TZ).strftime("%Y-%m-%d")
        box_high = custom_high
        box_low = custom_low
        box_date = custom_date
    else:
        # Fallback estremo (non dovrebbe mai accadere)
        box_high = prev_daily["high"]
        box_low = prev_daily["low"]
        box_date = prev_daily["date"][:10]

    # Inizializza SquareStrategy
    bot = SquareStrategy(
        bybit_client=None,  # il monitor non fa trading, solo visualizza segnali
        symbol=symbol,
        timeframe=INTRADAY_TIMEFRAME,
        risk_per_trade_pct=1.0,
        leverage=3,
        on_signal=lambda s: None,  # noop, il monitor non manda ordini
    )
    bot.update_box(
        yesterday_high=box_high,
        yesterday_low=box_low,
        date=box_date,
        session_open_unix=session_open_unix_oggi,
    )

    # Fai girare la state machine su ogni candela (ieri + oggi)
    last_signal = None
    for k in filtered:
        c = dict_to_candle(k)
        bot.on_new_candle(c)
        # Cattura eventuale segnale (uno solo per candela, l'ultimo vince)
        # on_new_candle chiama on_signal quando pattern completo, dentro setta in_position=True

    # Lo state finale + in_position indica se c'è segnale live
    signal_live = None
    if bot.in_position and bot.stop_price and bot.tp_price:
        signal_live = {
            "side": bot.position_side,
            "entry_trigger": None,  # SquareStrategy non espone entry_trigger, calcolato dentro _check_entry
            "stop": bot.stop_price,
            "tp": bot.tp_price,
            "state": bot.state,
            "state_name": {
                SquareStrategy.STATE_IDLE: "IDLE",
                SquareStrategy.STATE_FIRST_TEST_TOP: "FIRST_TEST_TOP",
                SquareStrategy.STATE_FIRST_TEST_BOTTOM: "FIRST_TEST_BOTTOM",
                SquareStrategy.STATE_RETEST_TOP: "RETEST_TOP",
                SquareStrategy.STATE_RETEST_BOTTOM: "RETEST_BOTTOM",
                SquareStrategy.STATE_IN_TRADE: "IN_TRADE",
            }.get(bot.state, "?"),
        }

    return bot, signal_live, filtered


def build_figure_square(symbol, bot, filtered, prev_daily):
    """Costruisce grafico plotly per square strategy: candele + box + no-trade zone + signal.
    FIX 2026-07-17 (Verifier): separatore verticale ieri/oggi a session_open_unix_oggi
    (= 9:00 London oggi = 10:00 Italy in estate, 10:00 Italy in inverno)."""
    if not bot or not bot.box or len(filtered) < 5:
        return None
    box = bot.box
    dates = [k["date"] for k in filtered]
    opens = [k["open"] for k in filtered]
    highs = [k["high"] for k in filtered]
    lows = [k["low"] for k in filtered]
    closes = [k["close"] for k in filtered]

    # FIX 2026-07-17 (Verifier): separatore ieri/oggi a 9:00 London (= 10:00 Italy in estate)
    # session_open_unix_oggi = 9:00 London oggi (unix seconds, gia' calcolato in run_square_for_symbol)
    # Lo recuperiamo indirettamente: la candela 0 di filtered e' la prima della sessione London ieri
    # quindi session_open_unix_ieri = filtered[0]["ts"] / 1000, e ieri = filtered[0:idx_today]
    # In realta' piu' semplice: il midpoint di filtered e' 12:00 dopo 12h = 21:00 London ieri
    # e session_open_unix_oggi = midpoint se len 48 candele... NO, piu' chiaro usare session_open_unix_oggi da cur_day
    # Per semplicita': idx_today = indice della candela piu' vicina a session_open_unix_oggi
    # Lo ricalcolo da filtered usando il calcolo 9:00 London oggi
    from zoneinfo import ZoneInfo as _ZI
    _london_tz = _ZI("Europe/London")
    _now = datetime.now(_london_tz)
    # 9:00 London di OGGI (oggi London time)
    _session_open_today_london = _now.replace(hour=9, minute=0, second=0, microsecond=0)
    _session_open_today_unix = int(_session_open_today_london.timestamp()) * 1000  # in ms
    # idx_today = prima candela >= session_open_today_unix
    idx_today = next((i for i, k in enumerate(filtered) if k["ts"] >= _session_open_today_unix), len(filtered))
    yesterday = filtered[:idx_today]
    today = filtered[idx_today:]
    # Date del separatore verticale (formato Europe/Rome = LOCAL_TZ)
    sep_date = datetime.fromtimestamp(_session_open_today_unix / 1000, tz=LOCAL_TZ).strftime("%Y-%m-%d %H:%M")

    # FIX 2026-07-17 (Verifier): separatore verticale 9:00 London (= 10:00 Italy in estate)
    # Aggiunto DOPO le candele, PRIMA del box
    fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.75, 0.25])
    if today and yesterday:
        # Linea verticale tratteggiata a 9:00 London oggi (= 10:00 Italy in estate)
        fig.add_shape(
            type="line", x0=sep_date, x1=sep_date,
            y0=box.bottom - (box.top - box.bottom) * 0.5, y1=box.top + (box.top - box.bottom) * 0.5,
            line=dict(color="rgba(255,255,255,0.65)", width=1.5, dash="dash"),
        )
        # Label "9:00 LONDON" sopra la linea (lato destro, "oggi")
        fig.add_annotation(
            xref="x", yref="paper", x=sep_date, y=1.02,
            text="<b>9:00 LONDON →</b>", showarrow=False,
            xanchor="left", yanchor="bottom",
            font=dict(color="#4dd0c1", size=11, family="monospace"),
            bgcolor="rgba(77,208,193,0.15)", bordercolor="#4dd0c1", borderwidth=1, borderpad=3
        )
        # Label "← 9:00 LONDON (IERI)" sul lato sinistro
        fig.add_annotation(
            xref="x", yref="paper", x=sep_date, y=1.02,
            text="<b>← 9:00 LONDON (IERI)</b>", showarrow=False,
            xanchor="right", yanchor="bottom",
            font=dict(color="#ffa726", size=11, family="monospace"),
            bgcolor="rgba(255,167,38,0.15)", bordercolor="#ffa726", borderwidth=1, borderpad=3
        )

    # candele ieri
    fig.add_trace(go.Candlestick(
        x=[k["date"] for k in yesterday], open=[k["open"] for k in yesterday],
        high=[k["high"] for k in yesterday], low=[k["low"] for k in yesterday],
        close=[k["close"] for k in yesterday],
        name=symbol + " 30m IERI", increasing_line_color="#26a69a", decreasing_line_color="#ef5350",
        line=dict(width=1.5)
    ), row=1, col=1)
    # candele oggi
    fig.add_trace(go.Candlestick(
        x=[k["date"] for k in today], open=[k["open"] for k in today],
        high=[k["high"] for k in today], low=[k["low"] for k in today],
        close=[k["close"] for k in today],
        name=symbol + " 30m OGGI (live)", increasing_line_color="#4dd0c1", decreasing_line_color="#e57373",
        line=dict(width=2)
    ), row=1, col=1)

    # BOX (range ieri) - rettangolo tratteggiato blu
    fig.add_shape(
        type="rect", x0=dates[0], x1=dates[-1], y0=box.bottom, y1=box.top,
        line=dict(color="rgba(33,150,243,0.4)", width=1, dash="dot"),
        fillcolor="rgba(33,150,243,0.06)",
        name="Range " + prev_daily["date"][:10]
    )
    # NO-TRADE ZONE centrale (rettangolo grigio chiaro)
    fig.add_shape(
        type="rect", x0=dates[0], x1=dates[-1], y0=box.no_trade_bot, y1=box.no_trade_top,
        line=dict(color="rgba(150,150,150,0.5)", width=1, dash="dash"),
        fillcolor="rgba(150,150,150,0.10)",
        name="NO-TRADE ZONE (15%)"
    )
    # TOP / BOTTOM / MID lines
    fig.add_shape(type="line", x0=dates[0], x1=dates[-1], y0=box.top, y1=box.top,
                  line=dict(color="rgba(33,150,243,1.0)", width=2.5, dash="dash"))
    fig.add_shape(type="line", x0=dates[0], x1=dates[-1], y0=box.bottom, y1=box.bottom,
                  line=dict(color="rgba(33,150,243,1.0)", width=2.5, dash="dash"))
    fig.add_shape(type="line", x0=dates[0], x1=dates[-1], y0=box.mid, y1=box.mid,
                  line=dict(color="rgba(255,152,0,1.0)", width=2, dash="dot"))

    # Etichette lato destro
    fig.add_annotation(xref="paper", x=0.985, y=box.top, text="TOP " + format(box.top, ".4f"),
                       showarrow=False, xanchor="left", yanchor="middle",
                       font=dict(color="#ef5350", size=12, family="monospace"),
                       bgcolor="rgba(239,83,80,0.15)", bordercolor="#ef5350", borderwidth=1, borderpad=4)
    fig.add_annotation(xref="paper", x=0.985, y=box.bottom, text="BOT " + format(box.bottom, ".4f"),
                       showarrow=False, xanchor="left", yanchor="middle",
                       font=dict(color="#4caf50", size=12, family="monospace"),
                       bgcolor="rgba(76,175,80,0.15)", bordercolor="#4caf50", borderwidth=1, borderpad=4)
    fig.add_annotation(xref="paper", x=0.985, y=box.mid, text="MID " + format(box.mid, ".4f"),
                       showarrow=False, xanchor="left", yanchor="middle",
                       font=dict(color="#ff9800", size=12, family="monospace"),
                       bgcolor="rgba(255,152,0,0.15)", bordercolor="#ff9800", borderwidth=1, borderpad=4)

    # STATO Strategy in alto a destra
    state_colors = {
        SquareStrategy.STATE_IDLE: "#888888",
        SquareStrategy.STATE_FIRST_TEST_TOP: "#ffa726",
        SquareStrategy.STATE_FIRST_TEST_BOTTOM: "#ffa726",
        SquareStrategy.STATE_RETEST_TOP: "#42a5f5",
        SquareStrategy.STATE_RETEST_BOTTOM: "#42a5f5",
        SquareStrategy.STATE_IN_TRADE: "#66bb6a",
    }
    state_names = {
        SquareStrategy.STATE_IDLE: "IDLE (in attesa test)",
        SquareStrategy.STATE_FIRST_TEST_TOP: "FIRST TEST TOP",
        SquareStrategy.STATE_FIRST_TEST_BOTTOM: "FIRST TEST BOTTOM",
        SquareStrategy.STATE_RETEST_TOP: "RETEST TOP (in attesa Doji RED)",
        SquareStrategy.STATE_RETEST_BOTTOM: "RETEST BOTTOM (in attesa Doji GREEN)",
        SquareStrategy.STATE_IN_TRADE: "IN TRADE",
    }
    state_color = state_colors.get(bot.state, "#888888")
    state_name = state_names.get(bot.state, "?")

    fig.add_annotation(
        xref="paper", yref="paper", x=0.50, y=1.06,
        text=f"<b>SQUARE STATE: {state_name}</b>", showarrow=False,
        xanchor="center", yanchor="bottom",
        font=dict(color=state_color, size=12, family="monospace"),
        bgcolor=f"rgba({int(state_color[1:3], 16)},{int(state_color[3:5], 16)},{int(state_color[5:7], 16)},0.15)",
        bordercolor=state_color, borderwidth=1, borderpad=4
    )

    # SEGNALE LIVE (se in trade)
    if bot.in_position and bot.stop_price and bot.tp_price:
        sig_text = f"{bot.position_side.upper()} | SL={bot.stop_price:.4f} | TP={bot.tp_price:.4f}"
        sig_color = "#4caf50" if bot.position_side == "long" else "#ef5350"
        fig.add_annotation(
            xref="paper", yref="paper", x=0.50, y=0.98,
            text=f"<b>>> {sig_text} <<</b>", showarrow=False,
            xanchor="center", yanchor="bottom",
            font=dict(color=sig_color, size=14, family="monospace"),
            bgcolor=f"rgba({int(sig_color[1:3], 16)},{int(sig_color[3:5], 16)},{int(sig_color[5:7], 16)},0.20)",
            bordercolor=sig_color, borderwidth=2, borderpad=6
        )

    # Volume bars (row 2)
    colors_vol = ["#26a69a" if closes[i] >= opens[i] else "#ef5350" for i in range(len(closes))]
    fig.add_trace(go.Bar(x=dates, y=[k["volume"] for k in filtered], marker_color=colors_vol, showlegend=False), row=2, col=1)

    fig.update_layout(
        title=f"<b>{symbol}</b> — Strategia del Quadrato (Square) | Box: {box.bottom:.4f} - {box.top:.4f} (mid {box.mid:.4f}) | Sessione 9:00 London → 9:00 London",
        xaxis_rangeslider_visible=False,
        template="plotly_dark",
        height=700,
        showlegend=True,
    )
    # FIX 2026-07-17 (Verifier): tick asse X ogni 1h (dtick=3600000 ms) + tick0 allineato a mezzanotte Rome
    fig.update_xaxes(dtick=3600000, tickformat="%H:%M", showgrid=True, gridcolor="rgba(255,255,255,0.08)")
    return fig


def main_loop():
    """Loop principale: rigenera monitor_square.html ogni LOOP_INTERVAL_SEC."""
    print(f"[square_monitor] avviato, rigenera ogni {LOOP_INTERVAL_SEC}s", flush=True)
    while True:
        try:
            # Carica SOLO symbol con strategy=square
            assets = load_assets(only_enabled=True, strategy="square")
            if not assets:
                print("[square_monitor] WARN: nessun symbol con strategy=square in rettangolo_assets.csv", flush=True)
                # genera HTML vuoto per non rompere link
                OUT_HTML.parent.mkdir(parents=True, exist_ok=True)
                OUT_HTML.write_text("<html><body><h1>NESSUN SYMBOL strategy=square</h1><p>Aggiungi un symbol in rettangolo_assets.csv con colonna strategy=square</p></body></html>", encoding="utf-8")
                time.sleep(LOOP_INTERVAL_SEC)
                continue

            figures_html = []
            for asset in assets:
                symbol = asset["symbol"]
                print(f"[square_monitor] processing {symbol}...", flush=True)
                try:
                    daily_klines = fetch_klines(symbol, "D", DAILY_DAYS)
                    intraday_klines = fetch_klines(symbol, INTRADAY_TIMEFRAME, INTRADAY_DAYS)
                    bot, signal, filtered = run_square_for_symbol(symbol, daily_klines, intraday_klines)
                    if bot is None or filtered is None:
                        print(f"  {symbol}: klines insufficienti, skip", flush=True)
                        continue
                    fig = build_figure_square(symbol, bot, filtered, daily_klines[-2])
                    if fig:
                        figures_html.append(fig.to_html(full_html=False, include_plotlyjs="cdn"))
                except Exception as e:
                    print(f"  {symbol}: ERRORE: {e}", flush=True)
                    continue

            if figures_html:
                OUT_HTML.parent.mkdir(parents=True, exist_ok=True)
                full_html = f"""<!DOCTYPE html>
<html><head><title>SQUARE Strategy Monitor</title>
<style>body{{background:#0d1117;color:#c9d1d9;font-family:monospace;margin:20px;}}</style>
</head><body>
<h1>🟦 STRATEGIA DEL QUADRATO (Square) — Live Monitor</h1>
<p>Auto-refresh: {LOOP_INTERVAL_SEC}s | Simboli attivi: {len(assets)} ({', '.join(a['symbol'] for a in assets)})</p>
<p>Filtri attivi: no-trade zone 15%, time filter 30min dopo 9:00, ATR ≥ 0.3%, volume ≥ 1.5x media</p>
<hr/>
{''.join(figures_html)}
</body></html>"""
                OUT_HTML.write_text(full_html, encoding="utf-8")
                print(f"[square_monitor] {OUT_HTML} rigenerato, {len(figures_html)} chart", flush=True)
            else:
                print(f"[square_monitor] nessun chart generato questo giro", flush=True)

        except Exception as e:
            print(f"[square_monitor] loop exception: {e}", flush=True)
        time.sleep(LOOP_INTERVAL_SEC)


# === FLASK SERVER + STARTUP ===
_health_state = {
    "ok": True,
    "service": "square-monitor",
    "ts": 0,
    "symbols": 0,
    "last_update": None,
    "html_path": str(OUT_HTML),
    "loop_alive": True,
}


def background_loop():
    """Loop background che rigenera monitor_square.html ogni LOOP_INTERVAL_SEC."""
    while True:
        try:
            assets = load_assets(only_enabled=True, strategy="square")
            _health_state["symbols"] = len(assets)
            _health_state["ts"] = int(time.time())
            _health_state["last_update"] = datetime.now().isoformat()

            if not assets:
                OUT_HTML.parent.mkdir(parents=True, exist_ok=True)
                OUT_HTML.write_text(
                    "<html><body><h1>NESSUN SYMBOL strategy=square</h1>"
                    "<p>Aggiungi un symbol in rettangolo_assets.csv con colonna strategy=square</p>"
                    "</body></html>",
                    encoding="utf-8",
                )
                time.sleep(LOOP_INTERVAL_SEC)
                continue

            figures_html = []
            for asset in assets:
                symbol = asset["symbol"]
                try:
                    daily_klines = fetch_klines(symbol, "D", DAILY_DAYS)
                    intraday_klines = fetch_klines(symbol, INTRADAY_TIMEFRAME, INTRADAY_DAYS)
                    bot, signal, filtered = run_square_for_symbol(symbol, daily_klines, intraday_klines)
                    if bot is None or filtered is None:
                        continue
                    fig = build_figure_square(symbol, bot, filtered, daily_klines[-2])
                    if fig:
                        figures_html.append(fig.to_html(full_html=False, include_plotlyjs="cdn"))
                except Exception as e:
                    print(f"  {symbol}: ERRORE: {e}", flush=True)
                    continue

            if figures_html:
                OUT_HTML.parent.mkdir(parents=True, exist_ok=True)
                full_html = (
                    "<!DOCTYPE html><html><head><title>SQUARE Strategy Monitor</title>"
                    "<style>body{background:#0d1117;color:#c9d1d9;font-family:monospace;margin:20px;}</style>"
                    "</head><body>"
                    f"<h1>SQUARE Strategy Monitor</h1>"
                    f"<p>Auto-refresh: {LOOP_INTERVAL_SEC}s | Simboli attivi: {len(assets)} "
                    f"({', '.join(a['symbol'] for a in assets)})</p>"
                    "<p>Filtri: no-trade zone 15%, time filter 30min dopo 9:00, ATR >= 0.3%, volume >= 1.5x media</p>"
                    "<hr/>" + "".join(figures_html) + "</body></html>"
                )
                OUT_HTML.write_text(full_html, encoding="utf-8")
                print(f"[square_monitor] {OUT_HTML} rigenerato, {len(figures_html)} chart", flush=True)
        except Exception as e:
            print(f"[square_monitor] loop exception: {e}", flush=True)
            _health_state["ok"] = False
        time.sleep(LOOP_INTERVAL_SEC)


def create_app():
    app = Flask(__name__)

    @app.route("/health")
    def health():
        return jsonify(_health_state)

    @app.route("/")
    def index():
        if OUT_HTML.exists():
            return send_from_directory(str(OUT_HTML.parent), OUT_HTML.name)
        return "<html><body><h1>monitor_square.html non ancora generato</h1><p>aspetta 60s</p></body></html>"

    return app


def main():
    """Entry point: avvia background loop + Flask server."""
    parser = argparse.ArgumentParser(description="Square Strategy Monitor")
    parser.add_argument("--port", type=int, default=5504, help="Porta Flask (default 5504)")
    parser.add_argument("--host", type=str, default="127.0.0.1", help="Host Flask (default 127.0.0.1)")
    args = parser.parse_args()

    print(f"[square_monitor] avviato su {args.host}:{args.port}, loop ogni {LOOP_INTERVAL_SEC}s", flush=True)

    # Background loop in thread separato
    t = threading.Thread(target=background_loop, daemon=True, name="square-monitor-loop")
    t.start()
    print("[square_monitor] background loop avviato", flush=True)

    # Flask server in main thread
    app = create_app()
    print(f"[square_monitor] Starting Flask su {args.host}:{args.port}", flush=True)
    app.run(host=args.host, port=args.port, debug=False, use_reloader=False)


if __name__ == "__main__":
    main()
