Skip to content
Daniel Esambu
Work
Work
Projects
Things I've built
Leka
A C++20 matching engine
Latency Lab
(opens in a new tab)
A C++ course I wrote
Resume
The formal version
Writing
Writing
Poetry
Poems, shelved by what they're about
Essays
Notes and essays
Math
Math
Notes
Notes I've written up
IB papers
Worked exam questions
Calculus lessons
(opens in a new tab)
On YouTube
Life
Life
Photos
Things I stopped to look at
Training
Reps, not phases
Modeling
Measurements and photos
Resume
Say hi
Say hi
Resume
Menu
Leka
A low-latency C++20 price-time-priority limit order book and matching engine
Replay viewer
Toggle main menu visibility
Loading...
Searching...
No Matches
order_index.hpp
1
//include/lob/index/order_index.hpp
2
3
#ifndef ORDER_INDEX_HPP
4
#define ORDER_INDEX_HPP
5
#include "lob/types/order_id.hpp"
6
#include "lob/order/order.hpp"
7
#include <cstddef>
8
#include <vector>
9
10
namespace
lob {
11
12
/**
13
* @brief Open-addressed OrderId -> Order* index with average O(1) lookup.
14
*
15
* OrderId::isValid() reserves zero, which lets an empty Slot's default
16
* OrderId double as the "unoccupied" sentinel: no separate occupancy bitmap
17
* or tombstone state is needed. Slots live in one contiguous std::vector, so
18
* every element of the whole probe sequence for a lookup is one flat array,
19
* not the one-heap-allocation-per-entry, pointer-chasing structure a chained
20
* hash table (such as std::unordered_map) builds.
21
*
22
* This is called on every add, cancel, reduce, and fill, so it is the
23
* hottest lookup in the engine. Unlike PriceLevel's PriceLadder, there is no
24
* natural fixed bound on how many orders can be live at once, so this grows
25
* by amortized doubling like std::vector rather than being pre-sized once.
26
*
27
* Removal uses backward-shift deletion rather than tombstones: on removal,
28
* every following entry in the same probe run is shifted back into the
29
* vacated slot if doing so keeps it reachable, and the search for the next
30
* vacated slot continues until a genuinely empty one is found. This keeps
31
* lookups a single clean scan for empty forever, with no growing tombstone
32
* debt from a long session with many cancels, at the cost of removal being
33
* more than a single slot write.
34
*/
35
class
OrderIndex
{
36
public
:
37
/**
38
* @brief Adds an order and rejects duplicate IDs.
39
*
40
* @details Only checks this index's own invariants: that @p order is
41
* non-null and that its ID is not already present. It does not
42
* re-check @p order's overall field validity (side, type, price,
43
* quantity, timestamp) — that is OrderBook::addOrderWithQuantities's
44
* job, and it always runs before an order reaches any book structure,
45
* this index included. PriceLevel::addOrder follows the same
46
* division: each class enforces only the invariant it alone owns,
47
* rather than every class re-verifying the whole Order on every
48
* insert.
49
*/
50
void
addOrder
(
Order
* order);
51
52
/** Removes an order after verifying pointer identity. */
53
void
removeOrder
(
Order
* order);
54
55
/** Finds an order by ID, or returns nullptr when absent. */
56
Order
*
findOrder
(
const
OrderId
& orderId)
const
;
57
58
/** Returns the number of indexed orders. */
59
std::size_t
size
()
const
{
return
count; }
60
61
/**
62
* @brief Grows the table, if needed, so @p orderCount entries fit
63
* under MaxLoadFactor without a later doubling.
64
*
65
* Unlike PriceLadder, this table has no fixed bound to pre-size to
66
* exactly, so it still grows on demand for anyone who does not call
67
* this first (see the class comment). But a table that doubles while
68
* holding many live entries pays for an O(n) rehash of everything
69
* still indexed at the moment it happens, not the O(1) amortized cost
70
* the growth policy implies on average — a single one of those, late
71
* in a run with a large book, is a real tail-latency event (see
72
* ARCH_DECISIONS.md ADR-008). Calling this once, for a known or
73
* comfortably over-estimated order count, moves that rehash out of
74
* the hot path the same way OrderPool::reserve() moves page creation
75
* out of it.
76
*/
77
void
reserve
(std::size_t orderCount);
78
79
private
:
80
/** One slot; an OrderId{} key (0, reserved invalid) means empty. */
81
struct
Slot {
82
OrderId
key;
83
Order
* value{
nullptr
};
84
};
85
86
static
constexpr
std::size_t InitialCapacity = 16;
// must stay a power of two
87
static
constexpr
double
MaxLoadFactor = 0.5;
88
89
std::vector<Slot> slots;
90
std::size_t count{0};
91
92
std::size_t indexFor(
const
OrderId&
id
, std::size_t capacity)
const
;
93
std::size_t findSlot(
const
OrderId&
id
)
const
;
94
void
growIfNeeded();
95
};
96
97
}
// namespace lob
98
#endif
// ORDER_INDEX_HPP
lob::OrderId
Type-safe identifier for an order; zero is reserved as invalid.
Definition
order_id.hpp:10
lob::OrderIndex
Open-addressed OrderId -> Order* index with average O(1) lookup.
Definition
order_index.hpp:35
lob::OrderIndex::size
std::size_t size() const
Returns the number of indexed orders.
Definition
order_index.hpp:59
lob::OrderIndex::findOrder
Order * findOrder(const OrderId &orderId) const
Finds an order by ID, or returns nullptr when absent.
Definition
order_index.cpp:170
lob::OrderIndex::reserve
void reserve(std::size_t orderCount)
Grows the table, if needed, so orderCount entries fit under MaxLoadFactor without a later doubling.
Definition
order_index.cpp:63
lob::OrderIndex::removeOrder
void removeOrder(Order *order)
Removes an order after verifying pointer identity.
Definition
order_index.cpp:137
lob::OrderIndex::addOrder
void addOrder(Order *order)
Adds an order and rejects duplicate IDs.
Definition
order_index.cpp:115
lob::Order
Represents a resting or incoming order and its lifecycle state.
Definition
order.hpp:17
include
lob
index
order_index.hpp
Generated by
1.18.0