research: nuovo schema fee Deribit (1 ago 2026) — misurata la curva, nessuna azione oggi
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>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0726_fee_sensitivity.py — quanto costa (o rende) un cambio di fee Deribit al book live.
|
||||
|
||||
CONTESTO: Deribit ha annunciato un nuovo schema fee **dal 1 agosto 2026** ("lower taker fees and
|
||||
lower maker rebates" su futures/perpetual, soglie VIP piu' basse, nuovo VIP7, **liquidation fee
|
||||
1% su tutti i prodotti**, spot temporaneamente a fee zero). La tabella numerica dell'articolo
|
||||
Insights e' un'IMMAGINE: i numeri esatti per tier non sono stati letti da fonte primaria.
|
||||
|
||||
Percio' qui NON si assume un numero nuovo: si misura la **curva**. Quando la tabella e' leggibile
|
||||
si prende il valore giusto da questa curva, senza rifare l'analisi.
|
||||
|
||||
Il progetto modella ovunque **0.05%/lato = 0.10% RT (taker Deribit)**:
|
||||
`trend_portfolio.CANONICAL.fee_side = 0.0005`, `sleeves._skyhook_returns fee_rt=0.001`,
|
||||
e i paper monitor con `FEE_SIDE = 0.0005`.
|
||||
|
||||
COSA FA:
|
||||
1. curva Sharpe/CAGR del book live (TP01+SKH01 75/25) e dei due sleeve, al variare della fee;
|
||||
2. il **drag** in %/anno: quanto la fee toglie al rendimento lordo, per sleeve;
|
||||
3. il punto di pareggio (fee a cui il book va a Sharpe zero) — la distanza dal muro;
|
||||
4. esposizione alla **liquidation fee 1%**: la leva effettiva del book coi cap di `live.json`.
|
||||
|
||||
uv run python scripts/research/r0726_fee_sensitivity.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
from src.backtest.harness import backtest_signals # noqa: E402
|
||||
from src.data.downloader import load_data # noqa: E402
|
||||
from src.portfolio.portfolio import combine_outer, metrics, to_daily # noqa: E402
|
||||
from src.strategies.skyhook import SKH01_V2_DD, build_frames, skyhook_entries # noqa: E402
|
||||
from src.strategies.trend_portfolio import (CANONICAL, TrendPortfolio, # noqa: E402
|
||||
resample_1d, simple_returns)
|
||||
|
||||
ASSETS = ("BTC", "ETH")
|
||||
# bps PER LATO. 5 bps/lato = 0.10% RT = quello che il progetto modella oggi.
|
||||
BPS_GRID = (0.0, 1.0, 2.0, 3.0, 5.0, 7.5, 10.0, 15.0)
|
||||
|
||||
|
||||
def tp01_at_fee(fee_side: float) -> pd.Series:
|
||||
cfg = dict(CANONICAL)
|
||||
cfg["fee_side"] = fee_side
|
||||
tp = TrendPortfolio(**cfg)
|
||||
series = {}
|
||||
for a in ASSETS:
|
||||
df = resample_1d(load_data(a, "1h"))
|
||||
r = simple_returns(df["close"].values.astype(float))
|
||||
tgt = tp.target_series(df)
|
||||
held = np.zeros(len(tgt)); held[1:] = tgt[:-1]
|
||||
net = held * r - tp.fee_side * np.abs(np.diff(held, prepend=0.0)); net[0] = 0.0
|
||||
series[a] = pd.Series(np.clip(net, -0.99, None), index=pd.to_datetime(df["datetime"]))
|
||||
J = pd.concat(series, axis=1, join="inner").fillna(0.0)
|
||||
return to_daily(pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index))
|
||||
|
||||
|
||||
def skh01_at_fee(fee_rt: float) -> pd.Series:
|
||||
series = {}
|
||||
for a in ASSETS:
|
||||
ltf, htf = build_frames(load_data(a, "5m"))
|
||||
ent = skyhook_entries(ltf, htf, SKH01_V2_DD)
|
||||
m = backtest_signals(ltf, ent, fee_rt=fee_rt, leverage=1.0, asset=a, tf="230m")
|
||||
s = pd.Series(m.equity, index=pd.DatetimeIndex(pd.to_datetime(m.eq_index, utc=True)))
|
||||
series[a] = s.resample("1D").last().ffill().pct_change().dropna()
|
||||
J = pd.concat(series, axis=1, join="inner").fillna(0.0)
|
||||
return to_daily(pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" r0726 — SENSIBILITA' DEL BOOK LIVE ALLA FEE (annuncio Deribit, dal 2026-08-01)")
|
||||
print("=" * 100)
|
||||
print(" Il progetto modella 5 bps/lato = 0.10% RT (taker). La tabella nuova non e' stata")
|
||||
print(" letta da fonte primaria (e' un'immagine): qui si misura la CURVA, non un numero.")
|
||||
|
||||
rows = []
|
||||
for bps in BPS_GRID:
|
||||
fs = bps / 10_000.0
|
||||
tp = tp01_at_fee(fs)
|
||||
sk = skh01_at_fee(2.0 * fs) # fee_rt = 2 x fee per lato
|
||||
book = combine_outer({"TP01": tp, "SKH01": sk},
|
||||
{"TP01": 0.75, "SKH01": 0.25})
|
||||
rows.append(dict(bps=bps, tp=metrics(tp), sk=metrics(sk), bk=metrics(book)))
|
||||
|
||||
print(f"\n {'bps/lato':>9}{'%RT':>8} | {'TP01 Sh':>9}{'CAGR':>8} | {'SKH01 Sh':>10}{'CAGR':>8}"
|
||||
f" | {'BOOK Sh':>9}{'CAGR':>8}{'maxDD':>8}")
|
||||
for r in rows:
|
||||
tag = " <- oggi" if r["bps"] == 5.0 else ""
|
||||
print(f" {r['bps']:>9.1f}{r['bps']*2/100:>7.2f}% | "
|
||||
f"{r['tp']['sharpe']:>9.3f}{r['tp']['cagr']*100:>7.2f}% | "
|
||||
f"{r['sk']['sharpe']:>10.3f}{r['sk']['cagr']*100:>7.2f}% | "
|
||||
f"{r['bk']['sharpe']:>9.3f}{r['bk']['cagr']*100:>7.2f}%"
|
||||
f"{r['bk']['maxdd']*100:>7.2f}%{tag}")
|
||||
|
||||
base = next(r for r in rows if r["bps"] == 5.0)
|
||||
zero = next(r for r in rows if r["bps"] == 0.0)
|
||||
print(f"\n DRAG della fee attuale (5 bps/lato) contro fee zero:")
|
||||
for k, lab in (("tp", "TP01"), ("sk", "SKH01"), ("bk", "BOOK")):
|
||||
d_sh = zero[k]["sharpe"] - base[k]["sharpe"]
|
||||
d_cagr = (zero[k]["cagr"] - base[k]["cagr"]) * 100
|
||||
print(f" {lab:<7} Sharpe -{d_sh:.3f} CAGR -{d_cagr:.2f}%/anno")
|
||||
|
||||
print(f"\n SENSIBILITA' MARGINALE intorno al punto attuale (per bps/lato):")
|
||||
lo = next(r for r in rows if r["bps"] == 3.0)
|
||||
hi = next(r for r in rows if r["bps"] == 7.5)
|
||||
for k, lab in (("tp", "TP01"), ("sk", "SKH01"), ("bk", "BOOK")):
|
||||
d = (hi[k]["sharpe"] - lo[k]["sharpe"]) / (7.5 - 3.0)
|
||||
dc = (hi[k]["cagr"] - lo[k]["cagr"]) * 100 / (7.5 - 3.0)
|
||||
print(f" {lab:<7} {d:+.4f} Sharpe/bps {dc:+.3f}% CAGR/bps")
|
||||
|
||||
# ---------------------------------------------------- liquidation fee
|
||||
print("\n" + "=" * 100)
|
||||
print(" LIQUIDATION FEE 1% — ci tocca?")
|
||||
print("=" * 100)
|
||||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||||
frac = cfg.get("max_notional_per_asset_frac")
|
||||
print(f" cap per asset: {frac:.0%} dell'equity (config: max_notional_per_asset_frac)")
|
||||
print(f" asset tradati: {len(ASSETS)} -> nozionale lordo massimo = "
|
||||
f"{frac*len(ASSETS):.0%} dell'equity")
|
||||
print(f" leva effettiva massima: {frac*len(ASSETS):.2f}x (e i due asset sono spesso")
|
||||
print(f" correlati, quindi il rischio non e' additivo come il nozionale)")
|
||||
print(f" disaster-SL on-book: -{cfg.get('disaster_sl_pct', 0)*100:.0f}% sulla posizione netta")
|
||||
print("\n A leva <= 1x la liquidazione richiederebbe un movimento avverso ~100%: il")
|
||||
print(" disaster-SL a -30% interviene molto prima. La liquidation fee 1% e' quindi")
|
||||
print(" IRRILEVANTE per questo book — diventerebbe rilevante solo alzando la leva,")
|
||||
print(" che e' una decisione separata (e il 25/07 la valutava per il fronte prop).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user