fd05307c96
Quattro filoni chiesti dall'operatore ("proposte"). Book, pesi, config: INVARIATI.
1. LUMP-SUM + VENUE (r0727_lumpsum_split.py). Tutte le traiettorie del 25-26/07 avevano
START=600 cablato: mai misurato un versamento iniziale, mentre ~10k EUR stanno fermi
altrove. Macchineria validata: con lump 0 riproduce IDENTICI i numeri del 26/07.
- 10k EUR oggi e mai piu' nulla -> traguardo 17.2a, P 62%, rendita 61.58 EUR/g
- equivalenza onesta: +154 EUR/mese per 13 anni = 24.523 EUR, cioe' 2.45x
(la prima stesura misurava i versamenti risparmiati: numero giusto, domanda sbagliata)
- col rischio venue: a 11.500$ lo split e' possibile (quota IB 26%, non 25%) e taglia
P(perso tutto) da 18.4% a 3.5% a p=1%, costando 1.9-2.6pp di P(arrivare)
- SPLIT-CASSA: seconda gamba ferma costa altri 0.6-0.8pp e protegge IDENTICO
-> la protezione non e' bloccata dal PRIIPs: serve un CONTO, non uno sleeve
2. FEE WATCH (scripts/live/fee_watch.py). Nuovo schema Deribit dal 1 agosto senza numeri
pubblicati -> sorvegliante invece di promemoria. Legge il tier base dall'endpoint
pubblico (oggi taker 5.00 bps), applica la regola congelata e allerta sui cambiamenti.
3. MONITOR HEALTH (src/live/monitor_health.py). Tre gate pre-registrati si decidono su
serie forward di cui una sola era sorvegliata. Misura coda E buchi interni: una serie
bucata ma fresca passa qualunque guardia di freschezza.
4. BANDA GTAA01 25% VALIDATA (r0727_gtaa_band_gate.py). 30 celle, 29.9 anni, dpy=252.
Non e' selection-on-holdout (4/30 IS, 5/30 OOS), DSR 0.999, tracking OK ma AL BORDO.
Il modo di fallire non e' il de-levering (la vol non scende) ma la perdita di tracking.
Impatto sul book: zero -> REBAL_BAND_USD non toccato, si applica al deploy.
Aggiunto anche il bullet edge_watch, cablato il 26/07 e mai finito in CLAUDE.md.
Test: 56 nuovi, 504/504 verdi.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
111 lines
4.8 KiB
Python
111 lines
4.8 KiB
Python
"""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__
|