feat(portfolio): Deribit-only executable book (TP01+SKH01) + periodic rebalancing

- deribit_book_sleeves(): TP01 75% + SKH01 25% — the two directional BTC/ETH legs on
  ONE venue (Deribit), both since 2019. Excludes XS01 (Hyperliquid/stat-mode) & VRP01
  (modeled options). FULL Sharpe 1.78 / HOLD 1.17 / DD 9.4% (research).
- rebalance_sim(): realistic PERIODIC rebalancing (drift between dates, turnover cost at
  Deribit-taker ~5bps/side) vs the idealized continuous rebalance of combined_daily.
  period=1 + cost=0 reduces to continuous (tested).
- run_deribit_book.py: report — continuous vs weekly/biweekly/monthly rebal, per-year,
  accumulation €2k & $600-real, min-order $5 note. Finding: turnover is LOW (0.2-0.4x/yr),
  so monthly rebal (€7,919) ~= continuous (€7,938) — cost is negligible; daily would be
  sub-min-order fiction at $600 -> use >= weekly.
- +2 tests (rebalance_sim continuity & cost). Full suite green.

TP01 is the only live-armed leg; SKH01 is the candidate 2nd leg (validate execution code first).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-23 20:26:53 +00:00
parent 50e2adf837
commit 160ad300be
4 changed files with 169 additions and 1 deletions
+38
View File
@@ -68,6 +68,44 @@ def yearly(daily: pd.Series) -> dict:
return out
def rebalance_sim(daily_cols: dict[str, pd.Series], weights: dict,
period_days: int, cost_rate: float = 0.0005) -> dict:
"""Ribilanciamento PERIODICO REALISTICO (vs il rebalance-continuo implicito di combined_daily).
Tra una data di ribilanciamento e l'altra ogni sleeve DERIVA col suo rendimento (i pesi si
scostano dal target). Ogni `period_days` si riporta al target pagando il turnover:
cost = cost_rate * sum_i |valore_i - target_i| (cost_rate = fee per-lato, Deribit taker ~0.0005)
Modella l'attrito reale che il rebalance-continuo (combined_daily) ignora. period_days=1 con
cost_rate=0 ricade sul rebalance-continuo. Ritorna serie netta + turnover annuo + n ribilanci."""
J = pd.concat(daily_cols, axis=1, join="inner").sort_index().fillna(0.0)
cols = list(J.columns)
w = np.array([weights[c] for c in cols], float); w = w / w.sum()
R = J.values
n = len(J)
E = 1.0
v = w * E
out = np.zeros(n)
n_rebal = 0
turn_tot = 0.0
for t in range(n):
Eprev = E
v = v * (1.0 + R[t])
E = float(v.sum())
if (t + 1) % period_days == 0: # giorno di ribilanciamento
target = w * E
turn = float(np.abs(v - target).sum())
cost = cost_rate * turn
E -= cost
v = w * E
n_rebal += 1
turn_tot += turn / max(Eprev, 1e-12)
out[t] = E / Eprev - 1.0 if Eprev > 0 else 0.0
years = n / DAYS_PER_YEAR
return dict(daily=pd.Series(out, index=J.index),
turnover_per_year=round(turn_tot / years, 2) if years > 0 else 0.0,
n_rebalances=n_rebal, period_days=period_days, cost_rate=cost_rate)
class StrategyPortfolio:
def __init__(self, sleeves: list[Sleeve], capital: float = 2000.0):
if not sleeves:
+13
View File
@@ -253,3 +253,16 @@ def active_sleeves() -> list[Sleeve]:
vrp_sleeve(weight=0.15), # options short-vol (put credit spread + gate IV-rank), dal 2021 (lead modellato, scorrelato)
skyhook_sleeve(weight=0.25), # dual-TF regime+breakout BTC/ETH, dal 2019 (quasi-ortogonale, exit %-asimmetrici, research)
]
def deribit_book_sleeves() -> list[Sleeve]:
"""BOOK DERIBIT-ONLY realmente eseguibile (TP01 + SKH01, 75/25): le DUE gambe direzionali
BTC/ETH sullo stesso venue (Deribit), entrambe dal 2019. Esclude XS01 (Hyperliquid, stat-mode)
e VRP01 (opzioni modellate). FULL Sharpe ~1.78 / HOLD ~1.17 / DD ~9% (research; SKH01 daily-step).
Pensato per il deploy reale a basso capitale: stesso conto, stesso feed, ribilanciamento
periodico (vedi src.portfolio.portfolio.rebalance_sim + scripts/portfolio/run_deribit_book.py).
TP01 e' gia' armato live; SKH01 e' il candidato 2a gamba (da validare codice d'esecuzione)."""
return [
tp01_sleeve(weight=0.75),
skyhook_sleeve(weight=0.25),
]