feat(games): sessioni 2-3 Blind Traders (opzioni/session/grid) + gate PORT06 e tooling reset

- Gioco GRID TRADERS (sessione 3, regola STRATEGIA_GRIGLIA.md): grid_engine
  (backtest causale fee-aware della griglia geometrica), grid_brief (digest
  anonimo per dimensionare la griglia), grid_arena (torneo 100 agenti);
  diario docs/diary/2026-06-10-grid-traders-game3.md
- Gioco OPZIONI: options_engine (BS + skew fittato + DVOL storica),
  options_arena, opt_calibrate (superficie premi REALE da cerbero-bite)
- Gioco SESSION: session_engine/session_arena (pattern orari intraday)
- arena: vincolo GAME_NO_LIVE=1 (vieta pairs e fade zscore/breakout/momentum
  gia' live, coercizione a trend/ma_cross) + normalize del candidato PRIMA
  della valutazione nel hill-climb
- Gate: grid_game_gate (griglia ETH vincitrice vs PORT06, mark-to-market),
  pairs30m_gate (ETH/BTC 30m ridondante col 15m gia' deployato?)
- reset_flatten: flatten one-shot del conto testnet per il reset portafoglio
- .gitignore: data/portfolio_paper_stats/ (stato runtime sleeve paper-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-11 09:49:17 +00:00
parent 8adf388e86
commit 8d69a0cef5
16 changed files with 2193 additions and 5 deletions
+120
View File
@@ -0,0 +1,120 @@
"""
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])