Reconstruct the top of book from L3 events
L3 events are every add, modify, cancel and fill on one venue’s book, order by order. Replay a session from its first event and you hold that book at any instant. This guide builds the book in plain Python, reads its top every second, and checks the result against the L2 rows, which are computed the same way.
You need the Python client and a key with bought bars. Ten
events bill one bar: a median ticker’s day is about 1,000 events (100 bars), a busy one millions.
Watch X-Bars-Charged, or try a quiet ticker first.
1. The book
Section titled “1. The book”from collections import defaultdict
orders = {} # order_id -> [side, price, size]levels = { "B": defaultdict(lambda: [0, 0]), "A": defaultdict(lambda: [0, 0]),} # price -> [shares, orders]
def put(oid, side, price, size): orders[oid] = [side, price, size] level = levels[side][price] level[0] += size level[1] += 1
def take(oid): order = orders.pop(oid, None) if order: side, price, size = order level = levels[side][price] level[0] -= size level[1] -= 1 if level[1] == 0: del levels[side][price] return order
def top(): bids, asks = levels["B"], levels["A"] bid = (b := max(bids), *bids[b]) if bids else (None, None, None) ask = (a := min(asks), *asks[a]) if asks else (None, None, None) return bid + ask # bid_px, bid_sz, bid_ct, ask_px, ask_sz, ask_ct2. Apply each event
Section titled “2. Apply each event”def apply(e): act, oid = e["action"], e["order_id"] if act == "A" and e["side"] in ("B", "A"): take(oid) # an id added again replaces the old order put(oid, e["side"], e["price"], e["size"]) elif act == "M": # new price and size, same side; size 0 removes it old = take(oid) if old and e["size"] > 0: put(oid, old[0], e["price"], e["size"]) elif act == "C": take(oid) elif act == "F": # a resting order traded: it shrinks at its own price old = take(oid) if old and old[2] > e["size"]: put(oid, old[0], old[1], old[2] - e["size"]) elif act == "R": # the venue cleared the book orders.clear() levels["B"].clear() levels["A"].clear() # T and B are prints and breaks: they rest nothingF reduces the resting order named by order_id. Its price is the print, which can differ
from the order’s own price, so the level the shares come off is the stored one.
3. Replay a session, one row per second
Section titled “3. Replay a session, one row per second”from markethistory import Client
mh = Client() # reads MARKET_HISTORY_KEYday = "2026-10-30"
mine, last, second = {}, (None,) * 6, Nonefor e in mh.each(mh.mbo, "AAPL", date=day, limit=100_000): s = e["timestamp"][:19] # the event's second, UTC if second is not None and s != second: t = top() # the book after the last event of the previous second if t != last: mine[second] = last = t second = s apply(e)if second is not None and top() != last: mine[second] = top()Events arrive in sequence order, the venue’s order of record, and a session starts from an empty
book, so no snapshot is needed. Keying the second on the timestamp text keeps every nanosecond
exact. A second gets a row only when its top differs from the row before, which is what the L2
endpoint serves.
4. Check it against L2
Section titled “4. Check it against L2”served = { r["timestamp"][:19]: ( r["bid_px"], r["bid_sz"], r["bid_ct"], r["ask_px"], r["ask_sz"], r["ask_ct"], ) for r in mh.each(mh.l2, "AAPL", date=day, limit=100_000)}differ = [s for s in served.keys() | mine.keys() if served.get(s) != mine.get(s)]print(f"{len(served)} seconds served, {len(mine)} rebuilt, {len(differ)} differ")Both come from the same events by the same rules, so the count that differs should be 0. From
here the book is yours: levels holds every price level, so depth, imbalance or queue position
at any event are a few lines more.
Things that bite
Section titled “Things that bite”- Order by
sequence, never bytimestamp. Across tickers, timestamps can step back by up to milliseconds; within one ticker’s pages the order is already right. order_idis not a key forever. An id can come back after a cancel;take()beforeput()handles it.- One venue. This is that venue’s book, not the national best bid and offer.
- Crossed or locked tops happen in the data as published. Do not drop them silently.