ITCHCPP 1.6.0
High-Performance NASDAQ TotalView-ITCH 5.0 Parser & Market-Data Platform
Loading...
Searching...
No Matches
parser.cpp
Go to the documentation of this file.
1#include "itch/parser.hpp"
2
3#include <array>
4#include <cstddef>
5#include <cstdint>
6#include <cstring>
7#include <set>
8#include <stdexcept>
9#include <utility>
10#include <vector>
11
12#include "itch/detail/wire.hpp"
13
14namespace itch {
15
16namespace utils {
17
18template <typename ValueType>
19auto unpack(const char* buffer, std::size_t& offset) -> ValueType {
20 ValueType value;
21 std::memcpy(&value, buffer + offset, sizeof(ValueType));
22 offset += sizeof(ValueType);
23
24 if constexpr (std::is_integral_v<ValueType> && sizeof(ValueType) > 1) {
25 return from_big_endian(value);
26 } else {
27 return value;
28 }
29}
30
31inline auto unpack_string(const char* buffer, std::size_t& offset, char* dest, std::size_t size)
32 -> void {
33 std::memcpy(dest, buffer + offset, size);
34 offset += size;
35}
36
37inline auto unpack_timestamp(const char* buffer, std::size_t& offset) -> std::uint64_t {
38 std::uint16_t high {};
39 std::uint32_t low {};
40 constexpr int LOWER_SHIFT = 32;
41 std::memcpy(&high, buffer + offset, sizeof(high));
42 offset += sizeof(high);
43 std::memcpy(&low, buffer + offset, sizeof(low));
44 offset += sizeof(low);
45 high = from_big_endian(high);
46 low = from_big_endian(low);
47 return (static_cast<std::uint64_t>(high) << LOWER_SHIFT) | low;
48}
49} // namespace utils
50
51auto unpack_message(SystemEventMessage& msg, const char* buffer, size_t& offset) -> void {
52 msg.event_code = utils::unpack<char>(buffer, offset);
53}
54
55auto unpack_message(StockDirectoryMessage& msg, const char* buffer, size_t& offset) -> void {
56 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
57 msg.market_category = utils::unpack<char>(buffer, offset);
58 msg.financial_status_indicator = utils::unpack<char>(buffer, offset);
59 msg.round_lot_size = utils::unpack<uint32_t>(buffer, offset);
60 msg.round_lots_only = utils::unpack<char>(buffer, offset);
61 msg.issue_classification = utils::unpack<char>(buffer, offset);
62
63 utils::unpack_string(buffer, offset, msg.issue_sub_type, 2);
64 msg.authenticity = utils::unpack<char>(buffer, offset);
65 msg.short_sale_threshold_indicator = utils::unpack<char>(buffer, offset);
66 msg.ipo_flag = utils::unpack<char>(buffer, offset);
67 msg.luld_ref = utils::unpack<char>(buffer, offset);
68 msg.etp_flag = utils::unpack<char>(buffer, offset);
69 msg.etp_leverage_factor = utils::unpack<uint32_t>(buffer, offset);
70 msg.inverse_indicator = utils::unpack<char>(buffer, offset);
71}
72
73auto unpack_message(StockTradingActionMessage& msg, const char* buffer, size_t& offset) -> void {
74 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
75 msg.trading_state = utils::unpack<char>(buffer, offset);
76 msg.reserved = utils::unpack<char>(buffer, offset);
77 utils::unpack_string(buffer, offset, msg.reason, 4);
78}
79
80auto unpack_message(RegSHOMessage& msg, const char* buffer, size_t& offset) -> void {
81 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
82 msg.reg_sho_action = utils::unpack<char>(buffer, offset);
83}
84
85auto unpack_message(MarketParticipantPositionMessage& msg, const char* buffer, size_t& offset)
86 -> void {
87 utils::unpack_string(buffer, offset, msg.mpid, 4);
88 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
89 msg.primary_market_maker = utils::unpack<char>(buffer, offset);
90 msg.market_maker_mode = utils::unpack<char>(buffer, offset);
91 msg.market_participant_state = utils::unpack<char>(buffer, offset);
92}
93
94auto unpack_message(MWCBDeclineLevelMessage& msg, const char* buffer, size_t& offset) -> void {
95 msg.level1 = utils::unpack<uint64_t>(buffer, offset);
96 msg.level2 = utils::unpack<uint64_t>(buffer, offset);
97 msg.level3 = utils::unpack<uint64_t>(buffer, offset);
98}
99
100auto unpack_message(MWCBStatusMessage& msg, const char* buffer, size_t& offset) -> void {
101 msg.breached_level = utils::unpack<char>(buffer, offset);
102}
103
104auto unpack_message(IPOQuotingPeriodUpdateMessage& msg, const char* buffer, size_t& offset)
105 -> void {
106 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
107 msg.ipo_quotation_release_time = utils::unpack<uint32_t>(buffer, offset);
108 msg.ipo_quotation_release_qualifier = utils::unpack<char>(buffer, offset);
109 msg.ipo_price = utils::unpack<uint32_t>(buffer, offset);
110}
111
112auto unpack_message(LULDAuctionCollarMessage& msg, const char* buffer, size_t& offset) -> void {
113 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
114 msg.auction_collar_reference_price = utils::unpack<uint32_t>(buffer, offset);
115 msg.upper_auction_collar_price = utils::unpack<uint32_t>(buffer, offset);
116 msg.lower_auction_collar_price = utils::unpack<uint32_t>(buffer, offset);
117 msg.auction_collar_extension = utils::unpack<uint32_t>(buffer, offset);
118}
119
120auto unpack_message(OperationalHaltMessage& msg, const char* buffer, size_t& offset) -> void {
121 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
122 msg.market_code = utils::unpack<char>(buffer, offset);
123 msg.operational_halt_action = utils::unpack<char>(buffer, offset);
124}
125
126auto unpack_message(AddOrderMessage& msg, const char* buffer, size_t& offset) -> void {
127 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
128 msg.buy_sell_indicator = utils::unpack<char>(buffer, offset);
129 msg.shares = utils::unpack<uint32_t>(buffer, offset);
130 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
131 msg.price = utils::unpack<uint32_t>(buffer, offset);
132}
133
134auto unpack_message(AddOrderMPIDAttributionMessage& msg, const char* buffer, size_t& offset)
135 -> void {
136 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
137 msg.buy_sell_indicator = utils::unpack<char>(buffer, offset);
138 msg.shares = utils::unpack<uint32_t>(buffer, offset);
139
140 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
141 msg.price = utils::unpack<uint32_t>(buffer, offset);
142 utils::unpack_string(buffer, offset, msg.attribution, 4);
143}
144
145auto unpack_message(OrderExecutedMessage& msg, const char* buffer, size_t& offset) -> void {
146 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
147 msg.executed_shares = utils::unpack<uint32_t>(buffer, offset);
148 msg.match_number = utils::unpack<uint64_t>(buffer, offset);
149}
150
151auto unpack_message(OrderExecutedWithPriceMessage& msg, const char* buffer, size_t& offset)
152 -> void {
153 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
154 msg.executed_shares = utils::unpack<uint32_t>(buffer, offset);
155 msg.match_number = utils::unpack<uint64_t>(buffer, offset);
156 msg.printable = utils::unpack<char>(buffer, offset);
157 msg.execution_price = utils::unpack<uint32_t>(buffer, offset);
158}
159
160auto unpack_message(OrderCancelMessage& msg, const char* buffer, size_t& offset) -> void {
161 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
162 msg.cancelled_shares = utils::unpack<uint32_t>(buffer, offset);
163}
164
165auto unpack_message(OrderDeleteMessage& msg, const char* buffer, size_t& offset) -> void {
166 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
167}
168
169auto unpack_message(OrderReplaceMessage& msg, const char* buffer, size_t& offset) -> void {
170 msg.original_order_reference_number = utils::unpack<uint64_t>(buffer, offset);
171 msg.new_order_reference_number = utils::unpack<uint64_t>(buffer, offset);
172 msg.shares = utils::unpack<uint32_t>(buffer, offset);
173 msg.price = utils::unpack<uint32_t>(buffer, offset);
174}
175
176auto unpack_message(NonCrossTradeMessage& msg, const char* buffer, size_t& offset) -> void {
177 msg.order_reference_number = utils::unpack<uint64_t>(buffer, offset);
178 msg.buy_sell_indicator = utils::unpack<char>(buffer, offset);
179 msg.shares = utils::unpack<uint32_t>(buffer, offset);
180
181 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
182
183 msg.price = utils::unpack<uint32_t>(buffer, offset);
184 msg.match_number = utils::unpack<uint64_t>(buffer, offset);
185}
186
187auto unpack_message(CrossTradeMessage& msg, const char* buffer, size_t& offset) -> void {
188 msg.shares = utils::unpack<uint64_t>(buffer, offset);
189 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
190
191 msg.cross_price = utils::unpack<uint32_t>(buffer, offset);
192 msg.match_number = utils::unpack<uint64_t>(buffer, offset);
193 msg.cross_type = utils::unpack<char>(buffer, offset);
194}
195
196auto unpack_message(BrokenTradeMessage& msg, const char* buffer, size_t& offset) -> void {
197 msg.match_number = utils::unpack<uint64_t>(buffer, offset);
198}
199
200auto unpack_message(NOIIMessage& msg, const char* buffer, size_t& offset) -> void {
201 msg.paired_shares = utils::unpack<uint64_t>(buffer, offset);
202 msg.imbalance_shares = utils::unpack<uint64_t>(buffer, offset);
203 msg.imbalance_direction = utils::unpack<char>(buffer, offset);
204
205 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
206
207 msg.far_price = utils::unpack<uint32_t>(buffer, offset);
208 msg.near_price = utils::unpack<uint32_t>(buffer, offset);
209 msg.current_reference_price = utils::unpack<uint32_t>(buffer, offset);
210 msg.cross_type = utils::unpack<char>(buffer, offset);
211 msg.price_variation_indicator = utils::unpack<char>(buffer, offset);
212}
213
215auto unpack_message(RPIMsg& msg, const char* buffer, size_t& offset) -> void {
216 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
217 msg.interest_flag = utils::unpack<char>(buffer, offset);
218}
219
220auto unpack_message(DLCRMessage& msg, const char* buffer, size_t& offset) -> void {
221 utils::unpack_string(buffer, offset, msg.stock, STOCK_LEN);
222 msg.open_eligibility_status = utils::unpack<char>(buffer, offset);
223 msg.minimum_allowable_price = utils::unpack<uint32_t>(buffer, offset);
224 msg.maximum_allowable_price = utils::unpack<uint32_t>(buffer, offset);
225 msg.near_execution_price = utils::unpack<uint32_t>(buffer, offset);
226 msg.near_execution_time = utils::unpack<uint64_t>(buffer, offset);
227 msg.lower_price_range_collar = utils::unpack<uint32_t>(buffer, offset);
228 msg.upper_price_range_collar = utils::unpack<uint32_t>(buffer, offset);
229}
230
231namespace {
232
234
237template <typename MsgType>
238auto decode_typed(const char* buffer) -> Message {
239 MsgType msg;
240 std::size_t offset = 1; // Skip the message type byte.
241
242 msg.stock_locate = utils::unpack<std::uint16_t>(buffer, offset);
243 msg.tracking_number = utils::unpack<std::uint16_t>(buffer, offset);
244 msg.timestamp = utils::unpack_timestamp(buffer, offset);
245
246 unpack_message(msg, buffer, offset);
247
248 return msg;
249}
250
252struct DispatchEntry {
253 std::uint16_t wire_size {0};
254 Message (*decode)(const char*) {nullptr};
255};
256
257constexpr std::size_t DISPATCH_TABLE_SIZE = 256;
258
262consteval auto build_dispatch_table() -> std::array<DispatchEntry, DISPATCH_TABLE_SIZE> {
263 std::array<DispatchEntry, DISPATCH_TABLE_SIZE> table {};
264
265 auto add = [&table]<typename MsgType>(char type) {
266 table[static_cast<unsigned char>(type)] =
267 DispatchEntry {static_cast<std::uint16_t>(WIRE_SIZE<MsgType>), &decode_typed<MsgType>};
268 };
269
270 // The canonical message-type registry lives in itch/detail/wire.hpp so the
271 // dispatch table, overlay, and encoder all share one definition.
273
274 return table;
275}
276
277constexpr auto DISPATCH_TABLE = build_dispatch_table();
278
279} // namespace
280
281auto Parser::report_error(ParseError error, char message_type) -> void {
282 switch (error) {
284 ++m_unknown_message_count;
285 break;
288 ++m_malformed_message_count;
289 break;
290 }
291 if (m_error_callback) {
292 m_error_callback(error, message_type);
293 }
294}
295
296auto Parser::parse_impl(const char* data, std::size_t size, const MessageCallback& callback)
297 -> std::optional<ParseError> {
298 std::size_t offset = 0;
299 while (offset < size) {
300 // Ensure we can read the 2-byte length prefix.
301 if (offset + sizeof(std::uint16_t) > size) {
302 report_error(ParseError::truncated, '\0');
304 }
305 std::uint16_t length {};
306 std::memcpy(&length, data + offset, sizeof(length));
307 length = utils::from_big_endian(length);
308 offset += sizeof(std::uint16_t);
309
310 if (length == 0) {
311 continue; // Skip zero-length padding frames.
312 }
313
314 // Ensure the full declared payload is present.
315 if (offset + length > size) {
316 report_error(ParseError::truncated, '\0');
318 }
319
320 const char* message = data + offset;
321 const char message_type = message[0];
322 offset += length;
323
324 const DispatchEntry& entry = DISPATCH_TABLE[static_cast<unsigned char>(message_type)];
325 if (entry.decode == nullptr) {
326 // Unknown type: skip and count rather than writing to a global stream.
328 continue;
329 }
330 // A frame shorter than the type requires would make the decoder read into
331 // the next frame; reject it. A longer frame is tolerated for forward
332 // compatibility (the trailing bytes are simply skipped).
333 if (length < entry.wire_size) {
335 continue;
336 }
337
338 callback(entry.decode(message));
339 }
340 return std::nullopt;
341}
342
343auto Parser::parse(const char* data, size_t size, const MessageCallback& callback) -> void {
344 if (parse_impl(data, size, callback).has_value()) {
345 throw std::runtime_error("Incomplete message at end of buffer.");
346 }
347}
348
349constexpr size_t average_message_size = 20;
350
351auto Parser::parse(const char* data, size_t size) -> std::vector<Message> {
352 std::vector<Message> messages;
353 messages.reserve(size / average_message_size);
354 parse(data, size, [&](const Message& msg) { messages.push_back(msg); });
355 return messages;
356}
357
358auto Parser::parse(const char* data, size_t size, const std::vector<char>& messages)
359 -> std::vector<Message> {
360 std::vector<Message> results;
361 std::set<char> filter(messages.begin(), messages.end());
362
363 if (filter.empty()) {
364 return results;
365 }
366 results.reserve(size / average_message_size);
367
368 auto callback = [&](const Message& msg) {
369 char message_type = std::visit([](auto&& arg) { return arg.message_type; }, msg);
370 if (filter.contains(message_type)) {
371 results.push_back(msg);
372 }
373 };
374 parse(data, size, callback);
375 return results;
376}
377
378namespace {
379// std::byte and char may legitimately alias; route through void* to obtain the
380// char view the framing core works with, without a forbidden reinterpret_cast.
381auto as_char_ptr(std::span<const std::byte> data) -> const char* {
382 // char may alias std::byte; the house style forbids reinterpret_cast, so the
383 // void* hop is the intended form here.
384 const void* raw = data.data();
385 return static_cast<const char*>(raw); // NOLINT(bugprone-casting-through-void)
386}
387} // namespace
388
389auto Parser::parse(std::span<const std::byte> data, const MessageCallback& callback) -> void {
390 parse(as_char_ptr(data), data.size(), callback);
391}
392
393auto Parser::parse(std::span<const std::byte> data) -> std::vector<Message> {
394 return parse(as_char_ptr(data), data.size());
395}
396
397auto Parser::parse(std::span<const std::byte> data, const std::vector<char>& messages)
398 -> std::vector<Message> {
399 return parse(as_char_ptr(data), data.size(), messages);
400}
401
402#ifdef __cpp_lib_expected
403auto Parser::try_parse(std::span<const std::byte> data, const MessageCallback& callback)
404 -> std::expected<void, ParseError> {
405 if (auto error = parse_impl(as_char_ptr(data), data.size(), callback)) {
406 return std::unexpected(*error);
407 }
408 return {};
409}
410
411auto Parser::try_parse(std::span<const std::byte> data
412) -> std::expected<std::vector<Message>, ParseError> {
413 std::vector<Message> messages;
414 messages.reserve(data.size() / average_message_size);
415 if (auto error = parse_impl(as_char_ptr(data), data.size(), [&](const Message& msg) {
416 messages.push_back(msg);
417 })) {
418 return std::unexpected(*error);
419 }
420 return messages;
421}
422#endif
423
425 m_error_callback = std::move(callback);
426}
427
428// Read the whole stream into a buffer
429static auto read_stream_into_buffer(std::istream& data) -> std::vector<char> {
430 data.seekg(0, std::ios::end);
431 auto size = data.tellg();
432 data.seekg(0, std::ios::beg);
433
434 if (size < 0) {
435 throw std::runtime_error("Failed to determine stream size.");
436 }
437
438 std::vector<char> buffer(static_cast<size_t>(size));
439 data.read(buffer.data(), size);
440 return buffer;
441}
442
443auto Parser::parse(std::istream& data, const MessageCallback& callback) -> void {
444 auto buffer = read_stream_into_buffer(data);
445 parse(buffer.data(), buffer.size(), callback);
446}
447
448auto Parser::parse(std::istream& data) -> std::vector<Message> {
449 auto buffer = read_stream_into_buffer(data);
450 return parse(buffer.data(), buffer.size());
451}
452
453auto Parser::parse(std::istream& data, const std::vector<char>& messages) -> std::vector<Message> {
454 auto buffer = read_stream_into_buffer(data);
455 return parse(buffer.data(), buffer.size(), messages);
456}
457
458} // namespace itch
auto parse(const char *data, size_t size, const MessageCallback &callback) -> void
Parses messages from a memory buffer and invokes a callback for each.
Definition parser.cpp:343
auto set_error_callback(ErrorCallback callback) -> void
Registers a callback invoked for each recoverable framing problem.
Definition parser.cpp:424
char message_type
Definition csv_sink.cpp:15
constexpr std::size_t WIRE_SIZE
The exact on-wire size, in bytes, of a fully formed message of type T.
Definition wire.hpp:28
constexpr auto for_each_message_type(Visitor &&visitor) -> void
Invokes visitor.template operator()<MsgType>(type_byte) once for each ITCH 5.0 message type,...
Definition wire.hpp:48
ParseError
Categories of recoverable problems encountered while framing a feed.
Definition parser.hpp:47
@ truncated
The buffer ended in the middle of a frame.
@ size_mismatch
The declared length is shorter than the message type requires.
@ unknown_type
The message type byte does not correspond to a known message.
constexpr int STOCK_LEN
Definition messages.hpp:485
std::function< void(ParseError, char)> ErrorCallback
The signature for the optional diagnostics callback.
Definition parser.hpp:58
std::variant< SystemEventMessage, StockDirectoryMessage, StockTradingActionMessage, RegSHOMessage, MarketParticipantPositionMessage, MWCBDeclineLevelMessage, MWCBStatusMessage, IPOQuotingPeriodUpdateMessage, LULDAuctionCollarMessage, OperationalHaltMessage, AddOrderMessage, AddOrderMPIDAttributionMessage, OrderExecutedMessage, OrderExecutedWithPriceMessage, OrderCancelMessage, OrderDeleteMessage, OrderReplaceMessage, NonCrossTradeMessage, CrossTradeMessage, BrokenTradeMessage, NOIIMessage, RetailPriceImprovementIndicatorMessage, DLCRMessage > Message
A variant able to hold any one of the ITCH 5.0 message structs.
Definition messages.hpp:483
constexpr size_t average_message_size
Definition parser.cpp:349
std::function< void(const Message &)> MessageCallback
The signature for the callback function used in streaming parse methods.
Definition parser.hpp:40
static auto read_stream_into_buffer(std::istream &data) -> std::vector< char >
Definition parser.cpp:429
auto unpack_message(SystemEventMessage &msg, const char *buffer, size_t &offset) -> void
Definition parser.cpp:51
std::uint16_t wire_size
Expected on-wire frame size (0 == unknown type).
Definition parser.cpp:253
Public parsing interface for NASDAQ TotalView-ITCH 5.0 feeds, plus the low-level byte-unpacking utili...
Add Order, With MPID Attribution (F): like Add Order, but attributed to a market participant.
Definition messages.hpp:229
Add Order, No MPID Attribution (A): a new displayable order has been accepted and placed on the book.
Definition messages.hpp:211
Broken Trade (B): a previously disseminated execution has been broken and should be removed from any ...
Definition messages.hpp:370
Cross Trade (Q): the result of a cross (opening, closing, halt/IPO cross) for a security.
Definition messages.hpp:352
Direct Listing with Capital Raise Price Discovery (O): price-discovery data for a Direct Listing with...
Definition messages.hpp:424
IPO Quoting Period Update (K): the anticipated IPO quotation release time and price for a security.
Definition messages.hpp:159
LULD Auction Collar (J): the auction collar thresholds for a security in a Limit-Up Limit-Down tradin...
Definition messages.hpp:175
MWCB Decline Level (V): the Market-Wide Circuit Breaker breach points for the day.
Definition messages.hpp:130
MWCB Status (W): notification that a Market-Wide Circuit Breaker level has been breached.
Definition messages.hpp:145
Market Participant Position (L): a market participant's (market maker's) status in a security.
Definition messages.hpp:109
Net Order Imbalance Indicator (I): the imbalance and price-discovery data disseminated during the ope...
Definition messages.hpp:385
Trade, Non-Cross (P): an execution of a non-displayable order.
Definition messages.hpp:333
Operational Halt (h): an operational halt or resumption for a security on a specific market center.
Definition messages.hpp:194
Order Cancel (X): a partial cancellation reduced the shares of a resting order.
Definition messages.hpp:285
Order Delete (D): a resting order was cancelled in its entirety and removed from the book.
Definition messages.hpp:299
Order Executed (E): a resting order was executed in whole or in part at its display price.
Definition messages.hpp:249
Order Executed With Price (C): a resting order was executed at a price different from its display pri...
Definition messages.hpp:266
Order Replace (U): a resting order was replaced with a new order at a new reference number,...
Definition messages.hpp:314
Reg SHO Short Sale Price Test Restriction (Y): the Reg SHO short-sale restriction state for a securit...
Definition messages.hpp:94
Retail Price Improvement Indicator (N): the presence of retail price improvement interest on the bid,...
Definition messages.hpp:408
Stock Directory (R): the trading and listing reference data for a security, disseminated at the start...
Definition messages.hpp:49
Stock Trading Action (H): a change in the trading status of a security (halted, paused,...
Definition messages.hpp:77
System Event (S): signals a market or data-feed handler event.
Definition messages.hpp:33
Single source of truth for the ITCH 5.0 wire layout metadata shared by the eager parser,...