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>
170 lines
7.3 KiB
Python
170 lines
7.3 KiB
Python
"""GATE PORT06 — ETH/BTC pairs a 15m (origine: gioco "Blind Traders", vincitore #43).
|
|
|
|
Domanda onesta sollevata dal gioco: la coppia ETH/BTC (gia' deployata in PR01 a 1h,
|
|
config UNIV n=50 z_in=2.0 z_exit=0.75 max_bars=72) MIGLIORA se girata a 15m con la
|
|
config trovata dal gioco (n=66 z_in=1.67 z_exit=1.0 max_bars=35), oppure e' solo una
|
|
variante piu' veloce, correlata, dello STESSO spread?
|
|
|
|
Metodo (engine di PRODUZIONE pairs_sim, NON il motore-giocattolo del gioco):
|
|
[1] PARITA': pairs_sim ETH/BTC 1h UNIV (pos0.15 lev3) == sleeve canonico PR_ETHBTC.
|
|
[2] CORRELAZIONE 1h vs 15m (rendimenti giornalieri): se ~1 e' ridondante.
|
|
[3] STANDALONE 1h vs 15m (+ griglia robustezza n x z_in su 15m, + stress fee 2x).
|
|
[4] GATE PORT06: baseline(1h) vs SWAP(15m) vs BLEND(0.5*1h+0.5*15m) per la sleeve
|
|
ETH/BTC; promosso se vs baseline l'OOS Sharpe non peggiora E il DD scende
|
|
(PORT06 e famiglia), come gli altri gate del progetto.
|
|
|
|
uv run python scripts/analysis/pairs15m_port06_gate.py
|
|
"""
|
|
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 scripts.analysis.combine_portfolio import port_returns, metrics, SPLIT, OOS_DATE, IDX
|
|
from scripts.analysis.pairs_research import pairs_sim, OOS_FRAC
|
|
from scripts.analysis.report_families import daily_from
|
|
from scripts.portfolios._defs import PORTFOLIOS
|
|
from src.portfolio import weighting as W
|
|
|
|
POS, LEV = 0.15, 3.0 # config CANONICA (== build_everything)
|
|
UNIV_1H = dict(tf="1h", n=50, z_in=2.0, z_exit=0.75, max_bars=72)
|
|
GAME_15M = dict(tf="15m", n=66, z_in=1.674, z_exit=1.0, max_bars=35) # vincitore gioco
|
|
|
|
|
|
def eth_btc_daily(cfg):
|
|
r = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV})
|
|
return daily_from(r["eq_ts"], r["eq_v"]), r
|
|
|
|
|
|
def std_metrics(cfg, fee_rt=0.001):
|
|
f = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV, "fee_rt": fee_rt})
|
|
o = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV, "fee_rt": fee_rt,
|
|
"split_frac": 1 - OOS_FRAC})
|
|
yrs = f["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0)
|
|
return f, o, pos_y, len(yrs)
|
|
|
|
|
|
def port_metrics(members, p):
|
|
ids = p.sleeve_ids
|
|
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
|
|
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
|
|
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
|
|
drp = port_returns({i: members[i] for i in ids}, w)
|
|
return metrics(drp), metrics(drp, lo=SPLIT)
|
|
|
|
|
|
def fam_metrics(eqs):
|
|
dr = port_returns(eqs)
|
|
return metrics(dr), metrics(dr, lo=SPLIT)
|
|
|
|
|
|
def blend(e1, e2, w1=0.5):
|
|
"""Sleeve combinata: media pesata dei rendimenti giornalieri (ribilancio 1D)."""
|
|
r1 = e1.reindex(IDX).ffill().bfill().pct_change().fillna(0.0)
|
|
r2 = e2.reindex(IDX).ffill().bfill().pct_change().fillna(0.0)
|
|
rb = w1 * r1 + (1 - w1) * r2
|
|
eq = (1 + rb).cumprod()
|
|
return eq / eq.iloc[0]
|
|
|
|
|
|
def main():
|
|
p = PORTFOLIOS["PORT06"]
|
|
pair_ids = [s.sid for s in p.sleeves if s.sid.startswith("PR_")]
|
|
print("=" * 100)
|
|
print(" GATE PORT06 — ETH/BTC pairs 15m (vincitore gioco) vs 1h deployato")
|
|
print(f" pos={POS} lev={LEV} (canonico) | OOS da {OOS_DATE} | coppie PORT06: {pair_ids}")
|
|
print("=" * 100)
|
|
|
|
from src.portfolio.sleeves import all_sleeve_equities
|
|
eq_base = dict(all_sleeve_equities())
|
|
|
|
# [1] PARITA'
|
|
print("\n[1] PARITA' pairs_sim ETH/BTC 1h UNIV (pos0.15 lev3) == sleeve canonico PR_ETHBTC:")
|
|
e1h, r1h = eth_btc_daily(UNIV_1H)
|
|
base = eq_base["PR_ETHBTC"]
|
|
corr = base.pct_change().fillna(0).corr(e1h.pct_change().fillna(0))
|
|
rb = (base.iloc[-1] / base.iloc[0] - 1) * 100
|
|
rr = (e1h.iloc[-1] / e1h.iloc[0] - 1) * 100
|
|
par_ok = corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01)
|
|
print(f" corr={corr:.5f} ret canon {rb:+.0f}% vs replay {rr:+.0f}% "
|
|
f"{'OK' if par_ok else '<-- MISMATCH (STOP)'}")
|
|
if not par_ok:
|
|
return
|
|
|
|
# [2] CORRELAZIONE 1h vs 15m
|
|
e15, r15 = eth_btc_daily(GAME_15M)
|
|
c = e1h.pct_change().fillna(0).corr(e15.pct_change().fillna(0))
|
|
print(f"\n[2] CORRELAZIONE rendimenti giornalieri ETH/BTC 1h vs 15m: {c:.3f}")
|
|
print(f" {'(quasi-duplicato se >0.8; diversificatore se <0.5)':<60s}")
|
|
|
|
# [3] STANDALONE 1h vs 15m
|
|
print("\n[3] STANDALONE ETH/BTC (netto fee 0.20% RT/coppia, leva 3x):")
|
|
print(f" {'cfg':<10s}{'trd':>6s}{'win%':>6s}{'FULL%':>9s}{'OOS%':>9s}{'CAGR%':>7s}"
|
|
f"{'DD%':>6s}{'oDD%':>7s}{'Shrp':>6s}{'anni+':>7s}{'fee2x FULL%':>12s}")
|
|
for tag, cfg in [("1h UNIV", UNIV_1H), ("15m gioco", GAME_15M)]:
|
|
f, o, py, ny = std_metrics(cfg)
|
|
f2, _, _, _ = std_metrics(cfg, fee_rt=0.002)
|
|
print(f" {tag:<10s}{f['trades']:>6d}{f['win']:>6.1f}{f['ret']:>+9.0f}{o['ret']:>+9.0f}"
|
|
f"{f['cagr']:>7.0f}{f['dd']:>6.0f}{o['dd']:>7.0f}{f['sharpe']:>6.2f}"
|
|
f"{f'{py}/{ny}':>7s}{f2['ret']:>+12.0f}")
|
|
|
|
# robustezza: plateau n x z_in su 15m (Sharpe>1?)
|
|
print("\n Robustezza 15m (Sharpe full, griglia n x z_in, z_exit=1.0 max_bars=35):")
|
|
ns = [40, 50, 66, 80]; zs = [1.5, 1.7, 2.0, 2.5]
|
|
cells = 0; tot = 0
|
|
hdr = " n\\z_in " + "".join(f"{z:>7.1f}" for z in zs)
|
|
print(hdr)
|
|
for n in ns:
|
|
row = f" {n:>6d} "
|
|
for z in zs:
|
|
s = pairs_sim("ETH", "BTC", tf="15m", n=n, z_in=z, z_exit=1.0,
|
|
max_bars=35, pos=POS, lev=LEV)["sharpe"]
|
|
tot += 1; cells += s > 1
|
|
row += f"{s:>7.2f}"
|
|
print(row)
|
|
print(f" -> {cells}/{tot} celle Sharpe>1 (plateau se ~tutte; picco se poche)")
|
|
|
|
# [4] GATE PORT06
|
|
print("\n[4] GATE PORT06 — sleeve ETH/BTC: baseline(1h) vs SWAP(15m) vs BLEND(50/50):")
|
|
variants = {
|
|
"baseline 1h": e1h,
|
|
"SWAP 15m": e15,
|
|
"BLEND 1h+15m": blend(e1h, e15, 0.5),
|
|
}
|
|
print(f" {'variante':<14s} | {'FULL Sh':>8s}{'FULL DD%':>9s}{'CAGR':>6s}"
|
|
f" | {'OOS Sh':>7s}{'OOS DD%':>8s} | {'famSh':>6s}{'famDD%':>7s}")
|
|
print(" " + "-" * 78)
|
|
res = {}
|
|
for tag, eth in variants.items():
|
|
members = dict(eq_base)
|
|
members["PR_ETHBTC"] = eth
|
|
f, o = port_metrics(members, p)
|
|
fam_eqs = {sid: (eth if sid == "PR_ETHBTC" else eq_base[sid]) for sid in pair_ids}
|
|
ff, _ = fam_metrics(fam_eqs)
|
|
res[tag] = (f, o, ff)
|
|
print(f" {tag:<14s} | {f['sharpe']:>8.2f}{f['dd']:>9.2f}{f['cagr']:>5.0f}%"
|
|
f" | {o['sharpe']:>7.2f}{o['dd']:>8.2f} | {ff['sharpe']:>6.2f}{ff['dd']:>7.1f}")
|
|
|
|
# VERDETTO
|
|
fb, ob, _ = res["baseline 1h"]
|
|
print("\n" + "=" * 100)
|
|
print(" VERDETTO vs baseline 1h: promosso se OOS Sharpe non peggiora E DD scende (PORT06 e famiglia)")
|
|
print("=" * 100)
|
|
for tag in ("SWAP 15m", "BLEND 1h+15m"):
|
|
f, o, ff = res[tag]
|
|
ok = (o["sharpe"] >= ob["sharpe"] - 0.02 and o["dd"] <= ob["dd"] + 1e-9
|
|
and f["sharpe"] >= fb["sharpe"] - 0.02)
|
|
print(f" {tag:<14s}: OOS Sh {ob['sharpe']:.2f}->{o['sharpe']:.2f} "
|
|
f"DD {ob['dd']:.2f}->{o['dd']:.2f} | FULL Sh {fb['sharpe']:.2f}->{f['sharpe']:.2f} "
|
|
f"DD {fb['dd']:.2f}->{f['dd']:.2f} => {'PROMOSSO' if ok else 'bocciato'}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|