Long da 84,120. Stop a 84,036, target a 84,271. Arriva la candela del minuto successivo: apertura 84,118, massimo 84,290, minimo 84,010, chiusura 84,240.
Entrambi i livelli sono dentro quella candela. Lo stop è stato toccato e anche il target, ma i quattro valori OHLC non contengono alcuna informazione su quale dei due sia arrivato prima. Eppure il backtest ha restituito un numero. Da qualche parte nel ciclo, una riga ha deciso: probabilmente una riga che, quando l’hai scritta, non consideravi un’ipotesi di modellazione.
È la principale fonte di performance fittizia che vedo nelle strategie a breve orizzonte, più delle commissioni e dello slippage, perché non sembra un’ipotesi. Sembra semplice codice di servizio.
Primo errore: lasciare decidere alla catena di if
Di solito il codice ha questa forma:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
Nessuno ha scelto «il target si risolve prima dello stop». Il controllo del target è stato scritto per primo perché era il percorso più semplice, quello a cui stavi pensando. Inverti i due rami e la curva del capitale cambia; questo dovrebbe già dirti che una parte del P&L della strategia dipende dall’editor che hai usato.
Ho testato uno scalper mean-reversion volutamente ordinario su BTCUSDT perp, con tre mesi di candele a 1 minuto e 4,812 operazioni, stop a 0.10% e target a 0.18% dall’ingresso. Stessi segnali e stesse commissioni; ho cambiato solo la convenzione per risolvere i casi ambigui.
Un ottavo delle operazioni determina l’intero risultato. È la matematica di una coppia stop-target ravvicinata: le operazioni ambigue sono quelle in cui il prezzo è andato in entrambe le direzioni, cioè la maggior parte di quelle interessanti, e ciascuna vale l’intera distanza tra stop e target, a seconda del lancio della moneta. 12.6% delle operazioni × 0.28% di ampiezza fanno il 3.5% del turnover nozionale lordo per unità di campione: molto più del vantaggio effettivo della strategia.
La frequenza dipende dalla dimensione delle candele rispetto alla distanza tra i livelli e peggiora rapidamente quando si allungano gli intervalli. Stessi stop e target, stessi segnali, intervalli diversi:
| Intervallo della candela | Operazioni con entrambi i livelli nella stessa candela | Sharpe (target per primi) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
Osserva cosa dice davvero la tabella. Con candele più ampie, il backtest migliora. L’istinto di ogni ricercatore è considerare le candele orarie la scelta più prudente: meno rumore, meno overfitting alla microstruttura. Ma se la convenzione intrabar risolve i casi a tuo favore, una candela più ampia è solo una scatola più grande in cui puoi presumere di essere stato fortunato. Con le candele a 1h, 7 operazioni su 10 dipendono interamente dalla convenzione. Quel backtest non sta verificando una strategia: sta verificando l’ordine di due istruzioni `if` per 3,400 volte.
Secondo errore: presumere che lo stop venga eseguito al prezzo dello stop
Mettiamo che tu corregga l’ordine. Lo stop si risolve per primo, registri una perdita esattamente dello 0.10% più la commissione taker e ti senti rigoroso. Restano comunque due problemi distinti.
Il primo è che uno stop è un trigger, non un’esecuzione. Su Binance USDⓈ-M un ordine STOP_MARKET diventa un ordine di mercato non appena si verifica la condizione di trigger, e poi consuma la liquidità disponibile nel book. In un minuto tranquillo, lo slippage è di uno o due tick. Nel minuto che ha effettivamente attivato lo stop — quello con una candela dal corpo di 40 punti e una cascata di liquidazioni in corso — il book è sottile proprio sul lato che stai attraversando. Nel mio campione, confrontando i trigger dello stop con il flusso dei tick, l’esecuzione mediana era 1.4 bps oltre il trigger e il 95º percentile era 11 bps. Con uno stop da 10 bps, la coda ti costa un altro decimo del rischio che pensavi di aver definito.
Il secondo problema è più sottile e specifico dei perp: quale prezzo attiva lo stop. Binance imposta per default gli ordini stop sul mark price, calcolato a partire dall’indice e da una base smussata, non dall’ultimo trade su quella piattaforma. La tua serie OHLC usa l’ultimo prezzo. Sono serie diverse e divergono maggiormente proprio durante gli eventi che attivano gli stop.
| Ultimo prezzo (le tue kline) | Mark price (trigger predefinito) | |
|---|---|---|
| Fonte | trade su questa piattaforma | indice di più piattaforme + base |
| Comportamento degli spike | escursione completa | fortemente attenuata |
| Divergenza tipica | 1–3 bps in condizioni calme, 20–35 bps durante un minuto di liquidazioni a cascata | |
| Conseguenza per il backtest | stop attivati quando non avrebbero dovuto, e viceversa | |
Quindi uno spike di 25 bps sul flusso dell’ultimo prezzo ti fa scattare lo stop nel backtest, mentre il mark price live non si è mai avvicinato a meno di 10 bps dal trigger. Oppure succede il contrario, il giorno in cui l’indice si muove e la tua piattaforma resta indietro. Se imposti workingType su CONTRACT_PRICE, almeno allinei il comportamento live ai tuoi dati. Di solito è la scelta giusta per chi fa ricerca, perché simulare correttamente un trigger sul mark price significa gestire una seconda serie in tutto il motore di esecuzione.
Ricordo bene un caso: qualcuno del nostro team «migliorò» una strategia spostando il take-profit da 0.18% a 0.21%. Lo Sharpe passò da 2.3 a 3.1. Nessun nuovo vantaggio. Il target si era semplicemente spostato oltre la parte più ampia della distribuzione degli spike a 1 minuto, quindi meno operazioni finivano nel gruppo ambiguo, dove il codice assegnava silenziosamente la vittoria. Avevano ottimizzato la regola di spareggio.
Terzo errore: presumere sempre il peggio e chiamarlo prudenza
La reazione istintiva è essere pessimisti. Se vengono toccati entrambi i livelli, applica lo stop. Fatto: niente più ottimismo, si parte.
Lo facevo anch’io. È meglio dell’alternativa, ma è comunque sbagliato, per due motivi.
Scarta strategie valide. Una risoluzione pessimistica sul 12.6% delle operazioni è costata a questa strategia 2.1 punti di Sharpe rispetto allo 0.94 ottenuto risolvendo sui tick. Se il valore reale è 0.94 e la tua convenzione indica 0.18, butti via l’idea e lavori su qualcosa di peggiore. Una prudenza che sbaglia di 2 punti di Sharpe non è prudenza: è rumore con una posa morale.
Peggio ancora, distorce l’ottimizzazione. Dai a una ricerca di parametri una regola pessimistica per risolvere i casi ambigui e l’ottimizzatore impara a evitarli, perché l’ambiguità diventa una penalità pura. Si orienterà verso stop ampi e target ravvicinati, oppure verso candele più lente in cui i due livelli coesistono di rado, e ti presenterà parametri scelti in base alla tua convenzione di esecuzione anziché al mercato. È lo stesso problema della versione favorevole, con segno opposto e altrettanto invisibile nel tearsheet.
La regola pratica che usiamo prima di tutto è questa: se stop_distance + target_distance è inferiore all’intervallo al 75º percentile delle tue candele, l’ipotesi intrabar pesa sul P&L più del segnale. Calcola entrambi i valori. Ci vogliono 4 righe e questo controllo ha chiuso più revisioni di strategie di qualsiasi altro singolo test.
Cosa funziona davvero
Il percorso dentro la candela è un dato. Recuperalo, oppure delimita ciò che non puoi ottenere.
- Risolvi usando la serie più granulare che hai. Gli aggTrades di Binance per i minuti rilevanti sono poche centinaia di righe e risolvono la questione una volta per tutte: quale livello è stato toccato per primo e a quale prezzo è stata eseguita l’operazione sullo spike. Non ti servono i dati tick per l’intero backtest, solo per le candele ambigue. Nel mio campione erano 606 minuti su 129,600. È un download piccolo, non un progetto infrastrutturale.
- Se i tick non sono disponibili, scendi di uno o due intervalli solo per risolvere i casi ambigui. Segnali su 15m, uscite risolte su candele a 1s o 1m. L’ambiguità scende dal 49% a una frazione di punto percentuale, e la parte residua è abbastanza piccola da poterla ignorare senza nasconderla.
- Riporta sempre l’intervallo dei risultati. Esegui ogni backtest due volte, con risoluzione ottimistica e pessimistica, e mostra entrambi gli Sharpe accanto a quello risolto. Questa differenza misura l’incertezza intrabar e va inserita nel tearsheet accanto all’intervallo di confidenza dello Sharpe. Se l’intervallo va da 0.2 a 2.3, non è reale nessuna conclusione al suo interno.
- Considera il tasso di ambiguità una metrica fondamentale. Noi lo mostriamo in cima a ogni scheda strategia, accanto al numero di operazioni e al turnover. Un tasso superiore a circa il 5% significa che stai verificando la logica di uscita, non quella di ingresso.
- Modella separatamente il trigger e l’esecuzione. Attiva lo stop usando la serie di prezzo adottata davvero dalla piattaforma; simula l’esecuzione al trigger più uno slippage estratto da una distribuzione calibrata sul flusso dei trade, non al prezzo del trigger.
Le azioni hanno lo stesso problema, solo con un aspetto diverso. Uno stop a 62.00 su un titolo che apre in gap a 58.40 dopo la notte non viene eseguito a 62.00: viene eseguito a un prezzo inferiore a quello di apertura. Un backtest su candele giornaliere che registra −$0.00 di slippage nei gap oltre lo stop ti dirà volentieri che uno stop-loss ha migliorato il drawdown. Non è così. Semplicemente non l’ha mai messo alla prova nei giorni che contano. Succede anche con le sospensioni: l’asta di riapertura è il momento in cui lo stop viene effettivamente eseguito, a un prezzo che il minimo della candela non mostra mai.
Niente di tutto questo è esotico. Significa riconoscere che una candela è un riepilogo e che una strategia stop-target scommette sull’ordine degli eventi che quel riepilogo ha scartato. Quando il motore di paper trading eseguirà finalmente la strategia sul flusso live, quel flusso avrà una sua opinione sull’ordine degli eventi, e non gli è mai importato quale ramo dell’istruzione if hai scritto per primo.
← Tutti gli articoli

