8d69a0cef5
- 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>
121 lines
5.1 KiB
Python
121 lines
5.1 KiB
Python
"""
|
|
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])
|