2 Commits

Author SHA1 Message Date
Adriano Dal Pastro eeac97dde4 feat(dashboard): Deribit-only book panel (TP01+SKH01) + accumulation forecast
New section showing the executable Deribit-only book (TP01 75% + SKH01 25%): combined
FULL/HOLD Sharpe+DD, plus the reinvest-winnings accumulation projection (historical &
conservative CAGR, €5k→5y/10y, conservative €/day run-rate). Reuses the already-computed
sleeve daily series (no extra heavy compute). Honest caveats (bull sample, no leverage,
SKH01 not live, ~€177k for €50/day).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:43:47 +00:00
Adriano Dal Pastro c8a390d6b7 feat(portfolio): reproducible accumulation forecast for the Deribit book
scripts/portfolio/forecast_deribit_book.py — projects the Deribit-only book (TP01+SKH01)
forward by pure compounding (reinvest winnings), monthly-aligned, NO external contributions
(not a PAC). Deterministic @historical & @conservative CAGR + Monte-Carlo block-bootstrap
(median/p10/p90), plus €/day run-rate @conservative. Parametric (--capital/--years/--cons-frac).
Honest caveats baked in (bull-crypto sample, no leverage, SKH01 not live, conditional projection).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:42:14 +00:00
2 changed files with 138 additions and 1 deletions
+106
View File
@@ -0,0 +1,106 @@
"""PROIEZIONE ACCUMULO del book DERIBIT-ONLY (TP01 + SKH01) — compounding puro (reinvesti le
vincite), allineamento MENSILE, NESSUN versamento esterno (non e' un PAC).
Base: rendimenti mensili del book Deribit-only (rebal mensile, netto costo turnover). Proietta in
avanti l'equity da un capitale iniziale:
- DETERMINISTICO @CAGR storico e @CAGR conservativo (= storico × cons_frac, default metà);
- MONTE CARLO block-bootstrap dei rendimenti mensili storici (mediana + banda p10/p90);
- €/giorno run-rate (cresce col capitale, perche' si rigiocano le vincite).
⚠️ ONESTA': lo storico e' un BULL crypto ~2019-26 -> il futuro sara' quasi certamente piu' magro.
Pianificare sulla colonna conservativa; il MC non contiene un vero bear pluriennale (anche il p10
e' ottimista). Nessuna leva. SKH01 e' research/forward-monitor (solo TP01 e' armato live). Non e'
una garanzia: e' una proiezione condizionata "se il futuro somigliasse al passato".
uv run python scripts/portfolio/forecast_deribit_book.py
uv run python scripts/portfolio/forecast_deribit_book.py --capital 5000 --years 1,3,5,10 --cons-frac 0.5
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
from src.portfolio.portfolio import StrategyPortfolio, rebalance_sim
from src.portfolio.sleeves import deribit_book_sleeves
def book_monthly_returns(rebal_days: int, cost_rate: float) -> pd.Series:
"""Rendimenti MENSILI del book Deribit-only (rebal periodico, netto costo)."""
sl = deribit_book_sleeves()
w = StrategyPortfolio(sl).weights()
cols = {s.name: s.daily() for s in sl}
r = rebalance_sim(cols, w, period_days=rebal_days, cost_rate=cost_rate)["daily"]
return ((1.0 + r).resample("ME").prod() - 1.0).dropna()
def main():
ap = argparse.ArgumentParser(description="Proiezione accumulo book Deribit-only (compounding, allineamento mensile)")
ap.add_argument("--capital", type=float, default=5000.0)
ap.add_argument("--years", type=str, default="1,2,3,5,8,10")
ap.add_argument("--cons-frac", type=float, default=0.5, help="CAGR conservativo = storico × questo (default 0.5)")
ap.add_argument("--sims", type=int, default=4000)
ap.add_argument("--block-months", type=int, default=3, help="blocco del bootstrap (mesi)")
ap.add_argument("--rebal-days", type=int, default=30)
ap.add_argument("--cost-rate", type=float, default=0.0005, help="fee per-lato sul turnover (Deribit taker)")
ap.add_argument("--seed", type=int, default=7)
a = ap.parse_args()
cap = a.capital
HY = [int(x) for x in a.years.split(",") if x.strip()]
m = book_monthly_returns(a.rebal_days, a.cost_rate)
mv = m.values
nm = len(mv)
g_month = float(np.prod(1.0 + mv) ** (1.0 / nm) - 1.0)
cagr = (1.0 + g_month) ** 12 - 1.0
vol_ann = float(mv.std() * np.sqrt(12))
cons_cagr = cagr * a.cons_frac
print("=" * 90)
print(f" PROIEZIONE ACCUMULO — book Deribit-only (TP01+SKH01) | compounding, allineamento mensile, no versamenti")
print(f" storico: {nm} mesi · CAGR {cagr*100:.1f}% · vol annua {vol_ann*100:.0f}% (bull crypto, no leva) | capitale €{cap:,.0f}")
print("=" * 90)
# Monte Carlo: block-bootstrap dei rendimenti mensili
rng = np.random.default_rng(a.seed)
blk = a.block_months
maxM = max(HY) * 12
nb = maxM // blk + 1
starts = rng.integers(0, nm - blk, size=(a.sims, nb))
paths = np.concatenate([mv[starts[:, k][:, None] + np.arange(blk)[None, :]] for k in range(nb)], axis=1)[:, :maxM]
eqMC = cap * np.cumprod(1.0 + paths, axis=1)
cons_m = (1.0 + cons_cagr) ** (1.0 / 12) - 1.0
print(f"\n ACCUMULO (reinvesti le vincite):")
print(f" {'oriz.':>6} | {'det @storico':>13} | {'det @conserv.':>14} | {'MC mediana':>11} | {'MC p10':>9} | {'MC p90':>10}")
print(f" {'':>6} | {'('+format(cagr*100,'.0f')+'%)':>13} | {'('+format(cons_cagr*100,'.0f')+'%)':>14} |")
print(" " + "-" * 80)
for y in HY:
mo = y * 12
det = cap * (1.0 + g_month) ** mo
detc = cap * (1.0 + cons_m) ** mo
c = eqMC[:, mo - 1]
print(f" {y:>4}a | €{det:>11,.0f} | €{detc:>12,.0f} | €{np.median(c):>9,.0f} | €{np.percentile(c,10):>7,.0f} | €{np.percentile(c,90):>8,.0f}")
# €/giorno run-rate @conservativo (cresce col capitale)
rd = (1.0 + cons_cagr) ** (1.0 / 365.0) - 1.0
print(f"\n €/GIORNO run-rate @conservativo ({cons_cagr*100:.1f}% CAGR) — cresce col capitale (rigiochi le vincite):")
print(f" {'oriz.':>6} | {'equity':>9} | {'€/giorno':>10} | {'€/mese':>8}")
print(" " + "-" * 42)
for y in [0] + HY:
E = cap * (1.0 + cons_cagr) ** y
print(f" {('oggi' if y==0 else str(y)+'a'):>6} | €{E:>7,.0f} | €{E*rd:>7,.2f} | €{E*rd*30:>6,.0f}")
E_end = cap * (1.0 + cons_cagr) ** max(HY)
print(f" media €/giorno su {max(HY)} anni: €{(E_end-cap)/(max(HY)*365):.2f}/g (profitto €{E_end-cap:,.0f})")
need = 50 * 365 / cons_cagr if cons_cagr > 0 else float('inf')
print(f" capitale per ~€50/giorno @{cons_cagr*100:.1f}%: ≈ €{need:,.0f}")
print(f"\n ⚠️ Proiezione condizionata (storico = bull crypto); pianifica sul conservativo. Nessuna leva.")
print(f" SKH01 = research/forward-monitor; solo TP01 e' armato live. Non e' una garanzia.")
if __name__ == "__main__":
main()
+32 -1
View File
@@ -32,6 +32,21 @@ def build():
pf = StrategyPortfolio(active_sleeves(), capital=2000.0)
bt = pf.backtest()
eq = bt["equity"]; idx = bt["index"]
# BOOK DERIBIT-ONLY eseguibile (TP01 75% + SKH01 25%), riusando i daily gia' calcolati
try:
sl_by = {s.name: s for s in pf.sleeves}
tp_d, sk_d = sl_by["TP01_trend_1d"].daily(), sl_by["SKH01_skyhook"].daily()
Jd = pd.concat({"t": tp_d, "s": sk_d}, axis=1, join="inner").fillna(0.0)
der = 0.75 * Jd["t"] + 0.25 * Jd["s"]
mm = ((1.0 + der).resample("ME").prod() - 1.0).dropna()
d_cagr = float(np.prod(1.0 + mm.values) ** (12.0 / len(mm)) - 1.0) if len(mm) else 0.0
cons = d_cagr * 0.5
rd = (1.0 + cons) ** (1.0 / 365.0) - 1.0 if cons > 0 else 0.0
deribit = dict(full=metrics(der), hold=metrics(der[der.index >= HOLDOUT]),
cagr=d_cagr, cons_cagr=cons, eday_5k=5000.0 * rd,
eq5_5y=5000.0 * (1.0 + cons) ** 5, eq5_10y=5000.0 * (1.0 + cons) ** 10)
except Exception as e:
deribit = {"error": f"{type(e).__name__}: {e}"}
# sparkline: subsample ~400 punti
step = max(1, len(eq) // 400)
spark = [(str(idx[i].date()), float(eq[i])) for i in range(0, len(eq), step)]
@@ -56,7 +71,7 @@ def build():
full=bt["full"], holdout=bt["holdout"], weights=bt["weights"],
per_sleeve=bt["per_sleeve"], yearly=bt["yearly"],
positions=pf.current_positions(), spark=spark, paper=paper, prevday=prevday,
combo=combo, gtaa_weights=gtaa_w,
combo=combo, gtaa_weights=gtaa_w, deribit=deribit,
shadow=shadow, trades=trades, bh=None,
)
_CACHE.update(t=time.time(), data=data)
@@ -146,6 +161,18 @@ def html():
+ f" <span style='color:#8a93a0'>(asof {asof})</span>")
else:
gw_html = "n/d (cache ETF assente — gira fetch_ib_equities.py)"
db = d.get("deribit")
if db and "error" not in db:
f2, h2 = db["full"], db["hold"]
deribit_html = (
f"FULL <b>Sh {f2['sharpe']:.2f}</b> ret {f2['ret']*100:+.0f}% DD {f2['maxdd']*100:.1f}% &nbsp;·&nbsp; "
f"HOLD-OUT <b>Sh {h2['sharpe']:.2f}</b> DD {h2['maxdd']*100:.1f}%<br>"
f"<span style='color:#8a93a0;font-size:13px'>accumulo (reinvesti le vincite, no leva) — CAGR storico "
f"<b>{db['cagr']*100:.0f}%</b>, conservativo <b>{db['cons_cagr']*100:.0f}%</b>: "
f"da €5k → ~€{db['eq5_5y']:,.0f} (5a) / ~€{db['eq5_10y']:,.0f} (10a) &nbsp;·&nbsp; "
f"run-rate oggi ~<b>€{db['eday_5k']:.2f}/g</b> @conservativo</span>")
else:
deribit_html = "n/d" + (f"{db['error']}" if db and db.get("error") else "")
sh = d.get("shadow")
if sh and "error" not in sh:
bits = " &nbsp;·&nbsp; ".join(
@@ -221,6 +248,10 @@ th{{color:#8a93a0;font-weight:500}}.y{{display:inline-block;background:#161b22;b
<div class=box><b>TP01 (Deribit) + GTAA (IB)</b> — le DUE gambe ESEGUIBILI a basso capitale, scorrelate (corr ~0.21) → blend Sharpe ~1.5, drawdown dimezzato. <b>Nessuna esecuzione reale</b>:<br>{combo_html}<br>
<span style="color:#8a93a0;font-size:13px">posizioni azionabili IB (GTAA, peso ETF):</span> {gw_html}</div>
<p class=warn>⚠️ PAPER cross-venue: valida l'operativita' su due conti (Deribit + IB) a rischio zero. Lo Sharpe ~1.5 e' ottimistico (finestra crypto corta/favorevole); il dato robusto e' la diversificazione (corr 0.21, DD dimezzato), non il livello. XS01/VRP01 esclusi (STAT-MODE): qui solo TP01+GTAA.</p>
<div class="section">BOOK DERIBIT-ONLY — eseguibile (TP01 + SKH01)</div>
<div class=box><b>TP01 75% + SKH01 25%</b> — le due gambe direzionali BTC/ETH sullo STESSO venue (Deribit), ribilancio mensile. Sottoinsieme realmente armabile (XS01/VRP01 esclusi):<br>{deribit_html}<br>
<span style="color:#8a93a0;font-size:13px">forecast: <code>scripts/portfolio/forecast_deribit_book.py</code> · report: <code>scripts/portfolio/run_deribit_book.py</code></span></div>
<p class=warn>⚠️ Accumulo = proiezione condizionata (storico bull crypto → pianifica sul conservativo); nessuna leva; SKH01 research/forward-monitor (solo TP01 armato live). A €50/g servono ~€177k @conservativo: la via è capitale+tempo, non leva.</p>
<div class="section">FORWARD-MONITOR — lead paper (non deploy)</div>
<div class=box><b>PREVDAY range-breakout</b> — lead ORTOGONALE a TP01 (corr ~0.15 full / ~0 hold; marginal ADDS, non-hedge, robusto allo shift del confine-giorno). Forward-only, <b>nessuna esecuzione reale</b>:<br>{prevday_html}</div>
<p class=warn>⚠️ LEAD in osservazione, NON deployato. Sopravvissuto alla verifica avversariale dell'onda intraday; lo teniamo in paper per validarlo fuori-campione-vero. I due libri (modeled vs real-$600) mostrano l'haircut di fill che lo scettico aveva segnalato.</p>