10namespace jc = detail::journal_codec;
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)
22 "admission field path");
23 std::memcpy(bytes.data() + length,
value.data(),
value.size());
24 length +=
value.size();
26 Path child(
const char* name)
const {
28 out.append(
"."); out.append(name);
31 Path element(std::uint64_t index)
const {
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));
41 bool is(std::string_view value)
const noexcept {
42 return value == std::string_view(bytes.data(), length);
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;
53 static constexpr bool loading =
false;
54 static constexpr bool owning =
false;
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;
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) {
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) {
73 const auto key = path.child(name);
74 out.word(
key.length); out.bytes(
key.bytes.data(),
key.length);
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;
84 }
else stopped =
true;
87 std::uint64_t begin_array(std::size_t slot) {
88 if (!array_elements || !good())
return 0;
91 rows = std::max(rows, (*array_rows)[slot]);
92 return (*array_elements)[slot];
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;
101template<
bool Owning>
struct Load {
102 static constexpr bool loading =
true;
103 static constexpr bool owning = Owning;
105 std::uint64_t rows = 0, expected_rows = 0;
106 JournalRecordSummary summary;
109 bool good()
const {
return in.status == Status::ok; }
110 template<
class T>
void field(
const Path& path,
const char* name, T& value) {
112 if (++rows > expected_rows) { in.status = Status::corrupt;
return; }
113 const auto key = path.child(name);
114 const auto length = in.word();
116 if (length !=
key.length) { in.status = Status::corrupt;
return; }
117 std::array<char, 256> bytes{};
118 in.bytes(bytes.data(),
key.length);
120 if (std::memcmp(bytes.data(),
key.bytes.data(),
key.length) || in.word() != tag<T>()) {
121 in.status = Status::corrupt;
return;
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;
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);
136 if (
key.is(
"event.observation.bar")) bar =
static_cast<int>(
value);
141template<
class A>
struct Schema {
143 template<
class T>
void field(
const Path& p,
const char* name, T& value) {
144 a.field(p, name, value);
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();
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;
164 if constexpr (A::owning) value = OrderBirth{};
173 if constexpr (A::owning) {
181 if constexpr (A::owning)
183 "invalid admission birth");
184 else a.in.status = Status::corrupt;
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);
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);
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();
214 if (present) sizing(p.child(
"original_sizing"),*x.original_sizing);
215 F(explicit_equity);
F(explicit_price);
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);
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);
226 void draft(
const Path& p, Draft& x) {
227 bool present = bool(x.observation());
228 field(p,
"observation_present",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()));
237 present = x.review().has_value(); field(p,
"review_present",present);
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);
244 present = x.sizing_revision().has_value(); field(p,
"sizing_revision_present",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);
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);
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));
270 std::uint64_t first = 0;
271 std::size_t slot = 0;
272 if constexpr (!A::loading) {
274 first = a.begin_array(slot);
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);
282 void event(
Event& value) {
284 auto kind =
static_cast<std::uint64_t
>(
value.index()); field(p,
"kind",kind);
285 if constexpr (A::loading) {
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;
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));
303 if (!x.observation) { a.out.status = Status::programming_error;
return; }
304 observation(p.child(
"observation"),
const_cast<CommandObservation&
>(*x.observation));
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);
320 array(p.child(
"causes"),x.causes,[&](
const auto& q,
auto&
id){field(q,
"sequence",id);});
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);
331template<
bool Owning>
Status decode(
const detail::ScratchByteSource& source,
333 if (!
page.data || !
page.size)
return Status::programming_error;
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;
345 summary.rejected_bar = load.bar;
348 }
catch (
const std::bad_alloc&) {
return Status::programming_error; }
349 catch (...) {
return Status::corrupt; }
356 Save count{counter,
true};
357 Schema<Save>{count}.event(
const_cast<Event&
>(event));
362 Schema<Save>{save}.event(
const_cast<Event&
>(event));
363 length_ = out.position;
364 status_ = out.checked_status();
365 }
catch (...) { status_ = Status::programming_error; }
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;
376 if (offset < next_offset_) {
377 next_offset_ = next_row_ = 0;
378 array_elements_.fill(0);
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; }
393 const auto status = decode<true>(
source,page,result,summary);
394 if (status == Status::ok)
event = std::move(result);
400 return decode<false>(
source,page,scratch,summary);
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;
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);
413 const auto text = [&] {
414 const auto length = in.word();
415 if (length > in.remaining()) { in.status = Status::corrupt;
return; }
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]));
425 for (std::uint64_t i = 0; i != rows && in.status == Status::ok; ++i) {
427 const auto kind = in.word(); word(kind);
428 if (kind == 3) text();
430 auto value = in.word();
432 if ((value & 0x7fffffffffffffffULL) == 0) value = 0;
433 else if ((value & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL
434 && (value & 0x000fffffffffffffULL)) value = 0x7ff8000000000000ULL;
437 }
else in.status = Status::corrupt;
439 const auto status = in.finish();
440 if (status == Status::ok) digest = h;
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)
JournalRecordSource(const Event &) noexcept
detail::ScratchStatus read(std::uint64_t, detail::ScratchBuffer) const noexcept override
JournalRecordSource encode_journal_record(const Event &) noexcept
bool rejected_command(Outcome outcome) 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)