"""Test della sorveglianza fee (scripts/live/fee_watch.py). Il nuovo schema fee Deribit entra in vigore il 2026-08-01 SENZA numeri pubblicati da fonte primaria. La regola e' stata decisa PRIMA di vedere il numero (r0726_fee_sensitivity.py): taker <=5bps -> nulla, >10bps -> rivedere il peso di SKH01. Questi test congelano la regola e la sua taratura, cosi' che il numero non possa muovere la soglia una volta arrivato. Il test piu' importante e' `test_baseline_e_quella_dei_backtest`: lega la soglia della sorveglianza alla fee usata nei backtest. Se qualcuno cambia una delle due senza l'altra, il confronto smette di avere senso e il test lo dice. """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "live")) import fee_watch as FW # noqa: E402 # =========================================================================== # la regola congelata # =========================================================================== def test_al_tier_di_oggi_non_si_tocca_nulla(): assert FW.verdict(5.0)[0] == "OK" def test_sotto_il_tier_di_oggi_i_backtest_restano_conservativi(): """Se la fee SCENDE i numeri pubblicati non vanno rifatti: sono un limite inferiore.""" assert FW.verdict(2.0)[0] == "OK" def test_fra_5_e_10_bps_si_riporta_il_costo_ma_non_si_agisce(): assert FW.verdict(5.1)[0] == "NOTA" assert FW.verdict(10.0)[0] == "NOTA" def test_sopra_10_bps_scatta_la_revisione_del_peso_di_skh01(): lvl, why = FW.verdict(10.1) assert lvl == "AZIONE" assert "SKH01" in why def test_le_soglie_sono_quelle_dichiarate_in_claude_md(): """Una regola decisa in anticipo vale solo se non la si puo' spostare dopo aver visto il numero: qui le due soglie sono congelate.""" assert (FW.SOGLIA_OK, FW.SOGLIA_AZIONE) == (5.0, 10.0) def test_baseline_e_quella_dei_backtest(): """5 bps/lato = 0.10% RT = il default di backtest_signals. Le due devono restare agganciate.""" from src.backtest.harness import backtest_signals import inspect default_fee_rt = inspect.signature(backtest_signals).parameters["fee_rt"].default assert FW.BASELINE_TAKER_BPS * 2 / 1e4 == default_fee_rt def test_sensibilita_marginale_e_quella_misurata(): """-0.017 Sharpe/bps di book (r0726_fee_sensitivity). Se cambia la misura, cambia qui.""" assert FW.D_SHARPE_PER_BPS == -0.017 # =========================================================================== # rilevazione del cambiamento — il motivo per cui e' un cron e non un promemoria # =========================================================================== def test_prima_lettura_non_e_un_evento(): """Senza stato precedente non c'e' un cambiamento: c'e' un'inizializzazione. Allertare qui vorrebbe dire allertare a ogni installazione.""" assert FW.diff_vs({}, {"BTC-PERPETUAL": dict(taker_bps=5.0, maker_bps=0.0, liq_bps=75.0)}) == [] def test_nessun_cambiamento_nessun_rumore(): cur = {"BTC-PERPETUAL": dict(taker_bps=5.0, maker_bps=0.0, liq_bps=75.0)} assert FW.diff_vs(dict(cur), cur) == [] def test_un_taker_che_sale_viene_visto(): prev = {"BTC-PERPETUAL": dict(taker_bps=5.0, maker_bps=0.0, liq_bps=75.0)} cur = {"BTC-PERPETUAL": dict(taker_bps=8.0, maker_bps=0.0, liq_bps=75.0)} ch = FW.diff_vs(prev, cur) assert len(ch) == 1 and "taker" in ch[0] and "8.00" in ch[0] def test_anche_maker_e_liquidazione_sono_sorvegliati(): """L'annuncio tocca tre cose: taker piu' bassi, rebate maker piu' bassi, liquidation fee 1%. Sorvegliarne una sola vorrebbe dire non accorgersi delle altre due.""" prev = {"ETH-PERPETUAL": dict(taker_bps=5.0, maker_bps=0.0, liq_bps=90.0)} cur = {"ETH-PERPETUAL": dict(taker_bps=5.0, maker_bps=1.0, liq_bps=100.0)} ch = FW.diff_vs(prev, cur) assert len(ch) == 2 assert any("maker" in c for c in ch) and any("liquidazione" in c for c in ch) # =========================================================================== # assunzione dichiarata # =========================================================================== def test_sorveglia_il_tier_base_e_lo_dichiara(): """A $600 il volume 30g e' trascurabile: il conto sta al tier BASE, che e' cio' che l'endpoint pubblico riporta. Se un giorno il conto salisse di tier, questo script misurerebbe la cosa sbagliata — l'assunzione sta nel docstring, e questo test la lega.""" assert "public/get_instrument" in FW.API or "get_instrument" in FW.API assert "tier BASE" in FW.__doc__ or "tier base" in FW.__doc__ def test_fee_reali_assenti_non_sono_fee_zero(): """{} = 'non misurata'. Un dict vuoto letto come 'zero' direbbe che il book trada gratis.""" assert FW.realized_fee_bps.__doc__ and "non misurata" in FW.realized_fee_bps.__doc__