Files
PythagorasGoal/scripts/live/paper_combo.py
T
Adriano Dal Pastro 3b552a92da feat(combo): paper-trade cross-venue TP01 (Deribit) + GTAA (IB), forward-only
L'unica cosa vera/deployabile della ricerca: diversificazione TP01+GTAA (corr 0.21, blend Sharpe ~1.5,
DD dimezzato). Si va in PAPER cross-venue.

- src/portfolio/gtaa.py: GTAA sleeve di prima classe (trend difensivo TSMOM vol-target 12% su
  SPY/QQQ/IWM/TLT/GLD/HYG). gtaa_returns() Sharpe 0.64; gtaa_weights() = pesi ETF correnti azionabili.
- scripts/live/paper_combo.py: tracker forward-only blend 50/50 TP01+GTAA (crypto compoundato su grid
  giorni-di-borsa), mostra posizioni azionabili su entrambi i venue. Solo gambe eseguibili.
- fetch_ib_equities.py --only: refresh mirato dei 6 ETF GTAA per il cron.
- cron_daily.sh: up gateway IB + refresh ETF GTAA + avanza paper_combo (dipendenza cross-venue gestita).

Init 2026-06-23: TP01 flat (risk-off), GTAA SPY13/QQQ8/IWM9/TLT17/GLD2/HYG17/cash34. Catena
gateway->refresh->paper testata end-to-end. PAPER (rischio zero), valida l'operativita' cross-venue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:26:51 +00:00

102 lines
4.2 KiB
Python

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