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:
+32
-1
@@ -7,7 +7,7 @@ import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from src.portfolio.portfolio import Sleeve, StrategyPortfolio, to_daily, metrics
|
||||
from src.portfolio.portfolio import Sleeve, StrategyPortfolio, to_daily, metrics, rebalance_sim
|
||||
|
||||
|
||||
def _const_sleeve(name, weight, val, n=400):
|
||||
@@ -15,6 +15,37 @@ def _const_sleeve(name, weight, val, n=400):
|
||||
return Sleeve(name, weight, lambda: pd.Series(val, index=idx))
|
||||
|
||||
|
||||
def _ret_series(vals):
|
||||
idx = pd.date_range("2020-01-01", periods=len(vals), freq="1D", tz="UTC")
|
||||
return pd.Series(vals, index=idx)
|
||||
|
||||
|
||||
def test_rebalance_sim_no_cost_period1_matches_continuous():
|
||||
"""period=1 + cost=0 deve coincidere col rebalance-continuo (weighted-return giornaliero)."""
|
||||
rng = np.random.default_rng(0)
|
||||
A = _ret_series(rng.normal(0.001, 0.02, 300))
|
||||
B = _ret_series(rng.normal(0.000, 0.03, 300))
|
||||
w = {"A": 0.6, "B": 0.4}
|
||||
sim = rebalance_sim({"A": A, "B": B}, w, period_days=1, cost_rate=0.0)
|
||||
cont = 0.6 * A + 0.4 * B
|
||||
assert np.allclose(sim["daily"].values, cont.values, atol=1e-12)
|
||||
assert sim["n_rebalances"] == 300
|
||||
|
||||
|
||||
def test_rebalance_sim_cost_reduces_return_and_counts():
|
||||
"""Il costo del turnover abbassa il rendimento; ribilanci meno frequenti = meno costo."""
|
||||
rng = np.random.default_rng(1)
|
||||
A = _ret_series(rng.normal(0.001, 0.02, 360))
|
||||
B = _ret_series(rng.normal(0.001, 0.04, 360))
|
||||
w = {"A": 0.5, "B": 0.5}
|
||||
free = rebalance_sim({"A": A, "B": B}, w, period_days=7, cost_rate=0.0)["daily"]
|
||||
weekly = rebalance_sim({"A": A, "B": B}, w, period_days=7, cost_rate=0.001)
|
||||
monthly = rebalance_sim({"A": A, "B": B}, w, period_days=30, cost_rate=0.001)
|
||||
assert weekly["daily"].sum() < free.sum() # il costo morde
|
||||
assert monthly["n_rebalances"] < weekly["n_rebalances"] # mensile ribilancia meno
|
||||
assert weekly["turnover_per_year"] > 0
|
||||
|
||||
|
||||
def test_single_sleeve_equals_itself():
|
||||
s = _const_sleeve("A", 1.0, 0.001)
|
||||
pf = StrategyPortfolio([s])
|
||||
|
||||
Reference in New Issue
Block a user