# 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**: ```python 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.