7#define SLICK_OB_INLINE inline
15 std::size_t interested_num_levels,
16 std::size_t initial_order_capacity,
17 std::size_t initial_level_capacity)
19 order_map_(initial_order_capacity),
20 order_pool_(initial_order_capacity),
22 interested_num_levels_(interested_num_levels) {
31 (void)initial_level_capacity;
44 Timestamp timestamp, uint64_t priority, uint64_t seq_num,
bool is_last_in_batch) {
58 if (order) [[unlikely]] {
59 if (order->
side != side) {
66 return (quantity == 0) ?
deleteOrder(order_id, timestamp, seq_num, is_last_in_batch) :
false;
75 return modifyOrder(order_id, price, quantity, timestamp, priority, seq_num, is_last_in_batch);
83 order =
order_pool_.construct(order_id, price, quantity, side, timestamp, priority);
95 level->insertOrder(order);
107 if (is_last_in_batch) {
115 level_idx, level_change_flag, seq_num);
125 Timestamp timestamp, uint64_t priority, uint64_t seq_num,
bool is_last_in_batch) {
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);
145 Timestamp new_timestamp, uint64_t new_priority,
146 uint64_t seq_num,
bool is_last_in_batch) {
167 if (new_quantity == 0) {
168 return deleteOrder(order_id, new_timestamp, seq_num, is_last_in_batch);
173 const uint64_t old_priority = order->
priority;
177 const uint64_t effective_new_priority = (new_priority == 0) ? new_timestamp : new_priority;
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);
184 if (!price_changed && !quantity_changed && !priority_changed) {
191 auto [old_level, old_level_idx] =
getLevel(side, old_price);
199 old_level->removeOrder(order);
200 old_level_total = old_level->getTotalQuantity();
201 const std::size_t old_level_orders = old_level->orderCount();
209 old_level_idx, old_level_change_flags, seq_num);
213 order->
price = new_price;
216 order->
priority = effective_new_priority;
219 auto [new_level, new_level_idx, is_new] =
getOrCreateLevel(side, new_price);
224 new_level->insertOrder(order);
228 if (quantity_changed) {
236 if (is_last_in_batch) {
241 notifyOrderUpdate(order, old_quantity, old_price, new_timestamp, new_level_idx, order_flags, seq_num);
243 new_level->orderCount(), new_level_idx, new_level_change_flags, seq_num);
255 if (priority_changed) {
257 level->removeOrder(order);
260 order->
priority = effective_new_priority;
261 level->insertOrder(order);
264 level->updateOrderQuantity(old_quantity, new_quantity);
272 if (is_new) [[unlikely]] {
276 if (is_last_in_batch) {
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);
313 auto [level, level_idx] =
getLevel(side, price);
318 if (is_last_in_batch) {
322 notifyOrderDelete(order, timestamp, std::numeric_limits<uint16_t>::max(), order_flags, seq_num);
331 level->removeOrder(order);
332 const Quantity level_total = level->getTotalQuantity();
333 const std::size_t level_orders = level->orderCount();
345 if (is_last_in_batch) {
352 notifyPriceLevelUpdate(timestamp, side, price, level_total, level_orders, level_idx, level_change_flags, seq_num);
382 SLICK_ASSERT(executed_quantity > 0 && executed_quantity <= order->quantity);
388 const uint64_t priority = order->
priority;
390 if (remaining == 0) {
392 return deleteOrder(order_id, timestamp, seq_num, is_last_in_batch);
395 return modifyOrder(order_id, price, remaining, timestamp, priority, seq_num, is_last_in_batch);
400 return order_map_.find(order_id);
410 return &bids.begin()->second;
419 return &asks.begin()->second;
429 const auto& level_map =
levels_[side];
430 std::vector<detail::PriceLevelL2> result;
432 if (level_map.empty()) {
436 const std::size_t count = (depth == 0) ? level_map.size() : std::min(depth, level_map.size());
437 result.reserve(count);
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);
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);
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};
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};
484 const auto& level_map = levels_[side];
485 if (index >= level_map.size()) {
488 auto it = level_map.begin();
489 std::advance(it, index);
495 return levels_[side].size();
500 std::size_t count = 0;
501 for (
const auto& [price, level] : levels_[side]) {
502 count += level.orderCount();
509 return levels_[side].empty();
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);
538 order_map_.erase(order->order_id);
539 level.orders.erase(order);
540 order_pool_.destroy(order);
551 auto& level_map =
levels_[side];
552 auto it = level_map.find(price);
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};
562 uint16_t index =
static_cast<uint16_t
>(std::distance(level_map.begin(), new_it));
563 return {&new_it->second, index,
true};
569 auto& level_map = levels_[side];
570 auto it = level_map.find(price);
572 if (it != level_map.end() && it->second.isEmpty()) {
581 const auto& level_map = levels_[side];
584 auto it = level_map.lower_bound(price);
587 if (it != level_map.end() && it->first == price) {
588 return static_cast<uint16_t
>(std::distance(level_map.begin(), it));
592 return static_cast<uint16_t
>(std::distance(level_map.begin(), it));
600 uint16_t level_index,
601 uint8_t change_flags,
602 uint64_t seq_num)
const {
623 uint16_t level_index,
624 uint8_t change_flags,
625 uint64_t seq_num)
const {
668 uint16_t level_index,
669 uint8_t change_flags,
670 uint64_t seq_num)
const {
681 static_cast<uint16_t
>(std::min<std::size_t>(order_count, std::numeric_limits<uint16_t>::max())),
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;
700 uint8_t bid_change_flags = 0;
701 uint8_t ask_change_flags = 0;
707 if (bid_change_flags || ask_change_flags) {
726 uint16_t level_idx = 0;
728 const auto& level = it->second;
729 for (
const auto& order : level.orders) {
750 const auto& level = it->second;
751 for (
const auto& order : level.orders) {
void notifyPriceLevelUpdate(const PriceLevelUpdate &update) const
Notify all observers of price level update.
void notifyTrade(const Trade &trade) const
Notify all observers of trade.
void notifyTopOfBookUpdate(const TopOfBook &tob) const
Notify all observers of top-of-book update.
void notifySnapshotEnd(SymbolId symbol, uint64_t seq_num, Timestamp timestamp) const
Notify all observers that snapshot processing is complete.
void notifySnapshotBegin(SymbolId symbol, uint64_t seq_num, Timestamp timestamp) const
Notify all observers that snapshot processing is beginning.
void notifyOrderUpdate(const OrderUpdate &update) const
Notify all observers of order update.
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.
detail::OrderMap order_map_
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,...
std::size_t interested_num_levels_
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_
ObserverManager observers_
uint16_t change_starting_index_
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.
virtual ~OrderBookL3()
Destructor.
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.
bool insert(Order *order) noexcept
Insert order into map.
Order * find(OrderId order_id) noexcept
Find order by OrderId.
bool erase(OrderId order_id) noexcept
Remove order from map.
#define SLICK_NAMESPACE_END
#define SLICK_UNLIKELY(x)
#define SLICK_NAMESPACE_BEGIN
#define SLICK_ASSERT(expr)
int64_t Quantity
Quantity type - volume or size.
int64_t Price
Price type - fixed-point representation Example: For 4 decimal places, price = actual_price * 10000 T...
uint64_t OrderId
Unique order identifier.
Side
Order side - using enum for easy array indexing.
constexpr uint16_t INVALID_INDEX
uint16_t SymbolId
Symbol identifier - can be hash or sequential ID.
uint64_t Timestamp
Timestamp in nanoseconds since epoch.
Level 3 order update event Note: quantity = 0 implies the order should be deleted.
Level 2 price level update event Note: quantity = 0 implies the price level should be deleted.
Top-of-book snapshot - best bid and ask.
std::array< uint8_t, 2 > change_flags
Trade event - represents an executed trade.
Order structure for Level 3 orderbook Uses intrusive list design for zero-allocation order queues at ...
Price level for Level 3 orderbook (order-by-order) Maintains a queue of individual orders at this pri...