Files
PythagorasGoal/docs/diary/2026-07-26-t1-esecuzione-skh-live.md
T
Adriano Dal Pastro 031b71bf54 ops(live): sorveglianza freschezza feed SKH + correzione dei docstring sulla latenza d'uscita
T1 (TP di SKH01 come limit resting on-book) NON e' stato implementato, per misura.
Leggendo il codice di produzione: TP01 e SKH01 tradano lo stesso strumento con una
sola posizione netta Deribit, quindi un ordine on-book al livello di SKH chiuderebbe
anche quota TP01. Misurati i due ostacoli: (A) segno compatibile nel 97% dei trade
che escono in TP = risolvibile; (B) divergenza modello/live apparentemente bloccante.

Ma (B) non esiste: resample_5m NON scarta il bin 230m in corso (21 barre 5m su 46
nell'ultimo bin) e _skyhook_positions ci itera dentro -> il live rileva gia' SL/TP
intra-barra, entro ~1h dal tocco. I docstring che dicevano "usa solo barre chiuse" e
"latenza fino alla chiusura della barra 230m" erano FALSI e hanno guidato tre analisi
(02/07, 24/07, T1). Corretti sul posto.

Conseguenza: la lente `hourly` sottostima il path live di +0.081 Sharpe FULL di book
(23/23 offset, banda appaiata); il live vero sta sopra il canonical sul FULL, e il fix
richiesto sarebbe un declassamento (+0.054 vs +0.081) in cambio di ordini parziali sul
netto in un percorso con soldi veri.

Cablato invece il problema vero trovato per strada: fresh_5m fallisce in SILENZIO
(fallback al certificato, rigenerato 1x/giorno) -> la latenza d'uscita di SKH01 passa
da ~1h a ~1 giorno senza segnalazione, e nessun controllo esistente scatta (il gate di
staleness guarda il feed di TP01). Stesso schema del feed-freeze del 14/07.

  * livefeed.feed_age_minutes: pura, eta' dalla CHIUSURA della barra, None = non
    misurata, clamp sugli skew d'orologio
  * book_report espone skh_feed_age_min (max fra gli asset)
  * book_execute stampa lo stato e allerta su Telegram sopra skh_feed_max_age_min (30m)

Scelta dichiarata: ALLERTA, NON blocca. Bloccare fermerebbe anche TP01 (nettato sullo
stesso strumento) per un guasto di rete; forzare SKH flat chiuderebbe posizioni buone
su un glitch.

Follow-up dichiarato: la stessa barra parziale tocca gli INGRESSI (ent[n-1] da breakout
non confermato). Disaccordo in 1 bin su 112, ma con 3 entry nel campione la taglia non
e' stimabile. E' un cambio di strategia, non di strumentazione -> misura dedicata prima.

Strategia, pesi e cadenza del cron INVARIATI. Nessun ordine inviato. Suite 266 verdi.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:20:01 +00:00

8.5 KiB
Raw Blame History

2026-07-26 — T1 nel live: il fix richiesto NON va fatto, e il perché è una correzione di modello

Richiesta: implementare nel live il fix T1 (TP di SKH01 come limit resting on-book). Esito: NON implementato, con i numeri sotto. Implementato invece ciò che le misure sostengono: la sorveglianza della freschezza del feed 5m di SKH01, e la correzione dei docstring che hanno causato tre settimane di misure sbagliate.

File toccati (produzione): src/live/livefeed.py, src/live/book.py, src/portfolio/sleeves.py (solo docstring), scripts/live/book_execute.py, config/live.json. Test: tests/test_skh_feed_freshness.py (11 casi) · suite 266 verdi. Strategia, pesi, cadenza del cron: INVARIATI. Nessun ordine inviato.


1. Cosa ho trovato leggendo il codice di produzione

Il fix T1 nasceva da una simulazione in cui SKH01 esce per conto suo. Il live non funziona così, e il vincolo era scritto nel codice:

book.py: «gli exit sono SOFTWARE (no bracket on-book per SKH, sennò chiuderebbero anche la quota TP01)»

TP01 e SKH01 tradano lo stesso strumento e Deribit tiene una sola posizione netta. Un ordine on-book al livello di TP di SKH chiuderebbe una fetta della posizione netta, che è per il 75% di TP01. Ho quindi misurato i due ostacoli invece di assumerli:

(A) Compatibilità di segno — risolvibile. Un ordine reduce-only può muovere la posizione solo verso lo zero. Se SKH è short mentre il netto è long (perché TP01 pesa di più), chiudere lo short significa aumentare il netto: non esprimibile. Frequenza sui trade che escono in TP — gli unici che il fix tocca: 97% compatibili (SKH long 100%, SKH short 94%). Il 3% si sarebbe potuto saltare con fallback all'uscita software.

(B) Divergenza modello/live — sembrava bloccante. Se il limit riempie a metà barra ma il modello non "vede" l'uscita, il cron successivo ricalcola il target includendo ancora la quota SKH e ricompra ciò che il TP ha appena chiuso. Misurato assumendo la rilevazione a fine barra 230m: ritardo mediano 115 min, p90 210, e nel 70% dei TP almeno un cron gira nella finestra (mediana 1.9 cron, p90 3.5).

2. …e poi ho scoperto che il problema (B) non esiste

Il problema (B) presuppone che il modello veda le uscite solo a barra 230m chiusa. È ciò che dicono i docstring:

sleeves._skyhook_positions: «fino all'ultima barra 230m chiusa. Causale: usa solo barre chiuse» book.py: «latenza fino alla chiusura della barra 230m corrente»

Sono falsi. resample_5m non scarta il bin in corso — aggrega le barre 5m che ci sono finora — e il ciclo di _skyhook_positions itera fino a n-1, quindi valuta gli hit di SL/TP anche sul bin parziale, col suo high/low corrente. Verificato sul feed reale: l'ultimo bin 230m conteneva 21 barre 5m su 46 (105 minuti su 230).

Quindi il live rileva il tocco entro il primo cron dopo il tocco (~1h), non a fine barra. E non è look-ahead: il livello è fissato all'ingresso da barre chiuse, e osservare che il prezzo l'ha già toccato è esattamente ciò che fa qualunque stop reale.

La conseguenza: il path live era modellato male da tre settimane

La lente hourly — usata dall'audit del 02/07, dal follow-up del 24/07 e dal mio stesso T1 di stamattina — assume la rilevazione a fine barra. Il modello fedele è un altro (fastdetect: rilevazione intra-barra a 5m, uscita a mercato al primo cron successivo). Sulla banda appaiata dei 23 offset, relativo al path live come veniva modellato:

modalità ΔFULL mediana [min,max] ΔHOLD mediana offset migliorati (FULL)
fastdetect = il live VERO +0.081 [+0.00,+0.14] +0.039 23/23
canonical (tetto teorico del backtest) +0.054 [0.05,+0.12] +0.099
onbook_tp (il fix richiesto) +0.054 [0.03,+0.07] +0.061 19/23
onbook (TP+SL on-book) +0.024 [0.07,+0.11] +0.031 17/23

Due letture, entrambe importanti:

  1. Il "0.35 di degrado del path live" è in gran parte un artefatto di modello, sopra l'artefatto di ancora già trovato stamattina. Sul FULL il live vero sta sopra il canonical.
  2. Il fix richiesto sarebbe un declassamento: +0.054 contro i +0.081 che il live già fa — e in cambio si porterebbero in un percorso con soldi veri ordini parziali sul netto, un 3% di casi non esprimibili, e la riconciliazione fra fill del broker e stato del modello.

Per questo non l'ho implementato. Non è una difficoltà tecnica: è che la misura, fatta sulla lente giusta, dice che peggiorerebbe.


3. Il problema vero, trovato per strada

fresh_5m è il pezzo che tiene bassa la latenza d'uscita. E fallisce in silenzio:

try:
    tail = _fetch_recent_5m(sym, lookback_days)
except Exception:
    return base          # ← nessun log, nessun errore, nessuna traccia

_fetch_recent_5m inghiotte a sua volta le eccezioni (except Exception: break). Se il fetch pubblico Deribit non risponde, il segnale SKH gira sul feed certificato, che il cron ricostruisce una volta al giorno. La latenza d'uscita passa da ~1 ora a ~1 giorno, e niente lo segnala: il conto è online, la posizione è leggibile, il gate di staleness guarda il feed di TP01 — nessuno dei controlli esistenti scatta. È lo stesso schema del feed-freeze del 2026-07-14, su un altro feed.

E conta esattamente quanto il resto di questo lavoro: la latenza d'uscita è dove vive la qualità del path live.

Cosa ho cablato

  • livefeed.feed_age_minutes(df5) — pura, testabile senza rete: età in minuti dell'ultima barra 5m, misurata dalla sua chiusura, con None per input non interpretabili (None = "non misurata", da trattare come sospetta: se tornasse 0.0 sembrerebbe fresca) e clamp a zero sugli skew d'orologio.
  • book.book_report(live_feed=True) avvolge il loader per misurare la freschezza del feed effettivamente usato, e la espone come skh_feed_age_min (massimo fra gli asset: se anche uno solo è stantio, il netto è sospetto).
  • book_execute stampa lo stato e allerta su Telegram sopra skh_feed_max_age_min (30 min, in config/live.json).

Scelta dichiarata: allerta, NON blocca. Bloccare fermerebbe anche il ribilancio di TP01 — sono nettati sullo stesso strumento — per un guasto di rete; e forzare SKH a flat chiuderebbe posizioni buone su un glitch. Non ho evidenza su quale politica di blocco sia giusta, quindi la decisione resta all'operatore, informato.

Docstring corretti

I due docstring falsi sono stati corretti sul posto, con la misura e il perché. Erano loro a sostenere la catena di ragionamenti sbagliata, ed erano la cosa più economica da riparare.


4. Follow-up dichiarato, non risolto

La stessa barra parziale che migliora le uscite tocca anche gli ingressi: ent[n-1] può venire da un bin parziale, cioè da un breakout non ancora confermato a fine barra. Se il segnale evapora, il book apre e richiude — churn puro.

Misurato su 112 bin campionati: entry sulla barra completa in 3, sulla parziale in 2, disaccordo in 1 (~1% dei bin). Ma con sole 3 entry nel campione la taglia dell'effetto non è stimabile: so che esiste, non so quanto pesa. Non l'ho toccato — a differenza delle uscite, qui il comportamento del live diverge dal backtest in modo sostanziale (entra su un segnale che il backtest non avrebbe visto), e sistemarlo è un cambio di strategia, non di strumentazione. Serve una misura dedicata prima di decidere in che verso.


5. Lezioni

  1. Prima di implementare una raccomandazione, leggere il codice che dovrà ospitarla. T1 era corretto sul suo modello e inapplicabile sull'architettura reale — e leggendo quel codice è saltato fuori che il modello stesso era sbagliato. La ricerca del mattino non lo poteva sapere: simulava lo sleeve, non il book nettato.
  2. Un docstring falso costa più di un bug, perché nessun test lo cattura e tutti ci ragionano sopra. Questi due hanno guidato tre analisi (02/07, 24/07, T1) verso una latenza che il codice non aveva.
  3. Un fallback silenzioso è un guasto silenzioso. fresh_5m fa la cosa giusta (mai operare a cieco) ma non dice di averla fatta: la scelta giusta è tenere il fallback e misurarlo.
  4. Terzo caso in due giorni in cui il numero di partenza era peggio di com'era stato scritto: la fortuna d'ancora sul degrado (stamattina), la lente hourly (qui), il wick indipendente (ieri). Ogni volta il difetto stava nella lente, non nella strategia.