How to Estimate Queue Position From L2 Data
Queue position decides whether a passive order earns the spread. Here is how to estimate it from aggregated book data, and when to buy MBO instead.
Queue position is your place in line among the orders resting at one price, and on a price-time priority venue it decides whether a passive order earns the spread or never trades at all. Aggregated market data will not hand that number to you: a Level 2 book shows how much size sits at each price, never whose. What follows is how to estimate queue position from the data most traders have, and how to tell when estimating has stopped being good enough.
Why queue position is the entire edge
A passive order rests in the book and waits for someone else to cross the spread into it. Under price-time priority, the matching engine ranks resting orders by price first and by arrival time second. If 10,000 shares are already bid at $10.00 and you add 100 behind them, those 10,000 shares have to trade or cancel before your turn comes. Cancel and re-enter at the same price and you return to the back of the line.
That ranking sets the economics. Near the front you trade often and collect the spread plus any add-liquidity rebate, the subject of maker-taker fees and rebates. Near the back you trade only after everyone ahead of you has traded, which happens when one-sided flow arrives in size. Fills at the back cluster in the moments before the price walks through your level. Same price, same order, opposite business.
What Level 2 data can and cannot show
Level 1 is the best bid and offer with the size at each. Level 2, also called market by price, adds depth: a list of price levels with the total size resting at each one. Both are aggregates, and Level 1 vs Level 2 market data compares them in full. When the bid at $10.00 drops from 10,000 shares to 8,500, the feed reports 1,500 shares gone. It does not say whether they traded, whether one order left or forty, or where in the line they had been sitting.
Executions are the visible half: the tape prints every trade with a size, so those you subtract exactly. The rest is cancellation, and cancellation is where the guessing starts. The panel below counts messages at the consolidated top of book against prints on the tape for one liquid stock through one ordinary session, Wednesday June 10, 2026.
The exact SQL behind every number
SELECT
q.et_time AS et_time,
q.quote_updates AS quote_updates,
t.trades AS trades,
round(q.quote_updates / t.trades, 1) AS updates_per_trade
FROM
(
SELECT
formatDateTime(toStartOfHour(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS et_time,
count() AS quote_updates
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 12:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY et_time
) AS q
INNER JOIN
(
SELECT
formatDateTime(toStartOfHour(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS trade_hour,
count() AS trades
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 12:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY trade_hour
) AS t ON q.et_time = t.trade_hour
ORDER BY et_timeThe 08:00 hour sits ahead of the opening bell: the top of book changed 0.8 times per print there, fewer messages than prints. Inside the regular session the ratio flips. In the 15:00 hour the top of book changed 1.6 times per print, across 233433 messages in that hour alone. Most of what happens to a price level in an open market is orders arriving and leaving without ever trading, and each of those events moves your place in line by an amount no aggregated feed states.
The uniform cancellation assumption, and where it flatters you
The standard first cut assumes cancellations are spread evenly through the queue. Call x your fractional depth: the shares ahead of you divided by the total resting at your price. The uniform model sets the chance that any cancelled share sat ahead of you equal to x, written p(x) = x. Sit halfway back in a 10,000 share queue, watch 1,000 shares cancel with no print, and the model advances you 500 places.
Real queues skew. An order that has already rested a while tends to belong to someone content to wait, while an order added a moment ago is far likelier to be a fleeting quote that disappears within seconds. Cancellations concentrate toward the back of the line, near you and behind you. Of those 1,000 cancelled shares, perhaps 200 sat ahead of you, and the model credited you with 500. Repeat that all day and the simulated queue advances faster than the real one. The error runs one way: more fills, at better moments, than live orders collect.
How the queue drains
Cancellations move you invisibly. Trades move you visibly, in the size of the prints.
The exact SQL behind every number
SELECT
multiIf(size < 100, 'under 100 shares',
size = 100, 'exactly 100 shares',
size <= 499, '101 to 499 shares',
size <= 999, '500 to 999 shares',
'1000 shares or more') AS trade_size_group,
round(100 * count() / sum(count()) OVER (), 1) AS share_of_prints_pct,
round(100 * sum(size) / sum(sum(size)) OVER (), 1) AS share_of_shares_pct
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY trade_size_group
ORDER BY min(size)Prints below a round lot were 90% of the count on this session and 42.5% of the shares. Blocks of a thousand shares or more were 0.2% of prints and 19.9% of the volume. Queues drain in small bites, so the gap between position 2,000 and position 3,500 is hundreds of prints of waiting. A simulator that grants a fill after two large trades has skipped most of the tape.
The order you cannot see
Depth feeds truncate. If you receive ten price levels and your order rests at the eleventh, your order is outside the data: not the size ahead of it, and not the orders joining behind it. At that point you are inventing the number rather than estimating it. The situation is ordinary rather than exotic, since an order that does not chase the market falls away from the inside quickly.
The exact SQL behind every number
SELECT
formatDateTime(toStartOfFifteenMinutes(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS et_time,
round(avg(toFloat64(bid_price)), 2) AS best_bid,
uniqExact(bid_price) AS bid_levels_touched
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
AND bid_price > 0
GROUP BY et_time
ORDER BY et_timeIn the 10:00 bucket the best bid averaged $290.29 and visited 187 distinct prices inside fifteen minutes. Each of those prices is a separate level on a stock quoted in pennies, so a ten-level view covers ten cents of book. An order left at one price through a session like this can spend long stretches below the deepest level its owner can see.
Where queue position is worth the most
Queue position is worth the most where price improvement is impossible. A stock pinned at a one-cent spread leaves no room to jump the line with a better price: everyone stacks at the same tick, and arrival time settles the rest. Where the spread is several cents wide, a trader steps in front of the whole queue for a penny, and the price decision outranks the line.
The exact SQL behind every number
SELECT
ticker AS symbol,
round(avg(toFloat64(ask_price - bid_price)) * 100, 2) AS avg_spread_cents,
round(100 * countIf(round(toFloat64(ask_price - bid_price) * 100) <= 1) / count(), 1) AS one_cent_pct
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'AAPL', 'KO', 'NVDA', 'MSFT', 'BKNG')
AND sip_timestamp >= '2026-06-10 15:00:00'
AND sip_timestamp < '2026-06-10 19:00:00'
AND bid_price > 0
AND ask_price > bid_price
GROUP BY ticker
ORDER BY avg_spread_centsAcross four midday hours of the same session, the tightest of the six names, KO, averaged 1.18 cents wide and showed a one-cent spread on 82.5% of its updates. The widest, MSFT, averaged 8.46 cents, with a one-cent spread on 1.1% of updates. A queue model calibrated on the first name says nothing useful about the second. Orders that rest between the quotes, covered in midpoint peg orders, form their own line under the same rules.
Four diagnostics for your own backtest
- Compare the simulated fill rate with your live fill rate on the same names over the same hours. A simulator that fills 70% of the orders that filled 40% of the time live is describing your assumption, not the market.
- Split simulated fills into two piles: those where the price level survived afterwards, and those that happened only as the level cleared completely. A pile weighted to the second kind means the simulator hands you fills right as the price trades through you.
- Bracket the assumption. Rerun with every cancellation taken from the front of the queue, then with every cancellation taken from the back. Those two runs are the honest error bars around any p(x) you choose.
- Measure how often your price sat outside the depth your feed carries. Fills there were invented. Look-ahead bias in backtesting describes the same failure from another angle: a result resting on information the strategy never had.
When to stop modelling and buy the order data
Market-by-order data (MBO) carries a message for every individual order, from the moment it arrives to the moment it executes or cancels, each tagged with its own identifier. Replay that feed and your place in line is a count rather than an estimate, once you assume a realistic delay for your own order reaching the venue. It costs more than a depth feed and it is much larger to store.
Diagnostic 3 is the decision rule. If the front and back brackets both leave the strategy profitable, a middle assumption will do. If the strategy makes money under one bracket and loses under the other, the queue model is the strategy, and buying the order-by-order feed is cheaper than defending a guess.
Pro-rata venues change the question
Some futures and options markets split an incoming order across the resting orders at a price in proportion to size rather than by arrival order. Time matters far less there, and quoted size becomes the lever: doubling your size roughly doubles your share of each fill. The failure mode moves with it, toward quoting more size than you want to own. Why options orders do not get filled covers how that looks from the retail side of an options book.
FAQ
What is queue position in trading?
Queue position is your rank among the orders resting at the same price on the same venue. On a price-time priority book, the orders ahead of you have to trade or cancel before yours can trade.
Can you calculate queue position from Level 2 data?
Not exactly. Level 2 shows total size per price level with no order identity, so when size disappears without a print you cannot tell whether those shares sat ahead of you or behind you. You can estimate the number and bracket the estimate. The exact figure needs order-by-order data.
Why are backtested fill rates usually too high?
The common assumption spreads cancellations evenly across the queue, while real cancellations concentrate toward the back. An even model advances your simulated order faster than the real line moves, which shows up as more fills, and better ones, than live trading delivers.
What is MBO data?
Market-by-order data gives every individual order its own messages, from arrival through execution or cancel. It is the feed where your place in line is countable rather than modelled, which matters most for strategies living at a one-tick spread.
Does queue position matter on pro-rata venues?
Less. Pro-rata allocation splits an incoming order across resting orders by size, so arriving first buys you little and quoted size does the work. Checking which allocation model a venue uses comes before any queue modelling.
Every panel here carries the SQL that produced it. Change the ticker, move the date, and ask the same question about the names you trade on the Strasmore terminal.