Files
PythagorasGoal/scripts/games/session_engine.py
T
Adriano Dal Pastro 8d69a0cef5 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>
2026-06-11 09:49:17 +00:00

99 lines
3.8 KiB
Python

"""
Motore del gioco-SESSION: pattern ORARI intraday. Ogni giorno si osserva il movimento
in una "fascia di controllo" [ctrl_hour, ctrl_hour+ctrl_len) e si scommette sul movimento
della finestra SUBITO DOPO (hold ore), seguendo (trend) o fadando (reversion) la fascia.
Cerca se esistono orari il cui comportamento ANTICIPA la finestra successiva, ripetibile nei
giorni. Dati orari reali (BTC=A, ETH=B), full history. PnL per-trade additivo, fee 0.10% RT.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
FEE_RT = 0.001
MIN_TRADES_PER_MONTH = 10.0
BARS_PER_MONTH = 24 * 30
def load_session(asset: str = "BTC"):
df = load_data(asset, "1h").copy()
dt = pd.to_datetime(df["datetime"])
return {"close": df["close"].to_numpy(float),
"open": df["open"].to_numpy(float),
"hour": dt.dt.hour.to_numpy(),
"day": (dt.dt.year * 366 + dt.dt.dayofyear).to_numpy(), # indice giorno
"dt": dt.to_numpy(), "n": len(df)}
def evaluate(data, spec, sl=None, fee=FEE_RT):
"""spec = {ctrl_hour, ctrl_len, entry_thr(%), direction, hold}. Una valutazione per giorno:
a fine fascia di controllo, se |ret_fascia| > entry_thr entra e tiene hold ore."""
c, hour = data["close"], data["hour"]
n = data["n"]
s, e = (sl if sl else (0, n))
ch = int(spec["ctrl_hour"]) % 24
cl = max(1, int(spec["ctrl_len"]))
thr = float(spec["entry_thr"]) / 100.0
hold = max(1, int(spec["hold"]))
sign = 1 if spec.get("direction", "trend") == "trend" else -1
# indici in cui inizia la fascia di controllo (bar all'ora ch)
starts = np.where(hour[s:e] == ch)[0] + s
rets = []
for st in starts:
be = st + cl - 1 # ultima barra della fascia
ex = be + hold # uscita
if ex >= e or st == 0:
continue
ctrl_ret = c[be] / c[st - 1] - 1.0 # ritorno della fascia (causale: chiude a be)
if abs(ctrl_ret) < thr:
continue
d = sign * (1 if ctrl_ret > 0 else -1) # trend segue, reversion fada
entry = c[be]; exit_px = c[ex]
net = d * (exit_px - entry) / entry - fee
rets.append(net)
rets = np.array(rets)
nbars = e - s
months = nbars / BARS_PER_MONTH
n_tr = len(rets)
tpm = n_tr / months if months > 0 else 0.0
if n_tr == 0:
return dict(n_trades=0, win_rate=0.0, pnl_pct=0.0, tpm=0.0, sharpe=0.0,
avg_ret=0.0, qualified=False, fitness=-1e6)
win = float(np.mean(rets > 0))
pnl = float(np.sum(rets)) * 100
avg = float(np.mean(rets)) * 100
sharpe = float(np.mean(rets) / (np.std(rets) + 1e-12) * np.sqrt(tpm * 12)) \
if np.std(rets) > 0 else 0.0
qualified = tpm >= MIN_TRADES_PER_MONTH
fitness = pnl + 50.0 * win
if not qualified:
fitness = -1e6 + pnl
return dict(n_trades=n_tr, win_rate=win, pnl_pct=pnl, tpm=tpm, sharpe=sharpe,
avg_ret=avg, qualified=qualified, fitness=fitness)
def splits3(data, train_frac=0.60, valid_frac=0.20):
n = data["n"]
c1 = int(n * train_frac); c2 = int(n * (train_frac + valid_frac))
return (0, c1), (c1, c2), (c2, n)
if __name__ == "__main__":
d = load_session("BTC"); tr, va, te = splits3(d)
for ch in [0, 8, 13, 20]:
for dr in ["trend", "reversion"]:
sp = {"ctrl_hour": ch, "ctrl_len": 2, "entry_thr": 0.3, "direction": dr, "hold": 4}
f = evaluate(d, sp, None); o = evaluate(d, sp, te)
print(f"h{ch:>2} {dr:>9} len2 hold4 thr0.3 | FULL pnl{f['pnl_pct']:7.0f} win{f['win_rate']*100:3.0f} "
f"tpm{f['tpm']:4.0f} Sh{f['sharpe']:5.1f} | OOS Sh{o['sharpe']:5.1f}")