The bug that taught me to care about this was boring in a way that made it worse. A grid strategy I had running on one exchange worked fine, and the same code pointed at a second exchange kept skipping fills for no reason I could see. Same symbol, same logic, same sizing math. It took me longer than I want to admit to figure out that the two venues rounded quantities differently, and my order sizes were landing just under the second exchange's minimum on about a third of the rungs. The exchange was not wrong. I was sending it garbage, and it was politely refusing.
Every exchange enforces a small set of filters on every order before it ever reaches the book. They go by different names in different API docs, but they almost always come down to three things. Tick size is the smallest price increment you are allowed to quote. Lot size, sometimes called step size, is the smallest quantity increment. Minimum notional is the smallest total order value, price times quantity, that the venue will accept. If your order violates any one of these, it gets rejected, and depending on how you wrote your error handling, you may not even notice.
Why these three quietly wreck strategies
The trouble is not any single rejected order. It is what happens when the rounding compounds. Take tick size first. If a symbol trades with a tick of 0.01 and you compute a limit price of 42.317 from some indicator, you have to round it before you send it. Round down and you are a hair below where you meant to be, round up and you are above. On a single trade this is noise. On a grid with fifty rungs, or a DCA ladder that fires the same relative offset over and over, that consistent rounding bias stacks up into a real shift in where your orders actually sit versus where your backtest thought they sat.
Lot size is the one that creates dust. Say you want to spend a fixed amount of quote currency on each buy, and you compute quantity by dividing that amount by the current price. You will almost never land exactly on a valid step. So you round the quantity down to the nearest step, which means you spend slightly less than intended and accumulate a little unspent quote balance each time. Do that across a few hundred DCA entries and you have meaningful drift between your intended and actual exposure. Round the wrong direction on a sell and you can leave a sliver of the base asset behind that is too small to ever sell again, because it sits below the minimum notional. That sliver is dust, and it is genuinely annoying to clean up.
Minimum notional is the filter people forget until it bites. It tends to bite hardest on the smallest orders in a strategy, which are exactly the ones a grid or a scaled-out exit produces. The last rung, the final take-profit slice, the tiny rebalance trade. Those are the orders most likely to fall under the minimum, and they fail silently while everything else fills, so your position ends up in a shape you did not design.
The pre-flight routine
The fix is not clever. It is just discipline about pulling the specs and applying them before you ever build an order. I treat it as a pre-flight step that runs once at startup and refreshes periodically, because exchanges do occasionally change these values, especially for newer or thinly traded symbols.
Here is the routine I run before a strategy sends its first order on any venue.
- Pull the exchange's symbol metadata from its instrument or exchange-info endpoint. Every venue exposes this. It is the same endpoint that lists tradable pairs, and it carries the filter values per symbol.
- For each symbol you trade, extract tick size, step size, and minimum notional, plus any minimum and maximum quantity bounds. Store them keyed by symbol so your order builder can look them up without another network call.
- Write two rounding helpers and route every price and every quantity through them. One snaps a price to the nearest valid tick, the other snaps a quantity to a valid step. Decide the rounding direction on purpose, not by whatever your language's default happens to be.
- After rounding, check the resulting notional against the minimum. If it falls short, do not silently send it. Either bump the quantity up to the smallest size that clears the minimum, or skip that order and log it, but make it a decision you can see.
- Log every rejection with the reason the exchange gave. If you are getting filter rejections in production, you want them loud, not swallowed.
The rounding direction matters more than it looks. My rule of thumb is to round quantities down on entries and be careful on exits. Rounding an entry quantity down means you buy slightly less than planned, which is safe. Rounding a sell quantity up past what you actually hold gets you an insufficient-balance rejection, so on exits I round the sell quantity down to what I can actually deliver, and if that leaves an unsellable sliver I accept it as dust rather than failing the whole exit.
Making venues behave the same
The reason this is worth a pre-flight step and not just an afterthought is that the whole point of a systematic strategy is that it behaves the same everywhere you run it. The moment two venues round differently and you have not accounted for it, your live results diverge from each other and from your backtest, and you will waste a weekend blaming your signal when the signal was fine.
A few things that have saved me repeated pain. Do the rounding with integer or decimal math, not raw floating point. Prices and step sizes are exactly the kind of values where floating point gives you 0.30000000000000004, and that trailing garbage is enough to fail a tick check on a strict venue. Most languages have a decimal type for exactly this. Use it for anything that touches an order parameter.
Also, derive the number of decimal places you are allowed from the tick and step sizes rather than hard-coding them. A symbol with a step of 0.001 allows three decimals of quantity, and a symbol with a step of 1 allows none. If you hard-code six decimals everywhere, you will send over-precise values that some venues truncate quietly and others reject outright, and you will get inconsistent behavior that is miserable to debug because it looks random.
One more, because it is the failure mode I see most often in other people's code. When you scale into or out of a position in slices, compute each slice against the specs independently. Do not size the slices first and round at the end, because the rounding on each slice can push the sum away from your target, and the smallest slice is the one most likely to duck under the minimum notional and vanish. Round each slice, check each slice, and if a slice cannot clear the minimum, fold its size into an adjacent slice instead of dropping it.
None of this is hard once you have written it once. The reusable piece is a small module that takes a raw price and quantity for a symbol and hands you back valid, venue-safe values or a clear reason it cannot. Build that early, run it as a pre-flight check on every venue you add, and most of the mysterious partial-fill and dust problems that plague grid and DCA strategies just stop happening. The specs were always there in the API. You just have to read them before you trade, not after the fills come back wrong.