Short answer: Trading bot risk management is a layer of controls that sits outside the strategy and checks every order before it reaches the exchange. The checks cover maximum order size and notional, price collars, position limits, daily loss and drawdown limits, duplicate-order protection and a kill switch that cancels working orders and blocks new ones. Reconciliation and alerts that reach a person complete it. These controls limit damage; they do not make a strategy profitable.
Calculate it with your own figures: Crypto position size calculator, Risk reward calculator and Max drawdown calculator.
A strategy decides what to trade; the risk layer decides whether an order may leave. The most expensive bot incidents are rarely a bad idea but software doing something nobody intended: repeating an order, sizing in the wrong units, or trading on a frozen price feed.
Regulated firms have detailed rules for this: the EU's RTS 6 for investment firms engaged in algorithmic trading, SEC Rule 15c3-5 for US broker-dealers with market access, and FIA's July 2024 best practices for automated trading. A private crypto bot is not subject to these, but they form a useful checklist. This guide maps them to a bot and ends with a tested Python risk gate; exchange details are current as of October 2026.
Pre-trade risk checks every bot needs
Pre-trade checks reject an order before it is sent if it is too large, too far from the market, would breach a position limit or duplicates an earlier order.
| Check | What it blocks | Industry reference |
|---|---|---|
| Maximum order quantity | Fat-finger sizes and unit errors | RTS 6 Art. 15 maximum order volumes; FIA maximum order size |
| Maximum order value | Oversized notional from a plausible quantity | RTS 6 Art. 15 maximum order values |
| Price collar | Limit prices far from a reference price | RTS 6 price collars; FIA price tolerance |
| Maximum position | Accumulation beyond plan | FIA maximum intraday position |
| Message throttle | Runaway loops and rate-limit bans | RTS 6 maximum messages limits |
| Repeated execution limit | A strategy firing again and again | RTS 6 Art. 15(3); FIA repeated automated execution limits |
| Duplicate check | The same order sent twice | SEC Rule 15c3-5 on duplicative orders |
| Market data check | Orders based on stale or aberrant prices | FIA market data reasonability |
RTS 6 also requires every order sent to count toward pre-trade limits immediately; in practice, open orders count as if they will fill. FIA recommends price tolerance checks on amendments as well as new orders.
Exchange filters are only a backstop. As of October 2026, Binance's exchangeInfo for BTCUSDT lists a PERCENT_PRICE_BY_SIDE filter with a bidMultiplierUp of 1.2, so a buy limit up to 20% above Binance's reference price passes the exchange's check. A bot's own collar should be far tighter.
Daily loss and max drawdown limits for a bot
A daily loss limit stops new trading for the session when realized plus unrealized losses reach a threshold, and a maximum drawdown limit stops the bot until a person reviews it when equity falls a set percentage below its peak.
Hypothetical sizing and limits: A bot trades a 50,000 USDT account and risks 0.5% (250 USDT) per trade. With an entry at 100 and a stop at 97, the risk per unit is 3, so it buys 83 units (8,300 USDT), within a 10,000 USDT maximum order value. A daily loss limit of 1.5% (750 USDT) is reached after about three full stop-outs (747 USDT before fees). If equity later peaks at 55,000 USDT, a 10% drawdown limit halts the bot at 49,500 USDT and requires a manual restart.
Stops are not guaranteed prices: if the market gaps from 100 to 95, the loss on 83 units is 415 USDT rather than 249, or 0.83% of equity instead of 0.5%.
Equity must include unrealized PnL at a sensible price, such as the mark price for futures; the session reset time must be fixed, for example 00:00 UTC; and a drawdown halt should never reset itself. The right percentages depend on the strategy's tested drawdowns and the loss you can bear, so test the limits in the backtest too (see backtesting pitfalls).
How to design a kill switch for a trading bot
A kill switch immediately blocks new orders and cancels working orders, and it must work even when the strategy process is the thing that has failed.
- Scope. RTS 6 Article 12 requires firms to be able to cancel immediately, as an emergency measure, any or all unexecuted orders. On Binance spot, DELETE /api/v3/openOrders cancels all active orders on a symbol.
- Independence. FIA encourages kill switch functionality that can be separate from the trading application, operable by the trader and the person responsible for risk, as a final backstop.
- Positions. FIA notes a kill switch may allow risk-reducing orders while preventing risk-increasing ones. Decide in advance whether the switch also flattens positions.
- Manual reset. RTS 6 keeps a system disabled by its repeated-execution throttle off until designated staff re-enable it; a kill switch should work the same way.
- Dead man's switch. Some exchanges cancel orders if the client stops checking in. Kraken's CancelAllOrdersAfter recommends a call every 15 to 30 seconds with a 60-second timeout; Binance's USD-M futures countdownCancelAll gives the example of a call every 30 seconds with a 120-second countdown.
Typical triggers: a loss limit breach, a reconciliation mismatch, stale data, a reject burst or a manual command.
Duplicate-order protection: client order IDs and idempotency
Duplicate orders are prevented by giving each intended order a unique client order ID before the first send and never resending until the status of the original is known.
The dangerous case is a timeout. Binance's documentation says that after an HTTP 5XX response the execution status is unknown and the request could have succeeded. Its newClientOrderId must be unique among open orders: a repeat is rejected with the message Duplicate order sent, but the ID becomes reusable once the original fills. Coinbase's Advanced Trade API goes further: if a client_order_id is not unique, no new order is created and the existing order is returned.
Derive the ID from the strategy, the rule that fired and a sequence number, so a restarted process regenerates the same ID for the same intent. Add a repeated-execution limit too, because idempotency stops technical duplicates but not a strategy that keeps re-entering. A worked example of the timeout case is in trading bot development.
Reconciliation, partial fills and exchange outages
Reconciliation compares the bot's view of orders, fills, balances and positions with the exchange's records, and any unexplained difference should stop trading.
RTS 6 Article 17 requires firms to reconcile their own trading logs with the outstanding orders and risk exposures reported by venues, in real time where the venue provides real-time information. FIA describes drop copy feeds as an independent, exchange-provided source for this comparison. For a crypto bot this means comparing local state with the exchange's open orders, trades and balances at startup, after reconnects and on a schedule.
Partial fills are routine. In Binance's spot API a partly filled order has the status PARTIALLY_FILLED, and IOC or market orders that fill partly end as EXPIRED. Exposure must come from the filled quantity, with the open remainder still counted against limits.
During an outage the bot cannot know its fills, so it should stop sending orders. FIA describes cancel-on-disconnect, a best-effort exchange service that cancels a session's resting orders when the connection drops, and a protective stop resting on the exchange keeps working while the bot is offline. Reconcile before resuming. Tracking orders, fills and positions in one place is the job of a crypto order management system. A cross-account view of balances and exposure is the subject of the crypto portfolio risk dashboard project.
Alerting: making sure a person notices
Alerts reduce risk only if they reach someone with the authority and access to act.
RTS 6 requires investment firms to generate real-time alerts within five seconds of the relevant event and to keep monitoring staff reachable. Knight Capital shows why design matters: according to the SEC, an internal system sent 97 automated emails flagging an error before the open on August 1, 2012; they were not designed as system alerts and were not acted upon that day.
Alert on kill switch activations, limit breaches, reject bursts, reconnect loops, reconciliation mismatches and missing heartbeats, through a channel separate from routine logs, with escalation. Protect the bot's keys as carefully as its limits; see API key security for trading bots.
A pre-trade risk gate in Python
A risk gate is a small, separately tested function that every order must pass; this sketch uses synthetic orders.
class RiskGate:
def __init__(self, equity, max_qty, max_notional, collar, max_pos,
daily_loss, max_dd, stale_s=5.0):
self.day_start = self.peak = equity
self.max_qty, self.max_notional, self.collar = max_qty, max_notional, collar
self.max_pos, self.daily_loss, self.max_dd = max_pos, daily_loss, max_dd
self.stale_s, self.long, self.short, self.seen = stale_s, 0.0, 0.0, set()
self.killed = None # reason; only a person resets it
def check(self, oid, side, qty, price, ref, data_age, equity):
self.peak = max(self.peak, equity)
if 1 - equity / self.day_start >= self.daily_loss:
self.killed = self.killed or "daily loss limit"
if 1 - equity / self.peak >= self.max_dd:
self.killed = self.killed or "max drawdown"
long_ = self.long + (qty if side == "BUY" else 0)
short = self.short + (qty if side == "SELL" else 0)
for failed, why in [
(self.killed, f"kill switch: {self.killed}"),
(oid in self.seen, "duplicate client order id"),
(data_age > self.stale_s, "stale market data"),
(qty > self.max_qty, "max order size"),
(qty * price > self.max_notional, "max notional"),
(abs(price / ref - 1) > self.collar, "price collar"),
(max(long_, short) > self.max_pos, "max position")]:
if failed:
return why
self.seen.add(oid)
self.long, self.short = long_, short # worst case per side, until reconciled
return "ok"
def reconcile(self, position, open_buy, open_sell): # from exchange records
self.long, self.short = position + open_buy, open_sell - position
gate = RiskGate(equity=50_000, max_qty=100, max_notional=10_000,
collar=0.02, max_pos=150, daily_loss=0.015, max_dd=0.10)
orders = [("a1", "BUY", 80, 100.2, 0.4), ("a1", "BUY", 80, 100.2, 0.4),
("a2", "BUY", 800, 100.1, 0.4), ("a3", "BUY", 50, 110.0, 0.4),
("a4", "BUY", 80, 100.1, 9.0), ("a5", "BUY", 80, 100.1, 0.4)]
for oid, side, qty, price, age in orders:
print(oid, gate.check(oid, side, qty, price, 100.0, age, 50_000))
print("a6", gate.check("a6", "SELL", 80, 99.9, 100.0, 0.4, 49_200))Output:
a1 ok
a1 duplicate client order id
a2 max order size
a3 price collar
a4 stale market data
a5 max position
a6 kill switch: daily loss limitOrder a2 has an extra zero, a3 is 10% above the reference, a4 uses 9-second-old data and a5 would lift the position to 160, above the 150 limit. At 49,200 USDT equity the 1.5% daily limit trips and the gate rejects everything, even the risk-reducing sell; a production version might allow reduce-only orders. Buys and sells are counted separately and neither is released until fills are reconciled, so a resting sell never frees room for another buy; fills and cancels call reconcile with the position and open orders from the exchange's records. Keep the gate in its own module with its own tests, so that a change to strategy code cannot silently loosen a limit; in a custom trading bot development project it is worth specifying as a separate deliverable. If you inherited a bot and cannot tell where its limits live, start with a written code review. Automated trading can lose money even with every control in place.
Frequently asked questions
What is a kill switch in a trading bot?
A control that immediately cancels working orders and blocks new ones. It should work outside the strategy process and stay off until a person re-enables it.
Should a kill switch close open positions?
Decide in advance. Closing at market in a disorderly market can lock in a larger loss, so a switch can be designed to allow only risk-reducing orders, as FIA's guidance describes.
What is a good max drawdown limit for a bot?
There is no universal number. Set it from the strategy's tested drawdowns and the loss you can bear, with a manual restart after a halt.
Do retail bots have to follow MiFID II RTS 6?
RTS 6 applies to investment firms engaged in algorithmic trading in the EU, not to private individuals. Its controls are still a useful checklist for any automated system.
Sources
- Commission Delegated Regulation (EU) 2017/589 (RTS 6), as adopted: Articles 12, 15, 16 and 17 (opens in a new tab)
- FIA: Best Practices for Automated Trading Risk Controls and System Safeguards (July 2024) (opens in a new tab)
- 17 CFR 240.15c3-5: Risk management controls for brokers or dealers with market access (Cornell LII) (opens in a new tab)
- SEC press release 2013-222: SEC Charges Knight Capital With Violations of Market Access Rule (opens in a new tab)
- Binance Spot API: Filters (PERCENT_PRICE_BY_SIDE) (opens in a new tab)
- Binance Spot API: Exchange information for BTCUSDT (live endpoint) (opens in a new tab)
- Binance Spot API: REST API (cancel all open orders, new order parameters) (opens in a new tab)
- Binance Spot API: General API information (5XX execution status unknown) (opens in a new tab)
- Binance Spot API: Enum definitions (order status) (opens in a new tab)
- Coinbase Developer Platform: Advanced Trade API, Create Order (client_order_id) (opens in a new tab)
- Kraken API: Cancel All Orders After X (dead man's switch) (opens in a new tab)
- Binance USD-M Futures API: Auto-Cancel All Open Orders (countdownCancelAll) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.