When to Use
Use this skill when decoding raw binary direct market data from Nasdaq venues (Nasdaq, Nasdaq BX, Nasdaq PSX) and maintaining the market-by-order (L3) book that results. TotalView-ITCH 5.0 publishes every displayed order's full lifecycle with nanosecond timestamps over MoldUDP64 or SoupBinTCP, which is what makes queue-position modelling, micro-price construction, and order-flow research possible in the first place.
The engine decodes the order-lifecycle message set — Add Order A/F, Order Executed E/C, Order Cancel X, Order Delete D, Order Replace U — plus the non-book Trade message P, and keys the book on the day-unique Order Reference Number.
Its second job is telling you when the book is wrong. A feed with a dropped message replays without raising; it just produces a book that quietly disagrees with the venue. Every message that cannot be applied to a consistent book is counted and surfaced rather than absorbed.
When NOT to Use
- As a transport or gap-detection layer. This decodes one already de-framed message. MoldUDP64 sequencing, A/B line arbitration and retransmission all sit upstream — see
sequence-number-gap-detection-for-feedsandexchange-multicast-feed-handling. - For the full ITCH message set. System Event
S, Stock DirectoryR, Trading ActionH, LULD, MWCB, NOII and Cross TradeQare not decoded. Skip unknown types by their transport-declared length; never guess a layout. - For a non-Nasdaq feed. ITCH 5.0 layouts do not transfer. NYSE XDP is little-endian with a different message set — see
nyse-arca-integrated-feed-handling. - For aggregated L2 depth or BBO. This maintains the per-order map only. Aggregation into price levels and BBO is
historical-order-book-reconstruction-from-message-logs; consuming pre-aggregated depth isorder-book-depth-processing-l2-l3. - On a latency-critical production hot path in CPython. Sustained full-depth ITCH rates need C++/Rust/FPGA; this is a reference decoder for replay, research and conformance testing. See
binary-protocol-parsing-for-low-latency-feedsfor the zero-copy decode techniques. - For multi-venue replay in one instance. One engine holds one venue's reference-number space; Nasdaq, BX and PSX numbering is independent. Shard upstream.
Prerequisites
- A de-framed ITCH 5.0 message: exactly one message starting at its 1-byte Message Type. A MoldUDP64 downstream packet carries a 20-byte header followed by message blocks, each prefixed with a 2-byte big-endian message length that excludes the length field itself — strip that prefix before calling in.
- The ITCH 5.0 specification at the version the feed publishes. Field offsets change between protocol versions; ITCH 4.1 timestamps are not even the same width.
- A chronologically ordered stream. The engine flags a timestamp regression but cannot repair one.
- Python 3.10+. Standard library only.
Workflow
-
De-frame before you decode. Read the transport's length prefix and confirm the whole message is present. The engine rejects any message whose length is not the spec's exact total for its type (
A36,F40,E31,C36,X23,D19,U35,P44 bytes including the type byte) — a leftover 2-byte MoldUDP64 prefix shows up here rather than as silent field shift. -
Dispatch on the type byte, and treat both Add variants as adds.
- Decision point —
Fis not an optional extra. Add Order with MPID Attribution carries the identical layout toAplus a 4-byte Attribution field, and rests on the book exactly the same way. Attributed quotes are a large share of displayed liquidity; droppingFdoes not degrade the book, it hollows it out.
- Decision point —
-
Apply modify messages as cumulative deductions.
- Spec §1.4: deduct the shares stated in the modify message from the order's current displayed size, and when it reaches zero the order is dead and must be removed.
- Decision point —
EandCare both executions. Order Executed With PriceCexists only because the fill happened away from the display price; it carries the same Executed Shares semantics. IgnoringCleaves already-executed shares resting on the book. - Decision point —
Xis notD. Order Cancel carries a Cancelled Shares count and is a partial reduction. Order Delete carries no share count and removes everything remaining. Mapping a delete onto a cancel requires inventing a share count.
-
Handle
UReplace as a delete-plus-add under a new identity.- Decision point — the reference number changes. The spec is explicit that "the Nasdaq system will use this new order reference number for all subsequent updates." Keeping the replacement under the old number makes every later message for that order look like a gap.
- Decision point —
Ucarries neither side nor stock. They cannot change, so they are omitted; "Firms should retain the side, stock symbol and MPID from the original Add Order message." If the original is not on the book, the replacement cannot be created — there is no side to place it on. Report the gap instead of inventing liquidity. - Decision point — Shares on
Uis the new total displayed quantity, not a deduction.
-
Route
Paway from the book. Trade Message (Non-Cross) reports a match between non-displayable order types. The spec states trade messages do not affect the book, and since 2010-12-06 its Order Reference Number is populated as zero. Use it for time-and-sales and volume; never as an order lifecycle event. -
Classify every anomaly before trusting the book. Malformed bytes (wrong length, non-ASCII Alpha field, a Buy/Sell Indicator that is not
B/S, a Printable flag that is notY/N) raiseITCHParseErrorimmediately. A well-formed message that cannot be applied is an integrity violation, counted inviolations_by_kind:UNKNOWN_ORDER,DUPLICATE_ORDER_ID,OVER_EXECUTE,OVER_CANCEL,TIMESTAMP_REGRESSION,PRICE_OUT_OF_RANGE.- Decision point — choose the policy before the run.
strict=TrueraisesITCHBookIntegrityErroron the first violation, which is right for a validated production pipeline; the default records and continues, which is right for exploratory replay of an imperfect archive. Either way, a non-zero violation count invalidates any microstructure statistic computed from that replay.
- Decision point — choose the policy before the run.
-
Generate the audit report.
generate_report()returnsPARSER_SUCCESSonly whenintegrity_violation_count == 0, andPARSER_INTEGRITY_VIOLATIONSwith a per-kind breakdown otherwise.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Wrong endianness. ITCH 5.0 states "All integer fields are big endian (network byte order) binary encoded numbers." Little-endian or native (
<,=) unpacking does not raise — it returns a plausible message with a corrupted price and a nonsense order reference number, which is worse than a crash.=is especially dangerous because it also enables alignment padding, silently shifting every field after the first. - Forgetting the Price (4) divisor — in either direction. Raw
1500000is $150.00, but a raw wire integer passed straight through reads as a perfectly plausible $1,500,000.00 order. Only the spec's stated ceiling catches it: Price (4) maxes out at 200,000.0000 (0x77359400), which the engine enforces asPRICE_OUT_OF_RANGE. - Silently absorbing a message for an order you never saw. The natural implementation is
if ref_num in self.active_orders:— which turns a dropped Add Order into a no-op and leaves a book that is wrong for the rest of the session with nothing in the output saying so. Every unmatchedE/C/X/D/Uis a divergence and must be counted. - Letting a deduction go negative.
shares -= executed_sharesfollowed byif shares <= 0: deletelooks like it handles over-execution. It does not: it hides it, because removing more shares than were resting means the book already diverged. It must be flagged, not clamped. - Skipping
UReplace because the book "still works". It does not. The original order stays resting at a stale size forever, and every subsequent message under the new reference number becomes anUNKNOWN_ORDER. One dropped replace poisons one order for the rest of the day. - Feeding
Pinto the book. A Trade message with a zero Order Reference Number, run through a delete or execute path, produces an endless stream of phantom gaps at ref 0 — or, worse, silently deletes whatever real order happens to occupy that key. - Treating the
PBuy/Sell Indicator as the resting side. Effective 2014-07-14 the field is alwaysBregardless of the actual resting side. Any aggressor-side classification built on it is not a signal, it is a constant. - Misreading the 6-byte timestamp. It is a 48-bit big-endian nanoseconds-since-midnight counter; decoding it as 32-bit truncates and as 64-bit consumes two bytes of the next field. It cannot overflow within a session (2⁴⁸ ns ≈ 78 hours), and the spec names no time zone in the message tables — mapping to wall-clock needs the session date and the venue's local zone.
- Stripping whitespace from both ends of an Alpha field. Alpha fields are "left justified and padded on the right with spaces", so a leading space is evidence the field did not start where the layout says.
.strip()erases that evidence;.rstrip()preserves it. - Reusing an order reference number. Reference numbers are day-unique. A second
Afor a live number silently overwrote the resting order in the naive implementation; it is nowDUPLICATE_ORDER_ID. - Treating a clean replay as a validated one. No exception raised means only that no message was malformed. Check
integrity_violation_count.
Verification
- Layout conformance: every
STRUCT_*size plus the type byte must equal the spec's published total —A36,F40,E31,C36,X23,D19,U35,P44 — and every format string must start with>. - Baseline lifecycle: Add
Afor AAPL, 100 shares @ $150.00 (1500000ticks, ref 1001) → order rests withprice_usd == 150.00. ExecuteE40 shares → 60 remaining. DeleteD→ order removed,integrity_violation_count == 0. - Replace semantics:
Ufrom ref 1 to ref 2 must leave 2 on the book and 1 off it, set size to the message's absolute quantity, inherit side and stock from the original Add, and accept later updates under ref 2. AUwhose original is unknown must countUNKNOWN_ORDERand create nothing. - Non-book messages: a
Pmust leave every resting order untouched, reportaffects_book is False, and not raise a gap for its zero reference number. - Integrity: an execute/cancel/delete for an unknown order counts
UNKNOWN_ORDER; over-deduction countsOVER_EXECUTE/OVER_CANCELwhile an exact-size execution counts neither; a duplicate Add countsDUPLICATE_ORDER_ID; a backwards timestamp countsTIMESTAMP_REGRESSIONwhile an equal one does not; a price above0x77359400countsPRICE_OUT_OF_RANGE.strict=Truemust raiseITCHBookIntegrityErroron the first. - Malformed input: empty bytes, a non-printable type byte, an unsupported type, a truncated message, trailing bytes, a Buy/Sell Indicator other than
B/S, a Printable flag other thanY/N, and a non-ASCII Stock field must all raiseITCHParseErrorand must not incrementparsed_messages_count. - Run
python -m unittest discover -s skills/nasdaq-totalview-itch-feed-parsing/scriptsand confirm 50/50 pass.