PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
admission_journal_codec.cpp
Go to the documentation of this file.
4#include <string_view>
5#include <new>
6
7namespace pineforge::admission {
8inline namespace market_admission_v2 {
9namespace {
10namespace jc = detail::journal_codec;
12
13// Path depth is fixed by the schema. Decimal array subscripts have at most
14// twenty digits; neither text values nor array populations enter this buffer.
15struct Path {
16 std::array<char, 256> bytes{};
17 std::size_t length = 0;
18 Path() { append("event"); }
19 void append(std::string_view value) {
20 if (value.size() > bytes.size() - length)
21 throw coded<std::length_error>(RunFailureCode::engine_invariant, {},
22 "admission field path");
23 std::memcpy(bytes.data() + length, value.data(), value.size());
24 length += value.size();
25 }
26 Path child(const char* name) const {
27 Path out = *this;
28 out.append("."); out.append(name);
29 return out;
30 }
31 Path element(std::uint64_t index) const {
32 Path out = *this;
33 out.append("[");
34 std::array<char, 20> digits{};
35 std::size_t count = 0;
36 do { digits[count++] = char('0' + index % 10); index /= 10; } while (index);
37 while (count) out.append(std::string_view(&digits[--count], 1));
38 out.append("]");
39 return out;
40 }
41 bool is(std::string_view value) const noexcept {
42 return value == std::string_view(bytes.data(), length);
43 }
44};
45template<class T> constexpr std::uint64_t tag() {
46 if constexpr (std::is_same_v<T, std::string>) return 3;
47 else if constexpr (std::is_same_v<T, double>) return 2;
48 else if constexpr (std::is_same_v<T, bool> || std::is_unsigned_v<T>) return 0;
49 else return 1;
50}
51
52struct Save {
53 static constexpr bool loading = false;
54 static constexpr bool owning = false;
55 jc::Writer& out;
56 bool counting = false;
57 std::uint64_t rows = 0;
58 std::uint64_t* next_offset = nullptr;
59 std::uint64_t* next_row = nullptr;
60 std::array<std::uint64_t, 4>* array_elements = nullptr;
61 std::array<std::uint64_t, 4>* array_rows = nullptr;
62 std::size_t arrays = 0;
63 bool stopped = false;
64 bool good() const { return out.checked_status() == Status::ok && !stopped; }
65 template<class T> void field(const Path& path, const char* name, T& value) {
66 if (!good()) return;
67 const auto row = rows++;
68 if (counting || (next_row && row < *next_row)) return;
69 if (next_offset && out.position >= out.offset + out.destination.size) {
70 stopped = true;
71 return;
72 }
73 const auto key = path.child(name);
74 out.word(key.length); out.bytes(key.bytes.data(), key.length);
75 out.word(tag<T>());
76 if constexpr (std::is_same_v<T, std::string>) out.text(value);
77 else if constexpr (std::is_same_v<T, double>) {
78 std::uint64_t bits = 0; std::memcpy(&bits, &value, sizeof bits); out.word(bits);
79 } else out.word(static_cast<std::uint64_t>(value));
80 if (next_offset && out.checked_status() == Status::ok) {
81 if (out.position <= out.offset + out.destination.size) {
82 *next_offset = out.position;
83 *next_row = rows;
84 } else stopped = true; // Retry only this field at the next page.
85 }
86 }
87 std::uint64_t begin_array(std::size_t slot) {
88 if (!array_elements || !good()) return 0;
89 // Restore the field ordinal at the first unfinished element without
90 // traversing any completed element, including its optional fields.
91 rows = std::max(rows, (*array_rows)[slot]);
92 return (*array_elements)[slot];
93 }
94 void end_element(std::size_t slot, std::uint64_t next) {
95 if (!array_elements || !good()) return;
96 (*array_elements)[slot] = next;
97 (*array_rows)[slot] = rows;
98 }
99};
100
101template<bool Owning> struct Load {
102 static constexpr bool loading = true;
103 static constexpr bool owning = Owning;
104 jc::Reader& in;
105 std::uint64_t rows = 0, expected_rows = 0;
106 JournalRecordSummary summary;
108 int bar = -1;
109 bool good() const { return in.status == Status::ok; }
110 template<class T> void field(const Path& path, const char* name, T& value) {
111 if (!good()) return;
112 if (++rows > expected_rows) { in.status = Status::corrupt; return; }
113 const auto key = path.child(name);
114 const auto length = in.word();
115 if (!good()) return;
116 if (length != key.length) { in.status = Status::corrupt; return; }
117 std::array<char, 256> bytes{};
118 in.bytes(bytes.data(), key.length);
119 if (!good()) return;
120 if (std::memcmp(bytes.data(), key.bytes.data(), key.length) || in.word() != tag<T>()) {
121 in.status = Status::corrupt; return;
122 }
123 if constexpr (std::is_same_v<T, std::string>) in.text(value, Owning);
124 else if constexpr (std::is_same_v<T, double>) in.real(value);
125 else if constexpr (std::is_same_v<T, bool>) {
126 const auto raw = in.word();
127 if (raw > 1) in.status = Status::corrupt;
128 value = raw == 1;
129 } else if constexpr (std::is_enum_v<T>) {
130 std::underlying_type_t<T> raw{}; in.integer(raw); value = static_cast<T>(raw);
131 } else in.integer(value);
132 if constexpr (std::is_integral_v<T> || std::is_enum_v<T>) {
133 if (key.is("event.observation.command") || key.is("event.receipt.sequence"))
134 summary.sequence = static_cast<std::uint64_t>(value);
135 if (key.is("event.outcome")) outcome = static_cast<Outcome>(value);
136 if (key.is("event.observation.bar")) bar = static_cast<int>(value);
137 }
138 }
139};
140
141template<class A> struct Schema {
142 A& a;
143 template<class T> void field(const Path& p, const char* name, T& value) {
144 a.field(p, name, value);
145 }
146 void birth(const Path& p, OrderBirth& value) {
147 auto cause = value.cause(); auto bar = value.bar(); auto timestamp = value.timestamp();
148 auto domain = value.cursor().domain(); auto position = value.cursor().position();
149 auto index = value.cursor().index(); auto count = value.cursor().count();
150 auto price = value.cursor_price();
151 auto first = value.first_fill(); auto last = value.last_fill();
152 auto ordinal = value.evaluation_ordinal();
153 field(p,"cause",cause); field(p,"bar",bar); field(p,"timestamp",timestamp);
154 field(p,"cursor_domain",domain); field(p,"cursor_position",position);
155 field(p,"cursor_index",index); field(p,"cursor_count",count);
156 field(p,"cursor_price",price); field(p,"first_fill",first);
157 field(p,"last_fill",last); field(p,"evaluation_ordinal",ordinal);
158 if constexpr (A::loading) {
159 if constexpr (A::owning) {
160 if (!a.good()) return;
161 }
162 switch (cause) {
164 if constexpr (A::owning) value = OrderBirth{};
165 break;
167 if constexpr (A::owning) value = OrderBirth::direct_command(bar,timestamp);
168 break;
170 if constexpr (A::owning) value = OrderBirth::chart_evaluation(bar,timestamp);
171 break;
173 if constexpr (A::owning) {
174 const auto cursor = position == BirthCursorPosition::Point
175 ? BirthCursor::point(domain,index,count) : BirthCursor::segment(domain,index,count);
176 value = OrderBirth::fill_evaluation(bar,timestamp,cursor,price,first,last,ordinal);
177 }
178 break;
179 }
180 default:
181 if constexpr (A::owning)
182 throw coded<std::invalid_argument>(RunFailureCode::engine_invariant, {},
183 "invalid admission birth");
184 else a.in.status = Status::corrupt;
185 }
186 }
187 }
188 void config(const Path& p, Configuration& x) {
189#define F(name) field(p,#name,x.name)
190 F(process_on_close); F(calc_on_fills); F(magnifier); F(fill_recalculation); F(scheduler);
191 F(slippage); F(pyramiding); F(default_quantity_type); F(default_quantity_value);
192 F(long_margin); F(short_margin); F(commission_value); F(commission_type); F(pointvalue);
193 F(fx); F(quantity_step); F(mintick); F(risk_direction); F(loss_days_limit);
194 F(drawdown_limit); F(intraday_loss_limit); F(position_limit); F(fill_cap_active); F(risk_halted);
195#undef F
196 }
197 void sizing(const Path& p, SizingObservation& x) {
198 field(p,"quantity",x.quantity); field(p,"equity",x.equity); field(p,"price",x.price);
199 field(p,"mark",x.mark); field(p,"fx",x.fx);
200 }
201 void observation(const Path& p, CommandObservation& x) {
202#define F(name) field(p,#name,x.name)
203 F(command); F(kind); birth(p.child("birth"),x.birth); F(id); F(requested_quantity);
204 F(quantity_type); F(buy); field(p.child("prices"),"limit",x.prices.limit);
205 field(p.child("prices"),"stop",x.prices.stop); F(oca_name); F(oca_type);
206 config(p.child("configuration"),x.configuration); F(bar); F(placement_side);
207 F(placement_cycle); F(prior_close_quantity); F(held_quantity); F(held_entries);
208 F(realized_equity); F(placement_equity); F(signal_close); F(quantized_fixed_quantity);
209 bool present = x.original_sizing.has_value();
210 field(p,"original_sizing_present",present);
211 if constexpr (A::loading) {
212 if (present) x.original_sizing.emplace(); else x.original_sizing.reset();
213 }
214 if (present) sizing(p.child("original_sizing"),*x.original_sizing);
215 F(explicit_equity); F(explicit_price);
216#undef F
217 }
218 void receipt(const Path& p, ReviewReceipt& x) {
219 field(p,"sequence",x.sequence); field(p,"checkpoint",x.checkpoint);
220 field(p,"bar",x.bar); field(p,"target_command",x.target_command);
221 }
222 void receipt(const Path& p, SizingRevision& x) {
223 field(p,"sequence",x.sequence); field(p,"cause_fill",x.cause_fill);
224 field(p,"bar",x.bar); field(p,"target_command",x.target_command);
225 }
226 void draft(const Path& p, Draft& x) {
227 bool present = bool(x.observation());
228 field(p,"observation_present",present);
229 if (present) {
230 if constexpr (A::loading) {
231 CommandObservation value;
232 observation(p.child("observation"),value);
233 if constexpr (A::owning)
234 if (a.good()) x.bind(std::make_shared<const CommandObservation>(std::move(value)));
235 } else observation(p.child("observation"),const_cast<CommandObservation&>(*x.observation()));
236 }
237 present = x.review().has_value(); field(p,"review_present",present);
238 if (present) {
239 ReviewReceipt value;
240 if constexpr (!A::loading) value = *x.review();
241 receipt(p.child("review"),value);
242 if constexpr (A::loading && A::owning) if (a.good()) x.reviewed(value);
243 }
244 present = x.sizing_revision().has_value(); field(p,"sizing_revision_present",present);
245 if (present) {
246 SizingRevision value;
247 if constexpr (!A::loading) value = *x.sizing_revision();
248 receipt(p.child("sizing_revision"),value);
249 if constexpr (A::loading && A::owning) if (a.good()) x.sizing_revised(value);
250 }
251 }
252 void book(const Path& p, BookObservation& x) {
253#define F(name) field(p,#name,x.name)
254 F(incarnation); F(priority); F(bar); F(type); F(placement_side); F(buy);
255 F(id); F(oca_name); F(oca_type); birth(p.child("birth"),x.birth);
256 field(p.child("prices"),"limit",x.prices.limit); field(p.child("prices"),"stop",x.prices.stop);
257 field(p.child("prices"),"trail_points",x.prices.trail_points);
258 field(p.child("prices"),"trail_price",x.prices.trail_price);
259 field(p.child("prices"),"trail_offset",x.prices.trail_offset); draft(p.child("draft"),x.draft);
260#undef F
261 }
262 template<class T, class Fn> void array(const Path& p, std::vector<T>& rows, Fn emit) {
263 auto count = static_cast<std::uint64_t>(rows.size());
264 field(p,"size",count);
265 if constexpr (A::loading) {
266 if (count > a.expected_rows - std::min(a.rows,a.expected_rows)
267 || count > rows.max_size()) { a.in.status = Status::corrupt; return; }
268 if constexpr (A::owning) rows.resize(static_cast<std::size_t>(count));
269 }
270 std::uint64_t first = 0;
271 std::size_t slot = 0;
272 if constexpr (!A::loading) {
273 slot = a.arrays++;
274 first = a.begin_array(slot);
275 }
276 for (std::uint64_t i = first; i != count && a.good(); ++i) {
277 if constexpr (A::loading && !A::owning) { T row{}; emit(p.element(i),row); }
278 else emit(p.element(i),rows[static_cast<std::size_t>(i)]);
279 if constexpr (!A::loading) a.end_element(slot,i+1);
280 }
281 }
282 void event(Event& value) {
283 const Path p;
284 auto kind = static_cast<std::uint64_t>(value.index()); field(p,"kind",kind);
285 if constexpr (A::loading) {
286 switch (kind) {
287 case 0: value.template emplace<0>(); break;
288 case 1: value.template emplace<1>(); break;
289 case 2: value.template emplace<2>(); break;
290 default: a.in.status = Status::corrupt; return;
291 }
292 }
293 std::visit([&](auto& x) {
294 using T = std::decay_t<decltype(x)>;
295 if constexpr (std::is_same_v<T,CommandEvent>) {
296 if constexpr (A::loading) {
297 CommandObservation input;
298 observation(p.child("observation"),input);
299 if constexpr (A::owning)
300 if (a.good()) x.observation =
301 std::make_shared<const CommandObservation>(std::move(input));
302 } else {
303 if (!x.observation) { a.out.status = Status::programming_error; return; }
304 observation(p.child("observation"),const_cast<CommandObservation&>(*x.observation));
305 }
306 field(p,"outcome",x.outcome); field(p,"admitted_incarnation",x.admitted_incarnation);
307 array(p.child("before"),x.before,[&](const auto& q,auto& row){book(q,row);});
308 array(p.child("removed"),x.removed,[&](const auto& q,auto& id){field(q,"incarnation",id);});
309 } else if constexpr (std::is_same_v<T,ReviewEvent>) {
310 receipt(p.child("receipt"),x.receipt); config(p.child("configuration"),x.configuration);
311 field(p,"open_price",x.open_price); field(p,"position_side",x.position_side);
312 field(p,"position_cycle",x.position_cycle);
313 array(p.child("book"),x.book,[&](const auto& q,auto& row){book(q,row);});
314 array(p.child("reviewed"),x.reviewed,[&](const auto& q,auto& row){book(q,row);});
315 array(p.child("resolutions"),x.resolutions,[&](const auto& q,auto& row){
316 field(q,"incarnation",row.incarnation); field(q,"kind",row.kind);
317 field(q,"peer_incarnation",row.peer_incarnation); field(q,"own_priority",row.own_priority);
318 field(q,"peer_priority",row.peer_priority); field(q,"transaction_quantity",row.transaction_quantity);
319 });
320 array(p.child("causes"),x.causes,[&](const auto& q,auto& id){field(q,"sequence",id);});
321 } else {
322 receipt(p.child("receipt"),x.receipt); field(p,"incarnation",x.incarnation);
323 sizing(p.child("before"),x.before); sizing(p.child("after"),x.after);
324 field(p,"affordability_equity_before",x.affordability_equity_before);
325 field(p,"affordability_equity_after",x.affordability_equity_after);
326 }
327 },value);
328 }
329};
330
331template<bool Owning> Status decode(const detail::ScratchByteSource& source,
332 detail::ScratchBuffer page, Event& event, JournalRecordSummary& summary) noexcept {
333 if (!page.data || !page.size) return Status::programming_error;
334 try {
335 jc::Reader in{source,page};
336 Load<Owning> load{in};
337 load.expected_rows = in.word();
338 if (load.expected_rows > in.remaining() / 24) return Status::corrupt;
339 Schema<Load<Owning>>{load}.event(event);
340 if (load.rows != load.expected_rows && in.status == Status::ok) in.status = Status::corrupt;
341 const auto status = in.finish();
342 if (status == Status::ok) {
343 summary = load.summary;
344 summary.rejected = rejected_command(load.outcome);
345 summary.rejected_bar = load.bar;
346 }
347 return status;
348 } catch (const std::bad_alloc&) { return Status::programming_error; }
349 catch (...) { return Status::corrupt; }
350}
351} // namespace
352
353JournalRecordSource::JournalRecordSource(const Event& event) noexcept : event_(&event) {
354 try {
355 jc::Writer counter;
356 Save count{counter,true};
357 Schema<Save>{count}.event(const_cast<Event&>(event));
358 rows_ = count.rows;
359 jc::Writer out;
360 out.word(rows_);
361 Save save{out};
362 Schema<Save>{save}.event(const_cast<Event&>(event));
363 length_ = out.position;
364 status_ = out.checked_status();
365 } catch (...) { status_ = Status::programming_error; }
366}
368 detail::ScratchBuffer buffer) const noexcept {
369 if (status_ != Status::ok) return status_;
370 if (offset > length_ || buffer.size > length_ - offset || (buffer.size && !buffer.data))
371 return Status::programming_error;
372 if (!buffer.size) return Status::ok;
373 try {
374 // Forward pages resume within the current array element. A seek before
375 // the saved field boundary (including the second pass) starts anew.
376 if (offset < next_offset_) {
377 next_offset_ = next_row_ = 0;
378 array_elements_.fill(0);
379 array_rows_.fill(0);
380 }
381 jc::Writer out{next_offset_,offset,buffer};
382 if (!next_offset_) out.word(rows_);
383 Save save{out,false,0,&next_offset_,&next_row_,&array_elements_,&array_rows_};
384 Schema<Save>{save}.event(const_cast<Event&>(*event_));
385 return out.checked_status();
386 } catch (...) { return Status::programming_error; }
387}
390 detail::ScratchBuffer page, Event& event) noexcept {
391 Event result;
392 JournalRecordSummary summary;
393 const auto status = decode<true>(source,page,result,summary);
394 if (status == Status::ok) event = std::move(result);
395 return status;
396}
398 detail::ScratchBuffer page, JournalRecordSummary& summary) noexcept {
399 Event scratch;
400 return decode<false>(source,page,scratch,summary);
401}
403 detail::ScratchBuffer page, std::uint64_t& digest) noexcept {
404 if (!page.data || !page.size) return Status::programming_error;
405 jc::Reader in{source,page};
406 const auto rows = in.word();
407 if (rows > in.remaining() / 24) return Status::corrupt;
408 auto h = digest;
409 const auto byte = [&](unsigned value) { h = (h ^ value) * 1099511628211ULL; };
410 const auto word = [&](std::uint64_t value) {
411 for (unsigned i = 0; i != 8; ++i) byte((value >> (8 * i)) & 255);
412 };
413 const auto text = [&] {
414 const auto length = in.word();
415 if (length > in.remaining()) { in.status = Status::corrupt; return; }
416 word(length);
417 std::array<std::byte, 97> bytes{};
418 for (std::uint64_t at = 0; at < length && in.status == Status::ok;) {
419 const auto count = static_cast<std::size_t>(std::min<std::uint64_t>(bytes.size(),length-at));
420 in.bytes(bytes.data(),count);
421 for (std::size_t i = 0; i != count; ++i) byte(std::to_integer<unsigned>(bytes[i]));
422 at += count;
423 }
424 };
425 for (std::uint64_t i = 0; i != rows && in.status == Status::ok; ++i) {
426 text();
427 const auto kind = in.word(); word(kind);
428 if (kind == 3) text();
429 else if (kind < 3) {
430 auto value = in.word();
431 if (kind == 2) {
432 if ((value & 0x7fffffffffffffffULL) == 0) value = 0;
433 else if ((value & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL
434 && (value & 0x000fffffffffffffULL)) value = 0x7ff8000000000000ULL;
435 }
436 word(value);
437 } else in.status = Status::corrupt;
438 }
439 const auto status = in.finish();
440 if (status == Status::ok) digest = h;
441 return status;
442}
443} // inline namespace market_admission_v2
444} // namespace pineforge::admission
#define F(name)
static BirthCursor point(BirthCursorDomain domain, int index, int count)
static OrderBirth direct_command(int bar, int64_t timestamp)
static OrderBirth chart_evaluation(int bar, int64_t timestamp)
static OrderBirth fill_evaluation(int bar, int64_t timestamp, BirthCursor cursor, double price, uint64_t first_fill, uint64_t last_fill, uint64_t evaluation_ordinal)
detail::ScratchStatus read(std::uint64_t, detail::ScratchBuffer) const noexcept override
JournalRecordSource encode_journal_record(const Event &) noexcept
detail::ScratchStatus scan_journal_record(const detail::ScratchByteSource &source, detail::ScratchBuffer page, JournalRecordSummary &summary) noexcept
detail::ScratchStatus fold_journal_record(const detail::ScratchByteSource &source, detail::ScratchBuffer page, std::uint64_t &digest) noexcept
std::variant< CommandEvent, ReviewEvent, SizingEvent > Event
detail::ScratchStatus decode_journal_record(const detail::ScratchByteSource &, detail::ScratchBuffer, Event &) noexcept
void receipt(const std::string &document, char *json, std::size_t capacity, std::size_t *required)
std::uint64_t page(std::uint64_t bytes)