order_book 0.3.0
Exchange feed order book that prints the best bid and offer
Loading...
Searching...
No Matches
itch_market.hpp
1#pragma once
2
3#include <array>
4#include <cstddef>
5#include <cstdint>
6#include <expected>
7#include <limits>
8#include <memory>
9#include <utility>
10#include <vector>
11
12#include <ankerl/unordered_dense.h>
13
14#include <order_book/feed.hpp>
15#include <order_book/itch.hpp>
16#include <order_book/levels.hpp>
17#include <order_book/order_book.hpp>
18
19namespace order_book {
20
37public:
44 [[nodiscard]] std::expected<bool, BookError> apply(const ItchMessage& message);
46 [[nodiscard]] std::expected<bool, BookError> add(const ItchMessage& message);
48 [[nodiscard]] std::expected<bool, BookError> execute(const ItchMessage& message);
50 [[nodiscard]] std::expected<bool, BookError> remove(const ItchMessage& message);
52 [[nodiscard]] std::expected<bool, BookError> replace(const ItchMessage& message);
57 static constexpr std::size_t prefetch_distance = 16;
60#if defined(__linux__) && defined(__x86_64__)
61 static constexpr bool prefetch_levels_enabled = false;
62#else
63 static constexpr bool prefetch_levels_enabled = true;
64#endif
67 void prefetch(std::uint64_t order, std::uint16_t locate) const;
70 void prefetch_levels(std::uint64_t order, std::uint16_t locate) const;
72 [[nodiscard]] Bbo bbo(std::uint16_t locate) const;
74 [[nodiscard]] std::size_t order_count() const { return m_order_count; }
76 [[nodiscard]] std::size_t level_count() const { return m_levels.size(); }
77
78private:
79 // A resting order in 8 bytes, as ITCH gives shares in 32 bits; level 0 marks an entry of the
80 // index that holds none
81 struct Order {
82 Shares remaining;
83 std::uint32_t level;
84 };
85 // Reference numbers below this live in the index, and larger ones in m_overflow. Nasdaq starts
86 // them low each day: the largest on 2019-12-30 is 308,631,072. The bound keeps m_chunks within
87 // 2^16 entries, so a larger reference number's chunk is past its end and lookups need no check.
88 static constexpr std::uint64_t paged_orders = std::uint64_t{1} << 32U;
89 // Small pages, because each worker keeps only the locates assigned to it, so its orders are a
90 // sparse subset of every range of reference numbers, and long-lived orders keep pages alive. On
91 // the 2019-12-30 day, 128 reference numbers a page gave the lowest peak with ten workers:
92 // smaller pages need more of the directory, which every worker keeps for every range it uses.
93 static constexpr unsigned page_bits = 7;
94 static constexpr std::size_t page_size = std::size_t{1} << page_bits;
95 // The orders of page_size consecutive reference numbers, exactly 1 KiB: with its live count
96 // beside the orders, the allocator would round a page up by a quarter
97 using Page = std::array<Order, page_size>;
98 // The pages of chunk_size consecutive page numbers, and how many live orders each holds. The
99 // index makes a chunk only for a page it uses, so a high reference number costs one chunk
100 // rather than a slot for every page below it.
101 static constexpr unsigned chunk_bits = 9;
102 static constexpr std::size_t chunk_size = std::size_t{1} << chunk_bits;
103 struct Chunk {
104 std::array<std::unique_ptr<Page>, chunk_size> pages;
105 std::array<std::uint8_t, chunk_size> live{};
106 };
107 static_assert(page_size <= std::numeric_limits<std::uint8_t>::max());
108
109 // Take `quantity` off `resting`, the order `order`, removing it and then its level once they
110 // reach 0; returns whether its book's best bid or offer changed. Removing an order of
111 // m_overflow invalidates `resting`.
112 bool reduce(std::uint64_t order, Order& resting, Shares quantity);
113 // The live order `order` of `locate`, or nullptr
114 [[nodiscard]] Order* find(std::uint64_t order, std::uint16_t locate);
115 // The index entry of `order`, live or not, or nullptr if its page does not exist
116 [[nodiscard]] const Order* indexed(std::uint64_t order) const;
117 // The chunk of `order`, which exists
118 [[nodiscard]] Chunk& chunk(std::uint64_t order);
119 // Where the page of `order` is in its chunk
120 [[nodiscard]] static std::size_t page_slot(std::uint64_t order)
121 {
122 return (order >> page_bits) & (chunk_size - 1);
123 }
124 // The levels of `locate`'s book, made if it has none yet
125 [[nodiscard]] BookLevels& book(std::uint16_t locate);
126
127 std::vector<BookLevels> m_books;
128 LevelPool m_levels;
129 // Chunks by the reference numbers' bits above both a page's and a chunk's; never freed
130 std::vector<std::unique_ptr<Chunk>> m_chunks;
131 // Emptied pages, kept for reuse
132 std::vector<std::unique_ptr<Page>> m_spare_pages;
133 // Orders the index does not hold, by reference number and locate: those whose slot holds a live
134 // order of another locate, and those from paged_orders up. Empty for a Nasdaq feed, whose
135 // reference numbers are unique for the day and start low.
136 ankerl::unordered_dense::map<std::pair<std::uint64_t, std::uint16_t>, Order> m_overflow;
137 std::size_t m_order_count = 0;
138};
139
140} // namespace order_book
std::expected< bool, BookError > add(const ItchMessage &message)
apply() for an Add, without dispatching on the kind
std::size_t level_count() const
Number of price levels in every book.
static constexpr std::size_t prefetch_distance
void prefetch_levels(std::uint64_t order, std::uint16_t locate) const
std::expected< bool, BookError > remove(const ItchMessage &message)
apply() for a Delete
std::expected< bool, BookError > apply(const ItchMessage &message)
std::expected< bool, BookError > execute(const ItchMessage &message)
apply() for an Execute
static constexpr bool prefetch_levels_enabled
std::expected< bool, BookError > replace(const ItchMessage &message)
apply() for a Replace
void prefetch(std::uint64_t order, std::uint16_t locate) const
Bbo bbo(std::uint16_t locate) const
Current best bid and offer of locate.
std::size_t order_count() const
Number of live orders in every book.
Best bid and offer; an empty side has no level.
Definition levels.hpp:21