Slick OrderBook 1.1.0
High-Performance C++23 OrderBook Library
Loading...
Searching...
No Matches
orderbook_l3_impl.hpp
Go to the documentation of this file.
1// Copyright 2026 Slick Quant
2// SPDX-License-Identifier: MIT
3
4#pragma once
5
6#ifndef SLICK_OB_INLINE
7#define SLICK_OB_INLINE inline
8#endif
9
10#include <limits>
11
13
14SLICK_OB_INLINE OrderBookL3::OrderBookL3(SymbolId symbol,
15 std::size_t interested_num_levels,
16 std::size_t initial_order_capacity,
17 std::size_t initial_level_capacity)
18 : symbol_(symbol),
19 order_map_(initial_order_capacity),
20 order_pool_(initial_order_capacity),
21 last_seq_num_(0),
22 interested_num_levels_(interested_num_levels) {
23 // Initialize level maps with comparators
24 // Note: Clang-17 with GCC-14 libstdc++ has issues with aggregate initialization
25 // Use constructor syntax instead of brace initialization to avoid C++23 tuple conversion issues
28
29 // Reserve capacity for price levels (flat_map doesn't have reserve in C++23)
30 // This will be used during insertions to pre-allocate space
31 (void)initial_level_capacity; // Mark as used
32
33 // Initialize cached top-of-book
35}
36
38 // Clean up all orders (no observer notifications during destruction)
41}
42
44 Timestamp timestamp, uint64_t priority, uint64_t seq_num, bool is_last_in_batch) {
45 SLICK_ASSERT(side < SideCount);
46
47 // Validate sequence number - reject out-of-order (seq_num < last_seq_num)
48 if (seq_num > 0) {
49 if (seq_num < last_seq_num_) {
50 // Out of order - reject
51 return false;
52 }
53 last_seq_num_ = seq_num;
54 }
55
56 // Check if order already exists
57 detail::Order* order = order_map_.find(order_id);
58 if (order) [[unlikely]] {
59 if (order->side != side) {
60 // Side mismatch implies OrderId reuse - reject.
61 SLICK_ASSERT(false);
62 return false;
63 }
64
65 if (SLICK_UNLIKELY(quantity <= 0)) {
66 return (quantity == 0) ? deleteOrder(order_id, timestamp, seq_num, is_last_in_batch) : false;
67 }
68
69 // Idempotent update - nothing to do.
70 if (order->price == price && order->quantity == quantity && order->priority == priority) {
71 return true;
72 }
73
74 // Call modifyOrder with the new parameters
75 return modifyOrder(order_id, price, quantity, timestamp, priority, seq_num, is_last_in_batch);
76 }
77
78 if (SLICK_UNLIKELY(quantity <= 0)) {
79 return false;
80 }
81
82 // Allocate order from pool
83 order = order_pool_.construct(order_id, price, quantity, side, timestamp, priority);
84 if (SLICK_UNLIKELY(order == nullptr)) {
85 return false;
86 }
87
88 // Get or create price level
89 auto [level, level_idx, is_new] = getOrCreateLevel(side, price);
90
91 // track starting index
92 change_starting_index_ = std::min<uint16_t>(change_starting_index_, level_idx);
93
94 // Insert order into level (maintains priority order)
95 level->insertOrder(order);
96
97 // Add to order map
98 order_map_.insert(order);
99
100 // New order: both price and quantity changed
101 uint8_t order_flags = PriceChanged | QuantityChanged;
102 uint8_t level_change_flag = QuantityChanged;
103 if (is_new) {
104 level_change_flag |= PriceChanged;
105 }
106 // Add LastInBatch flag if this is the last update
107 if (is_last_in_batch) {
108 order_flags |= LastInBatch;
109 level_change_flag |= LastInBatch;
110 }
111
112 // Notify observers
113 notifyOrderUpdate(order, 0, 0, timestamp, level_idx, order_flags, seq_num);
114 notifyPriceLevelUpdate(timestamp, side, price, level->getTotalQuantity(), level->orderCount(),
115 level_idx, level_change_flag, seq_num);
116 if (change_starting_index_ == 0 && is_last_in_batch) {
117 notifyTopOfBookIfChanged(timestamp);
119 }
120
121 return true;
122}
123
124SLICK_OB_INLINE bool OrderBookL3::addOrder(OrderId order_id, Side side, Price price, Quantity quantity,
125 Timestamp timestamp, uint64_t priority, uint64_t seq_num, bool is_last_in_batch) {
126 // Validate sequence number - reject out-of-order (seq_num < last_seq_num)
127 if (seq_num > 0) {
128 if (seq_num < last_seq_num_) {
129 // Out of order - reject
130 return false;
131 }
132 last_seq_num_ = seq_num;
133 }
134
135 // Strict add - fail if order already exists
136 if (order_map_.contains(order_id)) {
137 return false;
138 }
139 // Use timestamp as priority if not specified
140 uint64_t actual_priority = (priority == 0) ? timestamp : priority;
141 return addOrModifyOrder(order_id, side, price, quantity, timestamp, actual_priority, seq_num, is_last_in_batch);
142}
143
144SLICK_OB_INLINE bool OrderBookL3::modifyOrder(OrderId order_id, Price new_price, Quantity new_quantity,
145 Timestamp new_timestamp, uint64_t new_priority,
146 uint64_t seq_num, bool is_last_in_batch) {
147 // Validate sequence number - reject out-of-order (seq_num < last_seq_num)
148 if (seq_num > 0) {
149 if (seq_num < last_seq_num_) {
150 // Out of order - reject
151 return false;
152 }
153 last_seq_num_ = seq_num;
154 }
155
156 // Find order
157 detail::Order* order = order_map_.find(order_id);
158 if (!order) {
159 return false;
160 }
161
162 if (SLICK_UNLIKELY(new_quantity < 0)) {
163 return false;
164 }
165
166 // Check if this is a delete (quantity = 0)
167 if (new_quantity == 0) {
168 return deleteOrder(order_id, new_timestamp, seq_num, is_last_in_batch);
169 }
170
171 const Price old_price = order->price;
172 const Quantity old_quantity = order->quantity;
173 const uint64_t old_priority = order->priority;
174 const Side side = order->side;
175
176 // Calculate effective priority (0 = use timestamp as priority for FIFO ordering)
177 const uint64_t effective_new_priority = (new_priority == 0) ? new_timestamp : new_priority;
178
179 // Check what changed
180 const bool price_changed = (new_price != old_price);
181 const bool quantity_changed = (new_quantity != old_quantity);
182 const bool priority_changed = (effective_new_priority != old_priority);
183
184 if (!price_changed && !quantity_changed && !priority_changed) {
185 // No change
186 return true;
187 }
188
189 if (price_changed) {
190 // Price changed - remove from old level (if exists) and add to new level
191 auto [old_level, old_level_idx] = getLevel(side, old_price);
192 Quantity old_level_total = 0;
193
194 if (old_level) {
195 // track starting index
196 change_starting_index_ = std::min<uint16_t>(change_starting_index_, old_level_idx);
197
198 // Remove from old level
199 old_level->removeOrder(order);
200 old_level_total = old_level->getTotalQuantity();
201 const std::size_t old_level_orders = old_level->orderCount();
202
203 uint8_t old_level_change_flags = QuantityChanged;
204 if (removeLevelIfEmpty(side, old_price)) {
205 old_level_change_flags |= PriceChanged;
206 }
207 // Don't add LastInBatch to intermediate old level update
208 notifyPriceLevelUpdate(new_timestamp, side, old_price, old_level_total, old_level_orders,
209 old_level_idx, old_level_change_flags, seq_num);
210 }
211
212 // Update order fields
213 order->price = new_price;
214 order->quantity = new_quantity;
215 order->timestamp = new_timestamp;
216 order->priority = effective_new_priority;
217
218 // Get or create new level
219 auto [new_level, new_level_idx, is_new] = getOrCreateLevel(side, new_price);
220
221 change_starting_index_ = std::min<uint16_t>(change_starting_index_, new_level_idx);
222
223 // Insert into new level
224 new_level->insertOrder(order);
225
226 // Notify observers (price changed: both price and quantity flags)
227 uint8_t order_flags = PriceChanged;
228 if (quantity_changed) {
229 order_flags |= QuantityChanged;
230 }
231 uint8_t new_level_change_flags = QuantityChanged;
232 if (is_new) {
233 new_level_change_flags |= PriceChanged;
234 }
235 // Add LastInBatch flag if this is the last update
236 if (is_last_in_batch) {
237 order_flags |= LastInBatch;
238 new_level_change_flags |= LastInBatch;
239 }
240
241 notifyOrderUpdate(order, old_quantity, old_price, new_timestamp, new_level_idx, order_flags, seq_num);
242 notifyPriceLevelUpdate(new_timestamp, side, new_price, new_level->getTotalQuantity(),
243 new_level->orderCount(), new_level_idx, new_level_change_flags, seq_num);
244 if (change_starting_index_ == 0 && is_last_in_batch) {
245 notifyTopOfBookIfChanged(new_timestamp);
247 }
248
249 } else {
250 // Only quantity or priority changed (price stays the same)
251 auto [level, level_index, is_new] = getOrCreateLevel(side, old_price);
252
253 change_starting_index_ = std::min<uint16_t>(change_starting_index_, level_index);
254
255 if (priority_changed) {
256 // Priority changed - need to re-insert to maintain correct queue position
257 level->removeOrder(order);
258 order->quantity = new_quantity;
259 order->timestamp = new_timestamp;
260 order->priority = effective_new_priority;
261 level->insertOrder(order);
262 } else {
263 // Only quantity changed - update in place
264 level->updateOrderQuantity(old_quantity, new_quantity);
265 order->quantity = new_quantity;
266 order->timestamp = new_timestamp;
267 }
268
269 // Notify observers (only quantity changed)
270 uint8_t order_flags = QuantityChanged;
271 uint8_t level_change_flags = QuantityChanged;
272 if (is_new) [[unlikely]] {
273 level_change_flags |= PriceChanged;
274 }
275 // Add LastInBatch flag if this is the last update
276 if (is_last_in_batch) {
277 order_flags |= LastInBatch;
278 level_change_flags |= LastInBatch;
279 }
280
281 notifyOrderUpdate(order, old_quantity, old_price, new_timestamp, level_index, order_flags, seq_num);
282 notifyPriceLevelUpdate(new_timestamp, side, old_price, level->getTotalQuantity(), level->orderCount(), level_index, level_change_flags, seq_num);
283 if (change_starting_index_ == 0 && is_last_in_batch) {
284 notifyTopOfBookIfChanged(new_timestamp);
286 }
287 }
288
289 return true;
290}
291
292SLICK_OB_INLINE bool OrderBookL3::deleteOrder(OrderId order_id, Timestamp timestamp, uint64_t seq_num, bool is_last_in_batch) {
293 // Validate sequence number - reject out-of-order (seq_num < last_seq_num)
294 if (seq_num > 0) {
295 if (seq_num < last_seq_num_) {
296 // Out of order - reject
297 return false;
298 }
299 last_seq_num_ = seq_num;
300 }
301
302 // Find order
303 detail::Order* order = order_map_.find(order_id);
304 if (!order) {
305 return false;
306 }
307
308 order->timestamp = timestamp; // Update timestamp for deletion event
309 const Price price = order->price;
310 const Side side = order->side;
311
312 // Get level
313 auto [level, level_idx] = getLevel(side, price);
314 if (!level) {
315 // Level doesn't exist - data structure inconsistency
316 // Deletion: both price and quantity changed
317 uint8_t order_flags = PriceChanged | QuantityChanged;
318 if (is_last_in_batch) {
319 order_flags |= LastInBatch;
320 }
321 // Notify deletion, then clean up (use max index since level not found)
322 notifyOrderDelete(order, timestamp, std::numeric_limits<uint16_t>::max(), order_flags, seq_num);
323 order_map_.erase(order_id);
324 order_pool_.destroy(order);
325 return false;
326 }
327
328 change_starting_index_ = std::min<uint16_t>(change_starting_index_, level_idx);
329
330 // Remove from level
331 level->removeOrder(order);
332 const Quantity level_total = level->getTotalQuantity();
333 const std::size_t level_orders = level->orderCount();
334
335 // Remove from order map
336 order_map_.erase(order_id);
337
338 // Deletion: both price and quantity changed
339 uint8_t order_flags = PriceChanged | QuantityChanged;
340 uint8_t level_change_flags = QuantityChanged;
341 if (removeLevelIfEmpty(side, price)) {
342 level_change_flags |= PriceChanged;
343 }
344 // Add LastInBatch flag if this is the last update
345 if (is_last_in_batch) {
346 order_flags |= LastInBatch;
347 level_change_flags |= LastInBatch;
348 }
349
350 // Notify observers (before destroying order)
351 notifyOrderDelete(order, timestamp, level_idx, order_flags, seq_num);
352 notifyPriceLevelUpdate(timestamp, side, price, level_total, level_orders, level_idx, level_change_flags, seq_num);
353
354 // Destroy order
355 order_pool_.destroy(order);
356
357 // Notify ToB if changed
358 if (change_starting_index_ == 0 && is_last_in_batch) {
359 notifyTopOfBookIfChanged(timestamp);
361 }
362
363 return true;
364}
365
366SLICK_OB_INLINE bool OrderBookL3::executeOrder(OrderId order_id, Quantity executed_quantity, Timestamp timestamp, uint64_t seq_num, bool is_last_in_batch) {
367 // Validate sequence number - reject out-of-order (seq_num < last_seq_num)
368 if (seq_num > 0) {
369 if (seq_num < last_seq_num_) {
370 // Out of order - reject
371 return false;
372 }
373 last_seq_num_ = seq_num;
374 }
375
376 detail::Order* order = order_map_.find(order_id);
377 if (!order) {
378 return false;
379 }
380
381 if (SLICK_UNLIKELY(executed_quantity <= 0 || executed_quantity > order->quantity)) {
382 SLICK_ASSERT(executed_quantity > 0 && executed_quantity <= order->quantity);
383 return false;
384 }
385
386 const Quantity remaining = order->quantity - executed_quantity;
387 const Price price = order->price; // Preserve price
388 const uint64_t priority = order->priority; // Preserve priority
389
390 if (remaining == 0) {
391 // Fully executed - delete order (pass through timestamp, seq_num and is_last_in_batch)
392 return deleteOrder(order_id, timestamp, seq_num, is_last_in_batch);
393 } else {
394 // Partial execution - reduce quantity (pass through timestamp, priority, seq_num and is_last_in_batch)
395 return modifyOrder(order_id, price, remaining, timestamp, priority, seq_num, is_last_in_batch);
396 }
397}
398
400 return order_map_.find(order_id);
401}
402
404 const auto& bids = levels_[Side::Buy];
405 if (bids.empty()) {
406 return nullptr;
407 }
408 // FlatMap is sorted by key (Price) in ascending order by default
409 // For Buy side, we want highest price first, so use reverse iterator
410 return &bids.begin()->second;
411}
412
414 const auto& asks = levels_[Side::Sell];
415 if (asks.empty()) {
416 return nullptr;
417 }
418 // For Sell side, we want lowest price first (first element)
419 return &asks.begin()->second;
420}
421
425
426SLICK_OB_INLINE std::vector<detail::PriceLevelL2> OrderBookL3::getLevelsL2(Side side, std::size_t depth) const {
427 SLICK_ASSERT(side < SideCount);
428
429 const auto& level_map = levels_[side];
430 std::vector<detail::PriceLevelL2> result;
431
432 if (level_map.empty()) {
433 return result;
434 }
435
436 const std::size_t count = (depth == 0) ? level_map.size() : std::min(depth, level_map.size());
437 result.reserve(count);
438
439 if (side == Side::Buy) {
440 // For bids, iterate in reverse (highest price first)
441 auto it = level_map.begin();
442 for (std::size_t i = 0; i < count && it != level_map.end(); ++i, ++it) {
443 const auto& [price, level] = *it;
444 Timestamp latest_timestamp = level.orders.empty() ? 0 : level.orders.front()->timestamp;
445 result.emplace_back(price, level.getTotalQuantity(), latest_timestamp);
446 }
447 } else {
448 // For asks, iterate forward (lowest price first)
449 auto it = level_map.begin();
450 for (std::size_t i = 0; i < count && it != level_map.end(); ++i, ++it) {
451 const auto& [price, level] = *it;
452 Timestamp latest_timestamp = level.orders.empty() ? 0 : level.orders.front()->timestamp;
453 result.emplace_back(price, level.getTotalQuantity(), latest_timestamp);
454 }
455 }
456
457 return result;
458}
459
460SLICK_OB_INLINE std::pair<const detail::PriceLevelL3*, uint16_t> OrderBookL3::getLevel(Side side, Price price) const noexcept {
461 SLICK_ASSERT(side < SideCount);
462 const auto& level_map = levels_[side];
463 auto it = level_map.find(price);
464 if (it != level_map.end()) {
465 uint16_t index = static_cast<uint16_t>(std::distance(level_map.begin(), it));
466 return {&it->second, index};
467 }
468 return {nullptr, INVALID_INDEX};
469}
470
471SLICK_OB_INLINE std::pair<detail::PriceLevelL3*, uint16_t> OrderBookL3::getLevel(Side side, Price price) noexcept {
472 SLICK_ASSERT(side < SideCount);
473 auto& level_map = levels_[side];
474 auto it = level_map.find(price);
475 if (it != level_map.end()) {
476 uint16_t index = static_cast<uint16_t>(std::distance(level_map.begin(), it));
477 return {&it->second, index};
478 }
479 return {nullptr, INVALID_INDEX};
480}
481
482SLICK_OB_INLINE const detail::PriceLevelL3* OrderBookL3::getLevelByIndex(Side side, uint16_t index) const noexcept {
483 SLICK_ASSERT(side < SideCount);
484 const auto& level_map = levels_[side];
485 if (index >= level_map.size()) {
486 return nullptr;
487 }
488 auto it = level_map.begin();
489 std::advance(it, index);
490 return &it->second;
491}
492
493SLICK_OB_INLINE std::size_t OrderBookL3::levelCount(Side side) const noexcept {
494 SLICK_ASSERT(side < SideCount);
495 return levels_[side].size();
496}
497
498SLICK_OB_INLINE std::size_t OrderBookL3::orderCount(Side side) const noexcept {
499 SLICK_ASSERT(side < SideCount);
500 std::size_t count = 0;
501 for (const auto& [price, level] : levels_[side]) {
502 count += level.orderCount();
503 }
504 return count;
505}
506
507SLICK_OB_INLINE bool OrderBookL3::isEmpty(Side side) const noexcept {
508 SLICK_ASSERT(side < SideCount);
509 return levels_[side].empty();
510}
511
513 return levels_[Side::Buy].empty() && levels_[Side::Sell].empty();
514}
515
517 SLICK_ASSERT(side < SideCount);
518 releaseSide(side);
519 // Keep cached top-of-book in sync with the live levels
520 notifyTopOfBookIfChanged(timestamp);
521}
522
528
530 SLICK_ASSERT(side < SideCount);
531
532 // Delete all orders on this side
533 auto& level_map = levels_[side];
534 for (auto&& [price, level] : level_map) {
535 for (auto it = level.orders.begin(); it != level.orders.end(); ) {
536 auto* order = &(*it);
537 ++it; // Advance before unlinking/destroying.
538 order_map_.erase(order->order_id);
539 level.orders.erase(order);
540 order_pool_.destroy(order);
541 }
542 }
543
544 // Clear level map
545 level_map.clear();
546}
547
548SLICK_OB_INLINE std::tuple<detail::PriceLevelL3*, uint16_t, bool> OrderBookL3::getOrCreateLevel(Side side, Price price) {
549 SLICK_ASSERT(side < SideCount);
550
551 auto& level_map = levels_[side];
552 auto it = level_map.find(price);
553
554 if (it != level_map.end()) {
555 uint16_t index = static_cast<uint16_t>(std::distance(level_map.begin(), it));
556 return {&it->second, index, false};
557 }
558
559 // Create new level
560 auto [new_it, inserted] = level_map.emplace(price, detail::PriceLevelL3{price});
561 SLICK_ASSERT(inserted);
562 uint16_t index = static_cast<uint16_t>(std::distance(level_map.begin(), new_it));
563 return {&new_it->second, index, true};
564}
565
567 SLICK_ASSERT(side < SideCount);
568
569 auto& level_map = levels_[side];
570 auto it = level_map.find(price);
571
572 if (it != level_map.end() && it->second.isEmpty()) {
573 level_map.erase(it);
574 return true;
575 }
576 return false;
577}
578
579SLICK_OB_INLINE uint16_t OrderBookL3::calculateLevelIndex(Side side, Price price) const noexcept {
580 SLICK_ASSERT(side < SideCount);
581 const auto& level_map = levels_[side];
582
583 // Find the price level using lower_bound
584 auto it = level_map.lower_bound(price);
585
586 // If exact match, calculate its index
587 if (it != level_map.end() && it->first == price) {
588 return static_cast<uint16_t>(std::distance(level_map.begin(), it));
589 }
590
591 // Price not found - return the index where it would be inserted
592 return static_cast<uint16_t>(std::distance(level_map.begin(), it));
593}
594
596 const detail::Order* order,
597 Quantity old_quantity,
598 Price old_price,
599 Timestamp timestamp,
600 uint16_t level_index,
601 uint8_t change_flags,
602 uint64_t seq_num) const {
603 OrderUpdate update{
604 symbol_,
605 order->order_id,
606 order->side,
607 order->price,
608 order->quantity,
609 old_price,
610 old_quantity,
611 timestamp,
612 level_index,
613 order->priority,
614 change_flags,
615 seq_num
616 };
618}
619
621 const detail::Order* order,
622 Timestamp timestamp,
623 uint16_t level_index,
624 uint8_t change_flags,
625 uint64_t seq_num) const {
626 OrderUpdate update{
627 symbol_,
628 order->order_id,
629 order->side,
630 order->price,
631 0, // quantity = 0 means delete
632 order->price,
633 order->quantity,
634 timestamp,
635 level_index,
636 order->priority,
637 change_flags,
638 seq_num
639 };
641}
642
644 OrderId passive_order_id,
645 OrderId aggressive_order_id,
646 Side aggressor_side,
647 Price price,
648 Quantity quantity,
649 Timestamp timestamp) const {
650 Trade trade{
651 symbol_,
652 price,
653 quantity,
654 timestamp,
655 aggressor_side,
656 passive_order_id,
657 aggressive_order_id,
658 };
659 observers_.notifyTrade(trade);
660}
661
663 Timestamp timestamp,
664 Side side,
665 Price price,
666 Quantity total_quantity,
667 size_t order_count,
668 uint16_t level_index,
669 uint8_t change_flags,
670 uint64_t seq_num) const {
671 if (interested_num_levels_ > 0 && level_index >= interested_num_levels_) {
672 // Skip notifications for levels beyond the interested range
673 return;
674 }
675 PriceLevelUpdate update{
676 timestamp,
677 symbol_,
678 side,
679 price,
680 total_quantity,
681 static_cast<uint16_t>(std::min<std::size_t>(order_count, std::numeric_limits<uint16_t>::max())),
682 level_index,
683 change_flags,
684 seq_num
685 };
687}
688
690 // Compute current top-of-book
691 const auto* bid = getBestBid();
692 const auto* ask = getBestAsk();
693
694 Price new_best_bid = bid ? bid->price : 0;
695 Quantity new_bid_qty = bid ? bid->getTotalQuantity() : 0;
696 Price new_best_ask = ask ? ask->price : 0;
697 Quantity new_ask_qty = ask ? ask->getTotalQuantity() : 0;
698
699 // Check if best bid or ask changed
700 uint8_t bid_change_flags = 0;
701 uint8_t ask_change_flags = 0;
702 bid_change_flags += cached_tob_.best_bid != new_best_bid ? ChangeFlag::PriceChanged : 0;
703 bid_change_flags += cached_tob_.bid_quantity != new_bid_qty ? ChangeFlag::QuantityChanged : 0;
704 ask_change_flags += cached_tob_.best_ask != new_best_ask ? ChangeFlag::PriceChanged : 0;
705 ask_change_flags += cached_tob_.ask_quantity != new_ask_qty ? ChangeFlag::QuantityChanged : 0;
706
707 if (bid_change_flags || ask_change_flags) {
708 // Update cached values
709 cached_tob_.best_bid = new_best_bid;
710 cached_tob_.bid_quantity = new_bid_qty;
711 cached_tob_.best_ask = new_best_ask;
712 cached_tob_.ask_quantity = new_ask_qty;
713 cached_tob_.timestamp = timestamp;
714 cached_tob_.change_flags[0] = bid_change_flags;
715 cached_tob_.change_flags[1] = ask_change_flags;
716
717 // Notify observers
719 }
720}
721
724
725 // Bids: highest price first (best = index 0)
726 uint16_t level_idx = 0;
727 for (auto it = levels_[Side::Buy].begin(); it != levels_[Side::Buy].end(); ++it, ++level_idx) {
728 const auto& level = it->second;
729 for (const auto& order : level.orders) {
730 OrderUpdate update{
731 symbol_,
732 order.order_id,
733 order.side,
734 order.price,
735 order.quantity,
736 order.price,
737 order.quantity,
738 timestamp,
739 level_idx,
740 order.priority,
741 static_cast<uint8_t>(PriceChanged | QuantityChanged)
742 };
744 }
745 }
746
747 // Asks: lowest price first (best = index 0)
748 level_idx = 0;
749 for (auto it = levels_[Side::Sell].begin(); it != levels_[Side::Sell].end(); ++it, ++level_idx) {
750 const auto& level = it->second;
751 for (const auto& order : level.orders) {
752 OrderUpdate update{
753 symbol_,
754 order.order_id,
755 order.side,
756 order.price,
757 order.quantity,
758 order.price,
759 order.quantity,
760 timestamp,
761 level_idx,
762 order.priority,
763 static_cast<uint8_t>(PriceChanged | QuantityChanged)
764 };
766 }
767 }
768
770}
771
void notifyPriceLevelUpdate(const PriceLevelUpdate &update) const
Notify all observers of price level update.
Definition observer.hpp:94
void notifyTrade(const Trade &trade) const
Notify all observers of trade.
Definition observer.hpp:108
void notifyTopOfBookUpdate(const TopOfBook &tob) const
Notify all observers of top-of-book update.
Definition observer.hpp:115
void notifySnapshotEnd(SymbolId symbol, uint64_t seq_num, Timestamp timestamp) const
Notify all observers that snapshot processing is complete.
Definition observer.hpp:129
void notifySnapshotBegin(SymbolId symbol, uint64_t seq_num, Timestamp timestamp) const
Notify all observers that snapshot processing is beginning.
Definition observer.hpp:122
void notifyOrderUpdate(const OrderUpdate &update) const
Notify all observers of order update.
Definition observer.hpp:101
void notifyOrderDelete(const detail::Order *order, Timestamp timestamp, uint16_t level_index, uint8_t change_flags, uint64_t seq_num) const
Notify observers of order delete with level index.
virtual bool executeOrder(OrderId order_id, Quantity executed_quantity, Timestamp timestamp, uint64_t seq_num=0, bool is_last_in_batch=true)
Execute (partially fill) an order.
std::pair< const detail::PriceLevelL3 *, uint16_t > getLevel(Side side, Price price) const noexcept
Get L3 price level by price Access the orders at this price via level->orders (IntrusiveList)
TopOfBook getTopOfBook() const noexcept
Get top-of-book snapshot (aggregated L2 view)
void releaseSide(Side side) noexcept
Free all orders and levels on one side without any notification.
const detail::PriceLevelL3 * getLevelByIndex(Side side, uint16_t index) const noexcept
Get L3 price level by index (0 = best)
detail::FlatMap< Price, detail::PriceLevelL3, PriceComparator > PriceLevelMap
bool addOrModifyOrder(OrderId order_id, Side side, Price price, Quantity quantity, Timestamp timestamp, uint64_t priority, uint64_t seq_num=0, bool is_last_in_batch=true)
Add a new order or modify existing order if OrderId already exists If the order already exists,...
detail::ObjectPool< detail::Order > order_pool_
std::vector< detail::PriceLevelL2 > getLevelsL2(Side side, std::size_t depth=0) const
Get aggregated L2 price levels for a side Converts L3 data to L2 by aggregating quantities at each pr...
bool removeLevelIfEmpty(Side side, Price price) noexcept
Remove price level if empty.
void notifyPriceLevelUpdate(Timestamp timestamp, Side side, Price price, Quantity total_quantity, size_t order_count, uint16_t level_index, uint8_t change_flags, uint64_t seq_num) const
Notify observers of price level update (L2 aggregated view)
bool addOrder(OrderId order_id, Side side, Price price, Quantity quantity, Timestamp timestamp, uint64_t priority=0, uint64_t seq_num=0, bool is_last_in_batch=true)
Add a new order (strict - fails if order already exists)
void emitSnapshot(Timestamp timestamp)
Emit complete orderbook snapshot to observers (L3 MBO) Useful for replaying full book state to a new ...
void clear(Timestamp timestamp=0)
Clear all orders (both sides) Refreshes the cached top-of-book (and notifies onTopOfBookUpdate if it ...
void notifyOrderUpdate(const detail::Order *order, Quantity old_quantity, Price old_price, Timestamp timestamp, uint16_t level_index, uint8_t change_flags, uint64_t seq_num) const
Notify observers of order update with level index and change flags.
bool isEmpty() const noexcept
Check if book is empty (both sides)
const detail::PriceLevelL3 * getBestBid() const noexcept
Get best bid (highest buy price)
void notifyTopOfBookIfChanged(Timestamp timestamp)
Notify observers of top-of-book update if best changed Updates cached_tob_ after notification.
bool deleteOrder(OrderId order_id, Timestamp timestamp, uint64_t seq_num=0, bool is_last_in_batch=true)
Delete an order.
std::array< PriceLevelMap, SideCount > levels_
bool modifyOrder(OrderId order_id, Price new_price, Quantity new_quantity, Timestamp new_timestamp, uint64_t new_priority=0, uint64_t seq_num=0, bool is_last_in_batch=true)
Modify an existing order's price and/or quantity Compares new values with old to determine what chang...
std::size_t orderCount() const noexcept
Get total number of orders.
void clearSide(Side side, Timestamp timestamp=0)
Clear all orders on one side Does not emit order/level updates, but refreshes the cached top-of-book ...
const detail::Order * findOrder(OrderId order_id) const noexcept
Find order by OrderId.
uint16_t calculateLevelIndex(Side side, Price price) const noexcept
Calculate price level index for a given side and price.
void notifyTrade(OrderId passive_order_id, OrderId aggressive_order_id, Side aggressor_side, Price price, Quantity quantity, Timestamp timestamp) const
Notify observers of trade.
std::size_t levelCount(Side side) const noexcept
Get number of price levels on a side.
std::tuple< detail::PriceLevelL3 *, uint16_t, bool > getOrCreateLevel(Side side, Price price)
Get or create price level, returns pointer, index and if new level created.
const detail::PriceLevelL3 * getBestAsk() const noexcept
Get best ask (lowest sell price)
bool contains(OrderId order_id) const noexcept
Check if order exists.
Definition order_map.hpp:58
bool insert(Order *order) noexcept
Insert order into map.
Definition order_map.hpp:30
Order * find(OrderId order_id) noexcept
Find order by OrderId.
Definition order_map.hpp:47
bool erase(OrderId order_id) noexcept
Remove order from map.
Definition order_map.hpp:40
#define SLICK_NAMESPACE_END
Definition config.hpp:127
#define SLICK_UNLIKELY(x)
Definition config.hpp:91
#define SLICK_NAMESPACE_BEGIN
Definition config.hpp:126
#define SLICK_ASSERT(expr)
Definition config.hpp:135
int64_t Quantity
Quantity type - volume or size.
Definition types.hpp:21
int64_t Price
Price type - fixed-point representation Example: For 4 decimal places, price = actual_price * 10000 T...
Definition types.hpp:18
uint64_t OrderId
Unique order identifier.
Definition types.hpp:24
Side
Order side - using enum for easy array indexing.
Definition types.hpp:35
constexpr uint16_t INVALID_INDEX
Definition types.hpp:32
uint16_t SymbolId
Symbol identifier - can be hash or sequential ID.
Definition types.hpp:27
uint64_t Timestamp
Timestamp in nanoseconds since epoch.
Definition types.hpp:30
#define SLICK_OB_INLINE
#define SLICK_OB_INLINE
Level 3 order update event Note: quantity = 0 implies the order should be deleted.
Definition events.hpp:67
Level 2 price level update event Note: quantity = 0 implies the price level should be deleted.
Definition events.hpp:21
Top-of-book snapshot - best bid and ask.
Definition events.hpp:142
std::array< uint8_t, 2 > change_flags
Definition events.hpp:149
Trade event - represents an executed trade.
Definition events.hpp:115
Order structure for Level 3 orderbook Uses intrusive list design for zero-allocation order queues at ...
Definition order.hpp:14
Price level for Level 3 orderbook (order-by-order) Maintains a queue of individual orders at this pri...