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>
99 lines
3.8 KiB
Python
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}")
|