PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pine_state_hash.cpp
Go to the documentation of this file.
3
4#include "../broker_state_hash_internal.hpp"
5#include "../native_execution_consumer.hpp"
7
8#include <algorithm>
9#include <cmath>
10#include <cstring>
11#include <stdexcept>
12#include <type_traits>
13#include <variant>
14#include <vector>
15
16#ifndef PINEFORGE_TAIL_WORK
17#define PINEFORGE_TAIL_WORK(site, count) ((void)0)
18#endif
19#ifndef PINEFORGE_BROKER_HASH_OBSERVE
20#define PINEFORGE_BROKER_HASH_OBSERVE(rows, families, archived, digest) ((void)0)
21#endif
22
23namespace pineforge {
24namespace {
25
26// The admission journal is source-layer state (R5 lane N14): its reflected
27// fields fold here, through the source host's extension, exactly as they
28// did when this helper lived in the kernel-internal sink header.
29void hash_admission_field(BrokerStateHashSink& f, const admission::Field& field) {
30 f.s(field.path); f.u(field.value.index());
31 std::visit([&](const auto& value) {
32 using T = std::decay_t<decltype(value)>;
33 if constexpr (std::is_same_v<T, uint64_t>) f.u(value);
34 else if constexpr (std::is_same_v<T, int64_t>) f.i(value);
35 else if constexpr (std::is_same_v<T, double>) f.d(value);
36 else f.s(value);
37 }, field.value);
38}
39
40void hash_source_series(BrokerStateHashSink& f, const Series<double>& series) {
41 f.i(series.size());
42 for (int i = 0; i < series.size(); ++i) f.d(series[i]);
43}
44
45} // namespace
46
47namespace {
48
49void hash_source_run_identity(BrokerStateHashSink& f,
50 const native_order::RunIdentity& identity) {
51 f.s(identity.session_key);
52 // The monotonically increasing native generation protects stale handles;
53 // it does not change the source-visible state of a fresh run.
54 f.u(0);
55}
56void hash_scratch_digest(BrokerStateHashSink& f,
57 ::pineforge::detail::ScratchStatus status, std::uint64_t digest) {
59 throw coded<std::logic_error>(
62 {}, "scratch state digest failed");
63 f.u(digest);
64}
65void hash_scratch_store(BrokerStateHashSink& f,
66 const ::pineforge::detail::CommandScratchStore* store) {
67 f.b(store != nullptr);
68 if (!store) return;
69 // One page of workspace, independent of the record population. The state
70 // digest includes order.id, next_handle, record_count and every live row's
71 // handle/key/value, without depending on backing or cache placement.
72 std::vector<std::byte> page(store->page_size());
73 std::uint64_t digest = 0;
74 const auto status = store->state_digest({page.data(), page.size()}, digest);
77 hash_scratch_digest(f, status, digest);
78}
79void hash_exit_scratch(BrokerStateHashSink& f,
80 const source::detail::ExitScratchContext* scratch) {
81 f.b(scratch != nullptr);
82 if (!scratch) return;
83 hash_source_run_identity(f, scratch->run);
84 f.b(scratch->versions != nullptr);
85 f.b(scratch->lots != nullptr); f.b(scratch->working != nullptr);
86 hash_scratch_store(f, &scratch->store);
87}
88void hash_native_handle(BrokerStateHashSink& f, const native_order::RequestHandle& handle) {
89 hash_source_run_identity(f, handle.run); f.u(handle.incarnation);
90}
91void hash_native_handle_vector(BrokerStateHashSink& f,
92 const std::vector<native_order::RequestHandle>& handles) {
93 f.u(handles.size());
94 for (const auto& handle : handles) hash_native_handle(f, handle);
95}
96void hash_placement(BrokerStateHashSink& f, const source::PlacementSnapshot& value) {
97 f.i(static_cast<std::int64_t>(value.family)); f.s(value.source_id); f.s(value.from_entry);
98 f.s(value.comment); f.s(value.oca_name); f.i(value.oca_type); f.i(value.qty_type);
99 f.d(value.requested_qty); f.d(value.projection_remaining_qty);
100 f.d(value.qty_percent); f.b(value.is_long); f.b(value.immediately);
101 f.b(value.opening); f.b(value.deferred_cohort);
102 // Conditional v19/v4 retirement fact: an untouched placement retains
103 // its byte spelling. This receipt decides the filled-limit K1 exception.
104 if (value.filled_exit) f.u(0x66696c6c6564ULL);
105 f.b(value.reservation_deferred_to_pending_entry);
106 // Folded only when set, so a run that never issues a void exit keeps
107 // its digest (lane W3B-ENG-GRID).
108 if (value.void_issue) f.b(true);
109 // Folded only when set, like void_issue (MarginOpeningSwitches::
110 // void_flat_global_exit).
111 if (value.void_global_issue) f.u(0x766f6964676cULL);
112 // Folded only when set, like void_issue: the exit is bound to its entry
113 // id, not to lots (ExitBindingRuleSwitches).
114 if (value.pending_bound_exit) f.u(0x70656e64ULL);
115 // Folded only when set, like void_issue: one entry's bracket of a global
116 // exit that covers its position entry by entry (ExitBindingRuleSwitches).
117 if (value.per_entry_origin != 0) {
118 f.u(0x70657265ULL); f.u(value.per_entry_origin); f.d(value.per_entry_units);
119 }
120 // Folded only when set, like void_issue (lane W13-ENG-MARGIN-OPP).
121 if (value.crosses_zero) f.u(0x7a65726fULL);
122 if (!std::isnan(value.follow_up_fill)) f.d(value.follow_up_fill);
123 if (value.waypoint_margin_call) f.u(0x77617970ULL);
124 f.b(value.fixed_exit_reservation);
125 f.b(value.frozen_market_instruction);
126 f.d(value.frozen_market_own_units); f.d(value.frozen_market_transaction_units);
127 f.b(value.frozen_market_targeted_close); f.b(value.frozen_market_target_was_long);
128 f.b(value.direction_gate); f.b(value.affordability_policy_active);
129 f.b(value.affordability_close_only);
130 f.b(value.rounded_signal_cost_close_only);
131 f.b(value.affordability_keep_mc_close_surplus);
132 f.b(value.reverse_to); f.b(value.replaced_opening); f.b(value.replacement_predecessor_market);
133 f.b(value.terms_priced_reverse);
134 f.d(value.frozen_reversal_transaction);
135 f.i(value.placement_cycle); f.u(value.sequential_group); f.u(value.sequential_rank);
136 f.b(value.has_full_entry_bracket);
137 hash_source_run_identity(f, value.paired_reversal_parent.run);
138 f.u(value.paired_reversal_parent.incarnation);
139 hash_source_run_identity(f, value.preserved_by_close_all.run);
140 f.u(value.preserved_by_close_all.incarnation);
141 f.i(value.preserved_close_all_bar);
142 hash_source_run_identity(f, value.bracket_origin.run);
143 f.u(value.bracket_origin.incarnation);
144 f.u(value.source_sequence);
145 f.u(value.command_ordinal); f.u(value.placement_open_epoch);
146 f.u(value.command_sequence);
147 f.u(value.close_callsite_token); f.u(value.close_batch_calls);
148 f.s(value.close_first_id); f.d(value.close_first_target);
149 f.b(value.close_first_ledger_consumed); f.b(value.close_first_carry_valid);
150 f.d(value.close_first_carry_qty); f.b(value.close_retire_ledger_whole);
151 f.d(value.close_pending_later_qty);
152 f.i(value.placement_script_open_ms);
153 f.i(value.placement_sub_open_ms); f.i(value.projection_created_bar);
154 f.b(value.projection_created_bar_pinned);
155 f.i(value.projection_position_side); f.b(value.projection_after_close);
156 // Folded only when set, so a run without a close-first entry keeps its
157 // digest.
158 if (value.close_first_entry) f.u(0x636c6f7365666972ULL);
159 if (value.signal_price_refused) f.u(0x7369677265667573ULL);
160 f.b(value.projection_over_pyramiding);
161 f.b(value.projection_opposite_market_predecessor);
162 f.u(value.projection_predecessor);
163 f.u(value.chain_origin_sequence);
164 f.u(value.recreated_after_named_cancelled_entry_incarnation);
165 f.u(value.named_cancel_surviving_exit_incarnation);
166 f.b(value.retained_parent_topology);
167 f.b(value.defer_until_post_parent_calculation);
168 f.b(value.projection_predecessor_market); f.b(value.projection_predecessor_exit);
169 f.b(value.projection_created_during_coof); f.b(value.projection_coof_at_terminal);
170 f.b(value.projection_coof_mid_bar); f.d(value.forced_execution_price);
171 if (!std::isnan(value.forced_waypoint_print)) {
172 f.u(0x7761797074ULL); f.d(value.forced_waypoint_print);
173 }
174 if (value.coof_open_stop_next_waypoint) f.u(0x6f70656e73746f70ULL);
175 f.d(value.projection_tv_carry_qty);
176 f.d(value.projection_default_stop_equity);
177 f.d(value.projection_default_stop_signal_close);
178 f.d(value.projection_explicit_equity); f.d(value.projection_explicit_signal_close);
179 f.d(value.projection_affordability_equity);
180 f.d(value.projection_affordability_signal_price);
181 f.d(value.projection_affordability_held_qty);
182 f.d(value.sizing.equity); f.d(value.sizing.price);
183 f.d(value.sizing.fx); f.d(value.sizing.mark); f.d(value.sizing.frozen_units);
184 f.b(value.sizing.at_fill);
185 // Recorded only under a cash commission with a percent-of-equity default
186 // quantity (the folded run configuration decides), so every other run
187 // keeps its digest (R5 lane PAR-CASHFEE).
188 if (!std::isnan(value.sizing.strategy_equity)) f.d(value.sizing.strategy_equity);
189 f.d(value.exit_levels.limit); f.d(value.exit_levels.stop);
190 f.d(value.exit_levels.trail_points); f.d(value.exit_levels.trail_offset);
191 f.d(value.exit_levels.trail_price); f.d(value.exit_levels.profit_ticks);
192 f.d(value.exit_levels.loss_ticks);
193 f.d(value.trail_activation_level); f.d(value.retained_trail_best);
194 admission::reflect(value.market_admission, "placement.market_admission",
195 [&](const admission::Field& field) { hash_admission_field(f, field); });
196 f.i(static_cast<std::int64_t>(value.birth.cause())); f.i(value.birth.bar());
197 f.i(value.birth.timestamp()); f.i(static_cast<std::int64_t>(value.birth.cursor().domain()));
198 f.i(static_cast<std::int64_t>(value.birth.cursor().position()));
199 f.i(value.birth.cursor().index()); f.i(value.birth.cursor().count());
200 f.d(value.birth.cursor_price()); f.u(value.birth.first_fill()); f.u(value.birth.last_fill());
201 f.u(value.birth.evaluation_ordinal()); f.i(static_cast<std::int64_t>(value.birth_reach));
202 f.b(value.leg_activation.bounds().has_value());
203 if (value.leg_activation.bounds()) {
204 f.i(value.leg_activation.bounds()->position_cycle);
205 f.i(value.leg_activation.bounds()->stop_first_bar);
206 f.i(value.leg_activation.bounds()->limit_first_bar);
207 }
208 f.b(value.exit_activation.evidence().has_value());
209 if (value.exit_activation.evidence()) {
210 const auto& evidence = *value.exit_activation.evidence();
211 f.i(evidence.position_cycle); f.i(evidence.entry_bar); f.i(evidence.direction);
212 f.d(evidence.cursor_price); f.d(evidence.stop_level); f.d(evidence.limit_level);
213 f.b(evidence.limit_continuation.has_value());
214 if (evidence.limit_continuation) {
215 f.i(static_cast<std::int64_t>(evidence.limit_continuation->cause));
216 f.u(evidence.limit_continuation->observed_fill_sequence);
217 }
218 }
219 value.legs.visit(f);
220 f.b(value.restored_after_margin);
221 f.b(value.reservation_expansion.capture().has_value());
222 if (value.reservation_expansion.capture()) {
223 const auto& capture = *value.reservation_expansion.capture();
224 f.i(capture.position_cycle); f.i(static_cast<std::int64_t>(capture.side));
225 f.b(capture.first_later_admission.has_value());
226 if (capture.first_later_admission) f.u(*capture.first_later_admission);
227 }
228 f.b(value.reservation_growth_source.reservation_owner().has_value());
229 if (value.reservation_growth_source.reservation_owner())
230 f.u(*value.reservation_growth_source.reservation_owner());
231 f.u(value.reservation_growth_owner_incarnation);
232 f.b(value.stop_limit_activated); f.i(value.coof_cascade_seg_i);
233 f.b(value.coof_cascade_inflight_fires); f.b(value.paired_flat_market_candidate);
234 f.d(value.paired_flat_market_own_qty); f.d(value.paired_flat_market_signal_close);
235 f.d(value.paired_flat_market_signal_equity);
236 f.d(value.paired_flat_market_signal_margin_pct);
237 f.d(value.paired_flat_market_signal_pointvalue); f.d(value.paired_flat_market_signal_fx);
238 f.i(value.paired_flat_market_peer_seq); f.d(value.paired_flat_market_transaction_qty);
239 f.i(value.signal_close_mc_bar); f.u(value.signal_close_mc_entry_incarnation);
240 f.u(value.signal_close_mc_fill_seq); f.d(value.signal_close_mc_remaining_qty);
241 f.b(value.pooc_global_full_exit_dynamic_qty);
242 f.b(value.pooc_global_full_exit_tracks_bound_adds);
243 f.b(value.pooc_global_full_exit_bound_add);
244 f.i(static_cast<std::int32_t>(value.cancellation.cause)); f.i(value.cancellation.state);
245 f.i(value.cancellation.close_claim_release); f.u(value.cancellation.source_incarnation);
246 f.i(value.cancellation.source_sequence); f.u(value.cancellation.target_incarnation);
247 f.i(value.cancellation.target_owner); f.u(value.cancellation.target_revision);
248 f.d(value.cancellation.close_claim_consumed); f.d(value.cancellation.close_claim_retired);
249}
250
251void hash_short_seed_plan(BrokerStateHashSink& f, const source::ShortSeedPlan& value) {
252 hash_native_handle(f, value.long_entry); hash_native_handle(f, value.materialize_long);
253 hash_native_handle(f, value.final_short); f.s(value.seed_id); f.s(value.long_entry_id);
254 f.s(value.final_short_id); f.s(value.materialize_label); f.d(value.seed_qty);
255 f.i(value.seed_cycle); f.b(value.active); f.b(value.report_swap_pending);
256}
257
258void hash_native_request(BrokerStateHashSink& f, const native_order::Request& request) {
259 f.u(request.intent.index());
260 if (const auto* reduce = std::get_if<native_order::Reduce>(&request.intent)) {
261 f.u(reduce->size.index());
262 if (const auto* units = std::get_if<native_order::ExplicitUnits>(&reduce->size)) f.d(units->units);
263 } else if (const auto* transact = std::get_if<native_order::Transact>(&request.intent)) {
264 f.d(transact->signed_units);
265 } else if (const auto* reverse = std::get_if<native_order::ReverseTo>(&request.intent)) {
266 f.d(reverse->signed_units);
267 } else if (const auto* sized = std::get_if<native_order::HostSized>(&request.intent)) {
268 f.i(static_cast<std::int64_t>(sized->kind)); f.b(sized->side.has_value());
269 if (sized->side) f.i(static_cast<std::int64_t>(*sized->side));
270 } else if (const auto* core_sized = std::get_if<native_order::Sized>(&request.intent)) {
271 // A core-sized opening carries its whole quantity in the basis, so the
272 // source fingerprint must separate two commands that differ only there.
273 f.i(static_cast<std::int64_t>(core_sized->side));
274 f.u(core_sized->basis.index());
275 if (const auto* cash = std::get_if<native_order::CashValue>(&core_sized->basis)) {
276 f.d(cash->cash);
277 } else {
278 f.d(std::get<native_order::EquityFraction>(core_sized->basis).fraction);
279 }
280 f.i(static_cast<std::int64_t>(core_sized->time));
281 f.i(static_cast<std::int64_t>(core_sized->price));
282 f.i(static_cast<std::int64_t>(core_sized->grid_policy));
283 f.b(core_sized->reserve_percent_fee);
284 }
285 f.s(request.label); f.s(request.comment);
286 f.u(request.trigger.index());
287 if (const auto* limit = std::get_if<native_order::Limit>(&request.trigger)) f.d(limit->price);
288 else if (const auto* stop = std::get_if<native_order::Stop>(&request.trigger)) f.d(stop->price);
289 else if (const auto* stop_limit = std::get_if<native_order::StopLimit>(&request.trigger)) {
290 f.d(stop_limit->stop); f.d(stop_limit->limit);
291 } else if (const auto* trail = std::get_if<native_order::Trail>(&request.trigger)) {
292 f.d(trail->offset); f.b(trail->arm_price.has_value());
293 if (trail->arm_price) f.d(*trail->arm_price);
294 }
295 f.u(request.capacity.index());
296 if (const auto* budget = std::get_if<native_order::PointBudget>(&request.capacity)) f.d(budget->units);
297 f.u(request.owner.index());
298 if (const auto* wait = std::get_if<native_order::WaitForApplied>(&request.owner)) {
299 hash_native_handle(f, wait->parent);
300 } else if (const auto* opening = std::get_if<native_order::BindOpening>(&request.owner)) {
301 hash_native_handle(f, opening->opening); f.i(opening->cycle);
302 } else if (const auto* openings = std::get_if<native_order::BindOpenings>(&request.owner)) {
303 hash_native_handle_vector(f, openings->openings); f.i(openings->cycle);
304 } else if (const auto* cohort = std::get_if<native_order::BindCohort>(&request.owner)) {
305 f.u(cohort->cohort.value);
306 }
307 f.u(request.group.index());
308 if (const auto* group = std::get_if<native_order::Member>(&request.group)) {
309 f.u(group->group); f.i(group->cohort); f.i(static_cast<std::int64_t>(group->effect));
310 }
311}
312
313} // namespace
314
317 hash_exit_scratch(f, exit_scratch_.get());
318 f.u(source_sequence_); f.u(command_ordinal_); f.u(broker_open_epoch_);
319 f.i(last_broker_open_ms_); f.u(source_command_sequence_); f.b(host_ != nullptr);
320 f.b(config_.process_orders_on_close); f.b(config_.calc_on_order_fills);
321 f.d(config_.initial_capital); f.i(config_.default_qty_type); f.d(config_.default_qty_value);
322 f.i(config_.pyramiding); f.d(config_.commission_value); f.i(config_.commission_type);
323 f.i(config_.slippage); f.d(config_.margin_long); f.d(config_.margin_short);
324 f.b(config_.close_entries_rule_any); f.b(config_.src_series_active);
325 f.s(staged_.syminfo.ticker); f.s(staged_.syminfo.tickerid); f.s(staged_.syminfo.currency);
326 f.s(staged_.syminfo.basecurrency); f.s(staged_.syminfo.type); f.s(staged_.syminfo.timezone);
327 f.s(staged_.syminfo.session); f.s(staged_.syminfo.volumetype); f.s(staged_.syminfo.description);
328 f.d(staged_.syminfo.mintick); f.d(staged_.syminfo.pointvalue); f.d(staged_.syminfo.qty_step);
329 f.s(staged_.chart_timezone); f.d(staged_.account_fx);
330 f.u(staged_.account_fx_effective_from_ms.size());
331 for (const auto timestamp : staged_.account_fx_effective_from_ms) f.i(timestamp);
332 f.u(staged_.account_fx_per_quote.size());
333 for (const auto rate : staged_.account_fx_per_quote) f.d(rate);
334 f.b(staged_.quantity_grid.has_value()); if (staged_.quantity_grid) f.d(*staged_.quantity_grid);
335 // Lane XSYM-D: other symbols' feeds, folded only when installed. Their
336 // bars are the projected spec's instrument feeds, which the continuation
337 // digest folds, so the keys and sizes are what this fold adds.
338 if (!staged_.instrument_feeds.empty()) {
339 f.u(staged_.instrument_feeds.size());
340 for (const auto& feed : staged_.instrument_feeds) {
341 f.s(feed.instrument);
342 f.s(feed.tf);
343 f.u(feed.bars);
344 f.u(feed.columns);
345 }
346 }
347 std::vector<std::string> input_keys;
348 for (const auto& pair : staged_.inputs) input_keys.push_back(pair.first);
349 std::sort(input_keys.begin(), input_keys.end()); f.u(input_keys.size());
350 for (const auto& key : input_keys) { f.s(key); f.s(staged_.inputs.at(key)); }
351 // Replay every logical cohort in the same lexicographic order as before,
352 // including retired summaries, without materializing an id-sized vector.
353 f.u(cohorts_by_id_.size());
354 for (const auto& row : cohorts_by_id_.all_sorted()) {
355 const auto& key = row.first; const auto& cohort = row.second;
356 f.s(key); f.u(cohort.handle.value); f.i(cohort.cycle);
357 // v4: the origin roster is append-only; its members fold once each,
358 // when appended (OriginRoster::digest).
359 f.u(cohort.origins.size()); f.u(cohort.origins.digest());
360 hash_native_handle_vector(f, cohort.opened);
361 f.u(cohort.erased_opening_sides);
362 std::vector<std::uint64_t> live_origin_keys;
363 live_origin_keys.reserve(cohort.live_units_by_origin.size());
364 for (const auto& row : cohort.live_units_by_origin) live_origin_keys.push_back(row.first);
365 std::sort(live_origin_keys.begin(), live_origin_keys.end());
366 f.u(live_origin_keys.size());
367 for (const auto incarnation : live_origin_keys) {
368 f.u(incarnation); f.d(cohort.live_units_by_origin.at(incarnation));
369 }
370 }
371 f.u(cohorts_by_id_.size());
372 cohorts_by_id_.for_each_id([&](const auto& key) { f.s(key); });
373 // v4: the rows the table still holds, in incarnation order, and what the
374 // erased ones left: the high water, how many, and a running digest of
375 // their identities (PlacementTable::erased_digest).
376 // O1W's terminally filled, non-revivable limits use the same erase/fold
377 // path. This deliberately changes values inside the retirement epoch;
378 // no hash of a mutable placement is cached.
379 PINEFORGE_BROKER_HASH_OBSERVE(placement_, bracket_families_,
380 settled_bracket_families_, settled_bracket_digest_);
381 f.u(placement_.high_water()); f.u(placement_.erased_rows()); f.u(placement_.erased_digest());
382 f.u(placement_.size());
383 for (const auto& row : placement_) {
384 PINEFORGE_TAIL_WORK("hash_placements", 1);
385 f.u(row.first); hash_placement(f, row.second);
386 }
387 std::vector<std::uint64_t> live_keys;
388 for (const auto& pair : live_by_source_key_) live_keys.push_back(pair.first);
389 std::sort(live_keys.begin(), live_keys.end()); f.u(live_keys.size());
390 for (const auto key : live_keys) { f.u(key); hash_native_handle(f, live_by_source_key_.at(key)); }
391 std::vector<std::uint64_t> bracket_keys;
392 for (const auto& pair : bracket_families_) bracket_keys.push_back(pair.first);
393 std::sort(bracket_keys.begin(), bracket_keys.end()); f.u(bracket_keys.size());
394 for (const auto key : bracket_keys) {
395 // v4: the settled prefix as its count and running digest, the
396 // working tail in full, and the consumed legs of erased members.
397 const auto& family = bracket_families_.at(key);
398 PINEFORGE_TAIL_WORK("hash_families", 1);
399 f.u(key); f.u(family.settled_count()); f.u(family.settled_digest());
400 hash_native_handle_vector(f, family.working_tail());
401 f.u(family.consumed_facts().size());
402 for (const auto& fact : family.consumed_facts()) { f.u(fact.first); f.u(fact.second); }
403 }
404 // Immutable wholly settled families are still addressable for a later
405 // bracket cancel/re-issue. Their keyed digest changes on archive, restore
406 // and erase, so reads cost only active families. This versioned extension
407 // is absent when no family is archived; the v4 working-tail fold above
408 // keeps its spelling. Run identity is normalized as for native handles.
409 if (!settled_bracket_families_.empty()) {
410 f.s("pineforge-settled-bracket-families/v1");
411 f.u(settled_bracket_families_.size()); f.u(settled_bracket_digest_);
412 }
413 f.u(pending_bracket_legs_.size());
414 for (const auto& leg : pending_bracket_legs_) {
415 hash_native_request(f, leg.request); hash_placement(f, leg.snapshot);
416 f.s(leg.replacement_key); f.u(leg.family_key);
417 }
418 f.u(pending_entries_.size());
419 for (const auto& entry : pending_entries_) {
420 hash_native_request(f, entry.request); hash_placement(f, entry.snapshot);
421 f.s(entry.replacement_key);
422 }
423 f.u(delayed_market_orders_.size());
424 for (const auto& order : delayed_market_orders_) {
425 hash_native_request(f, order.request); hash_placement(f, order.snapshot);
426 f.s(order.replacement_key); f.u(order.release_open_epoch);
427 f.b(order.execute_at_open);
428 }
429 f.u(deferred_open_marketable_sells_.size());
430 for (const auto& sell : deferred_open_marketable_sells_) {
431 hash_placement(f, sell.snapshot); f.s(sell.replacement_key); f.d(sell.fill_price);
432 f.d(sell.path_position); f.b(sell.open_marketable);
433 }
434 f.u(throttled_reopen_rearm_.size());
435 for (const auto& snapshot : throttled_reopen_rearm_) hash_placement(f, snapshot);
436 f.i(entry_openings_interval_index_); f.i(entry_openings_this_interval_);
437 f.u(pending_same_bar_commands_.size());
438 for (const auto& command : pending_same_bar_commands_) {
439 hash_native_request(f, command.request); hash_placement(f, command.snapshot);
440 f.s(command.replacement_key); f.b(command.opening);
441 if (command.unbatched_request && command.unbatched_snapshot) {
442 f.u(0x756e626174636865ULL);
443 hash_native_request(f, *command.unbatched_request);
444 hash_placement(f, *command.unbatched_snapshot);
445 const auto& context = command.unbatched_context;
446 f.d(context.current); f.d(context.limit_price); f.d(context.stop_price);
447 f.b(context.default_sized); f.b(context.priced); f.b(context.reverses);
448 f.b(context.opposite_opening_pending); f.b(context.pure_stop_entry);
449 f.b(context.coof_market_next_open); f.b(context.coof_priced_next_open);
450 f.b(context.coof_market_at_second_extreme); f.b(context.paired_all_in_reentry);
451 }
452 // Folded only when set, so a batch without an R1 reissue keeps its digest.
453 if (command.staged_reversal) {
454 f.b(command.staged_reversal); f.d(command.staged_reversal_held_units);
455 }
456 }
457 f.u(source_shadow_pending_.size());
458 for (const auto& shadow : source_shadow_pending_) {
459 hash_placement(f, shadow.snapshot); f.s(shadow.label);
460 }
461 f.d(pending_same_bar_close_qty_);
462 f.u(pending_relative_exits_.size());
463 for (const auto& exit : pending_relative_exits_) {
464 f.s(exit.exit_id); f.s(exit.from_entry); f.d(exit.trail_points); f.d(exit.trail_offset);
465 f.d(exit.trail_price); f.d(exit.qty_percent); f.s(exit.comment); f.d(exit.qty);
466 f.s(exit.oca_name); f.d(exit.profit_ticks); f.d(exit.loss_ticks);
467 }
468 // R5 lane R4d: folded only while a leg is anchored, so every run that
469 // anchors none keeps its established hash.
470 if (!anchored_relative_legs_.empty() || anchored_relative_stats_.anchored != 0
471 || materializing_parent_.incarnation != 0) {
472 f.s("pineforge-source-anchored-legs/v1");
473 f.u(anchored_relative_stats_.anchored); f.u(anchored_relative_stats_.adopted);
474 f.u(anchored_relative_stats_.withdrawn); f.u(materializing_parent_.incarnation);
475 f.i(anchored_cohort_sequence_);
476 f.u(anchored_relative_legs_.size());
477 for (const auto& leg : anchored_relative_legs_) {
478 f.s(leg.exit_id); f.s(leg.from_entry); f.u(static_cast<std::uint64_t>(leg.family));
479 f.u(leg.handle.incarnation); f.u(leg.parent.incarnation); f.b(leg.parent_long);
480 f.d(leg.operand_ticks); f.d(leg.trail_offset);
481 hash_native_request(f, leg.request);
482 // hash_native_request predates anchors; an anchored leg's own
483 // spelling is folded here.
484 if (const auto* anchor = std::get_if<native_order::FromOwnerFill>(
485 &leg.request.anchor)) {
486 f.d(anchor->offset); f.b(anchor->ticks);
487 f.u(static_cast<std::uint64_t>(anchor->rounding));
488 }
489 f.b(leg.armed); f.d(leg.installed_level);
490 }
491 }
492 // Folded only while a global exit covers its position entry by entry
493 // (ExitBindingRuleSwitches::global_exit_per_entry_levels), so every other
494 // run keeps its established hash.
495 if (!per_entry_exits_.empty()) {
496 f.s("pineforge-source-per-entry-exits/v1");
497 f.u(per_entry_exits_.size());
498 for (const auto& exit : per_entry_exits_) {
499 f.s(exit.exit_id); f.i(exit.cycle); f.b(exit.long_side);
500 f.d(exit.profit_ticks); f.d(exit.loss_ticks); f.s(exit.comment);
501 f.u(exit.children.size());
502 for (const auto& child : exit.children) {
503 f.u(child.origin); f.d(child.fill_price); f.d(child.units); f.b(child.consumed);
504 }
505 }
506 }
507 f.u(pending_coof_requests_.size());
508 for (const auto& pending : pending_coof_requests_) {
509 hash_native_request(f, pending.request); hash_placement(f, pending.snapshot);
510 f.s(pending.replacement_key); f.b(pending.opening); f.u(pending.family_key);
511 f.b(pending.next_open);
512 }
513 f.u(pending_margin_revivals_.size());
514 for (const auto& pending : pending_margin_revivals_) {
515 hash_placement(f, pending.snapshot); f.i(pending.decline_bar);
516 }
517 hash_native_handle_vector(f, live_handles_); hash_native_handle_vector(f, first_open_newborns_);
518 // PendingIntentView is a derived read-only alias of live_handles_. Keep
519 // its historical fingerprint position and bytes without copying the
520 // roster at every mutation boundary.
521 hash_native_handle_vector(f, live_handles_);
522 // v4: a log no decision reads, folded once per receipt when recorded.
523 f.u(dropped_close_count_); f.u(dropped_close_digest_);
524 f.u(open_entry_fees_.size());
525 for (const auto& fee : open_entry_fees_) {
526 hash_native_handle(f, fee.opening); f.s(fee.source_id); f.d(fee.units);
527 f.d(fee.nonpercent_fee);
528 }
529 // v4: the final prefix folded once, the current bar's tail in full.
530 const auto trade_prefix = host_
531 ? NativeExecutionConsumer::bound(*host_).closed_trade_prefix()
532 : NativeExecutionConsumer::ClosedTradePrefix{};
533 const std::size_t phase_end = trade_prefix.base + trade_exit_phase_.size();
534 const auto* phase_consumer = host_ ? &NativeExecutionConsumer::bound(*host_) : nullptr;
535 f.u(phase_end);
536 if (phase_consumer && phase_consumer->exit_phases_restarted()) {
537 // Preserve the sink-off hash's raw reset marker AND its phase bytes.
538 // The bounded transform applies the sealed bytes to this incoming
539 // hash, rather than substituting a different digest representation.
540 f.u(0); f.u(BrokerStateHashSink{}.h);
541 f.h = phase_consumer->replay_sealed_exit_phases(f.h);
542 for (const auto phase : trade_exit_phase_) f.u(phase);
543 } else {
544 if (exit_phase_final_ < trade_prefix.base)
546 "hashed exit-phase fold precedes the sealed prefix");
547 f.u(exit_phase_final_); f.u(exit_phase_digest_);
548 for (std::size_t index = exit_phase_final_; index < phase_end; ++index)
549 f.u(trade_exit_phase_.at(index - trade_prefix.base));
550 }
551 // The entry-id table orders children once it holds two ids, and its size
552 // matters once its peak passes 13 keys; below both it decides nothing.
553 if (exit_rank_keys_.size() >= 2 || exit_rank_peak_ > 13 || exit_rank_alt_peak_ > 13) {
554 f.u(exit_rank_keys_.size()); f.u(exit_rank_keys_digest_);
555 f.u(exit_rank_peak_); f.u(exit_rank_alt_peak_);
556 }
557 std::vector<std::uint64_t> current_debit_ordinals;
558 current_debit_ordinals.reserve(current_debited_applied_ordinals_.size());
559 for (const auto ordinal : current_debited_applied_ordinals_) current_debit_ordinals.push_back(ordinal);
560 std::sort(current_debit_ordinals.begin(), current_debit_ordinals.end());
561 f.u(current_debit_ordinals.size());
562 for (const auto ordinal : current_debit_ordinals) f.u(ordinal);
563 std::vector<std::uint64_t> intraday_relabel_ordinals;
564 intraday_relabel_ordinals.reserve(intraday_loss_relabel_ordinals_.size());
565 for (const auto ordinal : intraday_loss_relabel_ordinals_)
566 intraday_relabel_ordinals.push_back(ordinal);
567 std::sort(intraday_relabel_ordinals.begin(), intraday_relabel_ordinals.end());
568 f.u(intraday_relabel_ordinals.size());
569 for (const auto ordinal : intraday_relabel_ordinals) f.u(ordinal);
570 consumed_partial_exit_cycles_.fold_sorted(f);
571 std::vector<std::uint64_t> shadowed_openings(
572 bracket_shadowed_openings_.begin(), bracket_shadowed_openings_.end());
573 std::sort(shadowed_openings.begin(), shadowed_openings.end());
574 f.u(shadowed_openings.size());
575 for (const auto opening : shadowed_openings) f.u(opening);
576 std::vector<std::string> named_cancel_keys;
577 named_cancel_keys.reserve(named_entry_cancel_tokens_.size());
578 for (const auto& row : named_entry_cancel_tokens_) named_cancel_keys.push_back(row.first);
579 std::sort(named_cancel_keys.begin(), named_cancel_keys.end());
580 f.u(named_cancel_keys.size());
581 for (const auto& key : named_cancel_keys) {
582 const auto& token = named_entry_cancel_tokens_.at(key);
583 f.s(key); f.u(token.entry_incarnation); f.u(token.surviving_exit_incarnation);
584 }
585 // Folded only when an exit's id binding outlived a cancel of its order,
586 // so a run that never keeps one folds what it folded before.
587 if (!id_bound_exits_.empty()) {
588 f.u(0x69646278ULL);
589 f.u(id_bound_exits_.size());
590 for (const auto& row : id_bound_exits_) {
591 f.s(row.exit_id); f.s(row.from_entry);
592 f.d(row.levels.limit); f.d(row.levels.stop); f.d(row.levels.trail_points);
593 f.d(row.levels.trail_offset); f.d(row.levels.trail_price);
594 f.d(row.levels.profit_ticks); f.d(row.levels.loss_ticks);
595 f.d(row.qty_percent); f.d(row.qty); f.s(row.comment); f.s(row.oca_name);
596 }
597 }
598 f.u(close_logical_units_.size());
599 for (const auto& row : close_logical_units_) { f.s(row.first); f.d(row.second); }
600 // The ledger's records fold only when there are two or more: a single
601 // record is the one id close_logical_units_ holds, and none is none, so a
602 // position of one id folds what it folded before the records existed.
603 if (close_ledger_records_.size() > 1) {
604 f.u(close_ledger_records_.size());
605 for (const auto& record : close_ledger_records_) { f.s(record.id); f.d(record.units); }
606 }
607 // The pyramiding records, unmerged, kept only under pyramiding above one;
608 // folded only while there are any, so every other run keeps its digest.
609 if (!pyramiding_records_.empty()) {
610 f.s("pineforge-pyramiding-records/v1");
611 f.u(pyramiding_records_.size());
612 for (const auto& record : pyramiding_records_) {
613 f.s(record.id); f.u(record.incarnation); f.d(record.units);
614 }
615 }
616 // The exit tombstones, folded only while there are any.
617 if (!exit_tombstones_.empty()) {
618 std::vector<SourceId> keys;
619 keys.reserve(exit_tombstones_.size());
620 for (const auto& row : exit_tombstones_) keys.push_back(row.first);
621 std::sort(keys.begin(), keys.end());
622 f.s("pineforge-exit-tombstones/v1");
623 f.u(keys.size());
624 for (const auto& key : keys) { f.s(key); f.u(exit_tombstones_.at(key)); }
625 }
626 // The retired reservation model's two maps and two per-site maps fold as
627 // the empty maps they always were outside a multi-call close site.
628 f.u(0); f.u(0); f.u(0); f.u(0);
629 f.u(close_batch_callsites_.size());
630 for (const auto& row : close_batch_callsites_) {
631 const auto& site = row.second;
632 f.u(row.first); f.b(site.active); f.u(site.token); f.i(site.calls);
633 f.s(site.first_id); f.s(site.id); f.s(site.comment);
634 f.d(site.target); f.u(site.queue_sequence);
635 }
636 f.i(close_batch_bar_); f.u(close_batch_queue_sequence_);
637 f.d(close_batch_pending_debt_); f.d(close_batch_admitted_total_);
638 f.u(receipt_cursor_);
639 f.u(last_applied_ordinal_);
640 f.u(terminal_receipt_cursor_);
641 f.i(entry_attempt_bar_); f.u(entry_attempts_on_bar_);
642 f.b(materializing_relative_);
643 f.i(current_position_cycle_);
644 f.i(current_position_sign_);
645 f.u(next_sequential_group_);
646 f.b(source_batch_mutated_);
647 f.b(coof_recalc_active_); f.b(coof_first_open_);
648 if (coof_open_stop_next_waypoint_) f.u(0x6f70656e6e657874ULL);
649 f.u(coof_market_entry_recalc_incarnation_);
650 f.u(coof_market_entry_recalc_fill_seq_); f.u(coof_current_fill_seq_);
651 // Live only inside a fill recalculation (NaN otherwise); folded then so
652 // the idle digest keeps its prior form.
653 if (coof_recalc_active_) f.d(coof_fill_cursor_t_);
654 if (coof_recalc_active_ && !std::isnan(coof_fill_target_print_)) {
655 f.u(0x7761797466ULL); f.d(coof_fill_target_print_);
656 }
657 if (coof_recalc_active_) f.b(coof_fill_forced_);
658 const auto& coof_coord = coof_context_.coordinate;
659 f.u(coof_coord.ordinal); f.i(coof_coord.interval_index); f.i(coof_coord.open_ms);
660 f.i(coof_coord.eligible_open_ms); f.i(coof_coord.last_traded_close_ms);
661 f.i(coof_coord.next_period_open_ms); f.i(coof_coord.next_input_open_ms);
662 f.i(coof_coord.effective_time_ms); f.i(coof_coord.source_price_time_ms);
663 f.i(static_cast<std::int64_t>(coof_coord.provenance));
664 f.i(static_cast<std::int64_t>(coof_coord.path_phase));
665 f.i(static_cast<std::int64_t>(coof_coord.completion)); f.i(coof_context_.decision_floor_ms);
666 const auto hash_coof_interval = [&](const native_calendar::NativeInterval& interval) {
667 f.i(interval.open_ms); f.i(interval.eligible_open_ms); f.i(interval.last_traded_close_ms);
668 f.i(interval.next_period_open_ms); f.i(interval.next_input_open_ms);
669 };
670 hash_coof_interval(coof_context_.input_interval);
671 hash_coof_interval(coof_context_.script_interval);
672 f.i(coof_context_.sub_index); f.i(coof_context_.sub_count);
673 f.b(coof_context_.is_terminal_sub_bar); f.i(coof_context_.sub_bar_open_ms);
674 f.i(coof_context_.script_bar_open_ms);
675 f.b(coof_context_.driver_statistics.intrabar_path_enabled);
676 f.i(coof_context_.driver_statistics.sub_bars_per_script_bar);
677 f.i(coof_context_.driver_statistics.samples_per_sub_bar);
678 f.u(coof_context_.driver_statistics.sub_bars_processed);
679 f.u(coof_context_.driver_statistics.sample_ticks_processed);
680 f.d(coof_script_bar_.open); f.d(coof_script_bar_.high); f.d(coof_script_bar_.low);
681 f.d(coof_script_bar_.close); f.d(coof_script_bar_.volume); f.i(coof_script_bar_.timestamp);
682 f.b(coof_script_bar_valid_);
683 // v4: a log no decision reads, folded once per new script bar.
684 f.u(pooc_close_basis_count_); f.i(pooc_close_basis_last_bar_); f.u(pooc_close_basis_digest_);
685 f.d(pooc_open_basis_); f.i(pooc_open_script_bar_); f.i(close_all_pending_script_bar_);
686 f.d(last_fx_rate_); f.i(position_open_script_bar_); f.u(position_open_epoch_); f.i(position_open_bar_index_);
687 f.u(static_cast<std::uint64_t>(position_open_phase_));
688 f.b(position_open_priced_);
689 f.i(last_margin_call_script_bar_); f.i(pooc_close_checkpoint_deferred_ms_);
690 // Live only from a close to the next open; folded only then (lane
691 // W13-ENG-MARGIN-OPP), so every other run keeps its digest.
692 if (close_margin_open_bar_ != std::numeric_limits<std::int64_t>::min())
693 f.i(close_margin_open_bar_);
694 // A short's lagged follow-up owed at a path point (a process_orders_on_close
695 // close owes the next open) and a close's after-script call the orders
696 // placed there read at the next open (MarginScheduleSwitches); folded only
697 // while set, so every other run keeps its digest.
698 if (owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min()) {
699 f.s("pineforge-owed-follow-up/v1");
700 f.i(owed_follow_up_bar_); f.i(owed_follow_up_point_); f.d(owed_follow_up_units_);
701 }
702 if (close_call_after_script_bar_ != std::numeric_limits<std::int64_t>::min()) {
703 f.s("pineforge-close-call-after-script/v1");
704 f.i(close_call_after_script_bar_); f.d(close_call_after_script_book_);
705 f.d(close_call_after_script_units_);
706 }
707 // An opening checkpoint owed after the exit legs marketable at an open
708 // (MarginOpeningSwitches::open_marketable_exit_first); folded only while
709 // set, so every other run keeps its digest.
710 if (open_exit_first_bar_ != std::numeric_limits<std::int64_t>::min()) {
711 f.s("pineforge-open-exit-first/v1");
712 f.i(open_exit_first_bar_); f.u(open_exit_first_origin_); f.s(open_exit_first_id_);
713 f.d(open_exit_first_fill_);
714 }
715 // The close-first fact (record_close_first); folded only once a whole
716 // close of an open position was recorded, so every other run keeps its
717 // digest.
718 if (close_first_script_bar_ != std::numeric_limits<std::int64_t>::min()) {
719 f.s("pineforge-close-first/v1");
720 f.i(close_first_script_bar_); f.i(close_first_side_);
721 }
722 f.u(last_margin_call_event_ordinal_);
723 f.u(last_margin_call_entry_incarnation_); f.i(last_margin_call_position_cycle_);
724 f.b(last_margin_call_at_script_close_);
725 f.d(last_margin_call_closed_units_); f.d(last_margin_call_remaining_units_);
726 // The G + L money mirror (gain_loss_money), over every closed trade
727 // whenever this hash is taken (the stored sums plus the trades booked
728 // since their last fold), so the digest never depends on when they were
729 // folded. Its sum is the engine's netprofit, already in the engine's
730 // state, wherever the two agree bit for bit; the sums are folded where
731 // they part, so a run they never part on keeps its digest.
732 if (source::detail::margin_rule_switches().gain_loss_money) {
733 if (const auto* pine = pine_view_of(host_)) {
734 // A new run's ledger shorter than the stored fold starts over,
735 // as fold_source_money does.
736 const std::uint64_t count = pine->closed_trade_count();
737 const bool restart = count < source_money_folded_;
738 if (count < trade_prefix.base)
740 "hashed source-money rewind precedes the sealed prefix");
741 double gains = restart ? trade_prefix.source_gains : source_gains_;
742 double losses = restart ? trade_prefix.source_losses : source_losses_;
743 for (std::uint64_t index = restart ? trade_prefix.base : source_money_folded_;
744 index < count; ++index) {
745 const double profit = pine->closed_trade(index).pnl;
746 if (profit > 0.0) gains += profit;
747 else losses += profit;
748 }
749 const double mirrored = gains + losses;
750 const double engine_net = pine->net_profit_sum_;
751 if (count != 0 && std::memcmp(&mirrored, &engine_net, sizeof(double)) != 0) {
752 f.d(gains); f.d(losses); f.u(count);
753 }
754 }
755 }
756 f.i(signal_close_mc_event_bar_);
757 f.i(signal_close_mc_position_cycle_); f.u(signal_close_mc_entry_incarnation_);
758 f.u(signal_close_mc_fill_seq_); f.d(signal_close_mc_before_qty_);
759 f.d(signal_close_mc_remaining_qty_); f.i(risk_coof_direct_script_bar_);
760 f.u(cap_latest_fill_);
761 f.b(source_margin_call_enabled_);
762 f.d(policy_script_bar_.open); f.d(policy_script_bar_.high);
763 f.d(policy_script_bar_.low); f.d(policy_script_bar_.close);
764 f.d(policy_script_bar_.volume); f.i(policy_script_bar_.timestamp);
765 f.b(policy_script_bar_valid_);
766 // Market-add provenance. It used to ride the
767 // generic lot as PyramidEntry::market_pyramid_add; it is adapter state
768 // now, so the source extension folds it. Unordered container: sort the
769 // keys, exactly like trail_state_at_open_ below.
770 std::vector<std::uint64_t> market_add_keys(market_pyramid_adds_.begin(),
771 market_pyramid_adds_.end());
772 std::sort(market_add_keys.begin(), market_add_keys.end());
773 f.u(market_add_keys.size());
774 for (const auto key : market_add_keys) f.u(key);
775 std::vector<std::uint64_t> trail_open_keys;
776 trail_open_keys.reserve(trail_state_at_open_.size());
777 for (const auto& row : trail_state_at_open_) trail_open_keys.push_back(row.first);
778 std::sort(trail_open_keys.begin(), trail_open_keys.end());
779 f.u(trail_open_keys.size());
780 for (const auto key : trail_open_keys) {
781 const auto& state = trail_state_at_open_.at(key);
782 f.u(key); f.b(state.activated); f.d(state.best_price);
783 f.d(state.current_level); f.u(state.activation_ordinal);
784 }
785 // The trails re-priced for the bar in flight (carried trail tick reach),
786 // empty between bars; folded only while set, so every run without one
787 // keeps its digest.
788 if (!carried_trail_retunes_.empty()) {
789 f.u(carried_trail_retunes_.size());
790 for (const auto& [handle, distance] : carried_trail_retunes_) {
791 f.u(handle.incarnation); f.d(distance);
792 }
793 }
794 f.b(stream_mode_);
795 f.i(day_ledger_.current_day); f.i(day_ledger_.last_loss_day); f.i(day_ledger_.consecutive_loss_days);
796 f.i(day_ledger_.intraday_loss_day); f.d(day_ledger_.intraday_start_equity);
797 f.d(day_ledger_.intraday_realized); f.u(day_ledger_.observed_applied_ordinal);
798 // Decision state only under an intraday loss rule (lane W8A-SIGSTATE-1
799 // R4); folded only then, so every run without one keeps its digest.
800 if (risk_.max_intraday_loss > 0.0) f.i(day_ledger_.intraday_start_script_bar);
801 f.i(risk_.direction); f.i(risk_.max_cons_loss_days); f.d(risk_.max_drawdown);
802 f.b(risk_.max_drawdown_percent); f.d(risk_.max_intraday_loss);
803 f.b(risk_.max_intraday_loss_percent); f.d(risk_.max_position_size); f.b(risk_.halted);
804 f.d(risk_.observed_peak_equity); f.d(risk_.observed_max_drawdown);
805 f.i(risk_.intraday_block_day); f.b(risk_.intraday_cancel_pending);
806 hash_short_seed_plan(f, short_seed_);
807 hash_short_seed_plan(f, pending_short_seed_.plan);
808 f.u(pending_short_seed_.expected_open_epoch); f.b(pending_short_seed_.ready);
809 hash_native_handle(f, short_seed_long_candidate_);
810 f.i(last_bar_dual_entry_path_); f.i(last_bar_dual_entry_script_open_ms_);
811 f.b(pending_view_.owner_ != nullptr);
812 f.i(static_cast<std::int64_t>(cap.attachment())); f.i(cap.configuration().limit);
813 f.b(cap.configuration().skip_noop_market); f.b(cap.configuration().defer_pooc_close);
814 f.b(cap.configuration().count_pooc_full_close);
815 // Which switches the host declared (rule CAP-ON) decides only an active
816 // cap's count, so it folds only while one is active.
817 if (cap.active()) f.u(cap.declared());
818 const auto& cap_budget = cap.budget();
819 f.b(cap_budget.day().has_value());
820 if (cap_budget.day()) f.i(cap_budget.day()->key);
821 f.i(cap_budget.charged_slots()); f.b(cap_budget.latched());
822 f.b(cap_budget.transfer().has_value());
823 if (const auto& transfer = cap_budget.transfer()) {
824 f.i(transfer->day.key); f.u(transfer->close_fill);
825 f.i(transfer->source_bar); f.u(transfer->inheritor);
826 }
827 f.b(cap.due_cause().has_value());
828 if (const auto& due = cap.due_cause()) {
829 f.u(due->action_id); f.i(due->charged_day.key); f.i(due->charged_slots);
830 f.i(due->trigger_bar); f.u(due->trigger_order);
831 }
832 f.u(cap.next_action()); f.b(priority.attached());
833 f.b(priority.retained_parent_first());
834 // The eager boundary fold and this query share the original v4 byte loop.
835 // A retired prefix substitutes for its events, without adding hash bytes.
836 fold_admission_events();
837 f.u(admission_journal.sequence_frontier());
838 f.u(admission_events_folded_); f.u(admission_events_digest_);
839}
840
841void source::PineExecutionAdapter::fold_admission_events() const {
842 const auto folded = admission_journal.folded_window();
843 admission_events_folded_ = folded.count;
844 admission_events_digest_ = folded.digest;
845 admission_events_last_ = folded.last_sequence;
846}
847
849 std::uint64_t digest, const DroppedCloseReceipt& receipt) noexcept {
851 f.h = digest;
852 f.s(receipt.source_id); f.s(receipt.comment); f.d(receipt.qty);
853 f.d(receipt.qty_percent); f.b(receipt.immediately); f.u(receipt.callsite_token);
854 f.u(receipt.command_ordinal);
855 return f.h;
856}
857
858std::uint64_t source::PineExecutionAdapter::fold_exit_phase(std::uint64_t digest,
859 std::uint8_t phase) noexcept {
861 f.h = digest;
862 f.u(phase);
863 return f.h;
864}
865
866void source::PineScheduler::fold_consumed_digests() const noexcept {
867 // ab9714be test_live_state_hash_recording: a shorter run is a hash prefix
868 // of the same longer feed. Retained future input is provider transport,
869 // not broker continuation state, so fold only the consumed source prefix.
870 // v3 (R5 lane V19-E): the consumed prefix as a running digest, each bar
871 // folded once, the first time a read passes it; a prefix run still reads
872 // like the full run up to its last bar.
873 const std::size_t consumed = std::min(
874 retained_n_, static_cast<std::size_t>(std::max(source_bar_count_, 0)));
875 const auto extend = [](std::size_t& folded, std::uint64_t& digest, std::size_t upto,
876 const auto& element) {
877 if (folded > upto) {
878 folded = 0;
879 digest = 1469598103934665603ULL;
880 }
881 for (; folded < upto; ++folded) {
883 g.h = digest;
884 element(g, folded);
885 digest = g.h;
886 }
887 };
888 extend(consumed_bars_folded_, consumed_bars_digest_, consumed,
889 [&](BrokerStateHashSink& g, std::size_t index) {
890 // owned is just the stream's backing for bars, not an additional
891 // broker fact: either storage folds the same bar bytes once.
892 const auto& bar = retained_.is_stream ? retained_.owned[index] : retained_.bars[index];
893 g.d(bar.open); g.d(bar.high); g.d(bar.low); g.d(bar.close); g.d(bar.volume);
894 g.i(bar.timestamp);
895 });
896 const std::size_t completion_prefix = std::min(input_script_size_, consumed);
897 // Fold both flags here, before release_batch_input clears the caller span.
898 // The independent cursor restarts with both digests for a shorter prefix.
899 if (consumed_completes_folded_ > completion_prefix
900 || consumed_boundaries_folded_ > completion_prefix) {
901 consumed_completes_folded_ = 0;
902 consumed_completes_digest_ = 1469598103934665603ULL;
903 consumed_boundaries_folded_ = 0;
904 consumed_boundaries_digest_ = 1469598103934665603ULL;
905 if (consumed_script_cursor_) consumed_script_cursor_->reset();
906 }
907 extend(consumed_completes_folded_, consumed_completes_digest_, completion_prefix,
908 [&](BrokerStateHashSink& g, std::size_t index) {
909 const auto flags = consumed_script_cursor_
910 ? consumed_script_cursor_->next(retained_.bars[index])
911 : ScriptBucketCursor::Flags{};
912 g.u(flags.complete);
913 BrokerStateHashSink boundary;
914 boundary.h = consumed_boundaries_digest_;
915 boundary.u(flags.boundary);
916 consumed_boundaries_digest_ = boundary.h;
917 consumed_boundaries_folded_ = index + 1;
918 });
919}
920
922 fold_consumed_digests();
923 // retained_n_ survives detachment. Using the now-empty span's size here
924 // would rewind all three digests and change every post-run hash (F8).
925 const std::size_t consumed = std::min(
926 retained_n_, static_cast<std::size_t>(std::max(source_bar_count_, 0)));
927 f.s("pineforge-pine-scheduler/v3");
928 f.u(consumed); f.u(consumed_bars_digest_);
929 f.s(retained_.input_tf); f.s(retained_.script_tf); f.b(retained_.bar_magnifier);
930 f.i(retained_.magnifier_samples); f.i(static_cast<std::int64_t>(retained_.distribution));
931 f.b(retained_.volume_weighted); f.i(retained_.volume_weighted_min_samples);
932 f.i(retained_.volume_weighted_max_samples); f.b(retained_.is_stream); f.i(retained_.warmup_n);
933 f.b(retained_.simple_run);
934 f.i(language_.pos_view_freeze_bar_); f.i(static_cast<std::int64_t>(language_.pos_view_frozen_side_));
935 f.d(language_.pos_view_frozen_qty_); hash_str_double_map(f, language_.pos_view_frozen_entry_qty_);
936 f.b(language_._src_series_active_); hash_source_series(f, language_._src_open_);
937 hash_source_series(f, language_._src_high_); hash_source_series(f, language_._src_low_);
938 hash_source_series(f, language_._src_close_); hash_source_series(f, language_._src_volume_);
939 hash_source_series(f, language_._src_hl2_); hash_source_series(f, language_._src_hlc3_);
940 hash_source_series(f, language_._src_ohlc4_); hash_source_series(f, language_._src_hlcc4_);
941 f.d(language_.prev_chart_close_); f.d(language_.last_chart_close_); f.i(language_.bar_index_offset_);
942 f.b(language_.is_first_tick_); f.b(language_.is_last_tick_); f.b(language_.history_slot_is_new_);
943 f.b(language_.coof_checkpoint_contains_current_bar_);
944 hash_source_series(f, language_.coof_checkpoint_src_open_);
945 hash_source_series(f, language_.coof_checkpoint_src_high_);
946 hash_source_series(f, language_.coof_checkpoint_src_low_);
947 hash_source_series(f, language_.coof_checkpoint_src_close_);
948 hash_source_series(f, language_.coof_checkpoint_src_volume_);
949 hash_source_series(f, language_.coof_checkpoint_src_hl2_);
950 hash_source_series(f, language_.coof_checkpoint_src_hlc3_);
951 hash_source_series(f, language_.coof_checkpoint_src_ohlc4_);
952 hash_source_series(f, language_.coof_checkpoint_src_hlcc4_);
953 f.d(language_.coof_checkpoint_prev_chart_close_); f.d(language_.coof_checkpoint_last_chart_close_);
954 f.i(current_script_open_ms_);
955 f.d(current_script_bar_.open); f.d(current_script_bar_.high); f.d(current_script_bar_.low);
956 f.d(current_script_bar_.close); f.d(current_script_bar_.volume); f.i(current_script_bar_.timestamp);
957 f.b(current_script_bar_valid_); f.b(saw_open_fill_); f.i(open_point_fills_); f.i(source_bar_count_);
958 f.b(expected_source_bars_ >= source_bar_count_);
959 f.u(applied_cursor_); f.i(coof_callback_script_open_);
960 f.i(last_published_script_open_ms_);
961 f.i(prior_input_script_open_ms_);
962 f.i(awaiting_legacy_script_open_ms_);
963 f.i(last_stream_input_open_ms_);
964 const std::size_t completion_prefix = std::min(input_script_size_, consumed);
965 f.u(completion_prefix); f.u(consumed_completes_digest_);
966 const std::size_t boundary_prefix = std::min(input_script_size_, consumed);
967 f.u(boundary_prefix); f.u(consumed_boundaries_digest_);
968 f.b(uses_aux_security_feed_);
969 if (retained_.is_stream) f.b(input_is_observed_ticks_);
970 if (retained_.is_stream) {
971 f.u(confirmed_input_next_);
972 f.u(confirmed_input_count_);
973 for (const auto& bar : confirmed_input_bars_) {
974 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close); f.d(bar.volume);
975 f.i(bar.timestamp);
976 }
977 f.u(confirmed_open_input_next_);
978 f.u(confirmed_open_input_count_);
979 for (const auto& bar : confirmed_open_input_bars_) {
980 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close); f.d(bar.volume);
981 f.i(bar.timestamp);
982 }
983 }
984 f.d(deferred_boundary_input_.bar.open); f.d(deferred_boundary_input_.bar.high);
985 f.d(deferred_boundary_input_.bar.low); f.d(deferred_boundary_input_.bar.close);
986 f.d(deferred_boundary_input_.bar.volume); f.i(deferred_boundary_input_.bar.timestamp);
987 f.i(deferred_boundary_input_.next_input_ms);
988 f.i(deferred_boundary_input_.prior_script_open_ms);
989 f.b(deferred_boundary_input_.calling_bar_complete);
990 f.b(deferred_boundary_input_.all_security_states);
991 f.b(deferred_boundary_input_.active);
992}
993
994namespace {
995
996// The selected window's pre-roll capture (src/source/pine_intent_state.hpp), in
997// its own domain and folded only while a selected run parks the state, so every
998// run without one keeps the hash it had. Each payload folds its own argument
999// fields in declaration order; the configuration and point folds follow the ones
1000// the adapter and the kernel already use.
1001constexpr char kSourceIntentDomain[] = "pineforge-source-intent/v1";
1002
1003void hash_intent_config(BrokerStateHashSink& f, const source::PineStrategyConfig& config) {
1004 f.b(config.process_orders_on_close); f.b(config.calc_on_order_fills);
1005 f.d(config.initial_capital); f.i(config.default_qty_type); f.d(config.default_qty_value);
1006 f.i(config.pyramiding); f.d(config.commission_value); f.i(config.commission_type);
1007 f.i(config.slippage); f.d(config.margin_long); f.d(config.margin_short);
1008 f.b(config.close_entries_rule_any); f.b(config.src_series_active);
1009}
1010
1011// The saved risk configuration alone: the seven setter fields with their
1012// sticky percent flags, and the cap's three configuration fields.
1013void hash_intent_risk(BrokerStateHashSink& f, const source::PineReplayRiskConfig& risk) {
1014 f.i(risk.direction); f.i(risk.max_cons_loss_days); f.d(risk.max_drawdown);
1015 f.b(risk.max_drawdown_percent); f.d(risk.max_intraday_loss);
1017 f.i(static_cast<std::int64_t>(risk.cap_attachment)); f.i(risk.cap_configuration.limit);
1020}
1021
1022void hash_intent_interval(BrokerStateHashSink& f, const native_calendar::NativeInterval& interval) {
1023 f.i(interval.open_ms); f.i(interval.eligible_open_ms); f.i(interval.last_traded_close_ms);
1024 f.i(interval.next_period_open_ms); f.i(interval.next_input_open_ms);
1025}
1026
1027// A retained point, every value field in declaration order. The four session-day
1028// facts are presentation to the kernel's own digests, but the replay reads the
1029// retained point back, so the retained value folds whole.
1030void hash_intent_point(BrokerStateHashSink& f, const NativeCurrentPointView& point) {
1031 const NativeCoordinate& c = point.decision.coordinate;
1032 f.u(c.ordinal); f.i(c.interval_index); f.i(c.input_interval_index); f.i(c.open_ms);
1033 f.i(c.eligible_open_ms); f.i(c.last_traded_close_ms); f.i(c.next_period_open_ms);
1034 f.i(c.next_input_open_ms); f.i(c.effective_time_ms); f.i(c.source_price_time_ms);
1035 f.u(static_cast<std::uint64_t>(c.provenance)); f.u(static_cast<std::uint64_t>(c.path_phase));
1036 f.u(static_cast<std::uint64_t>(c.completion));
1037 f.i(point.decision.decision_floor_ms);
1038 hash_intent_interval(f, point.decision.input_interval);
1039 hash_intent_interval(f, point.decision.script_interval);
1040 f.i(point.decision.sub_index); f.i(point.decision.sub_count);
1041 f.b(point.decision.is_terminal_sub_bar);
1042 f.b(point.decision.in_session); f.b(point.decision.opens_session_day);
1043 f.b(point.decision.closes_session_day); f.b(point.decision.closes_session_day_open_ended);
1044 f.i(point.decision.sub_bar_open_ms); f.i(point.decision.script_bar_open_ms);
1045 const NativeDriverStatistics& statistics = point.decision.driver_statistics;
1046 f.b(statistics.intrabar_path_enabled); f.i(statistics.sub_bars_per_script_bar);
1047 f.i(statistics.samples_per_sub_bar); f.u(statistics.sub_bars_processed);
1048 f.u(statistics.sample_ticks_processed);
1049 f.d(point.price); f.u(static_cast<std::uint64_t>(point.quote_kind));
1050 f.u(point.quote_origin_ordinal);
1051}
1052
1053void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentEntry& v) {
1054 f.s(v.id); f.b(v.is_long); f.d(v.limit_price); f.d(v.stop_price); f.d(v.qty);
1055 f.s(v.comment); f.s(v.oca_name); f.i(v.oca_type); f.i(v.qty_type);
1056}
1057void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentClose& v) {
1058 f.s(v.id); f.s(v.comment); f.d(v.qty); f.d(v.qty_percent); f.b(v.immediately);
1059}
1060void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentCloseToken& v) {
1061 f.s(v.id); f.s(v.comment); f.d(v.qty); f.d(v.qty_percent); f.b(v.immediately);
1062 f.u(v.callsite_token);
1063}
1064void hash_intent_payload(BrokerStateHashSink&, const source::detail::IntentCloseAll&) {}
1065void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentExit& v) {
1066 f.s(v.id); f.s(v.from_entry); f.d(v.limit_price); f.d(v.stop_price);
1067 f.d(v.trail_points); f.d(v.trail_offset); f.d(v.trail_price); f.d(v.qty_percent);
1068 f.s(v.comment); f.d(v.qty); f.s(v.oca_name); f.d(v.profit_ticks); f.d(v.loss_ticks);
1069}
1070void hash_intent_payload(BrokerStateHashSink& f,
1071 const source::detail::IntentExitCancelBracket& v) {
1072 f.s(v.exit_id); f.s(v.from_entry); f.s(v.comment);
1073}
1074void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentCancel& v) {
1075 f.s(v.id);
1076}
1077void hash_intent_payload(BrokerStateHashSink&, const source::detail::IntentCancelAll&) {}
1078void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentOrder& v) {
1079 f.s(v.id); f.b(v.is_long); f.d(v.qty); f.d(v.limit_price); f.d(v.stop_price);
1080 f.s(v.oca_name); f.i(v.oca_type);
1081}
1082void hash_intent_payload(BrokerStateHashSink& f,
1083 const source::detail::IntentRiskDirection& v) {
1084 f.i(v.direction);
1085}
1086void hash_intent_payload(BrokerStateHashSink& f,
1087 const source::detail::IntentRiskMaxConsLossDays& v) {
1088 f.i(v.value);
1089}
1090void hash_intent_payload(BrokerStateHashSink& f,
1091 const source::detail::IntentRiskMaxDrawdown& v) {
1092 f.d(v.value); f.b(v.percent);
1093}
1094void hash_intent_payload(BrokerStateHashSink& f,
1095 const source::detail::IntentRiskMaxIntradayLoss& v) {
1096 f.d(v.value); f.b(v.percent);
1097}
1098void hash_intent_payload(BrokerStateHashSink& f,
1099 const source::detail::IntentRiskMaxIntradayFilledOrders& v) {
1100 f.i(v.limit);
1101}
1102void hash_intent_payload(BrokerStateHashSink& f,
1103 const source::detail::IntentRiskMaxPositionSize& v) {
1104 f.d(v.value);
1105}
1106
1107void hash_selected_intents(BrokerStateHashSink& f, const source::detail::PineIntentState& state) {
1108 f.s(kSourceIntentDomain);
1109 // The native run follows the source identity normalization of a handle's run
1110 // (hash_source_run_identity): it protects a stale state and is not a
1111 // source-visible fact, so it is never a per-run nonce. The owner, the kind
1112 // tag, the bracket flag and the replay pointer are addresses or transient and
1113 // are not folded.
1114 f.u(0);
1115 f.b(state.has_evaluation); f.b(state.replayed); f.i(state.label_ms);
1116 hash_intent_config(f, state.start_config); hash_intent_config(f, state.end_config);
1117 hash_intent_risk(f, state.start_risk); hash_intent_risk(f, state.end_risk);
1118 hash_intent_point(f, state.terminal_point);
1119 f.u(state.rows.size());
1120 for (const source::detail::PineIntentRow& row : state.rows) {
1121 f.u(row.payload.index());
1122 std::visit([&f](const auto& payload) { hash_intent_payload(f, payload); }, row.payload);
1123 hash_intent_config(f, row.config);
1124 hash_intent_point(f, row.point);
1125 }
1126}
1127
1128} // namespace
1129
1132 if (!trade_scratch_)
1133 NativeExecutionConsumer::bound(*this).rethrow_released_command_scratch_failure(
1134 NativeExecutionConsumer::CommandScratchBinding::Trade);
1135 const auto released = NativeExecutionConsumer::bound(*this).released_command_scratch(
1136 NativeExecutionConsumer::CommandScratchBinding::Trade);
1137 if (!trade_scratch_ && released) {
1138 // Preserve the live store's exact presence/digest bytes after its
1139 // run-owned backing is released. This is no additional hash field.
1140 f.b(true);
1141 hash_scratch_digest(f, released->status, released->digest);
1142 } else {
1143 hash_scratch_store(f, trade_scratch_);
1144 }
1145 f.b(config_.process_orders_on_close); f.b(config_.calc_on_order_fills);
1146 f.d(config_.initial_capital); f.i(config_.default_qty_type); f.d(config_.default_qty_value);
1147 f.i(config_.pyramiding); f.d(config_.commission_value); f.i(config_.commission_type);
1148 f.i(config_.slippage); f.d(config_.margin_long); f.d(config_.margin_short);
1149 f.b(config_.close_entries_rule_any); f.b(config_.src_series_active);
1150 f.d(override_.initial_capital); f.d(override_.commission_value); f.d(override_.default_qty_value);
1151 f.i(override_.pyramiding); f.i(override_.slippage); f.i(override_.commission_type);
1152 f.i(override_.default_qty_type); f.i(override_.process_orders_on_close);
1153 f.i(override_.calc_on_order_fills); f.i(override_.close_entries_rule);
1156#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1157 // v4: the auxiliary feed and its chart ranges never change during a run;
1158 // their digest is taken when they are set (refresh_aux_security_digest:
1159 // aux_security_bars_, aux_security_input_tf_, aux_security_chart_begin_,
1160 // aux_security_chart_end_).
1164#endif
1165 // Per-site request.security semantics, in sec_id order. Folded under
1166 // their own domain and only when a site is registered, so a run without
1167 // request.security keeps the hash it had.
1168 if (!pine_security_states_.empty()) {
1170 f.u(pine_security_states_.size());
1171 for (const auto& [sec_id, pine] : pine_security_states_) {
1172 f.i(sec_id);
1173 f.b(pine.lookahead_on);
1174 f.b(pine.gaps_on);
1175 f.i(pine.publish_gate_tf_seconds);
1176 f.b(pine.calling_close_completes_partial);
1177 // Folded only when set: every site that loads bars keeps the
1178 // digest it had.
1179 if (pine.no_loaded_bars) f.b(pine.no_loaded_bars);
1180 f.b(pine.heikinashi);
1181 f.d(pine.ha_prev_open);
1182 f.d(pine.ha_prev_close);
1183 f.b(pine.ha_seeded);
1184 // The projection in hand, not the prepared list: later buckets
1185 // are a function of input the run has not consumed yet, and a
1186 // prefix run must hash like the full run up to its last bar.
1187 f.b(!pine.historical_projections.empty());
1188 if (!pine.historical_projections.empty()) {
1189 f.u(pine.historical_projection_cursor);
1190 f.b(pine.historical_projection_dispatched);
1191 const auto& projection = pine.historical_projections[std::min(
1192 pine.historical_projection_cursor,
1193 pine.historical_projections.size() - 1)];
1194 f.d(projection.bar.open); f.d(projection.bar.high);
1195 f.d(projection.bar.low); f.d(projection.bar.close);
1196 f.d(projection.bar.volume); f.i(projection.bar.timestamp);
1197 f.i(projection.first_child_ms);
1198 f.b(projection.is_complete);
1199 }
1200 f.b(pine.lower_tf_requested);
1201 f.b(pine.lower_tf_emulation);
1202 f.i(pine.lower_tf_ratio);
1203 f.i(pine.lower_tf_seconds);
1204 f.b(pine.lower_tf_array_requested);
1205 f.i(pine.lower_tf_sub_bar_index);
1206 f.b(pine.lower_tf_use_input);
1207 f.i(pine.lower_tf_input_aggregation_ratio);
1208 f.u(pine.lower_tf_input_buffer.size());
1209 for (const Bar& bar : pine.lower_tf_input_buffer) {
1210 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close);
1211 f.d(bar.volume); f.i(bar.timestamp);
1212 }
1213 f.b(pine.calling_open_latches_first);
1214 f.b(pine.first_bucket_published);
1215 f.i(pine.slice_open_label);
1216 f.i(pine.last_published_label);
1217 f.u(pine.deferred_aux.size());
1218 for (const auto& held : pine.deferred_aux) {
1219 f.d(held.bar.open); f.d(held.bar.high); f.d(held.bar.low);
1220 f.d(held.bar.close); f.d(held.bar.volume); f.i(held.bar.timestamp);
1221 f.i(held.next_input_ms);
1222 f.b(held.calling_bar_complete);
1223 }
1224 }
1225 }
1226 // Lane XSYM-D: the request sites of other symbols -- registration and
1227 // per-run cursors -- and the request data the run was handed (the feeds'
1228 // keys and sizes, the facts and the recorded series, through the digest
1229 // the setters keep; the feeds' bars are the projected spec's instrument
1230 // feeds, which the continuation digest folds). Under their own domain and
1231 // only when present, so every run without them keeps the hash it had.
1232 if (!foreign_security_sites_.empty() || !symbol_feeds_.empty()
1233 || !symbol_facts_.empty() || !recorded_series_.empty()) {
1235 f.u(symbol_feeds_.size());
1236 f.u(symbol_facts_.size());
1237 f.u(recorded_series_.size());
1240 f.u(foreign_security_series_.size());
1241 for (const int sec_id : foreign_security_series_) f.i(sec_id);
1242 f.u(foreign_security_sites_.size());
1243 for (const auto& [sec_id, site] : foreign_security_sites_) {
1244 f.i(sec_id);
1245 f.s(site.symbol);
1246 f.s(site.requested_tf);
1247 f.b(site.lookahead);
1248 f.b(site.gaps);
1249 f.b(site.ignore_invalid);
1250 f.s(site.tf);
1251 f.b(site.invalid);
1252 f.i(site.subscription);
1253 f.u(site.feed);
1254 f.i(site.delivered);
1255 f.i(site.close_ms);
1256 f.i(site.last_input);
1257 f.i(site.handed);
1258 }
1259 }
1260 // The selected window's pre-roll capture, only while a selected run parks it
1261 // (selected_intents allocates nothing and is null for every other run).
1262 if (const source::detail::PineIntentState* const intents = detail::selected_intents(*this))
1263 hash_selected_intents(f, *intents);
1264 adapter_.hash_state(f); scheduler_.hash_state(f);
1265}
1266
1267} // namespace pineforge
Base with a code: throw coded<std::invalid_argument>(code, {args}, text).
void hash_state(BrokerStateHashSink &) const
static std::uint64_t fold_exit_phase(std::uint64_t digest, std::uint8_t phase) noexcept
void exit(const SourceId &exit_id, const SourceId &from_entry, double limit_price, double stop_price, double trail_points=std::numeric_limits< double >::quiet_NaN(), double trail_offset=std::numeric_limits< double >::quiet_NaN(), double trail_price=std::numeric_limits< double >::quiet_NaN(), double qty_percent=100.0, const std::string &comment={}, double qty=std::numeric_limits< double >::quiet_NaN(), const std::string &oca_name={}, double profit_ticks=std::numeric_limits< double >::quiet_NaN(), double loss_ticks=std::numeric_limits< double >::quiet_NaN())
compat::pine::OrderPriority priority
void entry(const SourceId &id, bool is_long, double limit_price=std::numeric_limits< double >::quiet_NaN(), double stop_price=std::numeric_limits< double >::quiet_NaN(), double qty=std::numeric_limits< double >::quiet_NaN(), const std::string &comment={}, const std::string &oca_name={}, int oca_type=0, int qty_type=-1)
void order(const SourceId &id, bool is_long, double qty, double limit_price=std::numeric_limits< double >::quiet_NaN(), double stop_price=std::numeric_limits< double >::quiet_NaN(), const std::string &oca_name={}, int oca_type=0)
static std::uint64_t fold_dropped_close(std::uint64_t digest, const DroppedCloseReceipt &receipt) noexcept
void hash_state(BrokerStateHashSink &) const
void bar(const Bar &, const NativeDecisionContext &, PineStrategyHost &)
std::map< std::string, RecordedSeries > recorded_series_
std::vector< std::size_t > aux_security_chart_begin_
std::map< std::string, SymbolFacts > symbol_facts_
void hash_host_extension(BrokerStateHashSink &) const override
std::vector< NativeInstrumentFeed > symbol_feeds_
std::map< int, PineSecurityEvalState > pine_security_states_
std::vector< std::size_t > aux_security_chart_end_
std::map< int, ForeignSecuritySite > foreign_security_sites_
void reflect(const Draft &value, const std::string &path, const FieldVisitor &visit)
int boundary(char *error, std::size_t capacity, Function &&function) noexcept
void rethrow_carried_scratch_failure(const ReportScratchIoError *failure)
int g(int64_t c)
Definition color.hpp:43
MarginRuleSwitches & margin_rule_switches() noexcept
PineIntentState * selected_intents(const NativeStrategyHost &host) noexcept
constexpr char kSourceSecurityDomain[]
constexpr char kSourceAdapterDomain[]
constexpr char kSourceSymbolDataDomain[]
#define PINEFORGE_TAIL_WORK(site, count)
#define PINEFORGE_BROKER_HASH_OBSERVE(rows, families, archived, digest)
double open
Definition bar.hpp:7
double close
Definition bar.hpp:7
double low
Definition bar.hpp:7
double volume
Definition bar.hpp:7
int64_t timestamp
Definition bar.hpp:8
double high
Definition bar.hpp:7
compat::pine::CapConfiguration cap_configuration
compat::pine::CapAttachment cap_attachment