C'è una domanda che ogni backtest schiva: lo stesso codice, alimentato dallo stesso mercato in tempo reale, avrebbe fatto la stessa cosa? Non puoi rispondere con altro backtesting. Puoi rispondere solo facendo girare la strategia su dati live con semantica di esecuzione reale, e poi verificando — operazione per operazione — che i due mondi coincidano. Questo controllo lo facciamo ogni giorno, in automatico, ed è stato di gran lunga il più prolifico cacciatore di bug della piattaforma.
Giorno uno: costruire lo specchio
Ogni strategia promossa gira in un nodo live isolato tutto suo, alimentato da stream reali dell'exchange: quotazioni live per le esecuzioni, spread reali, orari di sessione reali. Nessuno stato condiviso tra strategie (questa l'abbiamo imparata a nostre spese — con un nodo condiviso le query sulle posizioni di una strategia finivano per contaminare quelle di un'altra). Ogni fill viene registrato insieme al valore di segnale che l'ha generato.
Ogni mattina: replay e diff
Un job giornaliero prende la finestra di paper di ogni strategia, fa girare il codice identico attraverso il motore di backtest sullo stesso periodo con i dati del catalogo, e accoppia i fill uno a uno: stesso lato, stessa tolleranza sul timestamp, stessa dimensione. Il risultato non è una correlazione né una sensazione. È una tabella: accoppiati, solo-paper, solo-replay. Una strategia in salute si assesta su 54 accoppiati su 55. Una malata ti dice esattamente quale operazione ha divergito, e quando.
Cosa si è rotto, nell'ordine in cui si è rotto
- Fill in warmup. Il nodo live alimentava lo storico per scaldare gli indicatori — e le strategie con gestori di barre personalizzati ci operavano sopra allegramente. I backtest non hanno mai visto quelle entrate fantasma. La correzione è stata una guardia a livello del layer di submission, non una patch alla singola strategia, perché altrimenti ogni strategia eredita il bug.
- Warmup sequenziale su più gambe. Una strategia cross-sectional scaldava uno strumento alla volta; ogni gamba vedeva le sorelle vuote e l'ancora non veniva mai inizializzata. In live non operava affatto; in replay operava benissimo. Zero operazioni accoppiate — ma solo il diff lo ha detto ad alta voce.
- Crash loop da precisione dei tick. I tick aggTrade live arrivano con una precisione diversa da quella che lo strumento nella sandbox si aspettava. Il nodo entrava in crash loop su un'asserzione che il backtest non avrebbe mai potuto far scattare, perché le barre del catalogo sono già normalizzate.
- Amnesia da riavvio. I riavvii del processo rieseguivano la logica di entrata su posizioni già esistenti — per una strategia di ribilanciamento, un riavvio diventava silenziosamente un ribilanciamento. Nel trading live non è una nota a piè di pagina sulla parità: è un campanello d'allarme con dei soldi attaccati.
Nota il filo conduttore: nessuno di questi è un bug di segnale. Sono tutti bug al confine dell'esecuzione — warmup, stato, precisione, riavvii. È esattamente la classe di guasti verso cui i backtest sono strutturalmente ciechi, perché in un backtest il motore di backtest è il confine dell'esecuzione.
Le asimmetrie che manteniamo di proposito
I due mondi non sono identici, e non per caso. Il backtest addebita nel PnL un mezzo spread modellato più un impatto a radice quadrata; il paper paga lo spread reale nel prezzo di esecuzione e nessun modello di impatto. Aggiungere lo spread modellato al paper (o quello reale ai backtest) significherebbe contarlo due volte. Parità vuol dire stesse decisioni, stessi fill entro tolleranza — non PnL identico al bit. Saper distinguere quali differenze sono di principio e quali sono bug è gran parte della disciplina.
Un backtest è un'ipotesi. Il paper trading è l'esperimento. La parità di replay è il quaderno di laboratorio che ti coglie mentre prendi in giro te stesso.
Se gestisci strategie e non confronti con regolarità il comportamento live con quello riprodotto in replay, hai un'intera categoria di bug che non hai mai visto. Noi ne troviamo uno più o meno ogni due settimane, e ognuno di essi era invisibile dall'interno del backtest.
← Tutti gli articoli
