itch5 0.1.5

Parser for Nasdaq TotalView-ITCH 5.0
Documentation

itch5

A fast, zero-copy parser for the Nasdaq TotalView-ITCH 5.0 binary protocol.

ITCH 5.0 is Nasdaq's real-time market data feed. It carries every order book event — adds, cancels, executes, replaces — plus market-wide events, trading halts, and cross-trade notifications, all encoded as compact fixed-length binary messages framed with a 2-byte big-endian length prefix.

Usage

Add the crate to your Cargo.toml:

[dependencies]
itch5 = "0.1"

# Optional: enable chrono for `messages::Timestamp::to_naive_time` method
# itch5 = {version = "0.1", features = ["chrono"]}  

Implement [MessageHandler] for the message types you care about, then feed bytes to [Parser]:

use std::ops::ControlFlow;
use itch5::{MessageHandler, Parser, messages::TradeMessage};

#[derive(Default)]
struct TradeCounter {
    count: u64,
}

impl MessageHandler for TradeCounter {
    fn on_trade_message(&mut self, msg: &TradeMessage) -> ControlFlow<()> {
        self.count += 1;
        ControlFlow::Continue(())
    }
}

fn main() {
    let data: &[u8] = /* your ITCH feed bytes */ &[];

    let mut handler = TradeCounter::default();
    Parser::new(data).parse_stream(&mut handler).unwrap();

    println!("trades: {}", handler.count);
}

Return ControlFlow::Break(()) from any handler method to stop parsing early.

Core API

Parser

Parser::new(buf: &[u8]) -> Parser<'_>
Parser::parse_stream(&mut self, handler: &mut impl MessageHandler) -> Result<(), ParseError>

Iterates over every framed message in buf, dispatching each one to the corresponding method on handler. The buffer is borrowed for the lifetime of the parser; no heap allocation occurs.

parse_one

parse_one(buf: &[u8]) -> Result<(&[u8], &[u8]), ParseError>

Parses a single length-prefixed message. Returns (body, remainder) where body starts with the one-byte message-type tag. Useful when you need the raw bytes rather than typed structs — for example, to forward messages to another consumer.

MessageHandler

A visitor trait with one method per message type. Every method has a default no-op implementation, so you only override what you need:

pub trait MessageHandler {
    fn on_add_order_no_mpid_attribution(&mut self, msg: &AddOrderNoMPIDAttribution) -> ControlFlow<()> { ... }
    fn on_order_executed_message(&mut self, msg: &OrderExecutedMessage) -> ControlFlow<()> { ... }
    fn on_trade_message(&mut self, msg: &TradeMessage) -> ControlFlow<()> { ... }
    // ... 20 more
}

ParseError

pub enum ParseError {
    EmptyBuffer,
    UnknownMessageType(u8),
    MalformedData,
}

Message Types

All 23 ITCH 5.0 message types are covered:

Tag Struct Description
S SystemEventMessage Market/session lifecycle events
R StockDirectory Daily instrument reference data
H StockTradingAction Trading halt and resume
Y RegSHORestriction Regulation SHO short-sale restriction
L MarketParticipantPosition Market maker status and mode
V MWCBDeclineLevelMessage Market-wide circuit breaker levels
W MWCBStatusMessage Market-wide circuit breaker status
K QuotingPeriodUpdate IPO quotation release
J LULDAuctionCollar Limit Up/Limit Down auction collar
h OperationalHalt Exchange-specific operational halt
A AddOrderNoMPIDAttribution New order (anonymous)
F AddOrderWithMPIDAttribution New order (attributed)
E OrderExecutedMessage Order execution
C OrderExecutedWithPriceMessage Order execution at non-display price
X OrderCancelMessage Partial order cancellation
D OrderDeleteMessage Full order removal
U OrderReplaceMessage Cancel-and-replace
P TradeMessage Non-displayed trade
Q CrossTradeMessage Opening/closing/halt cross execution
B BrokenTradeMessage Trade break
I NetOrderImbalanceIndicatorMessage Cross imbalance data
N RetailPriceImprovementIndicator Retail interest flags
O DirectListingwithCapitalRaisePriceDiscoveryMessage Direct listing price discovery

Price Types

Prices in the ITCH protocol are fixed-point integers. The crate provides two newtype wrappers:

Type Bytes Precision
Price4 4 4 decimal places
Price8 8 8 decimal places

Symbol

Symbol is an 8-byte ASCII stock ticker, space-padded on the right per the ITCH specification.

let sym: Symbol = msg.stock();
println!("{}", sym.as_str()); // e.g. "AAPL    " trimmed to "AAPL"

// Parse from a string
let sym: Symbol = "MSFT".parse().unwrap();

// Compact hash/unhash for use as a map key
let key: u64 = sym.hash();
let restored: Symbol = Symbol::from_hash(key);

Timestamp

Timestamp is a u48 representing nanoseconds since midnight. The crate provides a Timestamp newtype wrapper.

let ts: &Timestamp = Timestamp::ref_from_bytes(b"000001").unwrap();

let dur: std::time::Duration = ts.to_dur();
// requires feature `chrono` to be enabled.
let naivetime: chrono::NaiveTime = ts.to_naive_time();

Wire Format

Each ITCH message is framed as:

[2 bytes: big-endian message length][message body]

The message body begins with a single ASCII tag byte that identifies the message type, followed by fixed-width fields in network byte order (big-endian). All message structs are #[repr(C, packed)] and implement zerocopy::FromBytes, so parsing is a bounds-checked pointer cast with no copying.

Examples

Count every trade in an ITCH binary file:

cargo run --example parse_file -- /path/to/feed.itch

The source is in examples/parse_file.rs. It memory-maps the file and counts TradeMessage events.

Benchmarks

# Default: uses the 1M-message sample at ../data/itch_1000_000
taskset -c 3 cargo bench -p itch5

# Or override the sample location:
ITCH5_BENCH_FILE=/path/to/feed.itch cargo bench -p itch5

The suite (benches/itch_bench.rs) is structured around the questions an HFT consumer would actually ask:

  • itch5/framing/parse_one_only_100k — pure wire-format framing cost. Walks a synthetic stream with [parse_one] and only inspects the tag byte. This is the floor: length-prefix decode and slice splits, no dispatch, no cast.
  • itch5/per_msg_type/{add_no_mpid,add_with_mpid,order_executed, order_cancel,order_delete,order_replace,trade} — full parse_stream over a homogeneous synthetic stream of each hot message type, dispatched into a no-op handler. Surfaces dispatch + cast cost per type so a regression in one is isolatable.
  • itch5/full_file/{noop_handler,counting_handler} — the recorded sample replayed end-to-end. noop reports the parser's intrinsic ceiling; counting reports what an order-book consumer pays after touching every accessor on the hot message types. Reported in messages/sec and bytes/sec.
  • itch5/field_accessors/{price4_into_i64,timestamp_to_u64, symbol_to_u64} — confirms each zero-copy big-endian accessor folds to a single load (or load + bswap) at release optimization.

If the sample file is unavailable, the data-driven group prints a notice and skips; the synthetic and accessor groups still run.

License

Unlicense — public domain.