FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
data_source.hpp
Go to the documentation of this file.
1#pragma once
2// DataSource (8.4): a timestamp-ordered stream of normalized market-data events consumed
3// by the simulator. Every source implements sim::MdSource (a virtual next()/reset() pair;
4// one virtual call per event is noise next to the engine step) and satisfies the
5// DataSourceLike concept. Row-based sources (CSV, arrays) share RowAssembler, which groups
6// consecutive rows of one update into a single BookDelta/BookSnapshot message.
7//
8// Row format (CSV columns / array dtypes):
9// ts_ns int64 event time
10// type S | D | T | B (0 | 1 | 2 | 3 in arrays): snapshot level, delta level,
11// trade, book ticker (two rows: bid then ask)
12// inst uint32 dense instrument id
13// side B | A (0 | 1): bid/ask, or the aggressor side for trades
14// price decimal string / int64 raw 1e-8 / double
15// qty decimal string / int64 raw 1e-8 / double (0 == delete level)
16// seq uint64 venue update id; consecutive S/D rows with equal (ts, inst, seq) form
17// one message
19#include "fastmm/core/time.hpp"
20#include "fastmm/sim/md_source.hpp"
21
22#include <concepts>
23#include <cstddef>
24#include <cstdint>
25#include <cstring>
26#include <memory>
27#include <vector>
28
29namespace fastmm::bt {
30
31using sim::EventBuf;
32using sim::kMaxSourceEventBytes;
33using sim::MdSource;
34
35template <class S>
36concept DataSourceLike = requires(S& s) {
37 { s.next() } -> std::same_as<const EventHeader*>;
38 { s.reset() };
39};
40
41enum class RowType : std::uint8_t { Snapshot = 0, Delta = 1, Trade = 2, Ticker = 3 };
42
43struct Row {
44 std::int64_t ts = 0;
46 std::uint32_t inst = 0;
50 std::uint64_t seq = 0;
51};
53// Builds messages from a row stream. `Provider` is `bool(Row&)` returning false at end.
54class RowAssembler {
55 public:
56 explicit RowAssembler(VenueId venue = VenueId{0}) noexcept : venue_(venue) {}
57
58 template <class Provider>
59 const EventHeader* next(Provider&& provide) {
60 if (!have_row_ && !fetch(provide)) return nullptr;
61 have_row_ = false;
62 const Row first = row_;
63 switch (first.type) {
64 case RowType::Trade: {
65 auto& t = buf_.as<TradeMsg>();
67 stamp(t.hdr, first);
68 t.price = first.price;
69 t.qty = first.qty;
70 t.trade_id = first.seq;
71 t.aggressor = first.side;
72 return &t.hdr;
73 }
74 case RowType::Ticker: {
75 auto& t = buf_.as<BookTickerMsg>();
77 stamp(t.hdr, first);
78 t.bid_px = first.price;
79 t.bid_qty = first.qty;
80 if (fetch(provide) && row_.type == RowType::Ticker && row_.ts == first.ts &&
81 row_.inst == first.inst && row_.side == Side::Sell) {
82 t.ask_px = row_.price;
83 t.ask_qty = row_.qty;
84 have_row_ = false;
85 }
86 return &t.hdr;
87 }
89 case RowType::Delta: {
90 // Collect bids and asks of the same (ts, inst, seq, type) group.
91 std::uint32_t nb = 0;
92 std::uint32_t na = 0;
93 Row cur = first;
94 for (;;) {
95 if (cur.side == Side::Buy) {
96 if (nb < kMaxLevels) bids_[nb++] = Level{cur.price, cur.qty};
97 } else {
98 if (na < kMaxLevels) asks_[na++] = Level{cur.price, cur.qty};
99 }
100 if (!fetch(provide)) break;
101 if (row_.type != first.type || row_.ts != first.ts || row_.inst != first.inst ||
102 row_.seq != first.seq) {
103 break; // row_ stays buffered (have_row_ == true)
104 }
105 have_row_ = false;
106 cur = row_;
107 }
108 auto& d = buf_.as<BookDeltaMsg>();
109 const bool snap = first.type == RowType::Snapshot;
110 init_header(d,
112 InstrumentId{first.inst},
113 venue_,
114 BookDeltaMsg::size_for(nb, na));
115 if (snap) d.hdr.flags |= EventHeader::kSnapshot;
116 stamp(d.hdr, first);
117 d.bid_count = nb;
118 d.ask_count = na;
119 d.first_update_id = d.last_update_id = first.seq;
120 d.prev_update_id = 0;
121 if (nb > 0) std::memcpy(d.levels(), bids_, nb * sizeof(Level));
122 if (na > 0) std::memcpy(d.levels() + nb, asks_, na * sizeof(Level));
123 return &d.hdr;
124 }
125 }
126 return nullptr;
127 }
128 void reset() noexcept { have_row_ = false; }
129
130 private:
131 static constexpr std::uint32_t kMaxLevels = 256;
132 template <class Provider>
133 bool fetch(Provider& provide) {
134 have_row_ = provide(row_);
135 return have_row_;
136 }
137 static void stamp(EventHeader& h, const Row& r) noexcept {
138 h.exch_ts = h.recv_ts = Timestamp{r.ts};
139 h.t0_cycles = Cycles{static_cast<std::uint64_t>(r.ts)};
140 h.venue_seq = r.seq;
141 }
142
143 VenueId venue_;
144 Row row_{};
145 bool have_row_ = false;
146 EventBuf buf_{};
147 Level bids_[kMaxLevels];
148 Level asks_[kMaxLevels];
149};
150
151// Concatenation of sources that cover consecutive spans of time (one file per day). The
152// caller owns them and is responsible for the order; events must not go back in time across a
153// boundary.
154class ChainSource final : public MdSource {
155 public:
156 explicit ChainSource(std::vector<MdSource*> sources) : sources_(std::move(sources)) {}
157 const EventHeader* next() override {
158 while (at_ < sources_.size()) {
159 if (const EventHeader* h = sources_[at_]->next()) return h;
160 ++at_;
161 }
162 return nullptr;
163 }
164 void reset() override {
165 for (MdSource* s : sources_) s->reset();
166 at_ = 0;
167 }
168 [[nodiscard]] Timestamp start_ts() const override {
169 return sources_.empty() ? Timestamp{} : sources_.front()->start_ts();
171
172 private:
173 std::vector<MdSource*> sources_;
174 std::size_t at_ = 0;
175};
176
177// The events of `inner` in [t0, t1) of event time (exch_ts, recv_ts when unset), for walk-forward
178// folds. The part before t0 is read but not yielded: it only rebuilds the book of every
179// (venue, instrument) and keeps the last message of every other kind except trades, and those
180// states come out first, stamped t0 (one BookSnapshot per book, then the rest in key order).
181// A fold therefore starts on the book the full run would have had at t0. Ends at the first
182// event at or after t1. `inner` is borrowed; reset() rewinds it.
183class TimeSliceSource final : public MdSource {
184 public:
185 TimeSliceSource(MdSource& inner, Timestamp t0, Timestamp t1);
186 ~TimeSliceSource() override;
189 const EventHeader* next() override;
190 void reset() override;
191 [[nodiscard]] Timestamp start_ts() const override { return t0_; }
193 private:
194 struct Primer;
195 MdSource& inner_;
196 Timestamp t0_;
197 Timestamp t1_;
198 std::unique_ptr<Primer> primer_; // the state before t0 while it is being emitted
199 const EventHeader* first_ = nullptr; // first event at or after t0, held back by the primer
200 bool started_ = false;
201 bool done_ = false;
202};
203
204// k-way merge of several sources by event time (stable: lower source index first on ties).
205// The event yielded is the input's own, not a copy: a journal's record can be larger than an
206// EventBuf (a depth snapshot of up to kMaxBookLevelsPerMsg a side), so the input it came from is
207// advanced on the next call, when the pointer may lapse, not before.
208class MergedSource final : public MdSource {
209 public:
210 explicit MergedSource(std::vector<MdSource*> sources) : sources_(std::move(sources)) {
211 heads_.assign(sources_.size(), nullptr);
212 for (std::size_t i = 0; i < sources_.size(); ++i) heads_[i] = sources_[i]->next();
213 }
214 const EventHeader* next() override {
215 if (last_ != kNone) {
216 heads_[last_] = sources_[last_]->next();
217 last_ = kNone;
218 }
219 std::size_t best = kNone;
220 Timestamp best_ts = Timestamp::max();
221 for (std::size_t i = 0; i < heads_.size(); ++i) {
222 if (heads_[i] == nullptr) continue;
223 const Timestamp ts = time_of(*heads_[i]);
224 if (best == kNone || ts < best_ts) {
225 best = i;
226 best_ts = ts;
227 }
228 }
229 if (best == kNone) return nullptr;
230 last_ = best;
231 return heads_[best];
232 }
233 void reset() override {
234 for (std::size_t i = 0; i < sources_.size(); ++i) {
235 sources_[i]->reset();
236 heads_[i] = sources_[i]->next();
237 }
238 last_ = kNone;
239 }
240 // Earliest start among the inputs (invalid if none reports one).
241 [[nodiscard]] Timestamp start_ts() const override {
242 Timestamp t{};
243 for (const MdSource* s : sources_) {
244 const Timestamp st = s->start_ts();
245 if (st.valid() && (!t.valid() || st < t)) t = st;
246 }
247 return t;
248 }
249 [[nodiscard]] static Timestamp time_of(const EventHeader& h) noexcept {
250 return h.exch_ts.valid() ? h.exch_ts : h.recv_ts;
252
253 private:
254 static constexpr std::size_t kNone = static_cast<std::size_t>(-1);
255 std::vector<MdSource*> sources_;
256 std::vector<const EventHeader*> heads_;
257 std::size_t last_ = kNone; // the input whose head was yielded last
258};
259
260// MergedSource over sources it owns: open_data() of several specs ("a; b"), one per venue.
261class OwnedMergedSource final : public MdSource {
262 public:
263 explicit OwnedMergedSource(std::vector<std::unique_ptr<MdSource>> sources)
264 : owned_(std::move(sources)), merged_(raw(owned_)) {}
265 const EventHeader* next() override { return merged_.next(); }
266 void reset() override { merged_.reset(); }
267 [[nodiscard]] Timestamp start_ts() const override { return merged_.start_ts(); }
269 private:
270 static std::vector<MdSource*> raw(const std::vector<std::unique_ptr<MdSource>>& v) {
271 std::vector<MdSource*> out;
272 out.reserve(v.size());
273 for (const auto& s : v) out.push_back(s.get());
274 return out;
275 }
276 std::vector<std::unique_ptr<MdSource>> owned_;
277 MergedSource merged_;
278};
279
280} // namespace fastmm::bt
Concatenation of sources that cover consecutive spans of time (one file per day).
Definition data_source.hpp:156
const EventHeader * next() override
Definition data_source.hpp:159
Timestamp start_ts() const override
Definition data_source.hpp:170
void reset() override
Definition data_source.hpp:166
k-way merge of several sources by event time (stable: lower source index first on ties).
Definition data_source.hpp:210
Timestamp start_ts() const override
Earliest start among the inputs (invalid if none reports one).
Definition data_source.hpp:243
static Timestamp time_of(const EventHeader &h) noexcept
Definition data_source.hpp:251
const EventHeader * next() override
Definition data_source.hpp:216
void reset() override
Definition data_source.hpp:235
MergedSource over sources it owns: open_data() of several specs ("a; b"), one per venue.
Definition data_source.hpp:263
void reset() override
Definition data_source.hpp:268
const EventHeader * next() override
Definition data_source.hpp:267
Timestamp start_ts() const override
Definition data_source.hpp:269
Builds messages from a row stream. Provider is bool(Row&) returning false at end.
Definition data_source.hpp:56
void reset() noexcept
Definition data_source.hpp:130
const EventHeader * next(Provider &&provide)
Definition data_source.hpp:61
The events of inner in [t0, t1) of event time (exch_ts, recv_ts when unset), for walk-forward folds.
Definition data_source.hpp:185
TimeSliceSource(MdSource &inner, Timestamp t0, Timestamp t1)
Timestamp start_ts() const override
Definition data_source.hpp:193
const EventHeader * next() override
TimeSliceSource & operator=(const TimeSliceSource &)=delete
Event bus message structs (5.5).
Definition backtest_config.hpp:39
RowType
Definition data_source.hpp:43
constexpr void init_header(M &m, EventType type, InstrumentId inst={}, VenueId venue={}, std::uint32_t len=static_cast< std::uint32_t >(sizeof(M))) noexcept
For fixed-size messages len defaults to sizeof(M); BookDeltaMsg passes size_for().
Definition messages.hpp:75
Side
Definition enums.hpp:12
StrongId< struct VenueTag, std::uint8_t > VenueId
Definition strong_id.hpp:39
Bids then asks follow the fixed part as a flexible array of Level (bid_count + ask_count).
Definition messages.hpp:99
static constexpr std::uint32_t size_for(std::uint32_t bids, std::uint32_t asks) noexcept
Definition messages.hpp:107
Definition messages.hpp:150
Definition time.hpp:59
Definition messages.hpp:37
@ kSnapshot
BookDelta carries a full snapshot.
Definition messages.hpp:41
One price level. Shared by book messages and the L2 book.
Definition messages.hpp:30
Definition time.hpp:45
static constexpr Timestamp max() noexcept
Definition time.hpp:56
constexpr bool valid() const noexcept
Definition time.hpp:55
Definition messages.hpp:140
Definition data_source.hpp:45
std::uint32_t inst
Definition data_source.hpp:48
Price price
Definition data_source.hpp:50
RowType type
Definition data_source.hpp:47
Qty qty
Definition data_source.hpp:51
Side side
Definition data_source.hpp:49
std::int64_t ts
Definition data_source.hpp:46
std::uint64_t seq
Definition data_source.hpp:52
Time primitives.