What is tick size? Price increments and rounding in trading APIs
Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs.
Tick size is the smallest price increment a venue accepts for a symbol: a limit price or trigger price has to land exactly on that grid, and an order priced between two ticks is rejected before it reaches the order book.
The concept is simple. The trouble is that almost every price a program computes — a stop from a volatility measure, a limit at the mid, an entry a percentage below the last trade — falls between ticks, and the way you move it onto the grid changes what the order means. This page covers how venues express and enforce the rule, which direction to round for each kind of order, and how to do the arithmetic without floating-point surprises.
For the wider family of per-symbol rules — minimum notional, quantity step, open-order caps — see symbol filters and minimum order sizes. This page stays on price.
Why this matters now
A rejected order is the good outcome. The bad outcome is an order that passes the check because it was rounded, and was rounded the wrong way: a passive bid nudged up until it crosses the spread, a stop nudged away from price until the loss at the stop exceeds what you sized for. Nothing rejects those orders. The tick rule is satisfied and the intent is not.
It also matters because the rejection looks different on every venue and even between products on one venue, so error handling written for one will not recognise the other.
How it actually works
- The venue publishes a price rule per symbol. On Binance spot it is the
PRICE_FILTERinGET /api/v3/exchangeInfo, which bounds the price and defines its tick. On Hyperliquid, the tick-and-lot-size page states the rules as a limit on significant figures and a maximum number of decimals, which differ between perps and spot; read the current values there. - You compute a price. It is a real number with no reason to sit on the grid.
- You move it onto the grid, choosing a direction.
- The venue checks it. Off-tick prices are refused, and how that refusal looks depends on where you sent it — see the table below.
- Other price rules still apply. A price exactly on the tick can still be
rejected for being too far from the market: Binance’s
PERCENT_PRICEfilter, or Hyperliquid’s “Order price too far from oracle”. Those are separate checks, not tick errors.
| Where | How an off-tick price is rejected |
|---|---|
| Binance spot | -1013 with msg “Filter failure: PRICE_FILTER” |
| Binance USDⓈ-M futures | -4014 PRICE_NOT_INCREASED_BY_TICK_SIZE, “Price not increased by tick size.” — no “Filter failure” text |
| Hyperliquid | “Price must be divisible by tick size.” in the order’s status; historical status tickRejected |
Two details from that table catch people. Binance spot puts the specific
filter in msg behind a generic code, so handlers must read the message —
see Binance API error -1013. And Hyperliquid
returns per-order errors inside an HTTP 200 response with "status":"ok",
while some tick-size validation happens before the batch is processed and
produces a single error for the whole payload. A handler that only checks the
HTTP status will record a rejected order as sent.
Hyperliquid’s significant-figure rule has a further consequence: the smallest valid price step depends on how large the price is, so a single cached “tick” number is the wrong model there. Implement the rule as the page states it.
Which way to round
The honest answer is “in the direction that keeps the order’s meaning”. That is different for each kind of order.
| Order | Round | Why |
|---|---|---|
| Passive buy limit | Down | Up could make it more aggressive than intended, or cross the spread |
| Passive sell limit | Up | The mirror case |
| Post-only order | Away from the spread | Toward it risks a rejection such as Hyperliquid’s “Post only order would have immediately matched, bbo was {bbo}.” or, for a Binance LIMIT_MAKER, “Order would immediately match and take.” |
| Marketable limit meant to fill | Toward the aggressive side | Rounding back toward the book edge can leave it resting unfilled |
| Stop-loss trigger | Toward current price (tighter) | Wider means a larger loss than the position was sized for |
| Take-profit | Either, decided once | No risk budget depends on it, but inconsistency makes results harder to audit |
For stops, the order of operations matters as much as the direction. Snap the stop to the tick first, then compute position size from the snapped stop distance. Sizing from the unsnapped stop and rounding the stop afterwards leaves the size calibrated to a price you did not send. The position size calculator assumes the stop you enter is the stop you will use; give it the rounded one.
The arithmetic: floats, decimals and strings
Floating-point numbers cannot represent most decimal fractions exactly. The two classic failures:
- The remainder check lies. Testing
price % tick == 0on floats can report a valid price as off-grid, or the reverse. - The snapped value is not what you sent. Computing
round(price / tick) * tickin floats can produce a long tail of digits; the venue sees the tail and your logs, which print a rounded value, do not.
The fix is to do price arithmetic in a decimal type, built from strings, and to snap by division so it works whatever the tick is:
from decimal import Decimal, ROUND_FLOOR, ROUND_CEILING
def snap_price(price: str, tick: str, direction: str) -> str:
p, t = Decimal(price), Decimal(tick)
mode = ROUND_FLOOR if direction == "down" else ROUND_CEILING
snapped = (p / t).to_integral_value(rounding=mode) * t
return format(snapped.normalize(), "f")
Three details in that function are deliberate:
Decimal(price)takes a string. Building a decimal from a float copies the float’s error into it.- Division, not “round to N decimals”. Rounding to a number of decimal places only works when the tick happens to be a power of ten. Dividing by the tick works for any tick.
normalize()before formatting strips trailing zeros. That is cosmetic on some venues and not on others: Hyperliquid’s signing page lists trailing zeroes on numbers among its common errors, and its tick-and-lot-size page repeats that “trailing zeroes should be removed” when implementing signing. A price that is valid on the grid can still produce a bad signature if it is serialised as100.50instead of100.5.
Log the string you send, not the number you computed. When a tick rejection arrives, that string is the evidence.
Tick size vs the things it gets confused with
| Term | What it constrains |
|---|---|
| Tick size | The price grid |
| Lot size / step size | The quantity grid — see what is lot size |
| Percent-price or oracle band | How far the price may be from a reference, regardless of grid |
| Display precision | How many decimals a UI or ticker shows; not necessarily the rule the order check applies |
Where the boundary should sit
- Snap in code, after any model. A model asked for a stop or limit price returns a plausible number with no knowledge of the grid. Treat it as input to the snapping function, not as the order price.
- Choose the direction from the order’s purpose, using a table like the one above encoded once, not decided at each call site.
- Cache the rules, refresh on rejection. Per-symbol rules can change. A tick rejection on a price your code snapped is a signal to re-fetch, not to retry unchanged.
- Treat a changed price as a changed order. If snapping moved the price enough to matter — a stop, a post-only bid — the approval step should see the snapped price, not the one proposed.
FAQ
What is tick size in trading?
Tick size is the smallest price increment a venue accepts for a symbol. Limit and trigger prices must fall exactly on that increment, or the order is rejected before it reaches the book. It is set per symbol, published through the venue’s instrument or exchange-information data, and can differ between spot and derivatives products.
Why was my order rejected for tick size?
The price you sent was not an exact multiple of the symbol’s tick, often
because it was computed rather than typed, or because floating-point arithmetic
left extra digits. Binance spot reports it as -1013 with “Filter failure:
PRICE_FILTER”, Binance USDⓈ-M futures as -4014, and Hyperliquid as “Price must
be divisible by tick size.”
Should I round my price up or down to the tick?
It depends on the order’s purpose. Round passive buy limits down and passive sell limits up, keep post-only orders away from the spread, and round stop-loss triggers toward the current price so the loss at the stop does not exceed what you sized for. Then size the position from the rounded stop.
Why does float rounding cause tick-size errors?
Binary floating point cannot represent most decimal fractions exactly, so snapping a price in floats can produce a value with a tail of extra digits that is not on the grid. Do price arithmetic in a decimal type built from strings, snap by dividing by the tick, and log the exact string sent.