Mechanics · Lesson 4 of 8 · 13 min read
How a fill is priced
Your order walking the live book level by level: the plan, the volume-weighted average, the remainder, and why an unfilled order has no price at all.
- Assumes you have read
- How an order book works,Limit vs market orders
- After this you can
- Predict the average price and the leftover quantity of an order before you place it, and read a fill list back to check.
The short version
- Your order does not get “the price”. It gets a list of prices — one per level it consumed — and what you paid is their volume-weighted average.
- A market order walks outward from the best price until its quantity is done. A limit order does the same thing and stops at the first level on the wrong side of its price.
- An order can finish having filled part of itself. That is a normal outcome, not a failure: the book simply ran out of counterparties you were willing to trade with.
- Quantities round down and fees round up, every time. Both directions are chosen so a rounding step can never spend money the account does not have.
- An order that filled nothing has no average price. Our engine returns null rather than zero, because a zero would render as a real-looking price.
You press buy. Somewhere between that click and a number appearing in your balance, a decision was made about what you paid — and it was not made by looking up a price. It was made by walking the order book from the best price outward, taking what was there, and stopping when your quantity ran out or the prices stopped being acceptable.
This lesson opens that step. Every number on the page is produced by the same functions the engine uses to charge you — the page imports them rather than quoting them — so if the arithmetic here disagrees with the terminal, the page is what changes.
An order is a plan before it is money
Filling happens in two halves that never touch each other. First a plan is computed: a pure function is handed a side, a type, a quantity and the current book, and it returns the list of fills that book would produce. It reads nothing, writes nothing and knows no database exists. Then a separate step turns that plan into ledger entries.
The split is not tidiness. Planning is where every interesting mistake lives — an off-by-one on a level boundary, a rounding direction, a limit price compared the wrong way — and a pure function can be tested exhaustively in milliseconds. A function that also moved money could not be.
Swipe horizontally to compare all columns.
| The plan carries | Which means |
|---|---|
| A list of fills | One entry per level touched, each with its own price and quantity. Not a single average — the average is derived from these, never the other way round. |
| Filled quantity | How much of the order actually traded, in the base asset. |
| Notional | What those fills are worth in the quote asset, before any fee. |
| Remaining | The quantity the book could not fill at an acceptable price. What happens to it depends entirely on the order type. |
| Average price | The volume-weighted average of the fills — or null, if there were none. |
The walk, one level at a time
A buy consumes asks from the lowest price upward; a sell consumes bids from the highest price downward. In both cases the loop is the same three lines: take the smaller of what you still want and what the level holds, record a fill at that level’s price, subtract it from what remains. Repeat until nothing remains or the levels run out.
Take the book the first lesson uses, and send a market buy for 2.00 BTC into it. The engine produces exactly this:
- 0.40 BTC at 64,301.2025,720.48 USDT
- 0.60 BTC at 64,310.0038,586.00 USDT
- 1.00 BTC at 64,320.0064,320.00 USDT
Three prices, one order. That is the shape of every fill on every exchange, and the single most common surprise on a first real trade: there was never one price to get.
The price on screen is not what you pay. It is what the first slice of your order pays.
What you paid is an average
Add the fills up and divide by the quantity, weighting each price by how much traded at it, and you get the volume-weighted average price — the VWAP of your order. For the walk above that is 64,313.24, against a quoted best ask of 64,301.20.
A plain average of the three prices would be wrong, and wrong in a direction that flatters. The 64,320.0 level supplied half the order and 64,301.2 supplied a fifth; weighting them equally would understate what you paid. Weighting by size is not a refinement, it is the definition.
The wedge is the whole lesson in one shape. It is empty while the order is smaller than the first level, opens the instant the order outgrows it, and never closes — because nothing later in the walk can be cheaper than what came before it. The book is sorted; prices only get worse as you go.
Where a limit order stops
A limit order runs the same loop with one extra check. Before taking a level, it asks whether that level’s price is acceptable — at or below the limit for a buy, at or above it for a sell. If it is not, the walk stops there and then.
Stops, not skips. The book is sorted, so the first unacceptable level guarantees every level behind it is worse. Continuing would be wasted work at best; on a badly written engine it would be a buy filling at a price above its own limit, which is the one thing a limit order promises cannot happen.
Send the same 2.00 BTC as a limit buy priced at 64,310.0 instead, and the plan changes shape entirely:
- 0.40 BTC at 64,301.2025,720.48 USDT
- 0.60 BTC at 64,310.0038,586.00 USDT
- Next level is 64,320.0 — above the limitstopNot skipped. Every level beyond it is higher still, so there is nothing further worth checking.
You got a better average than the market order — 64,306.48 against 64,313.24 — and you got half the quantity. That is the trade the previous lesson describes, expressed as arithmetic instead of advice.
The part that did not fill
Every plan carries a remaining quantity, and the order type alone decides its fate. Nothing about the market is consulted a second time.
- A market order never rests. Its remainder expires immediately. An order reported as expired with a non-zero filled quantity is a partial fill whose leftover was cancelled — not a failure, and not an error to report to the user as one.
- A GTC limit order keeps it. The unfilled part joins our records as a resting order, its funds stay reserved, and a background sweep tries it again against the live book every few seconds.
- An order priced away from the market plans nothing at all. A buy limit at 64,000 against this book produces 0 fills and 2.00 BTC remaining. It is not rejected; it simply has nothing to do yet.
Rounding, and who eats the remainder
Venues quote quantities to a fixed number of decimal places, so almost every real order has to be snapped to a grid at some point. Which way it snaps is not a detail.
- Quantities round down. The order’s size is rounded down before the walk begins, and the amount taken from each level is rounded down again. Rounding a quantity up would ask to spend money the account does not have, or take more off a level than the level holds — which is inventing liquidity that was never there.
- Fees round up. A fee rounded down means the house pays the remainder on every single trade. Each individual shortfall looks exactly like a rounding error, which is precisely why it would never appear in any report.
Neither direction is a default. The rounding function in this codebase takes the direction as an argument and every caller states it, because a function that guessed would be a function that guessed wrong somewhere, once, quietly.
The fee comes out of what arrives
The fee is charged in the asset you receive — the base asset on a buy, the quote asset on a sell. That sounds like an accounting quirk and is actually what keeps reservations honest: a buy never has to lock more quote than the order costs, because the fee is taken out of what arrives rather than out of what was locked.
Reserving fee-inclusive amounts is where “insufficient funds” on a perfectly funded account comes from, and it is a bug this design cannot have.
- Base bought2.00000 BTCSpending 128,626.48 USDT across three levels.
- Taker fee at 10 bps of the base received0.00200 BTCRounded up, at the venue’s lot precision.
The full arithmetic of what a position costs — this fee twice, the spread, and the slippage above — is the next lesson.
What this engine cannot reproduce
Everything above is exactly what happens here, because the book being walked is the venue’s real one. Three things are ours rather than the market’s, and they all run in the same direction — in your favour:
- There is no queue in front of your resting order. This exchange keeps no book of its own, so a resting order fills when the venue’s book reaches its price, with nobody ahead of it. On a real venue you would wait behind everyone who arrived at that price first.
- Your order does not move the market. It consumes a snapshot of the venue’s book; the venue’s book does not notice. A real order of any size would remove that liquidity for everybody, and the next order would meet a different book.
- Resting orders are swept on an interval, not on every tick. A background worker re-plans open orders against the live book every few seconds. A real matching engine acts the instant a price crosses.
What is exact is the part that costs you money: the levels, the walk, the average, the fee and the rounding. Those come from the venue’s own book and this repo’s own arithmetic, and how this site works draws the same line across the whole product.
Four ways to misread a fill
Mistaken belief: “I got a bad fill — the price moved between my click and the execution.”
What actually happens: Usually it did not move at all. If your order was larger than the level in front of it, the average was always going to be worse than the quote, and the quote was only ever true for the quantity resting at it. Check the fill list: several rows at ascending prices is a walk, not a delay.
Mistaken belief: “My order partly filled, so something went wrong.”
What actually happens: Nothing went wrong. A limit order fills as much as the book offers at an acceptable price and keeps the rest. The remainder is still yours: it rests, its funds stay reserved, and it fills later or you cancel it.
Mistaken belief: “The average price of my unfilled order is zero.”
What actually happens: It has no average price. Zero would be a claim that something traded at zero, which is a different and much worse thing than nothing having traded. Our engine returns null and the UI shows nothing.
Mistaken belief: “A limit order at the top of the book is the same as a market order.”
What actually happens: Only while the book cooperates. A limit buy at the best ask takes exactly what is resting there and stops; a market order of the same size keeps going into the next levels. The two agree right up until your size exceeds one level, which is the only case where the difference matters.
Check yourself
Four questions against the book above: 0.40 at 64,301.2, 0.60 at 64,310.0, 1.20 at 64,320.0, 2.10 at 64,335.5.