"""PAPER COMBO — forward-only del combo DEPLOYABLE cross-venue: TP01 (Deribit) + GTAA (IB). Le DUE gambe realmente eseguibili a basso capitale (XS01/VRP01 restano STAT-MODE, esclusi qui): * TP01 = TSMOM difensivo BTC/ETH long-flat, gia' armato su Deribit; * GTAA = trend difensivo multi-asset su ETF, eseguibile su IB. Scorrelati (corr ~0.21) -> blend Sharpe ~1.5, maxDD dimezzato (diari 2026-06-22-deployable-combo). Traccia FORWARD-ONLY l'equity del blend (default 50/50 capitale), applicando i rendimenti giornalieri combinati man mano che arrivano barre nuove. Il crypto (calendario 7g) e' compoundato sul grid dei giorni di borsa per allinearlo all'equity. NESSUNA esecuzione reale. Mostra le POSIZIONI azionabili su entrambi i venue (TP01 target BTC/ETH + pesi ETF GTAA). uv run python scripts/live/paper_combo.py # avanza (init al 1o run) uv run python scripts/live/paper_combo.py --status # solo stato uv run python scripts/live/paper_combo.py --reset """ from __future__ import annotations import sys, json from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) import numpy as np, pandas as pd from src.portfolio.sleeves import _tp01_returns, _tp01_positions from src.portfolio.gtaa import gtaa_returns, gtaa_weights STATE_DIR = PROJECT_ROOT / "data" / "paper_combo" STATE = STATE_DIR / "state.json" EQ = STATE_DIR / "equity.csv" INITIAL = 2000.0 W_CRYPTO = 0.5 # blend di capitale TP01/GTAA (50/50; risk-parity ~30/70 alternativa) def combo_daily(wc: float = W_CRYPTO) -> pd.Series: """Rendimenti netti daily del blend sul grid giorni-di-borsa (crypto compoundato).""" tp = _tp01_returns() if tp.index.tz is None: tp.index = tp.index.tz_localize("UTC") eq = gtaa_returns().dropna() grid = eq.index[eq.index >= tp.index[0]] cum = (1.0 + tp).cumprod() tpg = (cum.reindex(cum.index.union(grid)).ffill().reindex(grid)).pct_change() J = pd.concat({"c": tpg, "e": eq.reindex(grid)}, axis=1).dropna() return (wc * J["c"] + (1 - wc) * J["e"]).dropna() def load(): return json.loads(STATE.read_text()) if STATE.exists() else None def save(st): STATE_DIR.mkdir(parents=True, exist_ok=True) STATE.write_text(json.dumps(st, indent=2)) def advance(): r = combo_daily() st = load() if st is None: last = str(r.index[-1]) st = dict(start=last, last=last, equity=INITIAL, initial=INITIAL, peak=INITIAL, max_dd=0.0, n_days=0, w_crypto=W_CRYPTO) save(st) EQ.write_text("date,equity\n" + f"{last},{INITIAL}\n") return st last = pd.Timestamp(st["last"]) new = r[r.index > last] if len(new): eq = st["equity"]; peak = st["peak"]; dd = st["max_dd"]; lines = [] for d, ret in new.items(): eq *= (1.0 + float(ret)); peak = max(peak, eq); dd = max(dd, (peak - eq) / peak if peak > 0 else 0) lines.append(f"{d},{eq:.4f}") st.update(equity=eq, last=str(new.index[-1]), peak=peak, max_dd=dd, n_days=st["n_days"] + len(new)) save(st) with open(EQ, "a") as f: f.write("\n".join(lines) + "\n") return st def main(): a = sys.argv[1:] if "--reset" in a: for f in (STATE, EQ): f.unlink(missing_ok=True) print("paper combo azzerato.") st = load() if "--status" in a else advance() if st is None: st = advance() days = (pd.Timestamp(st["last"]) - pd.Timestamp(st["start"])).days ret = st["equity"] / st["initial"] - 1 gw = gtaa_weights() asof = gw.pop("_asof", "?"); cash = gw.pop("_cash", None) print("PAPER COMBO — TP01 (Deribit) + GTAA (IB), forward-only, blend 50/50") print(f" start {st['start'][:10]} -> last {st['last'][:10]} ({days}g, {st['n_days']} barre)") print(f" equity {st['equity']:.2f} (start {st['initial']:.0f}) ret {ret*100:+.2f}% maxDD {st['max_dd']*100:.1f}%") print(f" --- POSIZIONI AZIONABILI ---") print(f" TP01 (Deribit, frazione equity x leva): {_tp01_positions()}") print(f" GTAA (IB, peso ETF, asof {asof}): " + ", ".join(f"{k} {v:.0%}" for k, v in gw.items() if v) + f" | cash {cash:.0%}") if __name__ == "__main__": main()