"""
grid_brief — digest ANONIMO per gli agenti del gioco GRID TRADERS (sessione 3).

Come agent_brief, ma con statistiche pensate per DIMENSIONARE una griglia:
oltre a vol/autocorrelazioni, l'escursione tipica (max/min - 1) su finestre
rolling e quanto spesso il prezzo "esce" da un range simmetrico attorno a un
punto di partenza entro N barre. L'agente non sa cosa siano X e Y.

  uv run python -m scripts.games.grid_brief 1h     # stampa il digest
  uv run python -m scripts.games.grid_brief --all  # scrive data/games/grid_digests.json
"""
from __future__ import annotations

import json

import numpy as np
import pandas as pd

from scripts.games.engine import load_anon
from scripts.games.agent_brief import _stats

TF_ID = {"15m": "T2", "30m": "T3", "1h": "T4", "2h": "T5", "4h": "T6", "1d": "T7"}


def _range_stats(close, windows=(100, 500, 2000)):
    """Escursione (max/min - 1) su finestre rolling: mediana e p90, in %."""
    s = pd.Series(close)
    out = {}
    for w in windows:
        if len(close) < w * 2:
            continue
        exc = (s.rolling(w).max() / s.rolling(w).min() - 1).dropna()
        out[f"w{w}"] = {"median_pct": round(float(exc.median() * 100), 2),
                        "p90_pct": round(float(exc.quantile(0.9) * 100), 2)}
    return out


def _escape_stats(close, half_widths=(0.05, 0.10, 0.20), horizon=500):
    """Da un punto di partenza, % di volte in cui il prezzo esce da
    +-half_width entro `horizon` barre (campionato ogni horizon/2)."""
    n = len(close)
    stepi = max(1, horizon // 2)
    starts = np.arange(0, n - horizon, stepi)
    out = {}
    for hw in half_widths:
        esc = 0
        for st in starts:
            w = close[st:st + horizon]
            p0 = w[0]
            if np.any(w > p0 * (1 + hw)) or np.any(w < p0 * (1 - hw)):
                esc += 1
        out[f"+-{hw*100:.0f}%"] = round(100.0 * esc / max(1, len(starts)), 1)
    return out


def make_grid_digest(tf: str, window: int = 60):
    data = load_anon(tf)
    n = data["n"]
    s = max(0, n - window)
    dig = {"timeframe_id": TF_ID.get(tf, "T?"), "n_bars_total": n, "series": {}}
    for name in ("A", "B"):
        o = data[name]
        c = o["close"]
        norm = c[s:] / c[s] * 100.0
        dig["series"][{"A": "X", "B": "Y"}[name]] = {
            "stats": _stats(c, o["high"], o["low"]),
            "range_excursion_rolling": _range_stats(c),
            "escape_from_range_within_500_bars_pct": _escape_stats(c),
            "recent_window_norm": [round(float(v), 2) for v in norm],
        }
    return dig


GRID_MENU = {
    "gioco": ("Configura una GRIGLIA di trading secondo la spec (griglia geometrica "
              "FISSA dentro un range attorno al prezzo di deploy; compra quando il "
              "prezzo scende attraverso un livello, rivendi quel livello quando "
              "risale al livello successivo; stop-loss sotto il range e take-profit "
              "sopra chiudono tutto; poi la griglia si ri-deploya sul prezzo corrente)."),
    "obiettivo": ("PnL netto positivo dopo i costi (0.10% andata+ritorno per ogni "
                  "round-trip di livello). Servono >=10 operazioni al mese. La "
                  "griglia monetizza le oscillazioni e PERDE nei trend: lo stop-loss "
                  "limita il danno. Non sai cosa siano X e Y."),
    "vincolo_break_even": ("passo_griglia = ((1+range_up)/(1-range_down))^(1/grid_levels) - 1 "
                           "DEVE superare 1.5 x 0.10% = 0.15%, o il bot si rifiuta "
                           "di partire. Griglie troppo fitte muoiono di fee."),
    "parametri": {
        "series": "X oppure Y",
        "range_down_pct": "estremo inferiore del range, % sotto il prezzo di deploy (2-30)",
        "range_up_pct": "estremo superiore del range, % sopra il prezzo di deploy (2-30)",
        "grid_levels": "numero di livelli della griglia (4-30)",
        "sl_buf_pct": "stop-loss: % sotto RANGE_LOW (1-15)",
        "tp_buf_pct": "take-profit: % sopra RANGE_HIGH (1-15)",
        "max_bars": "durata massima di una griglia in barre, poi liquida e ri-deploya (48-3000)",
    },
    "trade_off": ("range stretto + tanti livelli = tanti round-trip piccoli ma SL "
                  "frequenti nei trend; range largo = SL rari ma capitale spesso "
                  "fermo. Lo stop-loss largo aumenta la perdita quando scatta; "
                  "stretto scatta piu' spesso. Usa le statistiche di escursione "
                  "del digest per dimensionare range e stop."),
    "output_schema": {
        "series": "X|Y", "range_down_pct": "num", "range_up_pct": "num",
        "grid_levels": "int", "sl_buf_pct": "num", "tp_buf_pct": "num",
        "max_bars": "int", "hypothesis": "1-2 frasi: il tuo ragionamento",
    },
}


if __name__ == "__main__":
    import sys
    from pathlib import Path
    if "--all" in sys.argv:
        out = {tf: make_grid_digest(tf) for tf in TF_ID}
        p = Path("data/games/grid_digests.json")
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(json.dumps(out))
        print(f"scritti digest per {list(out)} -> {p}")
    else:
        tf = sys.argv[1] if len(sys.argv) > 1 else "1h"
        print(json.dumps(make_grid_digest(tf), indent=2)[:3000])
