""" 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])