Leka
A low-latency C++20 price-time-priority limit order book and matching engine
▶ Replay viewer
Loading...
Searching...
No Matches
order_book.cpp
1#include "lob/book/order_book.hpp"
2
3#include <stdexcept>
4
5namespace lob {
6
7/** @details See the header for why 65536 levels of tick 1 starting at 1 are adequate defaults for tests and examples only. */
9
10OrderBook::OrderBook(Price minPrice, Price tickSize, std::size_t levelCount)
11 : bids(minPrice, tickSize, levelCount, /*descending=*/true),
12 asks(minPrice, tickSize, levelCount, /*descending=*/false) {}
13
14/**
15 * @details A new order starts with equal original and remaining quantities.
16 * The shared insertion helper provides the same transactional guarantees as
17 * remainder insertion while keeping the public API concise.
18 */
20 Timestamp timestamp, OrderSide orderSide,
21 OrderType orderType, SequenceNumber sequenceNumber) {
22 return addOrderWithQuantities(orderId, price, quantity, quantity, timestamp,
23 orderSide, orderType, sequenceNumber);
24}
25
26/**
27 * @details The original quantity is preserved for audit and lifecycle
28 * semantics; only the remaining quantity is placed into the level aggregate.
29 */
31 OrderId orderId, Price price, Quantity originalQuantity,
32 Quantity remainingQuantity, Timestamp timestamp, OrderSide orderSide,
33 OrderType orderType, SequenceNumber sequenceNumber) {
34 return addOrderWithQuantities(orderId, price, originalQuantity,
35 remainingQuantity, timestamp, orderSide,
36 orderType, sequenceNumber);
37}
38
39/**
40 * @details Insertion is committed in this order: construct the Order, link it
41 * into the PriceLevel, then add the pointer to OrderIndex. On failure, the
42 * level link and any newly created level are removed before pool release.
43 */
44Order* OrderBook::addOrderWithQuantities(
45 OrderId orderId, Price price, Quantity originalQuantity,
46 Quantity remainingQuantity, Timestamp timestamp, OrderSide orderSide,
47 OrderType orderType, SequenceNumber sequenceNumber) {
48 if (orderType != OrderType::LIMIT) {
49 throw std::invalid_argument("OrderBook accepts resting limit orders only");
50 }
51 if (!originalQuantity.isValid() || !remainingQuantity.isValid() ||
52 remainingQuantity > originalQuantity) {
53 throw std::invalid_argument("Invalid original or remaining quantity");
54 }
55 // Duplicate IDs are detected by the index insertion below, which already
56 // hashes the key. Probing for the ID here first would hash it a second
57 // time on every accepted insert to catch a case the rollback handles.
58 Order* order = orderPool.allocate(orderId, price, originalQuantity,
59 remainingQuantity, timestamp, orderSide,
60 orderType, sequenceNumber);
61 bool addedToLevel = false;
62 PriceLadder* ladder = nullptr;
63 PriceLevel* level = nullptr;
64
65 try {
66 if (!order->isValid()) {
67 throw std::invalid_argument("Cannot add an invalid order");
68 }
69
70 // The level always exists; there is nothing to create and nothing
71 // that can fail here except an out-of-range or misaligned price.
72 ladder = order->isBuy() ? &bids : &asks;
73 level = &ladder->levelAt(order->getPrice());
74 const bool wasEmpty = level->isEmpty();
75 level->addOrder(order);
76 addedToLevel = true;
77 if (wasEmpty) {
78 ladder->markOccupied(order->getPrice());
79 }
80
81 orderIndex.addOrder(order);
82 } catch (...) {
83 if (addedToLevel) {
84 level->removeOrder(order);
85 if (level->isEmpty()) {
86 ladder->markEmpty(order->getPrice());
87 }
88 }
89 orderPool.release(order);
90 throw;
91 }
92
93 return order;
94}
95
96/** @details Looks up the order, then delegates all structural cleanup to removeOrder(). */
97bool OrderBook::cancelOrder(const OrderId& orderId) {
98 Order* order = orderIndex.findOrder(orderId);
99 if (order == nullptr) {
100 return false;
101 }
102 removeOrder(order);
103 return true;
104}
105
106/**
107 * @details The order is resolved once and mutated where it lies. Because the
108 * price is unchanged the order cannot move between levels, so no relinking,
109 * no level lookup, and no sequence number are involved.
110 */
111bool OrderBook::reduceOrder(const OrderId& orderId, Quantity newQuantity) {
112 Order* order = orderIndex.findOrder(orderId);
113 if (order == nullptr) {
114 return false;
115 }
116
117 const std::uint64_t remaining = order->getRemainingQuantity().getQuantity();
118 const std::uint64_t target = newQuantity.getQuantity();
119 if (target == 0 || target > remaining) {
120 throw std::invalid_argument(
121 "Reduction requires a nonzero quantity no greater than the remaining quantity");
122 }
123 if (target == remaining) {
124 return true;
125 }
126
127 order->setRemainingQuantity(newQuantity);
128 order->getPriceLevel()->reduceTotalQuantity(Quantity{remaining - target});
129 return true;
130}
131
132/**
133 * @details This is the single removal path used by cancellation and matching.
134 * It removes non-owning references while the Order is alive, then destroys and
135 * recycles the object through OrderPool.
136 *
137 * Order already carries a direct pointer to its PriceLevel, set when it was
138 * inserted, so removal uses that pointer instead of independently
139 * re-deriving the level from price through the ladder. PriceLevel::removeOrder
140 * still checks that the order actually belongs to the level it names, which
141 * is the same identity check a map-based re-lookup would have produced; the
142 * ladder-based lookup here would only have been useful for detecting a
143 * corrupted Order::priceLevel pointer, at the cost of a lookup on every
144 * removal to guard against a case OrderPool's own invariants already rule out.
145 */
147 if (order == nullptr) {
148 throw std::invalid_argument("Cannot remove a null order");
149 }
150 if (orderIndex.findOrder(order->getOrderId()) != order) {
151 throw std::logic_error("Order is not indexed in this book");
152 }
153
154 PriceLevel* level = order->getPriceLevel();
155 if (level == nullptr) {
156 throw std::logic_error("Order is not linked to a price level");
157 }
158 PriceLadder& ladder = order->isBuy() ? bids : asks;
159 const Price price = order->getPrice();
160
161 level->removeOrder(order);
162 if (level->isEmpty()) {
163 ladder.markEmpty(price);
164 }
165 orderIndex.removeOrder(order);
166 orderPool.release(order);
167}
168
170 return orderIndex.findOrder(orderId);
171}
172
173const Order* OrderBook::findOrder(const OrderId& orderId) const {
174 return orderIndex.findOrder(orderId);
175}
176
178 return bids.best();
179}
180
182 return asks.best();
183}
184
186 return bids.best();
187}
188
190 return asks.best();
191}
192
193void OrderBook::reserveOrderCapacity(std::size_t orderCount) {
194 orderPool.reserve(orderCount);
195 orderIndex.reserve(orderCount);
196}
197
198/**
199 * @details Walks each ladder outward from the touch via
200 * PriceLadder::forEachOccupied(), which visits levels in price priority
201 * without sorting because "best" is the lowest occupied index on both sides.
202 * Nothing here mutates book state, and no caller inside the matching path
203 * invokes it.
204 */
205void OrderBook::captureSnapshot(BookSnapshot& out, std::size_t depth) const {
206 out.bids.clear();
207 out.asks.clear();
208
209 bids.forEachOccupied(depth, [&out](const PriceLevel& level) {
210 out.bids.push_back(BookSnapshotLevel{
211 level.getPrice().getPrice(),
213 level.getOrderCount()});
214 });
215 asks.forEachOccupied(depth, [&out](const PriceLevel& level) {
216 out.asks.push_back(BookSnapshotLevel{
217 level.getPrice().getPrice(),
219 level.getOrderCount()});
220 });
221
222 out.bestBidRaw = out.bids.empty() ? 0 : out.bids.front().priceRaw;
223 out.bestAskRaw = out.asks.empty() ? 0 : out.asks.front().priceRaw;
224 out.bidLevelCount = bids.occupiedCount();
225 out.askLevelCount = asks.occupiedCount();
226}
227
228} // namespace lob
static constexpr std::size_t DefaultLevelCount
Number of representable price levels used by the default constructor.
void reserveOrderCapacity(std::size_t orderCount)
Pre-allocates order storage and index capacity for up to orderCount live orders.
PriceLevel * getBestBid()
Returns the mutable highest-priced bid level.
Order * addRestingRemainder(OrderId orderId, Price price, Quantity originalQuantity, Quantity remainingQuantity, Timestamp timestamp, OrderSide orderSide, OrderType orderType, SequenceNumber sequenceNumber)
Adds a partially filled limit order while preserving its history.
bool reduceOrder(const OrderId &orderId, Quantity newQuantity)
Shrinks a resting order in place, preserving its priority.
Order * addOrder(OrderId orderId, Price price, Quantity quantity, Timestamp timestamp, OrderSide orderSide, OrderType orderType, SequenceNumber sequenceNumber)
Adds a valid resting limit order to the appropriate side.
void captureSnapshot(BookSnapshot &out, std::size_t depth) const
Fills out with the top depth levels of each side.
OrderBook()
Creates an order book with a small default price range.
Definition order_book.cpp:8
bool cancelOrder(const OrderId &orderId)
Removes a resting order by ID.
Order * findOrder(const OrderId &orderId)
Finds an order by ID, or returns nullptr when absent.
void removeOrder(Order *order)
Removes a currently resting order from every book structure.
PriceLevel * getBestAsk()
Returns the mutable lowest-priced ask level.
Type-safe identifier for an order; zero is reserved as invalid.
Definition order_id.hpp:10
Represents a resting or incoming order and its lifecycle state.
Definition order.hpp:17
OrderId getOrderId() const
Returns the unique order identifier.
Definition order.cpp:23
Price getPrice() const
Returns the limit or reference price.
Definition order.cpp:24
PriceLevel * getPriceLevel() const
Returns the price level this order rests on, or nullptr when unlinked.
Definition order.cpp:36
Quantity getRemainingQuantity() const
Returns the quantity that has not yet executed.
Definition order.cpp:26
void setRemainingQuantity(Quantity quantity)
Updates quantity while the order remains at its current price level.
Definition order.cpp:62
bool isBuy() const
Returns true for a buy order.
Definition order.cpp:72
Fixed-range, tick-indexed array of PriceLevel slots for one side of a book.
void markEmpty(Price price)
Marks a level empty; call exactly once when its order count transitions from nonzero to zero.
Maintains FIFO resting orders at one price.
void reduceTotalQuantity(Quantity quantity)
Decreases aggregate quantity after a partial execution.
Price getPrice() const
Returns the level price.
std::size_t getOrderCount() const
Returns the number of resting orders.
void removeOrder(Order *order)
Unlinks an order and updates aggregate state.
bool isEmpty() const
Returns true when no orders are resting at this price.
Quantity getTotalQuantity() const
Returns the aggregate remaining quantity.
Type-safe nonzero price value used for price ordering.
Definition price.hpp:9
std::uint64_t getPrice() const
Returns the underlying numeric price.
Definition price.hpp:21
Type-safe unsigned order quantity; zero represents a filled state.
Definition quantity.hpp:8
bool isValid() const
Returns whether the quantity is valid for a submitted order.
Definition quantity.hpp:23
std::uint64_t getQuantity() const
Returns the underlying numeric quantity.
Definition quantity.hpp:20
Monotonic engine sequence used for deterministic processing order.
Nanoseconds since the Unix epoch; zero is reserved as invalid.
Definition timestamp.hpp:10
One aggregated price level as seen from outside the engine.
Top-of-book depth plus the counters a viewer needs for context.
std::uint64_t bestBidRaw
Best bid price in raw units, or 0 when no bids rest.
std::size_t bidLevelCount
Occupied level count per side, which may exceed the captured depth.
std::uint64_t bestAskRaw
Best ask price in raw units, or 0 when no asks rest.
std::vector< BookSnapshotLevel > bids
Top levels, best first.