Circuit Breakers for Trading Bots: How Dem Dey Work
Circuit breakers fit stop trading bot before bad session loss compound. Learn how daily loss limit dey trip and how volatility scaling dey change position size.
Circuit breakers for trading bots na rules wey go stop automated strategy from sending orders once e reach stated limit. Dem dey inside risk layer between strategy and broker, and dem run for every order whether strategy agree or no. Strategy dey decide wetin to trade. Risk layer dey decide whether trading go happen at all.
Na that split be the main design. Strategy wey dey police itself no get independent check for the exact moment wey its own assumptions break. Na that moment check dey matter pass.
Wetin circuit breaker dey do for trading bot
Risk layer get four parts, and each one dey prevent one particular, ordinary failure.
- Hard caps on position size, notional exposure per symbol, and order rate. Dem dey limit the damage from bug wey for otherwise no get limit.
- Drawdown circuit breaker wey dey stop new orders once account don lose stated amount for the session, or don fall by stated amount from its equity peak.
- Scaled order sizing, wey dem set from recent volatility or from fraction of the Kelly bet instead of fixed share count. E dey keep risk per trade roughly steady while market range dey move underneath am.
- Append-only audit log of every decision, including orders wey risk layer reject. Na the only record wey fit show difference between “strategy make mistake” and “check never run”.
Everything below na one of these four parts, wey we don explain with concrete details.
Wetin be reasonable daily loss limit for trading bot?
Daily loss limit dey stop new orders once the session loss pass one threshold. To choose the number, you need calibration, no be personal taste: if you set am inside normal market noise, the bot go dey halted most weeks; if you set am far outside, e no go ever trigger. The starting point na how often the market itself dey produce down day of a particular size.
The exact SQL behind every number
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 yearSessions wey fall 1% or worse number 15 for 2019 and 45 for 2020, out of about 250 trading days for one year. The count for 3%-or-worse days different: 0 for 2019 compared with 16 for 2020. The last row of 9 cover sessions only up to July 31, 2026.
The pattern no steady; e dey come in lumps, and na this lumpiness matter for the design. Hard days dey arrive in clusters. If bot halt on the first day of one cluster and resume on the second day, e never really halt.
The two thresholds get different jobs. Daily loss limit, commonly 2% of account equity, dey end the session. Trailing drawdown limit, measured from the equity high-water mark and commonly around 10%, dey end the strategy until human review. The first one na routine. The second one suppose happen rarely. Bot wey carry only the first one fit grind account down 2% at a time without triggering anything.
How volatility scaling dey change position size?
Volatility targeting dey size position inversely to recent realized volatility: when daily range double, position roughly dey halve, so dollar risk per trade stay nearly constant. Realized volatility for here na annualized standard deviation of daily returns, and e fit move more than most people expect.
The exact SQL behind every number
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)Realized volatility, measured 11.1% annualized, na 2024-01 for 12% and 2026-07 across 31 months. The second column dey convert each reading to the position wey 12% volatility target go carry, capped at the full line: 100% for 2024-01 against 99.7% for 2026-07. Na the same strategy and same conviction, but share count dey differ plenty.
The Kelly criterion dey approach the same problem from the other side. E sizes from estimated edge and variance instead of volatility alone. Most systematic operators dey use fraction of am, like half Kelly or quarter Kelly, because both inputs na estimates from limited sample. Kelly criterion position sizing dey explain the calculation.
Why trading bot dey enter again after stop?
Stop fire. Position close. Ninety seconds later, entry condition don become true again. Bot enter again, take the same loss, and repeat am. No single component spoil. Strategy do wetin dem write am to do. Stop do wetin dem write am to do. But account still dey lose money one round trip at a time.
The frequency come directly from the price path. This panel count, for each session, how many times SPY move from more than 0.1% above opening price to more than 0.1% below am, or move back again.
The exact SQL behind every number
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)SPY cross that band average of 0.7 times per session for 2025-08 and 1.5 times for 2026-07. One session for 2026-07 record 5 crossings. Any rule wey open position for one side of a level and put stop for the other side get that many chances to fire inside one day.
Four mechanics fit control am.
- Cooldown after every stop, measured in minutes or bars. During that time, no new order for that symbol go pass the risk layer.
- Daily trade-count cap for each symbol. E use turn an unlimited loop to one wey get clear limit.
- Halt flag wey latch. Once daily loss limit trip, e remain tripped until person clear am.
- Persistence for that flag outside process memory. If supervisor restart bot after e crash, e go give am clean slate. And na exactly that clean slate the flag dey prevent.
The last one dey catch people wey do every other thing correctly. Grid trading bots dey place ladders of orders by design, so trade-count cap become necessary protection, no be decoration.
Wetin dey happen when bot trade with stale price feed?
Quote wey stop dey update still look like number. Bot go read am, price order based on am, then send the order enter market wey don move. The failure dey happen quietly: nothing throw error, nothing log error, and the fills only look strange afterwards.
The version wey easy measure na overnight gap. Na when known price remain unchanged for hours while tradable price dey move.
The exact SQL behind every number
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 DESCThe widest name by 95th-percentile gap na TSLA at 4.41%, compared with 1% for KO. Most nights no loud reach: median gaps na 1.01% and 0.24% respectively. Na the tails risk layer dey prepare for, and the biggest single gap on TSLA during the period wey dem measure na 14.57%. Na those distances bot fit face when e act on price wey e read some time ago. Why stocks dey gap overnight explain the mechanics.
The defenses cheap. Put maximum age on every quote wey risk layer go use price against, commonly few seconds for intraday strategy. Take heartbeat from the feed separately from the data. This one go show difference between silent socket and quiet market. Treat missing data like halt, no be hold, because bot wey no get prices no fit evaluate im exits too.
Which hard caps suppose dey inside the risk layer?
- Maximum notional per symbol as a share of account equity. Ceiling of 10% dey stop one bad symbol from taking over the whole account.
- Maximum gross notional across all open positions. If you set am at 100% of equity, e mean say no leverage. Na decision wey you suppose make clearly, instead of inheriting am from broker default.
- Maximum order rate, per minute and per day. Ten orders per minute dey enough for most retail strategies, and e still fit stop runaway loop inside one minute.
- Maximum order size as a share of the symbol's average daily volume. Ceiling of 1% dey stop bot from moving the price wey e dey try trade. Average daily volume na the denominator.
All these limits belong inside the risk layer, no be inside the strategy. Every one of dem suppose run through the same code path for backtest, paper, and live. If limit dey only for live, nobody don test that limit.
Wetin a trading bot audit log need?
An append-only log dey write one record for every decision, and e no dey edit or delete anything. Every record get the timestamp, the quote wey dem use and how old e be, every limit check wey run together with the verdict, the order wey dem send, and the broker reply. Rejected orders dey get the same weight as filled ones.
Reconstruction na the main point. Six weeks after bad session, the question no be wetin the P&L be. Na which check pass, and which input dem use. Without the recorded input, you go end up deriving the bot state again from the market state. Na the same mistake as look-ahead bias for backtesting: using information wey the system no get when e make the decision.
The riskguard project na one open-source implementation of this separation. For there, limit checks dey inside a component wey the strategy calls, instead of logic wey scatter inside the strategy itself. Na one design among many, so read am before you decide to use am. Anything wey you depend on, pin a tagged release instead of default branch. Branch fit change between two runs of the same backtest, and risk layer wey change quietly worse pass no risk layer at all.
Why first deployment dey run for paper broker
Broker adapter dey default to paper, and live trading need explicit flag on purpose. The failure wey this one dey prevent na ordinary one: person copy config file, or environment variable no get override, then e send real orders with real money.
Paper run still dey produce the artifact wey matter: decision log from the same risk layer, using live prices. E dey show which limits fire and which ones no fire. This na evidence about the risk layer. E different from the question of whether the strategy dey make money. Paper trading before real money explain wetin paper record fit prove and wetin e no fit prove. multi-agent AI trading systems show why halt authority need dey outside every agent once several agents fit place orders.
FAQ about trading bot circuit breaker
Wetin be circuit breaker for trading bot?
Na rule for the risk layer wey go stop the bot from sending new orders once e reach a set limit. Most times, na daily loss threshold or drawdown from the account equity peak. E dey run separately from the strategy for every order, and e go remain tripped until something clear am.
How often market dey give two percent down day?
SPY record 5 sessions wey e fall two percent or more for 2019, and 25 sessions for 2020, out of roughly 250 trading days every year. This gap between calm year and stressed year na why people dey set loss limit with historical data, no be intuition.
How you fit stop bot from entering again after stop?
Cooldown window after every stop, together with per-symbol daily trade cap, go change unlimited loop to bounded one. Halt flag sef need latch and persist outside process memory. Otherwise, if supervisor restart bot after crash, e go give am fresh state wey no dey halted.
How bot fit know say price feed don stale?
E go check how old every quote be before e use am price order. E also need monitor heartbeat from the feed separately from the data. Overnight gaps show how much stale price fit hide: the 95th-percentile overnight move reach 4.41% on TSLA between January 2024 and July 2026.
Small trading bot really need audit log?
Log of fills dey record wetin happen. Log of decisions dey record wetin bot believe say e fit do. Na only that one fit show whether na wrong strategy or risk check wey never run cause the problem. Append-only log, including rejections, na the minimum useful version.
Every figure wey dey above come from stored query over minute bars, and every panel fit open to the SQL behind am. Point the same queries to your own symbol list for the Strasmore terminal.