ITCHCPP 1.6.0
High-Performance NASDAQ TotalView-ITCH 5.0 Parser & Market-Data Platform
Loading...
Searching...
No Matches
l3_book.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <utility>
5
6namespace itch::book {
7
8L3Book::L3Book(std::string symbol) : m_symbol {std::move(symbol)} {}
9
10auto L3Book::side_levels(Side side) noexcept -> std::vector<Level>& {
11 return side == Side::buy ? m_bids : m_asks;
12}
13
14auto L3Book::side_levels(Side side) const noexcept -> const std::vector<Level>& {
15 return side == Side::buy ? m_bids : m_asks;
16}
17
18auto L3Book::allocate_node() -> std::uint32_t {
19 if (m_free_head != NIL) {
20 const std::uint32_t index = m_free_head;
21 m_free_head = m_pool[index].next;
22 m_pool[index] = OrderNode {};
23 return index;
24 }
25 m_pool.push_back(OrderNode {});
26 return static_cast<std::uint32_t>(m_pool.size() - 1);
27}
28
29auto L3Book::free_node(std::uint32_t node_index) -> void {
30 m_pool[node_index] = OrderNode {};
31 m_pool[node_index].next = m_free_head;
32 m_free_head = node_index;
33}
34
35auto L3Book::find_level(Side side, std::uint32_t price) const -> std::uint32_t {
36 const auto& levels = side_levels(side);
37 // Bids are sorted descending, asks ascending; binary search accordingly.
38 // std::ranges::lower_bound would be the modern spelling, but it trips a
39 // known Clang/libstdc++ interop gap on some toolchains, so this sticks
40 // with the plain iterator-pair form for portability.
41 if (side == Side::buy) {
42 // NOLINTNEXTLINE(modernize-use-ranges)
43 const auto iter = std::lower_bound(
44 levels.begin(),
45 levels.end(),
46 price,
47 [](const Level& level, std::uint32_t value) { return level.price > value; }
48 );
49 if (iter != levels.end() && iter->price == price) {
50 return static_cast<std::uint32_t>(iter - levels.begin());
51 }
52 } else {
53 // NOLINTNEXTLINE(modernize-use-ranges)
54 const auto iter = std::lower_bound(
55 levels.begin(),
56 levels.end(),
57 price,
58 [](const Level& level, std::uint32_t value) { return level.price < value; }
59 );
60 if (iter != levels.end() && iter->price == price) {
61 return static_cast<std::uint32_t>(iter - levels.begin());
62 }
63 }
64 return NIL;
65}
66
67auto L3Book::find_or_create_level(Side side, std::uint32_t price) -> std::uint32_t {
68 auto& levels = side_levels(side);
69 // See find_level's comment above on why these stay iterator-pair calls.
70 auto position =
72 ? std::lower_bound( // NOLINT(modernize-use-ranges)
73 levels.begin(),
74 levels.end(),
75 price,
76 [](const Level& level, std::uint32_t value) { return level.price > value; }
77 )
78 : std::lower_bound( // NOLINT(modernize-use-ranges)
79 levels.begin(),
80 levels.end(),
81 price,
82 [](const Level& level, std::uint32_t value) { return level.price < value; }
83 );
84 if (position != levels.end() && position->price == price) {
85 return static_cast<std::uint32_t>(position - levels.begin());
86 }
87 Level new_level {};
88 new_level.price = price;
89 const auto inserted = levels.insert(position, new_level);
90 return static_cast<std::uint32_t>(inserted - levels.begin());
91}
92
94 std::uint64_t reference_number, Side side, std::uint32_t shares, std::uint32_t price
95) -> void {
96 if (m_index.find(reference_number) != OrderIndex::NPOS) {
97 return; // Duplicate add; ignore to keep the book consistent.
98 }
99 const std::uint32_t node_index = allocate_node();
100 OrderNode& node = m_pool[node_index];
101 node.reference_number = reference_number;
102 node.shares = shares;
103 node.price = price;
104 node.side = side;
105
106 const std::uint32_t level_index = find_or_create_level(side, price);
107 Level& level = side_levels(side)[level_index];
108
109 // Append to the tail of the level FIFO to preserve time priority.
110 node.prev = level.tail;
111 node.next = NIL;
112 if (level.tail != NIL) {
113 m_pool[level.tail].next = node_index;
114 } else {
115 level.head = node_index;
116 }
117 level.tail = node_index;
118 level.total_shares += shares;
119 ++level.order_count;
120
121 m_index.insert(reference_number, node_index);
122}
123
124auto L3Book::unlink_node(std::uint32_t node_index) -> void {
125 OrderNode& node = m_pool[node_index];
126 const std::uint32_t level_index = find_level(node.side, node.price);
127 if (level_index == NIL) {
128 return;
129 }
130 auto& levels = side_levels(node.side);
131 Level& level = levels[level_index];
132
133 if (node.prev != NIL) {
134 m_pool[node.prev].next = node.next;
135 } else {
136 level.head = node.next;
137 }
138 if (node.next != NIL) {
139 m_pool[node.next].prev = node.prev;
140 } else {
141 level.tail = node.prev;
142 }
143 level.total_shares -= node.shares;
144 --level.order_count;
145 if (level.order_count == 0) {
146 levels.erase(levels.begin() + level_index);
147 }
148}
149
150auto L3Book::execute_order(std::uint64_t reference_number, std::uint32_t shares) -> std::uint32_t {
151 const std::uint32_t node_index = m_index.find(reference_number);
152 if (node_index == OrderIndex::NPOS) {
153 return 0;
154 }
155 OrderNode& node = m_pool[node_index];
156 const std::uint32_t removed = std::min(shares, node.shares);
157
158 if (removed == node.shares) {
159 unlink_node(node_index);
160 free_node(node_index);
161 m_index.erase(reference_number);
162 return removed;
163 }
164
165 node.shares -= removed;
166 const std::uint32_t level_index = find_level(node.side, node.price);
167 if (level_index != NIL) {
168 side_levels(node.side)[level_index].total_shares -= removed;
169 }
170 return removed;
171}
172
173auto L3Book::reduce_order(std::uint64_t reference_number, std::uint32_t shares) -> std::uint32_t {
174 // A partial cancel removes shares exactly as an execution does.
175 return execute_order(reference_number, shares);
176}
177
178auto L3Book::delete_order(std::uint64_t reference_number) -> void {
179 const std::uint32_t node_index = m_index.find(reference_number);
180 if (node_index == OrderIndex::NPOS) {
181 return;
182 }
183 unlink_node(node_index);
184 free_node(node_index);
185 m_index.erase(reference_number);
186}
187
189 std::uint64_t old_reference_number,
190 std::uint64_t new_reference_number,
191 std::uint32_t shares,
192 std::uint32_t price
193) -> void {
194 const std::uint32_t node_index = m_index.find(old_reference_number);
195 if (node_index == OrderIndex::NPOS) {
196 return;
197 }
198 const Side side = m_pool[node_index].side;
199 delete_order(old_reference_number);
200 add_order(new_reference_number, side, shares, price);
201}
202
203auto L3Book::contains(std::uint64_t reference_number) const -> bool {
204 return m_index.contains(reference_number);
205}
206
207auto L3Book::order_price(std::uint64_t reference_number) const -> std::optional<std::uint32_t> {
208 const std::uint32_t node_index = m_index.find(reference_number);
209 if (node_index == OrderIndex::NPOS) {
210 return std::nullopt;
211 }
212 return m_pool[node_index].price;
213}
214
215auto L3Book::order_side(std::uint64_t reference_number) const -> std::optional<Side> {
216 const std::uint32_t node_index = m_index.find(reference_number);
217 if (node_index == OrderIndex::NPOS) {
218 return std::nullopt;
219 }
220 return m_pool[node_index].side;
221}
222
223auto L3Book::bbo() const -> Bbo {
224 Bbo result {};
225 if (!m_bids.empty()) {
226 result.has_bid = true;
227 result.bid_price = StandardPrice {m_bids.front().price};
228 result.bid_shares = m_bids.front().total_shares;
229 }
230 if (!m_asks.empty()) {
231 result.has_ask = true;
232 result.ask_price = StandardPrice {m_asks.front().price};
233 result.ask_shares = m_asks.front().total_shares;
234 }
235 return result;
236}
237
238auto L3Book::depth(Side side, std::size_t max_levels) const -> std::vector<DepthLevel> {
239 const auto& levels = side_levels(side);
240 const std::size_t count = max_levels == 0 ? levels.size() : std::min(max_levels, levels.size());
241 std::vector<DepthLevel> result;
242 result.reserve(count);
243 for (std::size_t index = 0; index < count; ++index) {
244 result.push_back(DepthLevel {
245 .price = StandardPrice {levels[index].price},
246 .shares = levels[index].total_shares,
247 .order_count = levels[index].order_count,
248 });
249 }
250 return result;
251}
252
253auto L3Book::orders_at(Side side, std::uint32_t price) const -> std::vector<OrderView> {
254 std::vector<OrderView> result;
255 const std::uint32_t level_index = find_level(side, price);
256 if (level_index == NIL) {
257 return result;
258 }
259 const Level& level = side_levels(side)[level_index];
260 result.reserve(level.order_count);
261 for (std::uint32_t node_index = level.head; node_index != NIL;
262 node_index = m_pool[node_index].next) {
263 const OrderNode& node = m_pool[node_index];
264 result.push_back(OrderView {
265 .reference_number = node.reference_number,
266 .shares = node.shares,
267 .price = StandardPrice {node.price},
268 });
269 }
270 return result;
271}
272
273auto L3Book::level_count(Side side) const noexcept -> std::size_t {
274 return side_levels(side).size();
275}
276
277} // namespace itch::book
auto execute_order(std::uint64_t reference_number, std::uint32_t shares) -> std::uint32_t
Removes shares from an order on execution (ITCH E/C), deleting it when fully filled.
Definition l3_book.cpp:150
auto replace_order(std::uint64_t old_reference_number, std::uint64_t new_reference_number, std::uint32_t shares, std::uint32_t price) -> void
Replaces an order with a new reference number, size, and price on the same side (ITCH U).
Definition l3_book.cpp:188
auto orders_at(Side side, std::uint32_t price) const -> std::vector< OrderView >
The resting orders at a given price on a side, in time priority.
Definition l3_book.cpp:253
auto delete_order(std::uint64_t reference_number) -> void
Deletes an order in its entirety (ITCH D).
Definition l3_book.cpp:178
auto level_count(Side side) const noexcept -> std::size_t
The number of active price levels on a side.
Definition l3_book.cpp:273
auto order_side(std::uint64_t reference_number) const -> std::optional< Side >
The side of a resting order, if present.
Definition l3_book.cpp:215
auto contains(std::uint64_t reference_number) const -> bool
Whether an order with the given reference number is resting.
Definition l3_book.cpp:203
L3Book(std::string symbol={})
Constructs a book, optionally tagged with its stock symbol.
Definition l3_book.cpp:8
auto order_price(std::uint64_t reference_number) const -> std::optional< std::uint32_t >
The raw limit price of a resting order, if present.
Definition l3_book.cpp:207
auto reduce_order(std::uint64_t reference_number, std::uint32_t shares) -> std::uint32_t
Removes shares from an order on a partial cancel (ITCH X), deleting it when it reaches zero.
Definition l3_book.cpp:173
auto add_order(std::uint64_t reference_number, Side side, std::uint32_t shares, std::uint32_t price) -> void
Adds a new resting order to the book (ITCH A/F).
Definition l3_book.cpp:93
auto depth(Side side, std::size_t max_levels=0) const -> std::vector< DepthLevel >
Aggregated L2 depth for a side, best level first.
Definition l3_book.cpp:238
auto bbo() const -> Bbo
The current best bid and offer.
Definition l3_book.cpp:223
static constexpr std::uint32_t NPOS
Sentinel returned by find when a key is absent.
std::string reference_number
Definition csv_sink.cpp:20
std::string symbol
Definition csv_sink.cpp:19
std::string price
Definition csv_sink.cpp:23
char side
Definition csv_sink.cpp:21
std::string shares
Definition csv_sink.cpp:22
Single-symbol, order-level (L3) limit order book with allocation-light internals.
Side
Which side of the book an order rests on.
Definition l3_book.hpp:26
Best bid and offer of a single book.
Definition l3_book.hpp:46
bool has_bid
Whether a bid side exists.
Definition l3_book.hpp:47
Aggregated state of one price level, for L2 depth snapshots.
Definition l3_book.hpp:32
StandardPrice price
The level's limit price.
Definition l3_book.hpp:33
A single resting order, for L3 order-level snapshots.
Definition l3_book.hpp:39
std::uint64_t reference_number
Exchange order reference number.
Definition l3_book.hpp:40