Skip to content

The order book's tick window

NLevelOrderBook is a flat array indexed by tick. That is what makes a level lookup a single load instead of a tree walk, and it is also the source of the one constraint you have to design around: the array is finite, so at any moment the book can only represent a bounded range of prices. This page describes where that range sits, when it moves, and what happens to a price outside it.

The window

A book of MaxLevels levels with tick size t covers MaxLevels consecutive ticks starting at a base index. Multiply out and the price range is MaxLevels * t wide:

Levels Tick Window width Room either side of the market
512 0.01 $5.12 $2.56
512 0.1 $51.20 $25.60
8192 0.01 $81.92 $40.96
512 0.001 (0..1 prediction market) 0.512 0.256

The right-hand column is the one that matters. The book centres its window on the market, so the usable headroom in either direction is half the level count, not all of it. SymbolContext gives each symbol a NLevelOrderBook<512>, which is where the first two rows come from.

When the window moves

A SNAPSHOT re-anchors unconditionally: the book measures the tick range of the levels in the snapshot, centres the window on it, and refills from scratch.

A DELTA carries only what changed, so the book has to decide for itself. It re-anchors when the delta adds a level outside the current window and everything already in the book still fits alongside it. Both conditions, not either. The new window is centred on the union of the two ranges, and the existing levels are carried across rather than dropped.

When the two do not fit together, the out-of-window level is discarded and the book is left alone. This is deliberate. A venue that emits one stale quote, a placeholder price, or a fat finger thousands of ticks from the market should not be able to evict live depth; losing one bad level is cheaper than losing the book.

A removal never re-anchors. A delta that zeroes a price outside the window is naming a level the book cannot be holding, so there is nothing to do.

A delta that arrives before any snapshot anchors the book on its own levels.

Why this matters on a live feed

Bybit and Bitget send a snapshot on connect and after a sequence gap, and nothing in between. Hyperliquid sends a snapshot with every update, so it never depends on any of this. On the first kind of feed, a book that only re-anchored on snapshots would follow the market until it reached the edge of its window and then stop: additions past the edge would be dropped while removals kept being applied, so one side would empty out, then the other, and neither would come back until the connection dropped and the feed re-sent a snapshot. On a 512-level book at a 0.1 tick, that is about $25 of BTC movement to lose one side and about $30 to lose both.

Sizing the book

Pick MaxLevels so that half of it, in ticks, comfortably exceeds the distance the market moves between snapshots on your feed. Re-anchoring costs a shift of both ladders, so a larger window is not free, but it happens once per window's worth of drift rather than once per update. The default of 8192 is generous for most instruments; SymbolContext's 512 is tuned for many symbols at once.

Tick size

The tick size must be positive. The constructor throws std::invalid_argument otherwise, and the bindings surface that the way each language expects:

Surface Bad tick size
C++ throws std::invalid_argument
Python raises ValueError
Node throws a RangeError
QuickJS throws a RangeError
Codon raises ValueError
C ABI lrvx_book_create returns NULL

A tick size arrives from instrument configuration and from user code, so a bad one is bad input, not a broken invariant, and it is reported rather than asserted.

Price is a fixed-point type with a scale of 1e8, so the smallest representable tick is 1e-8 and the sub-cent pairs really do quote there. Tick sizes of 1e-8 and 2e-8 work exactly like any other.

Mid price

mid() is tickSize * (bidTick + askTick) / 2, with a single division at the end. It agrees exactly with SymbolContext::mid(), which averages the two raw prices. Price is an integer underneath, so an odd sum of tick indices truncates by at most half a raw unit, which is 5e-9 at the standard price scale.