Files
PythagorasGoal/docs/diary/2026-08-25-manutenzione-deribit-e-resilienza-venue.md
T
Adriano Dal Pastro ee5c6ed539 ricerca: il piano a 10 anni dal conto vero, l'ETF-quando-flat SCARTATO, slippage rifatto
Giornata partita da "stato trades" e finita sul disegno del sistema. Versamento di
1.400 USDC atterrato alle 11:03:35Z (equity $667,88 -> $2.066,88); il giro delle 11:47
ha ribilanciato correttamente e ha ripiazzato i disaster-SL alla taglia nuova -- prima
volta che il ramo riparato stamattina gira sul serio.

(1) PIANO A 10 ANNI DAL CONTO VERO -- r0825_piano_10a_500.py (nuovo)
Le tabelle pubblicate partono da $600/$635: rifatte su $2.067, lente L3 CONGIUNTA.
Replica superata: chiede EUR 1.718/m dove la tabella da $635 chiedeva EUR 1.733.
EUR 500/mese per 10 anni -> mediana $114.934, rendita 18,35 EUR/g, P(>=50 EUR/g) = 0,0%
su 3.000 traiettorie. Il bersaglio con EUR 500/m arriva al 17o anno.

  L'EQUIVALENZA CHE ORDINA IL PIANO: EUR 100/mese in piu' == +4,07%/anno di drift,
  cioe' +27% su TUTTO il drift del libro. A 10 anni i bonifici fanno il 59%.
  Tutta la leva autorizzabile vale quanto EUR 100-150/mese: k=1,25 (+15,6%) vale MENO
  di EUR 100/mese in piu' (+19,1%), e si porta dietro il peggior giorno al 21,48%.

  E il libro a k=1 rende MENO dell'S&P (15,19% vs 17,40%): il vantaggio sta nello
  Sharpe (1,35 vs 0,89) e senza leva NON si converte in rendimento. Senza leva il libro
  non si giustifica come veicolo di ACCUMULO -- si giustifica come veicolo di RENDITA,
  dove serve 2,5x meno capitale ($254k contro $646k) perche' la perpetua vive sul DD.

(2) "ETF QUANDO IL LIBRO E' FLAT" -- SCARTATO, r0825_capitale_fermo.py (nuovo)
Il libro e' flat il 28,0% dei giorni (2,7% nel 2021, 77,0% nel 2022, 75,6% nel 2026);
live, esposto 14 giorni su 64, leva lorda mediana 0,00x e max 0,52x su un tetto di 1,0x
-- il cap non ha mai morso, il vincolo e' il segnale.
A ISO-RISCHIO il dinamico PERDE: Sharpe 1,01 contro 1,40 del 50/50 e 1,35 del libro. E
il null a maschera casuale (400 estrazioni, stessa quota, blocchi 20g) lo mette al 30o
percentile: fa PEGGIO di commutare a caso.
A iso-nozionale sembrava vincere (drift 17,09%, il piu' alto) perche' aveva la vol piu'
alta: e' la trappola di M6, il de-levering e' il PRIMO test.

  E IL MECCANISMO CHE SEMBRAVA OVVIO NON ESISTE. Aggregato: SPY 4,91% nei giorni flat
  contro 21,48% negli altri (-16,57%), e la storia si scrive da sola. FALSA: scomposta
  per anno il segno ALTERNA (3 su, 4 giu') e il 2022 -- l'anno che doveva reggerla --
  ha il segno OPPOSTO. Artefatto di composizione: i giorni flat stanno negli ANNI brutti
  per l'azionario, non nei GIORNI brutti. M9 ha fatto il suo lavoro su di me.

(3) SLIPPAGE RIFATTO ALLA TAGLIA VERA -- r0822_slip_audit.py riparato
26 fill (erano 18), $6-$319. I due piu' grandi mai eseguiti sono di oggi e hanno preso
1,01% e 0,54% della loro barra 5m, con il print a meta' del range (q=0,50). Il caso
peggiore resta un fill da $74 del 18/07 al 21,9%: un sabato, nastro inesistente.
L'attrito non e' funzione della TAGLIA ma di taglia/volume-della-barra -- l'estrapolazione
lineare che prevedeva ~71% a questo capitale e' refutata dalla misura diretta.
NB non e' "assente", e' "non ancora testato": manca un fill grande in una barra sottile,
e il weekend e' dove TP01 fa il 38% del proprio gross.

  DIFETTO P1 RIPARATO: l'equity era CABLATA a 636.0 con un commento che diceva di
  leggerla dal watermark. Il giorno del versamento avrebbe stampato $636 sbagliando di
  3,3x proprio la sezione che esiste per dire QUANDO la misura scade. Riparata leggendo
  il watermark -- e poi riparata di nuovo, perche' i fill del campione sono stati
  eseguiti a conti diversi ($597-$2.067) e una base sola e' sbagliata comunque la si
  scelga. Ora la normalizzazione e' PER FILL, e a $600 riproduce il 22,0% originale.

DUE ERRORI MIEI, CATTURATI PRIMA DI PUBBLICARLI
- maschera VUOTA vestita da risultato: la prima stesura di r0825_capitale_fermo prendeva
  i giorni flat dalla serie DE-LUCKATA, ma `deluck` sottrae una costante e lo zero esatto
  sparisce -> maschera vuota, dinamico identico al book, e lo script ha stampato tabelle
  piene e un p-value. Lo ha rivelato solo la riga "0 = 0.0%".
  REGOLA: stampare la CARDINALITA' di una maschera prima di usarla.
- il meccanismo falso della (2), demolito dalla scomposizione per anno.

Suite: 730 passati, 1 fallito -- quello gia' noto di 5.9 (deriva dati, non codice).
Watermark del libro live sopravvissuto intatto alla suite (fixture autouse di stamattina).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
2026-08-25 18:13:15 +00:00

15 KiB
Raw Blame History

2026-08-25 — La manutenzione del martedì, e i due difetti che ha scoperchiato

Scritto da Claude (agente), su richiesta dell'operatore. Firmato come vuole P13: l'analisi in prosa è opinione di un lettore fallibile, i numeri qui sotto vengono dai log e sono riproducibili.

Come è cominciata

Domanda dell'operatore: «stato trades». Report normale del libro di bordo — 24 fill, equity $598,06 → $667,88 (+11,67%), 16 round-trip di cui 15 in utile, netto +39,78. Poi il --reconcile ha stampato la riga che ha aperto la giornata:

venue: NON LETTO (HTTPError) — non e' 'zero trade', e' 'non misurato'

Non era il gateway. Era Deribit in manutenzione: system_maintenance, codice 11051, HTTP 503 sia sul pubblico che sul privato. Iniziata fra le 08:57:40Z (ultimo 200 OK nei log di cerbero-mcp) e le 09:01:46Z (primo 503). Rientrata verso le 09:20 — ~20 minuti, coerenti con i 15-30 annunciati da Deribit per le sue release.

Cosa dicono i log, contati invece che ricordati

1.499 giri di cron_book fra il 2026-06-23 e il 2026-08-25:

esito n %
manutenzione Deribit 3 0,20%
traceback duro 5 0,33%
giri senza esito utile 26 1,7%

E gli episodi di venue non sono sparsi:

2026-07-21T09:00  Tue  502 su get_positions (Deribit giù, vista dal gateway)
2026-08-11T09:07  Tue  system_maintenance 11051
2026-08-18T09:07  Tue  system_maintenance 11051   (giù anche alle 10:07)
2026-08-25T09:07  Tue  system_maintenance 11051

Quattro martedì su dieci, tutti fra le 09:00 e le 09:07 UTC. Il supporto Deribit conferma la meccanica: le release escono il martedì alle 09:00 UTC. Il minuto :07 del cron cadeva dentro quella finestra — e ci cadeva per costruzione, non per sfortuna.

Il punto singolo di guasto non è Deribit: siamo noi. Dei 5 traceback, 4 sono il nostro gateway cerbero-mcp.tielogic.xyz (un 404 su get_positions, un 502, due ReadTimeout).

I due difetti veri

1. Una riga sola per due guasti che vogliono azioni opposte

Fino a oggi qualunque guasto sul percorso Deribit stampava conto non leggibile (offline), con la nota di diagnosi cablata. Ma "Deribit in manutenzione" (aspetta, rientra da sola) e "il nostro gateway è rotto" (ripara) non sono la stessa notizia. È P4 violata, con l'aggravante di P4 seconda metà: una nota di diagnosi cablata è peggio di nessuna nota, perché si legge come una misura.

Peggio ancora: il 18/08 il codice 11051 era già dentro il processo, nello stesso minuto, raccolto da livefeed. Semplicemente non arrivava a chi decideva la gravità dell'allarme.

2. La finestra scoperta del disaster-SL — e l'asset che sparisce

ensure_disaster_sl ricostruisce un bracket incoerente cancellando prima e ripiazzando dopo. Fra le due chiamate la posizione è senza alcuno stop on-book. Finché il ripiazzamento sollevava, quell'eccezione risaliva fino a main(): il guasto peggiore (posizione scoperta) aveva la stessa faccia di un errore qualunque.

E c'è il corollario che è successo davvero. Il 2026-07-21 alle 09:00 UTC il 502 è arrivato dentro ensure_disaster_sl su BTC. Nel log di quel giro ETH non compare: non è stato ribilanciato e — quel che conta — la sua protezione non è stata verificata. Un guasto su un asset toglieva la rete di sicurezza all'altro.

Cosa è stato fatto

src/live/venue_probe.py (nuovo). Interroga l'API pubblica Deribit in diretta — niente gateway, niente credenziali — e classifica: VENUE_MANUTENZIONE / VENUE_GIU / GATEWAY / IGNOTO. La sonda parte solo dopo un guasto: sul percorso sano costa zero. Rispetta P1: non ridichiara la firma 11051, la importa da venue_watch.is_maintenance.

Su P9 (un allarme massimo speso per un evento atteso è un allarme che non verrà letto il giorno che è vero): la manutenzione dentro lo slot declassa il titolo a . Ma con due paletti, perché il rischio qui è costruire il silenzio proprio nell'ora in cui serve:

  • declassa solo su evidenza della sonda, mai sull'orologio da solo → un gateway rotto di martedì mattina resta 🛑;
  • declassa solo dentro la durata annunciata (30 min) → il 18/08 alle 10:07 la manutenzione aveva sforato, e torna una notizia. Stessa logica di MAINT_GRACE_HOURS, altra domanda.

Isolamento per asset in book_execute: un asset che esplode non ferma il ciclo, e il giro esce con codice 2 per essere contabile.

Stato naked in ensure_disaster_sl: due tentativi di ripiazzamento, e se falliscono entrambi lo stato è distinto da place-failed (P5: guasti diversi si distinguono anche quando l'azione è la stessa). Non è "non sono riuscito a proteggere": è "ho tolto la protezione e non sono riuscito a rimetterla" → 🚨 sempre, mai declassato da P9.

Non è stata invertita la sequenza in piazza-poi-cancella: due STOP reduce_only contemporanei sono probabilmente innocui, ma "probabilmente" non basta per cambiare il ciclo di vita dei bracket su un percorso con soldi veri senza misurarlo. Riparato il silenzio, non toccata la sequenza.

Cron :07:47. I due vincoli sono entrambi misurati e compatibili: fuori dai ~26s del minuto tondo (il collettore catena si auto-satura il rate-limit per-IP: 12.186 risposte 429 in 26 ore, 96% nel minuto :00, misura del 30/07) e fuori dallo slot di release. Il commento in cron_book.sh è stato riscritto: lasciarlo dire :07 sarebbe stato il difetto §5.7 in versione nuova.

Previsione dichiarata (M12). Sulle 4 finestre osservate, 3 sono rientrate entro l'ora. Il :47 ne avrebbe scavalcate 3 su 4. Se martedì prossimo il :47 becca comunque la manutenzione, la previsione è sbagliata e lo slot non è quello che credo.

Cosa NON è stato fatto, e perché

Il fallback diretto ai privati Deribit è bloccato, non rinviato. Le credenziali Deribit esistono solo dentro il gateway: in locale c'è CERBERO_TOKEN e basta. Leggere conto e posizioni scavalcando cerbero-mcp richiede chiavi API create dall'operatore sul conto Deribit. È una decisione con una superficie di rischio propria (una chiave in più che può trapelare), non un refactor.

È stato fatto il pezzo che non le richiede — la sonda pubblica — e resta a debito in §5.11 il resto. Nota onesta: la sonda pubblica dice di chi è il guasto, non lo aggira. Con il gateway giù il libro continua ad astenersi; sa solo dire perché.

Test

19 nuovi (12 in test_venue_probe.py, 7 in test_book_resilienza_venue.py), nessuno tocca la rete. Suite: 730 passati, 1 fallito — il fallito è quello già noto di §5.9 (SKH01 canonical 1,9223 contro banda cablata <1,9, deriva dei dati, non del codice).

Controllo positivo fatto, perché un test mai visto fallire non dimostra niente: contro il codice vecchio 5 dei 7 test di resilienza falliscono. Con una riserva da dichiarare — il test di isolamento, sul codice vecchio, fallisce perché il modulo di diagnosi non esiste, non perché dimostri l'isolamento rotto. La prova di quel difetto è il log del 21/07, dove ETH non compare.


Coda: la suite di test ha mandato un allarme falso sul telefono dell'operatore

Il primo giro al nuovo minuto :47 è andato bene — entrambi gli asset elaborati, entrambi i disaster-SL verificati ok, exit 0 — ma nel log c'era una riga che non poteva essere vera:

💰 USCITA DI FONDI: $5,000.00 -> $667.68 (-86.6%) · cap/asset ora $333.84

Il conto non ha mai visto $5.000. Ha visto $598-668 da sempre.

L'ho causato io, lanciando uv run pytest per verificare le riparazioni di oggi. test_book_live.py::test_la_formula_del_report_ha_potenza_anche_a_libro_flat prende solo monkeypatch (niente tmp_path), sostituisce shadow_report con uno che dichiara real_equity=5000.0, e chiama book.book_report() — che come effetto collaterale scrive data/live/equity_seen.json. Riprodotto isolando il singolo test: watermark $667,68 → $5.000.

L'helper _write_cfg esisteva già proprio per questo — il difetto gemello è del 2026-07-26 — ma quel test, aggiunto il 21/08, non lo usa. È la terza volta che la stessa scommessa perde.

Non era cosmetico

Il watermark alimenta il cap di fallback:

cap_fallback = min(cap_fisso_di_config, watermark × frac)

Con $5.000 dentro: min($3.000, $2.500) = $2.500 per asset invece di $334. Su un conto da $668 sono fino a $5.000 di nozionale lordo, ~7,5x di leva. E il ramo che ci arriva è raggiungibile: eq_fallback in book_execute allerta e NON blocca, per scelta dichiarata.

Cioè: lanciare la suite di test poteva armare esattamente il pericolo che il watermark esiste per impedire (nota del 26/07 in book.py sul cap fisso e la leva 3,35x).

Danno reale oggi: nessuno. Alle 09:47 l'equity era leggibile, quindi il cap è stato calcolato sull'equity vera e il watermark è stato riscritto col valore giusto. La finestra scoperta è stata ~09:25 → 09:47, e in quella finestra non c'è stato nessun giro con equity illeggibile. È andata bene per la direzione del caso, non perché ci fosse una protezione.

Riparazione, e perché è strutturale

tests/conftest.py con una fixture autouse che devia EQUITY_WATERMARK in tmp_path per ogni test. Chiedere a ogni autore di ricordarsi il monkeypatch è la scommessa che ha già perso due volte: la protezione deve valere anche per il test che qualcuno scriverà domani senza aver letto niente. Un test che vuole davvero pilotare il watermark continua a funzionare — il suo monkeypatch esplicito gira dopo e vince.

Più una guardia in test_cap_watermark.py che si accende se qualcuno rimuove la fixture: controllo positivo, perché una protezione mai vista fallire non è una protezione.

Resta aperto il principio più largo (§5.12): un test non dovrebbe poter scrivere in data/live/ affatto. Oggi è deviato solo il watermark — trades.db e book_executions.jsonl sono ancora esposti allo stesso errore.

La lezione

Un effetto collaterale su file trasforma un test puro in un attore sul sistema vivo. Qui il test non menzionava il watermark, non lo importava, non lo asseriva: lo scriveva passando per una funzione di produzione tre livelli più in basso. Il perimetro di un test non è quello che il test dice di toccare: è quello che tocca il codice che chiama.


Seconda parte della giornata: il versamento, e le tre misure che ne sono uscite

Alle 11:03:35Z sono atterrati 1.400 USDC: equity $667,88 → $2.066,88. Il giro delle 11:47 ha ribilanciato correttamente (cap/asset $1.033, BTC +$238, ETH +$319, disaster-SL placed su entrambi alla taglia nuova) — la prima volta che il ramo riparato al mattino gira sul serio.

Poi l'operatore ha chiesto tre cose in fila, e ognuna ha rotto qualcosa.

(1) «€500/mese per 10 anni»

Rifatto sul conto vero invece che citare la tabella da $635. Non centra: capitale mediano $114.934, rendita 18,35 €/g, P(≥50 €/g) = 0,0% su 3.000 traiettorie. Il bersaglio con €500/m arriva al 17° anno; per averlo a 10 servono €1.718/mese.

Il numero che ordina tutto: €100/mese in più valgono +4,07%/anno di drift, cioè +27% su tutto il drift del libro. Dettaglio in 30-piano-capitale-fisco.md.

(2) «A cosa serve XSR01, allora?» → e poi: «l'abbiamo fatto per la leva»

Due osservazioni dell'operatore, entrambe centrate, che hanno spostato la conversazione dal candidato al disegno.

XSR01 ha già fallito weights_tilt_null (peso ottimo ~0). Ma la risposta vera è che anche un uplift di strategia riuscito vale meno di un bonifico modesto, e questo si applica a qualunque sleeve, non solo a XSR01.

Poi la misura che ha ribaltato la discussione sulla leva: il libro non è poco levato, è FERMO. Esposizione lorda realizzata in 64 giorni live: mediana 0,00x, media 0,05x, max 0,52x su un tetto strutturale di 1,0x, esposto in 14 giorni su 64. Il cap non ha mai morso: il vincolo è il segnale. Il basso drawdown di TP01 non viene dall'essere piccolo nel mercato — viene dallo stare fuori dal mercato.

⚠️ Correzione che mi sono fatto da solo: quel 78% è il regime attuale, non la media. Sull'intera storia i giorni flat sono il 28,0% — 2,7% nel 2021, 77,0% nel 2022, 75,6% nel 2026.

(3) «Allora usiamo l'ETF quando il libro è flat» → SCARTATO

Test in r0825_capitale_fermo.py. Perde a iso-rischio (Sharpe 1,01 contro 1,40 del 50/50 e 1,35 del libro), e il null a maschera casuale lo mette al 30° percentile: fa peggio di commutare a caso.

E qui la parte che vale più del risultato. Stavo per scrivere un meccanismo pulito — «il libro va flat quando anche l'azionario soffre» — sostenuto da un aggregato netto (SPY 4,91% nei giorni flat contro 21,48% negli altri, 16,57%). La scomposizione per anno lo ha demolito: il segno alterna, 3 anni su e 4 giù, e il 2022 — l'anno che doveva reggerlo — ha il segno opposto. Artefatto di composizione: i giorni flat stanno negli anni brutti per l'azionario, non nei giorni brutti. M9 ha fatto esattamente il suo lavoro, su di me.

I due difetti di misurazione trovati addosso

  1. Maschera vuota vestita da risultato. La prima versione prendeva i giorni flat dalla serie de-luckata; ma deluck sottrae una costante, quindi lo zero esatto sparisce. Maschera vuota → dinamico identico al book → e lo script ha stampato tabelle piene e un p-value. Lo ha rivelato solo la riga che stampava 0 = 0.0%. Regola: stampare la CARDINALITÀ di una maschera prima di usarla.
  2. r0822_slip_audit.py aveva l'equity CABLATA a 636.0 con un commento che diceva di leggerla dal watermark — P1 in piena regola. Il giorno del versamento avrebbe continuato a stampare $636, sbagliando di 3,3x proprio la sezione che esiste per dire quando la misura scade. Riparato leggendo il watermark — e poi riparato di nuovo, perché la prima riparazione divideva tutti i fill per l'equity di oggi mentre i fill del campione sono stati eseguiti a conti diversi ($597$2.067). Ora la normalizzazione è per fill, e a $600 riproduce il 22,0% originale.

La misura di slippage, rifatta alla taglia vera

26 fill (erano 18), taglia $6$319. I due più grandi mai eseguiti sono di oggi e hanno preso 1,01% e 0,54% della loro barra 5m, con il print a metà del range (q=0,50). Il caso peggiore resta un fill da $74 del 18/07 al 21,9% — un sabato, nastro inesistente.

📌 L'attrito non è funzione della taglia: è funzione di taglia / volume della barra. Un fill 4,3× più grande in un'ora liquida ha fatto 20× meno partecipazione di uno piccolo in un'ora morta. ⚠️ Ma non è «assente», è «non ancora testato»: nel campione non c'è ancora un fill grande in una barra sottile, e il weekend è dove TP01 fa il 38% del proprio gross.