14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
179 lines
7.6 KiB
Python
179 lines
7.6 KiB
Python
"""Validazione DURA del solo edge sopravvissuto alla ricerca shape: ML walk-forward
|
|
(LogisticRegression) sulle feature di FORMA. Tutto il resto della famiglia shape e' rumore.
|
|
|
|
Candidato: BTC logit W24H12 th0.58 (FULL +219% / OOS +42% / Sharpe 2.72 / 8-9 anni+,
|
|
regge fee 0.20% RT). Prima di promuoverlo a strategia serve (metodologia obbligatoria):
|
|
1. ROBUSTEZZA MULTI-ASSET: stessa config su BTC/ETH/LTC/SOL/ADA/XRP 1h.
|
|
2. WALK-FORWARD ROLLING (train fisso 2y) oltre all'expanding -> niente "memoria infinita".
|
|
3. STRESS leva 2x + slippage doppio (fee 0.20% RT) -> regge in condizioni realistiche?
|
|
4. ROBUSTEZZA SU GRIGLIA (th, W, H) -> plateau, non picco.
|
|
5. CORRELAZIONE col MASTER + integrazione -> e' un diversificatore (free-lunch)?
|
|
|
|
Tutto netto-fee, OOS = ultimo 30%. Conta il PnL netto, non l'accuracy (lezione squeeze).
|
|
|
|
Run: uv run python scripts/analysis/shape_ml_validate.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
import warnings
|
|
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))
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
from scripts.analysis.explore_lab import get_df, evaluate, robust, FEE_RT, LEV, POS, OOS_FRAC
|
|
from scripts.analysis.shape_ml_research import ml_wf_entries, acc_oos
|
|
|
|
ASSETS = ["BTC", "ETH", "LTC", "SOL", "ADA", "XRP"]
|
|
TWO_YEARS_1H = 24 * 365 * 2 # ~17520 barre = finestra rolling 2 anni
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def line(name, ents, df, fees=(0.0, 0.001, 0.002)):
|
|
"""Riga evaluate + accuracy OOS, ritorna (res, robusto?)."""
|
|
res = evaluate(name, ents, df)
|
|
ac = acc_oos(ents, df)
|
|
rb = (res["full"]["ret"] > 0 and res["oos"]["ret"] > 0
|
|
and res["sweep"][0.002] > 0 and res["sweep_oos"][0.002] > 0)
|
|
print(f" ^ accOOS={ac:4.1f}% {'[ROBUST fee0.2%]' if rb else ''}")
|
|
return res, rb
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def sec_multi_asset(W=24, H=12, th=0.58):
|
|
print("\n[1] MULTI-ASSET — logit W%dH%d th%.2f, walk-forward EXPANDING (1h):" % (W, H, th))
|
|
ok = []
|
|
dfs = {}
|
|
for a in ASSETS:
|
|
df = get_df(a, "1h"); dfs[a] = df
|
|
ents = ml_wf_entries(df, W=W, H=H, model="logit", thresh=th)
|
|
_, rb = line(f"{a} exp", ents, df)
|
|
if rb:
|
|
ok.append(a)
|
|
print(f" -> EXPANDING robusti (fee0.2%): {ok if ok else 'NESSUNO'}")
|
|
return dfs, ok
|
|
|
|
|
|
def sec_rolling(dfs, W=24, H=12, th=0.58, tw=TWO_YEARS_1H):
|
|
print("\n[2] WALK-FORWARD ROLLING — train fisso ~2 anni (%d barre), stessa config:" % tw)
|
|
ok = []
|
|
for a in ASSETS:
|
|
ents = ml_wf_entries(dfs[a], W=W, H=H, model="logit", thresh=th, train_window=tw)
|
|
_, rb = line(f"{a} roll2y", ents, dfs[a])
|
|
if rb:
|
|
ok.append(a)
|
|
print(f" -> ROLLING robusti (fee0.2%): {ok if ok else 'NESSUNO'}")
|
|
return ok
|
|
|
|
|
|
def sec_stress(dfs, W=24, H=12, th=0.58):
|
|
print("\n[3] STRESS — leva 2x + slippage doppio (fee 0.20% RT) su BTC/ETH:")
|
|
print(" (la config nominale e' leva 3x fee 0.10%; qui peggioro entrambe)")
|
|
from scripts.analysis.explore_lab import simulate
|
|
for a in ["BTC", "ETH"]:
|
|
ents = ml_wf_entries(dfs[a], W=W, H=H, model="logit", thresh=th)
|
|
df = dfs[a]
|
|
split = int(len(df) * (1 - OOS_FRAC))
|
|
base = simulate(ents, df, fee_rt=0.001, lev=3.0)
|
|
stress_f = simulate(ents, df, fee_rt=0.002, lev=2.0)
|
|
stress_o = simulate(ents, df, fee_rt=0.002, lev=2.0, split=split)
|
|
print(f" {a}: base(3x,0.1%) FULL={base['ret']:+.0f}% Shrp={base['sharpe']:.2f} | "
|
|
f"STRESS(2x,0.2%) FULL={stress_f['ret']:+.0f}% OOS={stress_o['ret']:+.0f}% "
|
|
f"DD={stress_f['dd']:.0f}% Shrp={stress_f['sharpe']:.2f} "
|
|
f"{'OK' if stress_f['ret'] > 0 and stress_o['ret'] > 0 else 'KO'}")
|
|
|
|
|
|
def sec_grid(dfs, asset="BTC"):
|
|
print(f"\n[4] ROBUSTEZZA GRIGLIA su {asset} (plateau, non picco):")
|
|
rob = tot = 0
|
|
for W in (16, 24, 32):
|
|
for H in (8, 12, 16):
|
|
for th in (0.56, 0.58, 0.60):
|
|
ents = ml_wf_entries(dfs[asset], W=W, H=H, model="logit", thresh=th)
|
|
_, rb = line(f"{asset} W{W}H{H}th{th}", ents, dfs[asset])
|
|
tot += 1; rob += rb
|
|
print(f" -> {asset}: {rob}/{tot} celle robuste a fee 0.2% (plateau se alta frazione)")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
def shape_daily_equity(asset, IDX, W=24, H=12, th=0.58):
|
|
"""Equity giornaliera dello sleeve shape-ML (time-exit a H, non-overlap, pos 0.15,
|
|
leva 3x, fee 0.10% RT), normalizzata sull'indice comune dei portafogli."""
|
|
from src.data.downloader import load_data
|
|
df = get_df(asset, "1h")
|
|
c = df["close"].values
|
|
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
|
ents = ml_wf_entries(df, W=W, H=H, model="logit", thresh=th)
|
|
n = len(c); eq = np.full(n, 1000.0); cap = 1000.0; last_exit = -1
|
|
fee = FEE_RT * LEV
|
|
for e in sorted(ents, key=lambda x: x["i"]):
|
|
i, d, mb = e["i"], e["d"], e["max_bars"]
|
|
if i <= last_exit or i + mb >= n:
|
|
continue
|
|
j = i + mb
|
|
ret = (c[j] - c[i]) / c[i] * d * LEV - fee
|
|
cap = max(cap + cap * POS * ret, 10.0)
|
|
eq[j:] = cap; last_exit = j
|
|
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
|
|
return s / s.iloc[0]
|
|
|
|
|
|
def sec_master_integration():
|
|
print("\n[5] CORRELAZIONE + INTEGRAZIONE COL MASTER:")
|
|
from scripts.analysis.combine_portfolio import (
|
|
build_all_sleeves, port_returns, metrics, IDX, SPLIT,
|
|
)
|
|
sleeves = build_all_sleeves()
|
|
# sleeve shape: BTC + ETH (i due con piu' storia/edge)
|
|
shape = {}
|
|
for a in ("BTC", "ETH"):
|
|
shape[f"SH_{a}"] = shape_daily_equity(a, IDX)
|
|
dr_master = port_returns(sleeves) # MASTER equal-weight attuale
|
|
dr_shape = port_returns(shape)
|
|
corr = float(dr_master.corr(dr_shape))
|
|
print(f" correlazione daily MASTER vs sleeve-shape: {corr:+.3f}")
|
|
# correlazione media shape vs ogni sleeve esistente
|
|
cs = {k: float(port_returns({k: v}).corr(dr_shape)) for k, v in sleeves.items()}
|
|
print(" corr shape vs singole sleeve: " + ", ".join(f"{k}={v:+.2f}" for k, v in cs.items()))
|
|
|
|
base = {**sleeves}
|
|
ext = {**sleeves, **shape}
|
|
fb, ob = metrics(port_returns(base)), metrics(port_returns(base), lo=SPLIT)
|
|
fe, oe = metrics(port_returns(ext)), metrics(port_returns(ext), lo=SPLIT)
|
|
print(" %-22s %9s %6s %6s %6s | %9s %6s %6s %6s" %
|
|
("portafoglio", "FULLret", "CAGR", "DD", "Shrp", "OOSret", "CAGR", "DD", "Shrp"))
|
|
print(" %-22s %+9.0f %6.0f %6.1f %6.2f | %+9.0f %6.0f %6.1f %6.2f" %
|
|
("MASTER (9 sleeve)", fb["ret"], fb["cagr"], fb["dd"], fb["sharpe"],
|
|
ob["ret"], ob["cagr"], ob["dd"], ob["sharpe"]))
|
|
print(" %-22s %+9.0f %6.0f %6.1f %6.2f | %+9.0f %6.0f %6.1f %6.2f" %
|
|
("MASTER + shape", fe["ret"], fe["cagr"], fe["dd"], fe["sharpe"],
|
|
oe["ret"], oe["cagr"], oe["dd"], oe["sharpe"]))
|
|
better = oe["sharpe"] > ob["sharpe"] and oe["dd"] <= ob["dd"] + 1
|
|
print(f" -> aggiungere shape MIGLIORA il MASTER OOS (Sharpe up, DD ~stabile)? "
|
|
f"{'SI' if better else 'NO'}")
|
|
|
|
|
|
def run():
|
|
t0 = time.time()
|
|
print("=" * 100)
|
|
print(" VALIDAZIONE DURA — shape-ML (LogisticRegression walk-forward sulle feature di forma)")
|
|
print("=" * 100)
|
|
dfs, _ = sec_multi_asset()
|
|
sec_rolling(dfs)
|
|
sec_stress(dfs)
|
|
sec_grid(dfs, "BTC")
|
|
sec_master_integration()
|
|
print(f"\n tempo totale: {time.time() - t0:.0f}s")
|
|
print("=" * 100)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|