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>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user