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
price_ladder.hpp
1
// include/lob/book/price_ladder.hpp
2
#ifndef PRICE_LADDER_HPP
3
#define PRICE_LADDER_HPP
4
5
#include "lob/book/price_level.hpp"
6
#include "lob/types/price.hpp"
7
8
#include <concepts>
9
#include <cstddef>
10
#include <cstdint>
11
#include <vector>
12
13
namespace
lob {
14
15
/**
16
* @brief Fixed-range, tick-indexed array of PriceLevel slots for one side of a book.
17
*
18
* A price is not treated as an ordered-map key; it is treated as an index.
19
* Every level in [minPrice, minPrice + (levelCount-1)*tickSize] is constructed
20
* once, at PriceLadder construction, and never destroyed or moved for the
21
* ladder's lifetime. Adding an order at a price that already has resting
22
* orders, or that has never had one, is the same O(1) array access with no
23
* allocation and no tree rebalancing; the old std::map<Price, PriceLevel>
24
* design paid a node allocation for the former and a pointer-chasing tree
25
* descent for the latter on every best-of-book query.
26
*
27
* Because PriceLevel addresses are stable for the ladder's lifetime, Order's
28
* cached PriceLevel* pointer stays valid across every insert and remove at
29
* any other price: nothing is ever reallocated out from under it.
30
*
31
* "Best" is always the lowest occupied index. Index 0 is the worst price a
32
* resting order can have on that side, so the two sides differ only in which
33
* direction price increases with index: ascending for asks (index 0 is the
34
* lowest, hence best, ask) and descending for bids (index 0 is the highest,
35
* hence best, bid). This lets both sides share one implementation and one
36
* "lowest set bit" query instead of a min-heap on one side and a max-heap on
37
* the other.
38
*
39
* Occupancy is tracked in a bitset rather than by asking each PriceLevel
40
* whether it is empty, so finding the best level after it empties is a
41
* hardware find-first-set over a handful of 64-bit words rather than a linear
42
* scan of PriceLevel objects. The best index is additionally cached, so the
43
* common case, a level away from the current best changing occupancy, costs
44
* one array access and one bit flip with no search at all.
45
*/
46
class
PriceLadder
{
47
public
:
48
/** Sentinel returned when no level is occupied. */
49
static
constexpr
std::size_t
npos
=
static_cast<
std::size_t
>
(-1);
50
51
/**
52
* @brief Pre-allocates every level the ladder will ever hold.
53
*
54
* @param minPrice Lowest representable price on this side.
55
* @param tickSize Price increment between adjacent indices.
56
* @param levelCount Number of representable price levels.
57
* @param descending True for the bid side, where index 0 is the
58
* highest price rather than the lowest.
59
* @throws std::invalid_argument if minPrice is zero, tickSize is
60
* zero, levelCount is zero, or the configured range overflows.
61
*/
62
PriceLadder
(
Price
minPrice,
Price
tickSize, std::size_t levelCount,
63
bool
descending);
64
65
PriceLadder
(
const
PriceLadder
&) =
delete
;
66
PriceLadder
& operator=(
const
PriceLadder
&) =
delete
;
67
PriceLadder
(
PriceLadder
&&) =
delete
;
68
PriceLadder
& operator=(
PriceLadder
&&) =
delete
;
69
~PriceLadder
() =
default
;
70
71
/**
72
* @brief Returns the level slot for a price; the slot always exists.
73
* @throws std::out_of_range if the price falls outside the
74
* configured range.
75
* @throws std::invalid_argument if the price does not fall on a
76
* configured tick boundary.
77
*/
78
PriceLevel
&
levelAt
(
Price
price);
79
80
/**
81
* @brief Marks a level occupied; call exactly once when its order
82
* count transitions from zero to nonzero.
83
*/
84
void
markOccupied
(
Price
price);
85
86
/**
87
* @brief Marks a level empty; call exactly once when its order count
88
* transitions from nonzero to zero.
89
*/
90
void
markEmpty
(
Price
price);
91
92
/** Returns the best occupied level, or nullptr when the side is empty. */
93
PriceLevel
*
best
();
94
/** Returns the best occupied level for read-only inspection. */
95
const
PriceLevel
*
best
()
const
;
96
97
/** Returns the number of currently occupied levels. */
98
std::size_t
occupiedCount
()
const
{
return
occupied; }
99
100
/**
101
* @brief Walks up to @p maxLevels occupied levels, best first.
102
*
103
* Read-only traversal for market-data snapshots. Because "best" is
104
* always the lowest occupied index on either side (see the class
105
* comment), walking outward from the touch is the same forward
106
* bit-scan on both bids and asks, and it visits levels in true
107
* price priority order without sorting anything.
108
*
109
* This deliberately stops after @p maxLevels rather than walking the
110
* whole ladder: a snapshot consumer wants the top of book, and the
111
* ladder may span hundreds of thousands of mostly-empty levels.
112
*/
113
template
<
typename
Fn>
114
requires
std::invocable<Fn&, const PriceLevel&>
115
void
forEachOccupied
(std::size_t maxLevels, Fn&& fn)
const
{
116
std::size_t index = bestIndex;
117
for
(std::size_t seen = 0; seen < maxLevels && index !=
npos
; ++seen) {
118
fn(levels[index]);
119
index = nextSetBit(index + 1);
120
}
121
}
122
123
private
:
124
std::size_t indexOf(
Price
price)
const
;
125
std::size_t nextSetBit(std::size_t from)
const
;
126
127
std::uint64_t minPrice;
128
std::uint64_t tickSize;
129
std::uint64_t maxPrice;
// minPrice + (levelCount - 1) * tickSize, cached at construction
130
std::size_t levelCount;
131
bool
descending;
132
133
// One entry per tick; constructed once and never resized, so every
134
// PriceLevel's address is stable for the life of the ladder.
135
std::vector<PriceLevel> levels;
136
// Occupancy bitset, 64 levels per word.
137
std::vector<std::uint64_t> words;
138
// Lowest occupied index, kept exact; npos when occupied == 0.
139
std::size_t bestIndex{
npos
};
140
std::size_t occupied{0};
141
};
142
143
}
// namespace lob
144
145
#endif
// PRICE_LADDER_HPP
lob::PriceLadder::markOccupied
void markOccupied(Price price)
Marks a level occupied; call exactly once when its order count transitions from zero to nonzero.
Definition
price_ladder.cpp:68
lob::PriceLadder::PriceLadder
PriceLadder(Price minPrice, Price tickSize, std::size_t levelCount, bool descending)
Pre-allocates every level the ladder will ever hold.
Definition
price_ladder.cpp:8
lob::PriceLadder::npos
static constexpr std::size_t npos
Sentinel returned when no level is occupied.
Definition
price_ladder.hpp:49
lob::PriceLadder::occupiedCount
std::size_t occupiedCount() const
Returns the number of currently occupied levels.
Definition
price_ladder.hpp:98
lob::PriceLadder::levelAt
PriceLevel & levelAt(Price price)
Returns the level slot for a price; the slot always exists.
Definition
price_ladder.cpp:60
lob::PriceLadder::forEachOccupied
void forEachOccupied(std::size_t maxLevels, Fn &&fn) const
Walks up to maxLevels occupied levels, best first.
Definition
price_ladder.hpp:115
lob::PriceLadder::markEmpty
void markEmpty(Price price)
Marks a level empty; call exactly once when its order count transitions from nonzero to zero.
Definition
price_ladder.cpp:85
lob::PriceLadder::best
PriceLevel * best()
Returns the best occupied level, or nullptr when the side is empty.
Definition
price_ladder.cpp:115
lob::PriceLevel
Maintains FIFO resting orders at one price.
Definition
price_level.hpp:16
lob::Price
Type-safe nonzero price value used for price ordering.
Definition
price.hpp:9
include
lob
book
price_ladder.hpp
Generated by
1.18.0