Strasmore Research
Formazione Matt ConnorDi Matt Connor · Aggiornato 2026-08-04

Circuit breaker per trading bot: come funzionano

Scopri come i circuit breaker fermano un trading bot prima che una sessione negativa si aggravi, quando scatta il limite di perdita giornaliero e come la volatilità riduce la posizione.

I circuit breaker per i trading bot sono regole che impediscono a una strategia automatizzata di inviare ordini quando viene raggiunto un limite prestabilito. Si collocano in un livello di risk management tra la strategia e il broker e si applicano a ogni ordine, indipendentemente dal fatto che la strategia li riconosca o meno. La strategia decide su cosa operare. Il livello di rischio decide se l’operatività può avvenire.

Questa separazione è il principio centrale del design. Una strategia che controlla se stessa non dispone di una verifica indipendente nel momento in cui le proprie ipotesi vengono meno, cioè proprio quando quella verifica è più utile.

Cosa fa un circuit breaker in un trading bot

Un livello di controllo del rischio ha quattro componenti. Ognuna serve a prevenire uno specifico errore, banale ma potenzialmente dannoso.

  • Limiti rigidi sulla dimensione della posizione, sull’esposizione nozionale per simbolo e sulla frequenza degli ordini. Limitano il danno derivante da un bug che altrimenti potrebbe diventare illimitato.
  • Un circuit breaker sul drawdown che blocca i nuovi ordini quando il conto registra una perdita pari all’importo stabilito nella sessione o quando scende di un importo stabilito rispetto al picco di equity.
  • Un dimensionamento scalato degli ordini, determinato dalla volatilità recente o da una frazione della puntata di Kelly anziché da un numero fisso di azioni. In questo modo il rischio per trade resta approssimativamente costante mentre l’ampiezza dei movimenti di mercato cambia.
  • Un audit log in modalità append-only di ogni decisione, compresi gli ordini rifiutati dal livello di controllo del rischio. È l’unico elemento che consente di distinguere tra «la strategia era sbagliata» e «il controllo non è mai stato eseguito».

Tutto ciò che segue è una concretizzazione di uno di questi quattro elementi.

Qual è un limite giornaliero ragionevole per le perdite di un trading bot?

Un limite giornaliero alle perdite blocca i nuovi ordini quando la perdita della sessione supera una soglia. La scelta del livello è una questione di calibrazione, non di preferenze: se lo si fissa all’interno della normale volatilità di mercato, il bot resterà bloccato per gran parte delle settimane; se lo si colloca molto al di fuori, non scatterà mai. Il punto di partenza è la frequenza con cui il mercato registra sedute negative di una certa entità.

QueryGiornate di ribasso di SPY per entità, per anno, dal 2018 a luglio 2026
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2017-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY d
),
with_prev AS (
    SELECT d,
           close_px,
           any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
    FROM daily
)
SELECT toYear(d) AS year,
       countIf(close_px / prev_close - 1 <= -0.01) AS down_1pct_days,
       countIf(close_px / prev_close - 1 <= -0.02) AS down_2pct_days,
       countIf(close_px / prev_close - 1 <= -0.03) AS down_3pct_days
FROM with_prev
WHERE prev_close > 0
  AND d >= toDate('2018-01-01')
GROUP BY year
ORDER BY year
Run this yourself

Le sedute con un calo dell’1% o superiore sono state 15 nel 2019 e 45 nel 2020, su circa 250 sedute di trading all’anno. Il conteggio delle sedute con un calo del 3% o superiore è diverso: 0 nel 2019 contro 16 nel 2020. L’ultima riga di 9 copre soltanto le sedute fino al 31 luglio 2026.

La distribuzione è irregolare, non costante, ed è proprio questo il punto. Le giornate difficili arrivano a grappoli. Un bot che si blocca il primo giorno di un cluster e riprende il secondo non si è realmente fermato.

Le due soglie svolgono funzioni diverse. Un limite giornaliero alle perdite, generalmente pari al 2% del capitale del conto, chiude la sessione. Un limite di drawdown trailing misurato dal massimo storico dell’equity, generalmente intorno al 10%, interrompe la strategia in attesa di una revisione umana. Il primo è una misura ordinaria, il secondo dovrebbe scattare raramente. Un bot che applica soltanto il primo può ridurre progressivamente il capitale del conto del 2% alla volta senza mai attivare alcun blocco.

In che modo lo scaling della volatilità modifica la dimensione della posizione?

Il volatility targeting dimensiona una posizione in modo inversamente proporzionale alla volatilità realizzata recente: quando l’escursione giornaliera raddoppia, la posizione si dimezza all’incirca, mantenendo pressoché costante il rischio in dollari per trade. In questo contesto, la volatilità realizzata è la deviazione standard annualizzata dei rendimenti giornalieri e tende a muoversi più di quanto la maggior parte degli investitori si aspetti.

QueryVolatilità realizzata di SPY per mese e posizione che comporterebbe un target di volatilità del 12%
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2023-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY d
),
rets AS (
    SELECT d,
           close_px / any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) - 1 AS ret
    FROM daily
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
       round(stddevSamp(ret) * sqrt(252) * 100, 1) AS realized_vol_pct,
       round(least(100.0, 1200.0 / (stddevSamp(ret) * sqrt(252) * 100)), 1) AS vol_target_size_pct
FROM rets
WHERE d >= toDate('2024-01-01')
  AND isFinite(ret)
GROUP BY toStartOfMonth(d)
HAVING count() >= 15
ORDER BY toStartOfMonth(d)
Run this yourself

Volatilità realizzata misurata 11.1% su base annualizzata in 2024-01 e 12% in 2026-07, nell’arco di 31 mesi. La seconda colonna converte ciascuna lettura nella posizione che un target di volatilità del 12% assumerebbe, con un limite pari alla linea completa: 100% in 2024-01 rispetto a 99.7% in 2026-07. La strategia e il livello di convinzione restano invariati, ma il numero di azioni cambia nettamente.

Il criterio di Kelly affronta lo stesso problema dall’estremo opposto: dimensiona la posizione sulla base dell’edge stimato e della varianza, invece che della sola volatilità. La maggior parte degli operatori sistematici utilizza una frazione del criterio, come half Kelly o quarter Kelly, perché entrambi gli input sono stime ricavate da un campione finito. Dimensionamento della posizione secondo il criterio di Kelly illustra questi calcoli.

Perché un trading bot continua a rientrare dopo uno stop?

Scatta uno stop. La posizione viene chiusa. Novanta secondi dopo, la condizione di ingresso torna vera, il bot rientra, registra la stessa perdita e ripete il ciclo. Nessun singolo componente è guasto. La strategia ha fatto ciò per cui è stata scritta, lo stop ha fatto ciò per cui è stato impostato, ma il conto continua a perdere capitale, un round trip alla volta.

La frequenza dipende direttamente dal percorso dei prezzi. Questo pannello conta, per sessione, quante volte SPY è passato da oltre lo 0,1% sopra il prezzo di apertura a oltre lo 0,1% sotto, o viceversa.

QueryFrequenza con cui SPY torna ad attraversare il proprio prezzo di apertura, per sessione, per mese
L'esatto SQL dietro ogni numero
WITH mins AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           window_start AS ts,
           toFloat64(close) AS px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2025-08-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
),
opens AS (
    SELECT d, argMin(px, ts) AS open_px
    FROM mins
    GROUP BY d
),
zoned AS (
    SELECT m.d AS d,
           m.ts AS ts,
           multiIf(m.px >= o.open_px * 1.001, 1,
                   m.px <= o.open_px * 0.999, -1,
                   0) AS zone
    FROM mins AS m
    INNER JOIN opens AS o ON m.d = o.d
),
flips AS (
    SELECT d,
           zone,
           any(zone) OVER (PARTITION BY d ORDER BY ts ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_zone
    FROM zoned
    WHERE zone != 0
),
per_day AS (
    SELECT d, countIf(prev_zone != 0 AND zone != prev_zone) AS crossings
    FROM flips
    GROUP BY d
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
       round(avg(crossings), 1) AS avg_crossings_per_session,
       max(crossings) AS max_crossings_in_a_session
FROM per_day
GROUP BY toStartOfMonth(d)
ORDER BY toStartOfMonth(d)
Run this yourself

SPY ha attraversato quella banda in media 0.7 volte per sessione nel 2025-08 e 1.5 volte per sessione nel 2026-07; in una sessione del 2026-07 ha registrato 5 attraversamenti. Qualsiasi regola che apre una posizione da un lato di un livello e applica lo stop dall’altro ha altrettante occasioni di attivarsi nell’arco della stessa giornata.

Quattro meccanismi limitano il problema.

  • Un cooldown dopo ogni stop, misurato in minuti o barre, durante il quale nessun nuovo ordine su quel simbolo supera il livello di controllo del rischio.
  • Un limite giornaliero al numero di operazioni per simbolo, che trasforma un ciclo potenzialmente illimitato in uno con un limite definito.
  • Un flag di halt persistente. Una volta raggiunto il limite di perdita giornaliero, resta attivo finché non viene azzerato da una persona.
  • La persistenza di quel flag al di fuori della memoria del processo. Un supervisor che riavvia un bot andato in crash gli assegna uno stato iniziale pulito, proprio ciò che il flag serve a impedire.

L’ultimo meccanismo mette in difficoltà chi ha fatto tutto il resto correttamente. I bot di grid trading collocano per definizione scalette di ordini; per questo il limite al numero di operazioni è essenziale, non ornamentale.

Cosa succede quando un bot opera su un feed di prezzi obsoleto?

Una quotazione che ha smesso di aggiornarsi continua ad apparire come un numero. Il bot la legge, calcola il prezzo di un ordine sulla base di quel dato e invia l’ordine in un mercato che nel frattempo si è mosso. Il problema resta silenzioso: non viene generato alcun errore, non viene registrato alcun log e gli eseguiti sembrano anomali solo a posteriori.

La versione più facilmente misurabile è il gap overnight, quando un prezzo noto resta invariato per ore mentre il prezzo effettivamente negoziabile si muove.

QueryGap overnight dalla chiusura precedente all’apertura successiva: sei titoli, gennaio 2024–luglio 2026
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT ticker,
           toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMin(toFloat64(open), window_start) AS open_px,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY', 'KO', 'MSFT', 'AAPL', 'NVDA', 'TSLA')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2024-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, d
),
gaps AS (
    SELECT ticker,
           d,
           open_px,
           any(close_px) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
    FROM daily
)
SELECT ticker,
       round(quantileDeterministic(0.5)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS median_gap_pct,
       round(quantileDeterministic(0.95)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS p95_gap_pct,
       round(max(abs(open_px / prev_close - 1) * 100), 2) AS max_gap_pct
FROM gaps
WHERE prev_close > 0
GROUP BY ticker
ORDER BY p95_gap_pct DESC
Run this yourself

Il titolo con il gap al 95° percentile più ampio è stato TSLA, a 4.41%, contro 1% per KO. Nelle notti tipiche i movimenti erano molto più contenuti: gap mediani pari rispettivamente a 1.01% e 0.24%. È per gestire le code della distribuzione che si costruisce un livello di controllo del rischio; il gap singolo più ampio su TSLA nel periodo osservato è stato di 14.57%. Queste sono le distanze a cui può trovarsi un bot che agisce su un prezzo letto tempo prima. Perché le azioni registrano gap overnight illustra il meccanismo.

Le contromisure sono semplici e poco costose. Impostate un’età massima per ogni quotazione sulla quale il livello di controllo del rischio calcolerà i prezzi, in genere pochi secondi per una strategia intraday. Rilevate separatamente dal feed un heartbeat, cioè un segnale di attività, distinto dai dati: permette di distinguere un socket silenzioso da un mercato fermo. Trattate i dati mancanti come un halt e non come una quotazione invariata, perché un bot privo di prezzi non può valutare neppure le proprie uscite.

Quali limiti rigidi devono rientrare nel livello di rischio?

  • Nozionale massimo per simbolo, espresso come quota del patrimonio del conto. Un limite del 10% impedisce che un singolo simbolo comprometta l’intero conto.
  • Nozionale lordo massimo su tutte le posizioni aperte. Impostarlo al 100% del patrimonio significa non usare leva. È una decisione da prendere esplicitamente, invece di ereditarla dalle impostazioni predefinite del broker.
  • Numero massimo di ordini al minuto e al giorno. Dieci ordini al minuto sono sufficienti per la maggior parte delle strategie retail e limitano comunque un loop fuori controllo nell’arco di un minuto.
  • Dimensione massima dell’ordine, espressa come quota del volume medio giornaliero del simbolo. Un limite dell’1% impedisce al bot di muovere il prezzo del titolo che sta cercando di negoziare; volume medio giornaliero è il denominatore.

Tutti questi limiti appartengono al livello di rischio, non alla strategia, e tutti devono seguire lo stesso percorso di codice in backtest, paper trading e live. Un limite presente solo in live è un limite che nessuno ha testato.

Che cosa deve contenere il log di audit di un trading bot?

Un log append-only scrive un record per ogni decisione e non modifica né elimina mai i record. Ogni record contiene il timestamp, la quotazione utilizzata e la sua anzianità, ogni controllo dei limiti eseguito con il relativo esito, l’ordine inviato e la risposta del broker. Gli ordini rifiutati vengono registrati con la stessa rilevanza di quelli eseguiti.

L’obiettivo è poter ricostruire gli eventi. Sei settimane dopo una seduta negativa, la domanda non è mai quale fosse il P&L. È quale controllo sia stato superato e su quale input. Senza l’input registrato, si finisce per ricostruire lo stato del bot a partire da quello del mercato. È lo stesso errore del look-ahead bias nel backtesting: usare informazioni che il sistema non aveva al momento della decisione.

Il progetto riskguard è un’implementazione open source di questa separazione. I controlli dei limiti risiedono in un componente richiamato dalla strategia, invece di essere distribuiti nella logica della strategia stessa. È uno dei diversi approcci possibili. Vale la pena studiarlo, senza adottarlo automaticamente. Qualunque componente si utilizzi, è meglio fissare una release contrassegnata invece di dipendere dal default branch. Un branch può cambiare tra due esecuzioni dello stesso backtest. Un livello di rischio modificato silenziosamente è peggiore dell’assenza di un livello di rischio.

Perché il primo deployment viene eseguito su un broker paper

L’adapter del broker è impostato di default su paper, mentre il live trading richiede volutamente un flag esplicito. In questo modo si previene un errore banale ma concreto: un file di configurazione copiato o una variabile d’ambiente mai sovrascritta che invia ordini reali con denaro reale.

Un’esecuzione paper produce inoltre l’artefatto più importante: un log delle decisioni generato dallo stesso livello di controllo del rischio, ma su prezzi live. Il log mostra quali limiti sono scattati e quali no. È una prova sul funzionamento del livello di controllo del rischio, una questione distinta dal fatto che la strategia generi profitti. Il paper trading prima del denaro reale spiega che cosa dimostra e che cosa non dimostra un report paper, mentre sistemi di trading AI multi-agent mostra perché l’autorità di arresto deve essere esterna a ogni agent quando più agent possono inserire ordini.

FAQ sul circuit breaker di un trading bot

Che cos’è un circuit breaker in un trading bot?

È una regola del livello di gestione del rischio che interrompe l’invio di nuovi ordini quando viene raggiunto un limite prestabilito, nella maggior parte dei casi una soglia di perdita giornaliera o un drawdown dal picco dell’equity del conto. Funziona indipendentemente dalla strategia, su ogni ordine, e resta attiva finché non viene ripristinata.

Con quale frequenza il mercato registra una seduta in calo del 2%?

SPY ha registrato 5 sedute con un calo pari o superiore al 2% nel 2019 e 25 nel 2020, su circa 250 giornate di trading all’anno. Questa differenza tra un anno tranquillo e uno caratterizzato da stress di mercato spiega perché il limite di perdita debba essere calibrato sulla base dei dati storici, non dell’intuito.

Come si impedisce a un bot di rientrare dopo uno stop?

Una finestra di cooldown dopo ogni stop e un limite giornaliero al numero di trade per simbolo trasformano un loop senza limiti in un processo delimitato. Anche il flag di halt deve essere persistente e salvato al di fuori della memoria del processo: altrimenti, quando un supervisor riavvia un bot in crash, gli assegna uno stato nuovo e non bloccato.

Come può un bot capire che un price feed è diventato obsoleto?

Controllando l’età di ogni quotazione prima di usarla per prezzare un ordine e monitorando separatamente l’heartbeat del feed e i dati. I gap overnight mostrano l’entità di ciò che un prezzo obsoleto può nascondere: il movimento overnight al 95° percentile ha raggiunto 4.41% il TSLA tra gennaio 2024 e luglio 2026.

Un piccolo trading bot ha davvero bisogno di un audit log?

Un log degli eseguiti registra ciò che è successo. Un log delle decisioni registra ciò che il bot riteneva di poter fare. È l’unico modo per distinguere una strategia errata da un controllo del rischio che non è mai stato eseguito. Un log append-only, che includa anche i rifiuti, è la versione minima realmente utile.


Tutti i dati numerici sopra riportati provengono da una stored query sulle barre a un minuto, e ogni pannello consente di aprire il codice SQL sottostante. Esegui le stesse query sulla tua lista di simboli nel terminale Strasmore.

#risk-management#trading-bots#drawdown#position-sizing#automation