a937e3766f
L'annuncio (taker piu' bassi, maker rebate piu' bassi, soglie VIP abbassate, VIP7,
liquidation fee 1%, spot a zero) NON contiene numeri, e la tabella nell'articolo
Insights e' un'IMMAGINE: non letta da fonte primaria. I valori indicativi (base ~5bps
taker / 2bps maker, VIP7 2/0) vengono da un riassunto SECONDARIO ed e' dichiarato.
Quindi misurata la CURVA invece di aspettare il numero — vale per qualunque valore esca.
Le repliche parametrizzate riproducono BIT-EXACT gli sleeve di produzione alla fee
canonica (max|dif| = 0.0), altrimenti la curva descriverebbe un'altra strategia.
bps/lato %RT | TP01 Sh | SKH01 Sh | BOOK Sh CAGR
0.0 0.00% | 1.322 | 1.567 | 1.849 21.69%
3.0 0.06% | 1.303 | 1.495 | 1.799 20.99%
5.0 0.10% | 1.290 | 1.446 | 1.766 20.53% <- oggi
10.0 0.20% | 1.258 | 1.324 | 1.682 19.39%
15.0 0.30% | 1.226 | 1.200 | 1.597 18.26%
Sensibilita' del book: -0.017 Sharpe/bps, -0.23% CAGR/bps. Anche un RADDOPPIO del taker
costa 0.08 di Sharpe, MENO della banda d'ancora dello stesso book (2.222 -> 1.946): la
fee va messa nella sua scala di grandezza.
SKH01 e' ~4x piu' sensibile di TP01 (-0.69% vs -0.09% CAGR/bps: round-trip discreti vs
posizione continua vol-targeted) -> se il taker salisse, il primo parametro da rivedere
e' il peso 75/25.
REGOLA DECISA IN ANTICIPO (per non decidere col numero davanti): taker <=5bps/lato ->
non si tocca nulla; >10bps/lato -> rivedere il peso di SKH01.
Punti irrilevanti e perche': maker (il book manda ordini market; tocca solo la
raccomandazione T1 gia' non implementata); liquidation fee 1% (live.json da' nozionale
lordo max 1.00x l'equity con disaster-SL -30% -> servirebbe un movimento avverso ~100%);
VIP (a $600 il volume 30g e' trascurabile).
La conclusione sulla liquidation fee poggia sul CAP, non sulla strategia: cablata una
guardia di decisione (test_leva_massima_da_config_resta_sotto_o_uguale_a_1x) che ROMPE
se qualcuno alza il cap, invece di lasciarla valida per inerzia.
AZIONE 1 agosto: leggere il tier reale in Account Settings e applicare la regola sopra.
Book, pesi, cron, config INVARIATI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
4.1 KiB
Python
96 lines
4.1 KiB
Python
"""Test per r0726_fee_sensitivity.py — sensibilita' del book live al cambio fee Deribit.
|
|
|
|
Due categorie:
|
|
* le repliche parametrizzate sulla fee devono riprodurre BIT-EXACT gli sleeve di produzione
|
|
alla fee canonica (5 bps/lato), altrimenti la curva descrive un'altra strategia;
|
|
* il test sul CAP e' una guardia di DECISIONE: la conclusione "liquidation fee 1% irrilevante"
|
|
poggia sulla leva <= 1x che discende da `live.json`. Se qualcuno alza il cap, quella
|
|
conclusione decade e il test deve dirlo invece di lasciarla in giro valida per inerzia.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
|
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
|
|
|
import r0726_fee_sensitivity as FS # noqa: E402
|
|
|
|
|
|
# ===========================================================================
|
|
# le repliche alla fee canonica == produzione
|
|
# ===========================================================================
|
|
def test_tp01_alla_fee_canonica_riproduce_lo_sleeve():
|
|
from src.portfolio.portfolio import to_daily
|
|
from src.portfolio.sleeves import _tp01_returns
|
|
from src.strategies.trend_portfolio import CANONICAL
|
|
|
|
mine = FS.tp01_at_fee(CANONICAL["fee_side"])
|
|
ref = to_daily(_tp01_returns())
|
|
assert len(mine) == len(ref)
|
|
assert float(np.max(np.abs(mine.values - ref.values))) == 0.0
|
|
|
|
|
|
def test_skh01_alla_fee_canonica_riproduce_lo_sleeve():
|
|
from src.portfolio.portfolio import to_daily
|
|
from src.portfolio.sleeves import _skyhook_returns
|
|
|
|
mine = FS.skh01_at_fee(0.001) # fee_rt canonico dello sleeve
|
|
ref = to_daily(_skyhook_returns())
|
|
assert len(mine) == len(ref)
|
|
assert float(np.max(np.abs(mine.values - ref.values))) == 0.0
|
|
|
|
|
|
def test_la_fee_canonica_del_progetto_e_5bps_per_lato():
|
|
"""Se qualcuno cambia il default, la curva va ri-ancorata: 'oggi' non sarebbe piu' 5 bps."""
|
|
from src.strategies.trend_portfolio import CANONICAL
|
|
|
|
assert CANONICAL["fee_side"] == pytest.approx(0.0005)
|
|
assert 5.0 in FS.BPS_GRID
|
|
|
|
|
|
# ===========================================================================
|
|
# monotonicita': piu' fee, meno rendimento
|
|
# ===========================================================================
|
|
def test_piu_fee_meno_sharpe_e_meno_cagr():
|
|
from src.portfolio.portfolio import metrics
|
|
|
|
lo = metrics(FS.tp01_at_fee(0.0))
|
|
hi = metrics(FS.tp01_at_fee(0.0015))
|
|
assert lo["sharpe"] > hi["sharpe"]
|
|
assert lo["cagr"] > hi["cagr"]
|
|
|
|
|
|
def test_skh01_e_piu_sensibile_alla_fee_di_tp01():
|
|
"""Il fatto che guida la raccomandazione (se il taker salisse, rivedere il peso di SKH01):
|
|
SKH01 fa round-trip discreti, TP01 e' una posizione lenta vol-targeted."""
|
|
from src.portfolio.portfolio import metrics
|
|
|
|
d_tp = metrics(FS.tp01_at_fee(0.0))["cagr"] - metrics(FS.tp01_at_fee(0.001))["cagr"]
|
|
d_sk = metrics(FS.skh01_at_fee(0.0))["cagr"] - metrics(FS.skh01_at_fee(0.002))["cagr"]
|
|
assert d_sk > d_tp
|
|
|
|
|
|
# ===========================================================================
|
|
# guardia di decisione: la leva su cui poggia "liquidation fee irrilevante"
|
|
# ===========================================================================
|
|
def test_leva_massima_da_config_resta_sotto_o_uguale_a_1x():
|
|
"""La conclusione 'liquidation fee 1% irrilevante' vale perche' il cap di live.json
|
|
limita il nozionale lordo a <= 1x l'equity (2 asset x equity/2), con disaster-SL a -30%.
|
|
Se il cap sale, quella conclusione NON vale piu': questo test deve rompersi."""
|
|
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
|
frac = cfg.get("max_notional_per_asset_frac")
|
|
assert frac is not None, "cap dinamico rimosso: rivedere l'analisi liquidation fee"
|
|
leva_max = frac * len(FS.ASSETS)
|
|
assert leva_max <= 1.0, (
|
|
f"leva massima {leva_max:.2f}x > 1x: la liquidation fee 1% torna rilevante, "
|
|
"rivedere r0726_fee_sensitivity e il diario 2026-07-26-fee-deribit.md")
|
|
assert cfg.get("disaster_sl_pct", 0) > 0, "disaster-SL disattivato: idem"
|