Main Message Types and Common Fields
First, let's identify the common message structure and fields that appear across different message types:
#![allow(unused)] fn main() { // Common message header fields that appear in all messages struct MessageHeader { message_type: MessageType, stock_locate: u16, tracking_number: u16, timestamp: u64, // Nanoseconds since midnight } // Enum for all possible message types #[derive(Debug, Clone, Copy, PartialEq)] #[repr(u8)] enum MessageType { SystemEvent = b'S', // System Event Message StockDirectory = b'R', // Stock Directory Message StockTradingAction = b'H', // Stock Trading Action Message RegShoRestriction = b'Y', // Reg SHO Short Sale Price Test Restricted Indicator MarketParticipantPosition = b'L', // Market Participant Position MwcbDeclineLevel = b'V', // MWCB Decline Level Message MwcbStatus = b'W', // MWCB Status Message IpoQuotingPeriodUpdate = b'K', // IPO Quoting Period Update Message LuldAuctionCollar = b'J', // LULD Auction Collar OperationalHalt = b'h', // Operational Halt AddOrderNoMpid = b'A', // Add Order – No MPID Attribution AddOrderMpid = b'F', // Add Order with MPID Attribution OrderExecuted = b'E', // Order Executed Message OrderExecutedWithPrice = b'C', // Order Executed With Price Message OrderCancel = b'X', // Order Cancel Message OrderDelete = b'D', // Order Delete Message OrderReplace = b'U', // Order Replace Message Trade = b'P', // Trade Message (Non-Cross) CrossTrade = b'Q', // Cross Trade Message BrokenTrade = b'B', // Broken Trade Message Noii = b'I', // Net Order Imbalance Indicator (NOII) Message RpiiIndicator = b'N', // Retail Price Improvement Indicator (RPII) DirectListingWithCapitalRaise = b'O', // Direct Listing with Capital Raise Price Discovery Message } }
System Event Message
#![allow(unused)] fn main() { struct SystemEventMessage { header: MessageHeader, event_code: SystemEventCode, } enum SystemEventCode { StartOfMessages = b'O', StartOfSystemHours = b'S', StartOfMarketHours = b'Q', EndOfMarketHours = b'M', EndOfSystemHours = b'E', EndOfMessages = b'C', } }
Stock Directory Message
#![allow(unused)] fn main() { struct StockDirectoryMessage { header: MessageHeader, stock: [u8; 8], // Stock symbol, right padded with spaces market_category: MarketCategory, financial_status_indicator: FinancialStatusIndicator, round_lot_size: u32, round_lots_only: RoundLotsOnly, issue_classification: u8, // Alpha issue_sub_type: [u8; 2], // Alpha authenticity: Authenticity, short_sale_threshold_indicator: ShortSaleThresholdIndicator, ipo_flag: IpoFlag, luld_reference_price_tier: LuldReferencePriceTier, etp_flag: EtpFlag, etp_leverage_factor: u32, inverse_indicator: InverseIndicator, } enum MarketCategory { NasdaqGlobalSelectMarket = b'Q', NasdaqGlobalMarket = b'G', NasdaqCapitalMarket = b'S', Nyse = b'N', NyseAmerican = b'A', NyseArca = b'P', BatsZExchange = b'Z', InvestorsExchange = b'V', NotAvailable = b' ', } enum FinancialStatusIndicator { Deficient = b'D', Delinquent = b'E', Bankrupt = b'Q', Suspended = b'S', DeficientAndBankrupt = b'G', DeficientAndDelinquent = b'H', DelinquentAndBankrupt = b'J', DeficientDelinquentAndBankrupt = b'K', CreationsRedemptionsSuspended = b'C', Normal = b'N', NotAvailable = b' ', } enum RoundLotsOnly { RoundLotsOnly = b'Y', NoRestrictions = b'N', } enum Authenticity { LiveProduction = b'P', Test = b'T', } enum ShortSaleThresholdIndicator { Restricted = b'Y', NotRestricted = b'N', NotAvailable = b' ', } enum IpoFlag { SetUpAsNewIpo = b'Y', NotNewIpo = b'N', NotAvailable = b' ', } enum LuldReferencePriceTier { Tier1 = b'1', Tier2 = b'2', NotAvailable = b' ', } enum EtpFlag { Etp = b'Y', NotEtp = b'N', NotAvailable = b' ', } enum InverseIndicator { InverseEtp = b'Y', NotInverseEtp = b'N', } }
Stock Trading Action Message
#![allow(unused)] fn main() { struct StockTradingActionMessage { header: MessageHeader, stock: [u8; 8], // Stock symbol, right padded with spaces trading_state: TradingState, reason: [u8; 4], // Trading Action reason } enum TradingState { Halted = b'H', Paused = b'P', QuotationOnly = b'Q', Trading = b'T', } }
Add Order Messages
#![allow(unused)] fn main() { struct AddOrderNoMpidMessage { header: MessageHeader, order_reference_number: u64, buy_sell_indicator: BuySellIndicator, shares: u32, stock: [u8; 8], // Stock symbol, right padded with spaces price: u32, // Price (4 decimal places) } struct AddOrderMpidMessage { header: MessageHeader, order_reference_number: u64, buy_sell_indicator: BuySellIndicator, shares: u32, stock: [u8; 8], // Stock symbol, right padded with spaces price: u32, // Price (4 decimal places) attribution: [u8; 4], // MPID } enum BuySellIndicator { Buy = b'B', Sell = b'S', } }
Order Execute/Modify Messages
#![allow(unused)] fn main() { struct OrderExecutedMessage { header: MessageHeader, order_reference_number: u64, executed_shares: u32, match_number: u64, } struct OrderExecutedWithPriceMessage { header: MessageHeader, order_reference_number: u64, executed_shares: u32, match_number: u64, printable: Printable, execution_price: u32, // Price (4 decimal places) } struct OrderCancelMessage { header: MessageHeader, order_reference_number: u64, cancelled_shares: u32, } struct OrderDeleteMessage { header: MessageHeader, order_reference_number: u64, } struct OrderReplaceMessage { header: MessageHeader, original_order_reference_number: u64, new_order_reference_number: u64, shares: u32, price: u32, // Price (4 decimal places) } enum Printable { NonPrintable = b'N', Printable = b'Y', } }
Trade Messages
#![allow(unused)] fn main() { struct TradeMessage { header: MessageHeader, order_reference_number: u64, buy_sell_indicator: BuySellIndicator, shares: u32, stock: [u8; 8], // Stock symbol, right padded with spaces price: u32, // Price (4 decimal places) match_number: u64, } struct CrossTradeMessage { header: MessageHeader, shares: u64, stock: [u8; 8], // Stock symbol, right padded with spaces cross_price: u32, // Price (4 decimal places) match_number: u64, cross_type: CrossType, } struct BrokenTradeMessage { header: MessageHeader, match_number: u64, } enum CrossType { NasdaqOpeningCross = b'O', NasdaqClosingCross = b'C', CrossForIpoAndHaltedPaused = b'H', ExtendedTradingClose = b'A', } }
NOII Message
#![allow(unused)] fn main() { struct NoiiMessage { header: MessageHeader, paired_shares: u64, imbalance_shares: u64, imbalance_direction: ImbalanceDirection, stock: [u8; 8], // Stock symbol, right padded with spaces far_price: u32, // Price (4 decimal places) near_price: u32, // Price (4 decimal places) current_reference_price: u32, // Price (4 decimal places) cross_type: CrossType, price_variation_indicator: PriceVariationIndicator, } enum ImbalanceDirection { Buy = b'B', Sell = b'S', NoImbalance = b'N', InsufficientOrders = b'O', Paused = b'P', } enum PriceVariationIndicator { LessThan1Percent = b'L', OneToTwoPercent = b'1', TwoToThreePercent = b'2', ThreeToFourPercent = b'3', FourToFivePercent = b'4', FiveToSixPercent = b'5', SixToSevenPercent = b'6', SevenToEightPercent = b'7', EightToNinePercent = b'8', NineToTenPercent = b'9', TenToTwentyPercent = b'A', TwentyToThirtyPercent = b'B', ThirtyPercentOrMore = b'C', NotAvailable = b' ', } }
Main Parsing Structure
Now let's put it all together with a main parser struct:
#![allow(unused)] fn main() { struct NasdaqTotalViewItchParser { // Any state your parser needs to maintain } impl NasdaqTotalViewItchParser { fn new() -> Self { Self {} } fn parse_message(&mut self, data: &[u8]) -> Result<ParsedMessage, ParseError> { if data.is_empty() { return Err(ParseError::EmptyData); } // First byte is the message type let message_type = data[0]; match message_type { b'S' => self.parse_system_event(data), b'R' => self.parse_stock_directory(data), b'H' => self.parse_stock_trading_action(data), // ... add more message types _ => Err(ParseError::UnknownMessageType(message_type)), } } // Individual parsing methods for each message type fn parse_system_event(&self, data: &[u8]) -> Result<ParsedMessage, ParseError> { // Implementation } // ... other parsing methods } enum ParsedMessage { SystemEvent(SystemEventMessage), StockDirectory(StockDirectoryMessage), StockTradingAction(StockTradingActionMessage), // ... other message variants } enum ParseError { EmptyData, UnknownMessageType(u8), InvalidMessageLength, // ... other error types } }
This provides a solid framework for your Rust protocol parser for the Nasdaq TotalView-ITCH format. You'll need to implement the individual parsing methods for each message type, taking into account the binary format and field alignments specified in the documentation.
Would you like me to expand on any specific message type or provide more detailed implementation for certain parts of the parser?
Approach for implementation of the parser
If you're starting from scratch and implementing a low-latency protocol parser in Rust (e.g., for HFT), verifying correctness and performance is crucial. Here’s a structured approach:
1. Define the Protocol & Expected Behavior
Before coding, fully understand the protocol you're parsing (e.g., NASDAQ ITCH, CME MDP 3.0, FIX/FAST).
- Read the exchange specification document (e.g., NASDAQ ITCH 5.0).
- Identify message types (e.g., orders, trades, cancellations) and their binary layouts.
- Define test cases (valid messages, edge cases, malformed inputs).
2. Implement the Parser in Rust
Key Rust Features for Performance & Safety
- Zero-copy parsing: Use
&[u8]slices instead of allocations. - No heap allocations: Avoid
Vec,Stringin hot paths; usearrayvecorbytes::Bytes. - Branchless code: Leverage
match,unwrap_unchecked(carefully) to reduce CPU stalls. - SIMD optimizations: For fixed-width fields (e.g., prices), use
packed_simd(orstd::simdin nightly).
Example (Simplified ITCH Parser)
#![allow(unused)] fn main() { use bytes::Buf; // Define message types (ITCH example) #[derive(Debug)] pub enum ItchMessage { OrderAdd { stock: [u8; 8], price: u64, qty: u32 }, Trade { stock: [u8; 8], price: u64, qty: u32 }, // ... } pub fn parse_itch(buffer: &[u8]) -> Option<ItchMessage> { let mut buf = bytes::Bytes::copy_from_slice(buffer); match buf.get_u8() { // Message type byte b'A' => Some(ItchMessage::OrderAdd { stock: buf.copy_to_bytes(8).as_ref().try_into().unwrap(), price: buf.get_u64_le(), qty: buf.get_u32_le(), }), b'T' => Some(ItchMessage::Trade { /* ... */ }), _ => None, // Unknown message } } }
3. Verify Correctness
Unit Tests
- Test valid messages against known outputs.
- Test edge cases: Empty messages, max values, malformed data.
#![allow(unused)] fn main() { #[test] fn test_order_add_parse() { let msg = [b'A', b'A', b'A', b'P', b'L', 0, 0, 0, 0, 0x80, 0, 0, 0, 0, 0, 0, 0x64, 0, 0, 0]; let parsed = parse_itch(&msg).unwrap(); assert!(matches!(parsed, ItchMessage::OrderAdd { stock: b"AAPL", price: 128, qty: 100 })); } }
Fuzzing
Use cargo fuzz to test robustness against random inputs:
cargo install cargo-fuzz
cargo fuzz init
# Write a fuzz target that feeds random bytes to the parser.
Replay Real Market Data
- Capture real market data (e.g., NASDAQ ITCH pcap files).
- Replay it through your parser and compare with reference implementations (e.g., exchange-provided tools).
4. Performance Analysis
Benchmarking
Use criterion for microbenchmarks:
#![allow(unused)] fn main() { use criterion::{criterion_group, criterion_main, Criterion}; fn bench_parse(c: &mut Criterion) { let msg = [b'A', b'A', b'A', b'P', b'L', /* ... */]; c.bench_function("parse_itch", |b| b.iter(|| parse_itch(&msg))); } criterion_group!(benches, bench_parse); criterion_main!(benches); }
Run with:
cargo bench
Latency Measurement
- Measure end-to-end parsing time (from packet arrival to parsed output).
- Use
rdtsc(cycle counters) for nanosecond precision:#![allow(unused)] fn main() { #[inline(always)] fn rdtsc() -> u64 { unsafe { std::arch::x86_64::_rdtsc() } } }
Profiling
perf(Linux):perf record --call-graph dwarf cargo bench perf report- Flamegraph: Identify hotspots.
cargo flamegraph --bench my_benchmark
Key Metrics
- Throughput: Messages parsed per second (aim for 1M+/sec in HFT).
- Latency: 99th percentile <1µs for HFT-grade parsers.
- CPU Cache Efficiency: Check with
perf stat -e cache-misses.
5. Compare with Alternatives
- Baseline vs. Optimized: Compare your Rust parser against a C++ reference (e.g., QuickFAST, SBE).
- DPDK/FPGA Acceleration: If latency is still too high, consider offloading parsing to hardware.
Summary
- Start with specs and test cases.
- Implement in Rust with zero-copy, branchless code.
- Verify correctness with unit tests, fuzzing, and real data.
- Profile aggressively (perf, criterion, flamegraphs).
- Optimize until latency/throughput meet HFT standards.
For HFT, even 100ns saved in parsing can be the difference between profit and loss. Rust’s control over memory and lack of runtime overhead makes it a strong choice.
One week plan to build a protocol parser.
Absolutely — you can implement a solid ITCH parser in a week, especially given how much you've already done.
Here’s how I’d structure it to make fast progress, without getting stuck:
🗓️ One-Week Plan to Build a Functional ITCH Parser
Day 1–2: Core Infrastructure
- ✅ You've already got the Rust structs and enums for all the message types — great start!
- Next steps:
- Set up a
nomorzerocopy-based binary parser framework - Create a main loop that:
- Reads the binary file or socket stream
- Matches message types using the header byte
- Dispatches to the appropriate parser per message type
- Create a
ParsedMessageenum that wraps each type
- Set up a
#![allow(unused)] fn main() { enum ParsedMessage { AddOrderNoMpid(AddOrderNoMpidMessage), OrderExecuted(OrderExecutedMessage), // etc. } }
Day 3–4: Real-World Sample Feed
- Get a NASDAQ historical ITCH file (you can use TotalView-ITCH 5.0 sample files)
- Build a replay engine that:
- Reads one message at a time
- Parses and prints (or logs) what it sees
Add unit tests like:
#![allow(unused)] fn main() { #[test] fn test_add_order_parsing() { let raw: [u8; 36] = [ /* binary bytes for AddOrderNoMpid */ ]; let msg = parse_add_order(&raw).unwrap(); assert_eq!(msg.shares, 100); // ... } }
Day 5: In-Memory Order Book (Optional)
- If you're up for it: implement a very basic order book using a
BTreeMap<u32, Vec<Order>>- Insert on
AddOrder - Remove on
Cancel/Delete - Match on
Trade
- Insert on
Day 6–7: Polish & Pitch
- Add performance logging (how fast is your parser?)
- Write a README or gig proposal:
“Efficient binary parser for NASDAQ ITCH built in Rust, capable of parsing X msgs/sec from historical or live feeds. Pluggable design allows integration with custom order books or analytics.”
💡 Tips for Hitting the 1-Week Target
- Focus on AddOrder, Cancel, Trade, and Execution first — the rest can follow
- Use
#[repr(packed)]orzerocopy::FromBytesfor zero-allocation parsing if you're optimizing - You don’t need to build a full order book yet — just parse & log initially
You already did the hard part: understanding the protocol.
The rest is just writing Rust code in a clean, modular way. Want me to scaffold a minimal Rust parser framework for this so you can get coding right away?