The bot that gets you banned is almost never the one you are testing. It is the one that has run fine for weeks, because on a normal day you are nowhere near any limit and everything looks healthy. Then the market moves, your order flow triples, your polling loop starts retrying, and the exchange starts handing you 429s right when you most need to be in the market. I have watched this happen to more careful engineers than you would guess, and the pattern is always the same. The limits were never the problem on calm days. The bot was just built to assume calm days would continue.
So it is worth understanding how these limits actually work before you find out the hard way. There are really three separate systems you are up against, and they fail in different ways.
Weight budgets are not request counts
The first thing that trips people up is assuming a rate limit means a number of requests per minute. Most serious exchanges do not count requests, they count weight. Every endpoint has a cost, and a fat query that pulls the full order book depth or a batch of account data costs far more than a single ticker read. You get a budget per interval, often expressed as something like a per-minute weight allowance, and each call subtracts its weight. Hit zero and you are throttled.
This matters because two bots making the same number of calls can have wildly different risk. The one hammering deep order book snapshots every second can blow a weight budget with a handful of calls, while a bot pulling lightweight data can make many more and stay comfortable. When you plan your call pattern, tally the weight, not the calls. Most exchanges publish the weight of every endpoint, and reading that table before you write the loop saves you a lot of grief.
Exchanges also usually return your current usage in the response headers. Something like a used-weight header ticks up with every call, and it resets on the interval boundary. Read those headers on every response and keep a running view of how close you are. If you are sitting at eighty percent of budget, that is your signal to back off, not the 429 that comes after. By the time you get the 429 you have already lost the window, and on some venues repeated 429s escalate into a temporary IP ban that lasts minutes and grows if you keep pushing.
Order rate limits are a separate bucket
Here is the part that surprises people. Your weight budget for reading data and your cap on placing orders are usually two different accounting systems. You can have plenty of weight left and still get rejected for placing orders too fast, because order submission is metered separately, often as orders per second and orders per some longer window like ten seconds or a day.
This bites market-making and grid bots hardest, because those strategies fire and cancel constantly. A tight grid that repositions on every tick can burn through an orders-per-second cap in a burst without you noticing, since the average across a minute looks fine. The exchange does not care about your average. It cares about the burst. So the rule of thumb is to budget for your worst second, not your typical minute.
A few habits keep you out of trouble here:
- Batch orders where the API supports it. One batch call that places or cancels several orders usually costs less against your order limit than the same orders sent one at a time, and it is dramatically faster during a fast market.
- Prefer cancel-replace or amend endpoints over cancel-then-place. Modifying an existing order is often cheaper and avoids the window where you have no order resting at all.
- Do not cancel and repost an order that has not actually moved. A shocking amount of order-limit pressure comes from bots rewriting orders to prices they already have.
- Track order-limit headers separately from weight headers. They are different counters and one being healthy tells you nothing about the other.
Self-trade prevention will cancel orders you did not expect
Self-trade prevention, or STP, is the mechanism that stops your own buy from matching your own sell. Exchanges enforce it because self-trading looks like wash trading, and they do not want to be the venue that let you fake volume. The catch is that STP is not one behavior, it is a mode you often get to choose, and the default may not be what you want.
The common modes come down to what the exchange does when your resting order would match your own incoming order. It might cancel the older resting order, cancel the newer incoming one, or cancel both. If you run more than one strategy on the same account, or a strategy that quotes both sides of a book, these modes will silently eat your orders. A market maker quoting a tight spread can watch its own bid get canceled the instant its ask crosses, and if the bot is not reading the cancel reason it will just see an order vanish and, worse, might resubmit it, burning order-limit budget in a loop.
So before you deploy anything that touches both sides of a market, decide the STP mode deliberately, log the cancel reasons the exchange sends back, and make sure your bot distinguishes a self-trade cancel from a normal fill. Running each strategy on its own subaccount is often the cleanest fix, since STP is usually scoped per account or per group.
Build a local order book so you stop polling
The single biggest way to stay under budget is to stop asking the exchange for things it will happily push to you. Most venues offer a websocket stream of order book diffs, and the intended pattern is to pull one full snapshot over REST, then apply the incremental updates from the websocket to keep your own copy of the book in sync. After that first snapshot you are maintaining the book locally for free, weight-wise, instead of polling depth over and over.
The wiring has a sharp edge worth naming. Each diff carries sequence numbers, and you have to line the stream up with the snapshot correctly, discarding updates older than your snapshot and confirming the sequence is continuous. If you ever detect a gap in the sequence, the only safe move is to throw away your local book, pull a fresh snapshot, and resync. Bots that skip the gap check drift out of sync during fast markets, which is exactly when a wrong book gets you filled at a price you did not mean. So treat the sequence check as non-negotiable, not an optimization.
Once the book lives in memory, most of your read traffic disappears. Prices, spreads, and depth all come from local state, and your remaining REST budget is free for the calls that actually need it, like placing orders and checking fills.
Before any of this touches real size, there is a short checklist worth running. When I look at a trading bot, I check a handful of things that separate the ones that survive volatility from the ones that get throttled into a loss:
- The bot reads rate-limit headers on every response and backs off proactively at a threshold well below the limit, not after the first rejection.
- It treats a 429 or a ban as a first-class state with exponential backoff and jitter, not a retry-immediately loop that makes the ban worse.
- Weight budget and order budget are tracked as separate counters, because they are.
- It maintains a local order book from websocket diffs with a real sequence-gap check and resync path.
- STP mode is chosen on purpose, cancel reasons are logged, and multi-strategy accounts are split into subaccounts.
- The whole thing has been load tested against a simulated volatility spike, not just a quiet afternoon.
The reason this all matters is that limits are invisible until they are the only thing that matters. On a slow day none of it shows up in your metrics. When we built the execution routing at Blockcircle across a lot of different venues, the recurring lesson was that every exchange draws these lines slightly differently, so the safe design is to assume the tightest limits and leave headroom everywhere. A bot that runs at sixty percent of budget on a calm day still has somewhere to go when the market doubles your order flow. A bot that runs at ninety-five percent has already spent the room it needed most, and it will find that out at the worst possible time.