tickstreamdocs

DOCS

L3 / market-by-order

Order-by-order depth: every individual resting order with its exchange order id, queue priority and nanosecond timestamp.

requiresL3 / Market-by-order or above

The l3 channel streams depth per order instead of per price level. Every individual resting order is its own event — placed, modified, cancelled or filled — carrying the exchange order id, its queue priority at that price and a nanosecond exchange timestamp. It's included from L3 / Market-by-order or above.

L2 vs L3

L2 tells you there are 42 contracts bid at 23481.25. L3 tells you those 42 are seven separate orders, in what queue order, and which one just pulled. That's the difference between seeing size and seeing intent — queue position, iceberg detection and order-lifetime studies all need L3.

Subscribe

{ "op": "subscribe", "channel": "l3", "symbols": ["NQ"] }
from tickstream import Tickstream

ts = Tickstream("sk_live_…")
for e in ts.stream("NQ", channel="l3"):
    print(e["action"], e["order_id"], e["side"], e["price"], e["size"])

L3 is available for the CME futures roots (NQ, ES, YM, RTY…) — the same symbols as Level 2. Subscribing to l3 does not replace book: you can run both on one connection, and the aggregated book frames are built from this very order feed.

The l3 message

{
  "type": "l3",
  "symbol": "NQ",
  "contract": "NQZ6",
  "order_id": "7412998336104",
  "action": "new",
  "side": "bid",
  "price": 23481.25,
  "size": 3,
  "priority": 110934567812,
  "seq": 49437907,
  "ord": 1789775234000188,
  "snap": false,
  "snap_i": 0,
  "snap_rows": 0,
  "ts": 1753900800,
  "ns": 412903771
}
FieldTypeDescription
contractstringThe front-month contract the root resolved to when the frame was sent, e.g. NQZ6.
order_idstringExchange order id. Stable for the whole life of the order — this is what lets you track one participant's order across modifications.
actionstringnew (order added), change (size/price modified) or delete (cancelled or fully filled).
sidestringbid or ask.
pricenumberLimit price of this order.
sizenumberDisplayed size of this order (not the level total).
prioritynumberExchange queue priority at that price — lower means closer to the front of the queue. A large opaque number (around 1.1e11); compare, don't interpret.
seqnumberExchange sequence number. The authoritative event order — sort by this, not by arrival.
snapbooleanThe opening image. Whenever our upstream feed starts or restarts — announced to every subscriber by a {"type":"feed"} frame — the feed broadcasts every resting order as it stood at that moment, each one carrying snap: true and action: "new". Reset your book when the first one arrives and apply the rest as ordinary adds. The anchor is seq, not the clock: the image asserts that at that exchange sequence the resting orders were exactly these. Its ts is our capture time, because the exchange sends no clock with a snapshot. A new subscription joins mid-stream — there is no per-client replay of the image, so seed from the replay or wait for the next one
ordnumberOur ingest ordinal: strictly increasing, never reused, assigned in arrival order. It exists because seq does not always separate two events — over a full NQ session eleven keys collide on (ns, order_id, action), one of them covering seven distinct price changes under a single seq. Use it only to break those ties. Where seq separates events, seq wins; ord orders arrival at our socket, not matching at the exchange. Opaque — compare with <, never read it as a timestamp.
snap_i / snap_rowsintegerInside an opening image: this order's 1-based position and the image's total row count, so you know when it is complete. 0 outside an image.
tsintegerExchange timestamp in Unix seconds.
nsintegerNanoseconds within that second, as stamped by the exchange.

Rebuilding a book from L3

An order feed is a delta feed: apply each event to a map keyed by order_id and you hold the exact book. Aggregate that map by price whenever you need a level view.

# an L3 stream IS the book — aggregate it whenever you want a level view
book = {}  # order_id -> (side, price, size)

def apply(e):
    if e["action"] in ("new", "change"):
        book[e["order_id"]] = (e["side"], e["price"], e["size"])
    elif e["action"] == "delete":
        book.pop(e["order_id"], None)

def levels(side):
    out = {}
    for s, px, sz in book.values():
        if s == side:
            out[px] = out.get(px, 0) + sz
    return sorted(out.items(), reverse=(side == "bid"))
gaps are visible, never silent

If your connection can't keep up, we send a {"type":"warning","warning":{"code":"l3_lagged","dropped":N}} frame instead of quietly skipping events — a book rebuilt across a hidden gap is wrong in ways that are very hard to notice. On that warning, drop your local book and resubscribe.

GET /v1/l3 — replay

Replay recorded order-level events over REST. Retention is a rolling ~7 days, and only NQ and ES are recorded for replay (the live l3 channel covers more symbols). The response is {symbol, start, end, count, truncated, contract, contractSince, events: [...]}; each event has the stream's order fields (order_id, action, side, price, size, priority, seq, ord, snap, snap_i, snap_rows, ns) but no type or symbol, and its ts is in microseconds, not seconds.

ParamRequiredDescription
symbolyesThe instrument, e.g. NQ.
start / endnoISO-8601 UTC or Unix seconds. Max window 1 hour per request — an L3 hour is hundreds of thousands of events. Defaults: end = now, start = 5 minutes before.
limitnoMax events (default 50,000, max 500,000).
# start/end must fall inside the last ~7 days
curl "https://api.tick-stream.xyz/v1/l3?symbol=NQ&start=2026-09-25T13:30:00Z&end=2026-09-25T14:00:00Z" \
  -H "Authorization: Bearer sk_live_…"
no L3 history before we recorded it

There is no vendor archive for order-level data — nobody sells CME MBO history back in time, and our own tick/L2 archive (2019 →) does not contain it either. L3 replay is a rolling ~7-day window of our own recording. If you need L3 for a specific study, tell us the symbol so it's in the recording set: support@tick-stream.xyz.

plan

Without the package, subscribing to l3 returns an error frame {"type":"error","error":{"code":"plan_required",…}} (the socket stays open), and /v1/l3 returns HTTP 403 plan_required. See what the packages include.