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