"""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 RIMOSSA (2026-08-26) — e il perche' va letto, non saltato # =========================================================================== # Qui viveva `test_leva_massima_da_config_resta_sotto_o_uguale_a_1x`: verificava che # `max_notional_per_asset_frac x n_asset <= 1.0`, a presidio della conclusione # "liquidation fee 1% irrilevante" (r0726_fee_sensitivity). # # E' stato CANCELLATO, non aggiustato, per la ragione registrata in CLAUDE.md §5.5: # misurava `frac x n_asset`, ma se il progetto introducesse la chiave di scala di # SPEC-scale-key la grandezza vera diventerebbe `frac x n_asset x scala` — e questo # test avrebbe CONTINUATO A PASSARE smettendo di controllare (P1: un sorvegliante che # non deriva il bersaglio dal codice sorvegliato passa sempre). Una guardia che passa # a bersaglio cambiato e' peggio di nessuna guardia: certifica una proprieta' che non # vale piu'. # # La guardia GIUSTA e' gia' scritta nella spec: GATE SCALA-01 impone il tetto sul # PRODOTTO `n_asset · frac · scala · disaster_sl_pct <= 0,50` — va cablata QUANDO (se) # la chiave di scala entrera' in config, dentro il codice che la legge, non qui. # Fino ad allora: la leva resta 1,0x per costruzione (nessuna chiave di scala esiste, # verificato in §3 di CLAUDE.md) e il disaster-SL e' presidiato dai test del book.