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 (the source-runtime extraction): 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 (the cash-sizing fee correction).
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 // Folded only when set, like PlacementSnapshot::void_global_issue.
468 if (exit.void_global) f.u(0x766f6964676cULL);
469 }
470 // The anchored-leg integration: folded only while a leg is anchored, so every run that
471 // anchors none keeps its established hash.
472 if (!anchored_relative_legs_.empty() || anchored_relative_stats_.anchored != 0
473 || materializing_parent_.incarnation != 0) {
474 f.s("pineforge-source-anchored-legs/v1");
475 f.u(anchored_relative_stats_.anchored); f.u(anchored_relative_stats_.adopted);
476 f.u(anchored_relative_stats_.withdrawn); f.u(materializing_parent_.incarnation);
477 f.i(anchored_cohort_sequence_);
478 f.u(anchored_relative_legs_.size());
479 for (const auto& leg : anchored_relative_legs_) {
480 f.s(leg.exit_id); f.s(leg.from_entry); f.u(static_cast<std::uint64_t>(leg.family));
481 f.u(leg.handle.incarnation); f.u(leg.parent.incarnation); f.b(leg.parent_long);
482 f.d(leg.operand_ticks); f.d(leg.trail_offset);
483 hash_native_request(f, leg.request);
484 // hash_native_request predates anchors; an anchored leg's own
485 // spelling is folded here.
486 if (const auto* anchor = std::get_if<native_order::FromOwnerFill>(
487 &leg.request.anchor)) {
488 f.d(anchor->offset); f.b(anchor->ticks);
489 f.u(static_cast<std::uint64_t>(anchor->rounding));
490 }
491 f.b(leg.armed); f.d(leg.installed_level);
492 }
493 }
494 // Folded only while a global exit covers its position entry by entry
495 // (ExitBindingRuleSwitches::global_exit_per_entry_levels), so every other
496 // run keeps its established hash.
497 if (!per_entry_exits_.empty()) {
498 f.s("pineforge-source-per-entry-exits/v1");
499 f.u(per_entry_exits_.size());
500 for (const auto& exit : per_entry_exits_) {
501 f.s(exit.exit_id); f.i(exit.cycle); f.b(exit.long_side);
502 f.d(exit.profit_ticks); f.d(exit.loss_ticks); f.s(exit.comment);
503 f.u(exit.children.size());
504 for (const auto& child : exit.children) {
505 f.u(child.origin); f.d(child.fill_price); f.d(child.units); f.b(child.consumed);
506 }
507 }
508 }
509 f.u(pending_coof_requests_.size());
510 for (const auto& pending : pending_coof_requests_) {
511 hash_native_request(f, pending.request); hash_placement(f, pending.snapshot);
512 f.s(pending.replacement_key); f.b(pending.opening); f.u(pending.family_key);
513 f.b(pending.next_open);
514 }
515 f.u(pending_margin_revivals_.size());
516 for (const auto& pending : pending_margin_revivals_) {
517 hash_placement(f, pending.snapshot); f.i(pending.decline_bar);
518 }
519 hash_native_handle_vector(f, live_handles_); hash_native_handle_vector(f, first_open_newborns_);
520 // PendingIntentView is a derived read-only alias of live_handles_. Keep
521 // its historical fingerprint position and bytes without copying the
522 // roster at every mutation boundary.
523 hash_native_handle_vector(f, live_handles_);
524 // v4: a log no decision reads, folded once per receipt when recorded.
525 f.u(dropped_close_count_); f.u(dropped_close_digest_);
526 f.u(open_entry_fees_.size());
527 for (const auto& fee : open_entry_fees_) {
528 hash_native_handle(f, fee.opening); f.s(fee.source_id); f.d(fee.units);
529 f.d(fee.nonpercent_fee);
530 }
531 // v4: the final prefix folded once, the current bar's tail in full.
532 const auto trade_prefix = host_
533 ? NativeExecutionConsumer::bound(*host_).closed_trade_prefix()
534 : NativeExecutionConsumer::ClosedTradePrefix{};
535 const std::size_t phase_end = trade_prefix.base + trade_exit_phase_.size();
536 const auto* phase_consumer = host_ ? &NativeExecutionConsumer::bound(*host_) : nullptr;
537 f.u(phase_end);
538 if (phase_consumer && phase_consumer->exit_phases_restarted()) {
539 // Preserve the sink-off hash's raw reset marker AND its phase bytes.
540 // The bounded transform applies the sealed bytes to this incoming
541 // hash, rather than substituting a different digest representation.
542 f.u(0); f.u(BrokerStateHashSink{}.h);
543 f.h = phase_consumer->replay_sealed_exit_phases(f.h);
544 for (const auto phase : trade_exit_phase_) f.u(phase);
545 } else {
546 if (exit_phase_final_ < trade_prefix.base)
548 "hashed exit-phase fold precedes the sealed prefix");
549 f.u(exit_phase_final_); f.u(exit_phase_digest_);
550 for (std::size_t index = exit_phase_final_; index < phase_end; ++index)
551 f.u(trade_exit_phase_.at(index - trade_prefix.base));
552 }
553 // The entry-id table orders children once it holds two ids, and its size
554 // matters once its peak passes 13 keys; below both it decides nothing.
555 if (exit_rank_keys_.size() >= 2 || exit_rank_peak_ > 13 || exit_rank_alt_peak_ > 13) {
556 f.u(exit_rank_keys_.size()); f.u(exit_rank_keys_digest_);
557 f.u(exit_rank_peak_); f.u(exit_rank_alt_peak_);
558 }
559 std::vector<std::uint64_t> current_debit_ordinals;
560 current_debit_ordinals.reserve(current_debited_applied_ordinals_.size());
561 for (const auto ordinal : current_debited_applied_ordinals_) current_debit_ordinals.push_back(ordinal);
562 std::sort(current_debit_ordinals.begin(), current_debit_ordinals.end());
563 f.u(current_debit_ordinals.size());
564 for (const auto ordinal : current_debit_ordinals) f.u(ordinal);
565 std::vector<std::uint64_t> intraday_relabel_ordinals;
566 intraday_relabel_ordinals.reserve(intraday_loss_relabel_ordinals_.size());
567 for (const auto ordinal : intraday_loss_relabel_ordinals_)
568 intraday_relabel_ordinals.push_back(ordinal);
569 std::sort(intraday_relabel_ordinals.begin(), intraday_relabel_ordinals.end());
570 f.u(intraday_relabel_ordinals.size());
571 for (const auto ordinal : intraday_relabel_ordinals) f.u(ordinal);
572 consumed_partial_exit_cycles_.fold_sorted(f);
573 std::vector<std::uint64_t> shadowed_openings(
574 bracket_shadowed_openings_.begin(), bracket_shadowed_openings_.end());
575 std::sort(shadowed_openings.begin(), shadowed_openings.end());
576 f.u(shadowed_openings.size());
577 for (const auto opening : shadowed_openings) f.u(opening);
578 std::vector<std::string> named_cancel_keys;
579 named_cancel_keys.reserve(named_entry_cancel_tokens_.size());
580 for (const auto& row : named_entry_cancel_tokens_) named_cancel_keys.push_back(row.first);
581 std::sort(named_cancel_keys.begin(), named_cancel_keys.end());
582 f.u(named_cancel_keys.size());
583 for (const auto& key : named_cancel_keys) {
584 const auto& token = named_entry_cancel_tokens_.at(key);
585 f.s(key); f.u(token.entry_incarnation); f.u(token.surviving_exit_incarnation);
586 }
587 // Folded only when an exit's id binding outlived a cancel of its order,
588 // so a run that never keeps one folds what it folded before.
589 if (!id_bound_exits_.empty()) {
590 f.u(0x69646278ULL);
591 f.u(id_bound_exits_.size());
592 for (const auto& row : id_bound_exits_) {
593 f.s(row.exit_id); f.s(row.from_entry);
594 f.d(row.levels.limit); f.d(row.levels.stop); f.d(row.levels.trail_points);
595 f.d(row.levels.trail_offset); f.d(row.levels.trail_price);
596 f.d(row.levels.profit_ticks); f.d(row.levels.loss_ticks);
597 f.d(row.qty_percent); f.d(row.qty); f.s(row.comment); f.s(row.oca_name);
598 }
599 }
600 f.u(close_logical_units_.size());
601 for (const auto& row : close_logical_units_) { f.s(row.first); f.d(row.second); }
602 // The ledger's records fold only when there are two or more: a single
603 // record is the one id close_logical_units_ holds, and none is none, so a
604 // position of one id folds what it folded before the records existed.
605 if (close_ledger_records_.size() > 1) {
606 f.u(close_ledger_records_.size());
607 for (const auto& record : close_ledger_records_) { f.s(record.id); f.d(record.units); }
608 }
609 // The pyramiding records, unmerged, kept only under pyramiding above one;
610 // folded only while there are any, so every other run keeps its digest.
611 if (!pyramiding_records_.empty()) {
612 f.s("pineforge-pyramiding-records/v1");
613 f.u(pyramiding_records_.size());
614 for (const auto& record : pyramiding_records_) {
615 f.s(record.id); f.u(record.incarnation); f.d(record.units);
616 }
617 }
618 // The exit tombstones, folded only while there are any.
619 if (!exit_tombstones_.empty()) {
620 std::vector<SourceId> keys;
621 keys.reserve(exit_tombstones_.size());
622 for (const auto& row : exit_tombstones_) keys.push_back(row.first);
623 std::sort(keys.begin(), keys.end());
624 f.s("pineforge-exit-tombstones/v1");
625 f.u(keys.size());
626 for (const auto& key : keys) { f.s(key); f.u(exit_tombstones_.at(key)); }
627 }
628 // The retired reservation model's two maps and two per-site maps fold as
629 // the empty maps they always were outside a multi-call close site.
630 f.u(0); f.u(0); f.u(0); f.u(0);
631 f.u(close_batch_callsites_.size());
632 for (const auto& row : close_batch_callsites_) {
633 const auto& site = row.second;
634 f.u(row.first); f.b(site.active); f.u(site.token); f.i(site.calls);
635 f.s(site.first_id); f.s(site.id); f.s(site.comment);
636 f.d(site.target); f.u(site.queue_sequence);
637 }
638 f.i(close_batch_bar_); f.u(close_batch_queue_sequence_);
639 f.d(close_batch_pending_debt_); f.d(close_batch_admitted_total_);
640 f.u(receipt_cursor_);
641 f.u(last_applied_ordinal_);
642 f.u(terminal_receipt_cursor_);
643 f.i(entry_attempt_bar_); f.u(entry_attempts_on_bar_);
644 f.b(materializing_relative_);
645 f.i(current_position_cycle_);
646 f.i(current_position_sign_);
647 f.u(next_sequential_group_);
648 f.b(source_batch_mutated_);
649 f.b(coof_recalc_active_); f.b(coof_first_open_);
650 if (coof_open_stop_next_waypoint_) f.u(0x6f70656e6e657874ULL);
651 f.u(coof_market_entry_recalc_incarnation_);
652 f.u(coof_market_entry_recalc_fill_seq_); f.u(coof_current_fill_seq_);
653 // Live only inside a fill recalculation (NaN otherwise); folded then so
654 // the idle digest keeps its prior form.
655 if (coof_recalc_active_) f.d(coof_fill_cursor_t_);
656 if (coof_recalc_active_ && !std::isnan(coof_fill_target_print_)) {
657 f.u(0x7761797466ULL); f.d(coof_fill_target_print_);
658 }
659 if (coof_recalc_active_) f.b(coof_fill_forced_);
660 const auto& coof_coord = coof_context_.coordinate;
661 f.u(coof_coord.ordinal); f.i(coof_coord.interval_index); f.i(coof_coord.open_ms);
662 f.i(coof_coord.eligible_open_ms); f.i(coof_coord.last_traded_close_ms);
663 f.i(coof_coord.next_period_open_ms); f.i(coof_coord.next_input_open_ms);
664 f.i(coof_coord.effective_time_ms); f.i(coof_coord.source_price_time_ms);
665 f.i(static_cast<std::int64_t>(coof_coord.provenance));
666 f.i(static_cast<std::int64_t>(coof_coord.path_phase));
667 f.i(static_cast<std::int64_t>(coof_coord.completion)); f.i(coof_context_.decision_floor_ms);
668 const auto hash_coof_interval = [&](const native_calendar::NativeInterval& interval) {
669 f.i(interval.open_ms); f.i(interval.eligible_open_ms); f.i(interval.last_traded_close_ms);
670 f.i(interval.next_period_open_ms); f.i(interval.next_input_open_ms);
671 };
672 hash_coof_interval(coof_context_.input_interval);
673 hash_coof_interval(coof_context_.script_interval);
674 f.i(coof_context_.sub_index); f.i(coof_context_.sub_count);
675 f.b(coof_context_.is_terminal_sub_bar); f.i(coof_context_.sub_bar_open_ms);
676 f.i(coof_context_.script_bar_open_ms);
677 f.b(coof_context_.driver_statistics.intrabar_path_enabled);
678 f.i(coof_context_.driver_statistics.sub_bars_per_script_bar);
679 f.i(coof_context_.driver_statistics.samples_per_sub_bar);
680 f.u(coof_context_.driver_statistics.sub_bars_processed);
681 f.u(coof_context_.driver_statistics.sample_ticks_processed);
682 f.d(coof_script_bar_.open); f.d(coof_script_bar_.high); f.d(coof_script_bar_.low);
683 f.d(coof_script_bar_.close); f.d(coof_script_bar_.volume); f.i(coof_script_bar_.timestamp);
684 f.b(coof_script_bar_valid_);
685 // v4: a log no decision reads, folded once per new script bar.
686 f.u(pooc_close_basis_count_); f.i(pooc_close_basis_last_bar_); f.u(pooc_close_basis_digest_);
687 f.d(pooc_open_basis_); f.i(pooc_open_script_bar_); f.i(close_all_pending_script_bar_);
688 f.d(last_fx_rate_); f.i(position_open_script_bar_); f.u(position_open_epoch_); f.i(position_open_bar_index_);
689 f.u(static_cast<std::uint64_t>(position_open_phase_));
690 f.b(position_open_priced_);
691 f.i(last_margin_call_script_bar_); f.i(pooc_close_checkpoint_deferred_ms_);
692 // Live only from a close to the next open; folded only then (lane
693 // W13-ENG-MARGIN-OPP), so every other run keeps its digest.
694 if (close_margin_open_bar_ != std::numeric_limits<std::int64_t>::min())
695 f.i(close_margin_open_bar_);
696 // A short's lagged follow-up owed at a path point (a process_orders_on_close
697 // close owes the next open) and a close's after-script call the orders
698 // placed there read at the next open (MarginScheduleSwitches); folded only
699 // while set, so every other run keeps its digest.
700 if (owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min()) {
701 f.s("pineforge-owed-follow-up/v1");
702 f.i(owed_follow_up_bar_); f.i(owed_follow_up_point_); f.d(owed_follow_up_units_);
703 }
704 if (close_call_after_script_bar_ != std::numeric_limits<std::int64_t>::min()) {
705 f.s("pineforge-close-call-after-script/v1");
706 f.i(close_call_after_script_bar_); f.d(close_call_after_script_book_);
707 f.d(close_call_after_script_units_);
708 }
709 // An opening checkpoint owed after the exit legs marketable at an open
710 // (MarginOpeningSwitches::open_marketable_exit_first); folded only while
711 // set, so every other run keeps its digest.
712 if (open_exit_first_bar_ != std::numeric_limits<std::int64_t>::min()) {
713 f.s("pineforge-open-exit-first/v1");
714 f.i(open_exit_first_bar_); f.u(open_exit_first_origin_); f.s(open_exit_first_id_);
715 f.d(open_exit_first_fill_);
716 }
717 // The close-first fact (record_close_first); folded only once a whole
718 // close of an open position was recorded, so every other run keeps its
719 // digest.
720 if (close_first_script_bar_ != std::numeric_limits<std::int64_t>::min()) {
721 f.s("pineforge-close-first/v1");
722 f.i(close_first_script_bar_); f.i(close_first_side_);
723 }
724 f.u(last_margin_call_event_ordinal_);
725 f.u(last_margin_call_entry_incarnation_); f.i(last_margin_call_position_cycle_);
726 f.b(last_margin_call_at_script_close_);
727 f.d(last_margin_call_closed_units_); f.d(last_margin_call_remaining_units_);
728 // The G + L money mirror (gain_loss_money), over every closed trade
729 // whenever this hash is taken (the stored sums plus the trades booked
730 // since their last fold), so the digest never depends on when they were
731 // folded. Its sum is the engine's netprofit, already in the engine's
732 // state, wherever the two agree bit for bit; the sums are folded where
733 // they part, so a run they never part on keeps its digest.
734 if (source::detail::margin_rule_switches().gain_loss_money) {
735 if (const auto* pine = pine_view_of(host_)) {
736 // A new run's ledger shorter than the stored fold starts over,
737 // as fold_source_money does.
738 const std::uint64_t count = pine->closed_trade_count();
739 const bool restart = count < source_money_folded_;
740 if (count < trade_prefix.base)
742 "hashed source-money rewind precedes the sealed prefix");
743 double gains = restart ? trade_prefix.source_gains : source_gains_;
744 double losses = restart ? trade_prefix.source_losses : source_losses_;
745 for (std::uint64_t index = restart ? trade_prefix.base : source_money_folded_;
746 index < count; ++index) {
747 const double profit = pine->closed_trade(index).pnl;
748 if (profit > 0.0) gains += profit;
749 else losses += profit;
750 }
751 const double mirrored = gains + losses;
752 const double engine_net = pine->net_profit_sum_;
753 if (count != 0 && std::memcmp(&mirrored, &engine_net, sizeof(double)) != 0) {
754 f.d(gains); f.d(losses); f.u(count);
755 }
756 }
757 }
758 f.i(signal_close_mc_event_bar_);
759 f.i(signal_close_mc_position_cycle_); f.u(signal_close_mc_entry_incarnation_);
760 f.u(signal_close_mc_fill_seq_); f.d(signal_close_mc_before_qty_);
761 f.d(signal_close_mc_remaining_qty_); f.i(risk_coof_direct_script_bar_);
762 f.u(cap_latest_fill_);
763 f.b(source_margin_call_enabled_);
764 f.d(policy_script_bar_.open); f.d(policy_script_bar_.high);
765 f.d(policy_script_bar_.low); f.d(policy_script_bar_.close);
766 f.d(policy_script_bar_.volume); f.i(policy_script_bar_.timestamp);
767 f.b(policy_script_bar_valid_);
768 // Market-add provenance. It used to ride the
769 // generic lot as PyramidEntry::market_pyramid_add; it is adapter state
770 // now, so the source extension folds it. Unordered container: sort the
771 // keys, exactly like trail_state_at_open_ below.
772 std::vector<std::uint64_t> market_add_keys(market_pyramid_adds_.begin(),
773 market_pyramid_adds_.end());
774 std::sort(market_add_keys.begin(), market_add_keys.end());
775 f.u(market_add_keys.size());
776 for (const auto key : market_add_keys) f.u(key);
777 std::vector<std::uint64_t> trail_open_keys;
778 trail_open_keys.reserve(trail_state_at_open_.size());
779 for (const auto& row : trail_state_at_open_) trail_open_keys.push_back(row.first);
780 std::sort(trail_open_keys.begin(), trail_open_keys.end());
781 f.u(trail_open_keys.size());
782 for (const auto key : trail_open_keys) {
783 const auto& state = trail_state_at_open_.at(key);
784 f.u(key); f.b(state.activated); f.d(state.best_price);
785 f.d(state.current_level); f.u(state.activation_ordinal);
786 }
787 // The trails re-priced for the bar in flight (carried trail tick reach),
788 // empty between bars; folded only while set, so every run without one
789 // keeps its digest.
790 if (!carried_trail_retunes_.empty()) {
791 f.u(carried_trail_retunes_.size());
792 for (const auto& [handle, distance] : carried_trail_retunes_) {
793 f.u(handle.incarnation); f.d(distance);
794 }
795 }
796 f.b(stream_mode_);
797 f.i(day_ledger_.current_day); f.i(day_ledger_.last_loss_day); f.i(day_ledger_.consecutive_loss_days);
798 f.i(day_ledger_.intraday_loss_day); f.d(day_ledger_.intraday_start_equity);
799 f.d(day_ledger_.intraday_realized); f.u(day_ledger_.observed_applied_ordinal);
800 // Decision state only under an intraday loss rule (lane W8A-SIGSTATE-1
801 // R4); folded only then, so every run without one keeps its digest.
802 if (risk_.max_intraday_loss > 0.0) f.i(day_ledger_.intraday_start_script_bar);
803 f.i(risk_.direction); f.i(risk_.max_cons_loss_days); f.d(risk_.max_drawdown);
804 f.b(risk_.max_drawdown_percent); f.d(risk_.max_intraday_loss);
805 f.b(risk_.max_intraday_loss_percent); f.d(risk_.max_position_size); f.b(risk_.halted);
806 f.d(risk_.observed_peak_equity); f.d(risk_.observed_max_drawdown);
807 f.i(risk_.intraday_block_day); f.b(risk_.intraday_cancel_pending);
808 hash_short_seed_plan(f, short_seed_);
809 hash_short_seed_plan(f, pending_short_seed_.plan);
810 f.u(pending_short_seed_.expected_open_epoch); f.b(pending_short_seed_.ready);
811 hash_native_handle(f, short_seed_long_candidate_);
812 f.i(last_bar_dual_entry_path_); f.i(last_bar_dual_entry_script_open_ms_);
813 f.b(pending_view_.owner_ != nullptr);
814 f.i(static_cast<std::int64_t>(cap.attachment())); f.i(cap.configuration().limit);
815 f.b(cap.configuration().skip_noop_market); f.b(cap.configuration().defer_pooc_close);
816 f.b(cap.configuration().count_pooc_full_close);
817 // Which switches the host declared (rule CAP-ON) decides only an active
818 // cap's count, so it folds only while one is active.
819 if (cap.active()) f.u(cap.declared());
820 const auto& cap_budget = cap.budget();
821 f.b(cap_budget.day().has_value());
822 if (cap_budget.day()) f.i(cap_budget.day()->key);
823 f.i(cap_budget.charged_slots()); f.b(cap_budget.latched());
824 f.b(cap_budget.transfer().has_value());
825 if (const auto& transfer = cap_budget.transfer()) {
826 f.i(transfer->day.key); f.u(transfer->close_fill);
827 f.i(transfer->source_bar); f.u(transfer->inheritor);
828 }
829 f.b(cap.due_cause().has_value());
830 if (const auto& due = cap.due_cause()) {
831 f.u(due->action_id); f.i(due->charged_day.key); f.i(due->charged_slots);
832 f.i(due->trigger_bar); f.u(due->trigger_order);
833 }
834 f.u(cap.next_action()); f.b(priority.attached());
835 f.b(priority.retained_parent_first());
836 // The eager boundary fold and this query share the original v4 byte loop.
837 // A retired prefix substitutes for its events, without adding hash bytes.
838 fold_admission_events();
839 f.u(admission_journal.sequence_frontier());
840 f.u(admission_events_folded_); f.u(admission_events_digest_);
841}
842
843void source::PineExecutionAdapter::fold_admission_events() const {
844 const auto folded = admission_journal.folded_window();
845 admission_events_folded_ = folded.count;
846 admission_events_digest_ = folded.digest;
847 admission_events_last_ = folded.last_sequence;
848}
849
851 std::uint64_t digest, const DroppedCloseReceipt& receipt) noexcept {
853 f.h = digest;
854 f.s(receipt.source_id); f.s(receipt.comment); f.d(receipt.qty);
855 f.d(receipt.qty_percent); f.b(receipt.immediately); f.u(receipt.callsite_token);
856 f.u(receipt.command_ordinal);
857 return f.h;
858}
859
860std::uint64_t source::PineExecutionAdapter::fold_exit_phase(std::uint64_t digest,
861 std::uint8_t phase) noexcept {
863 f.h = digest;
864 f.u(phase);
865 return f.h;
866}
867
868void source::PineScheduler::fold_consumed_digests() const noexcept {
869 // ab9714be test_live_state_hash_recording: a shorter run is a hash prefix
870 // of the same longer feed. Retained future input is provider transport,
871 // not broker continuation state, so fold only the consumed source prefix.
872 // v3 (the retired-row compaction): the consumed prefix as a running digest, each bar
873 // folded once, the first time a read passes it; a prefix run still reads
874 // like the full run up to its last bar.
875 const std::size_t consumed = std::min(
876 retained_n_, static_cast<std::size_t>(std::max(source_bar_count_, 0)));
877 const auto extend = [](std::size_t& folded, std::uint64_t& digest, std::size_t upto,
878 const auto& element) {
879 if (folded > upto) {
880 folded = 0;
881 digest = 1469598103934665603ULL;
882 }
883 for (; folded < upto; ++folded) {
885 g.h = digest;
886 element(g, folded);
887 digest = g.h;
888 }
889 };
890 extend(consumed_bars_folded_, consumed_bars_digest_, consumed,
891 [&](BrokerStateHashSink& g, std::size_t index) {
892 // owned is just the stream's backing for bars, not an additional
893 // broker fact: either storage folds the same bar bytes once.
894 const auto& bar = retained_.is_stream ? retained_.owned[index] : retained_.bars[index];
895 g.d(bar.open); g.d(bar.high); g.d(bar.low); g.d(bar.close); g.d(bar.volume);
896 g.i(bar.timestamp);
897 });
898 const std::size_t completion_prefix = std::min(input_script_size_, consumed);
899 // Fold both flags here, before release_batch_input clears the caller span.
900 // The independent cursor restarts with both digests for a shorter prefix.
901 if (consumed_completes_folded_ > completion_prefix
902 || consumed_boundaries_folded_ > completion_prefix) {
903 consumed_completes_folded_ = 0;
904 consumed_completes_digest_ = 1469598103934665603ULL;
905 consumed_boundaries_folded_ = 0;
906 consumed_boundaries_digest_ = 1469598103934665603ULL;
907 if (consumed_script_cursor_) consumed_script_cursor_->reset();
908 }
909 extend(consumed_completes_folded_, consumed_completes_digest_, completion_prefix,
910 [&](BrokerStateHashSink& g, std::size_t index) {
911 const auto flags = consumed_script_cursor_
912 ? consumed_script_cursor_->next(retained_.bars[index])
913 : ScriptBucketCursor::Flags{};
914 g.u(flags.complete);
915 BrokerStateHashSink boundary;
916 boundary.h = consumed_boundaries_digest_;
917 boundary.u(flags.boundary);
918 consumed_boundaries_digest_ = boundary.h;
919 consumed_boundaries_folded_ = index + 1;
920 });
921}
922
924 fold_consumed_digests();
925 // retained_n_ survives detachment. Using the now-empty span's size here
926 // would rewind all three digests and change every post-run hash (F8).
927 const std::size_t consumed = std::min(
928 retained_n_, static_cast<std::size_t>(std::max(source_bar_count_, 0)));
929 f.s("pineforge-pine-scheduler/v3");
930 f.u(consumed); f.u(consumed_bars_digest_);
931 f.s(retained_.input_tf); f.s(retained_.script_tf); f.b(retained_.bar_magnifier);
932 f.i(retained_.magnifier_samples); f.i(static_cast<std::int64_t>(retained_.distribution));
933 f.b(retained_.volume_weighted); f.i(retained_.volume_weighted_min_samples);
934 f.i(retained_.volume_weighted_max_samples); f.b(retained_.is_stream); f.i(retained_.warmup_n);
935 f.b(retained_.simple_run);
936 f.i(language_.pos_view_freeze_bar_); f.i(static_cast<std::int64_t>(language_.pos_view_frozen_side_));
937 f.d(language_.pos_view_frozen_qty_); hash_str_double_map(f, language_.pos_view_frozen_entry_qty_);
938 f.b(language_._src_series_active_); hash_source_series(f, language_._src_open_);
939 hash_source_series(f, language_._src_high_); hash_source_series(f, language_._src_low_);
940 hash_source_series(f, language_._src_close_); hash_source_series(f, language_._src_volume_);
941 hash_source_series(f, language_._src_hl2_); hash_source_series(f, language_._src_hlc3_);
942 hash_source_series(f, language_._src_ohlc4_); hash_source_series(f, language_._src_hlcc4_);
943 f.d(language_.prev_chart_close_); f.d(language_.last_chart_close_); f.i(language_.bar_index_offset_);
944 f.b(language_.is_first_tick_); f.b(language_.is_last_tick_); f.b(language_.history_slot_is_new_);
945 f.b(language_.coof_checkpoint_contains_current_bar_);
946 hash_source_series(f, language_.coof_checkpoint_src_open_);
947 hash_source_series(f, language_.coof_checkpoint_src_high_);
948 hash_source_series(f, language_.coof_checkpoint_src_low_);
949 hash_source_series(f, language_.coof_checkpoint_src_close_);
950 hash_source_series(f, language_.coof_checkpoint_src_volume_);
951 hash_source_series(f, language_.coof_checkpoint_src_hl2_);
952 hash_source_series(f, language_.coof_checkpoint_src_hlc3_);
953 hash_source_series(f, language_.coof_checkpoint_src_ohlc4_);
954 hash_source_series(f, language_.coof_checkpoint_src_hlcc4_);
955 f.d(language_.coof_checkpoint_prev_chart_close_); f.d(language_.coof_checkpoint_last_chart_close_);
956 f.i(current_script_open_ms_);
957 f.d(current_script_bar_.open); f.d(current_script_bar_.high); f.d(current_script_bar_.low);
958 f.d(current_script_bar_.close); f.d(current_script_bar_.volume); f.i(current_script_bar_.timestamp);
959 f.b(current_script_bar_valid_); f.b(saw_open_fill_); f.i(open_point_fills_); f.i(source_bar_count_);
960 f.b(expected_source_bars_ >= source_bar_count_);
961 f.u(applied_cursor_); f.i(coof_callback_script_open_);
962 f.i(last_published_script_open_ms_);
963 f.i(prior_input_script_open_ms_);
964 f.i(awaiting_legacy_script_open_ms_);
965 f.i(last_stream_input_open_ms_);
966 const std::size_t completion_prefix = std::min(input_script_size_, consumed);
967 f.u(completion_prefix); f.u(consumed_completes_digest_);
968 const std::size_t boundary_prefix = std::min(input_script_size_, consumed);
969 f.u(boundary_prefix); f.u(consumed_boundaries_digest_);
970 f.b(uses_aux_security_feed_);
971 if (retained_.is_stream) f.b(input_is_observed_ticks_);
972 if (retained_.is_stream) {
973 f.u(confirmed_input_next_);
974 f.u(confirmed_input_count_);
975 for (const auto& bar : confirmed_input_bars_) {
976 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close); f.d(bar.volume);
977 f.i(bar.timestamp);
978 }
979 f.u(confirmed_open_input_next_);
980 f.u(confirmed_open_input_count_);
981 for (const auto& bar : confirmed_open_input_bars_) {
982 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close); f.d(bar.volume);
983 f.i(bar.timestamp);
984 }
985 }
986 f.d(deferred_boundary_input_.bar.open); f.d(deferred_boundary_input_.bar.high);
987 f.d(deferred_boundary_input_.bar.low); f.d(deferred_boundary_input_.bar.close);
988 f.d(deferred_boundary_input_.bar.volume); f.i(deferred_boundary_input_.bar.timestamp);
989 f.i(deferred_boundary_input_.next_input_ms);
990 f.i(deferred_boundary_input_.prior_script_open_ms);
991 f.b(deferred_boundary_input_.calling_bar_complete);
992 f.b(deferred_boundary_input_.all_security_states);
993 f.b(deferred_boundary_input_.active);
994}
995
996namespace {
997
998// The selected window's pre-roll capture (src/source/pine_intent_state.hpp), in
999// its own domain and folded only while a selected run parks the state, so every
1000// run without one keeps the hash it had. Each payload folds its own argument
1001// fields in declaration order; the configuration and point folds follow the ones
1002// the adapter and the kernel already use.
1003constexpr char kSourceIntentDomain[] = "pineforge-source-intent/v1";
1004
1005void hash_intent_config(BrokerStateHashSink& f, const source::PineStrategyConfig& config) {
1006 f.b(config.process_orders_on_close); f.b(config.calc_on_order_fills);
1007 f.d(config.initial_capital); f.i(config.default_qty_type); f.d(config.default_qty_value);
1008 f.i(config.pyramiding); f.d(config.commission_value); f.i(config.commission_type);
1009 f.i(config.slippage); f.d(config.margin_long); f.d(config.margin_short);
1010 f.b(config.close_entries_rule_any); f.b(config.src_series_active);
1011}
1012
1013// The saved risk configuration alone: the seven setter fields with their
1014// sticky percent flags, and the cap's three configuration fields.
1015void hash_intent_risk(BrokerStateHashSink& f, const source::PineReplayRiskConfig& risk) {
1016 f.i(risk.direction); f.i(risk.max_cons_loss_days); f.d(risk.max_drawdown);
1017 f.b(risk.max_drawdown_percent); f.d(risk.max_intraday_loss);
1019 f.i(static_cast<std::int64_t>(risk.cap_attachment)); f.i(risk.cap_configuration.limit);
1022}
1023
1024void hash_intent_interval(BrokerStateHashSink& f, const native_calendar::NativeInterval& interval) {
1025 f.i(interval.open_ms); f.i(interval.eligible_open_ms); f.i(interval.last_traded_close_ms);
1026 f.i(interval.next_period_open_ms); f.i(interval.next_input_open_ms);
1027}
1028
1029// A retained point, every value field in declaration order. The four session-day
1030// facts are presentation to the kernel's own digests, but the replay reads the
1031// retained point back, so the retained value folds whole.
1032void hash_intent_point(BrokerStateHashSink& f, const NativeCurrentPointView& point) {
1033 const NativeCoordinate& c = point.decision.coordinate;
1034 f.u(c.ordinal); f.i(c.interval_index); f.i(c.input_interval_index); f.i(c.open_ms);
1035 f.i(c.eligible_open_ms); f.i(c.last_traded_close_ms); f.i(c.next_period_open_ms);
1036 f.i(c.next_input_open_ms); f.i(c.effective_time_ms); f.i(c.source_price_time_ms);
1037 f.u(static_cast<std::uint64_t>(c.provenance)); f.u(static_cast<std::uint64_t>(c.path_phase));
1038 f.u(static_cast<std::uint64_t>(c.completion));
1039 f.i(point.decision.decision_floor_ms);
1040 hash_intent_interval(f, point.decision.input_interval);
1041 hash_intent_interval(f, point.decision.script_interval);
1042 f.i(point.decision.sub_index); f.i(point.decision.sub_count);
1043 f.b(point.decision.is_terminal_sub_bar);
1044 f.b(point.decision.in_session); f.b(point.decision.opens_session_day);
1045 f.b(point.decision.closes_session_day); f.b(point.decision.closes_session_day_open_ended);
1046 f.i(point.decision.sub_bar_open_ms); f.i(point.decision.script_bar_open_ms);
1047 const NativeDriverStatistics& statistics = point.decision.driver_statistics;
1048 f.b(statistics.intrabar_path_enabled); f.i(statistics.sub_bars_per_script_bar);
1049 f.i(statistics.samples_per_sub_bar); f.u(statistics.sub_bars_processed);
1050 f.u(statistics.sample_ticks_processed);
1051 f.d(point.price); f.u(static_cast<std::uint64_t>(point.quote_kind));
1052 f.u(point.quote_origin_ordinal);
1053}
1054
1055void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentEntry& v) {
1056 f.s(v.id); f.b(v.is_long); f.d(v.limit_price); f.d(v.stop_price); f.d(v.qty);
1057 f.s(v.comment); f.s(v.oca_name); f.i(v.oca_type); f.i(v.qty_type);
1058}
1059void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentClose& v) {
1060 f.s(v.id); f.s(v.comment); f.d(v.qty); f.d(v.qty_percent); f.b(v.immediately);
1061}
1062void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentCloseToken& v) {
1063 f.s(v.id); f.s(v.comment); f.d(v.qty); f.d(v.qty_percent); f.b(v.immediately);
1064 f.u(v.callsite_token);
1065}
1066void hash_intent_payload(BrokerStateHashSink&, const source::detail::IntentCloseAll&) {}
1067void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentExit& v) {
1068 f.s(v.id); f.s(v.from_entry); f.d(v.limit_price); f.d(v.stop_price);
1069 f.d(v.trail_points); f.d(v.trail_offset); f.d(v.trail_price); f.d(v.qty_percent);
1070 f.s(v.comment); f.d(v.qty); f.s(v.oca_name); f.d(v.profit_ticks); f.d(v.loss_ticks);
1071}
1072void hash_intent_payload(BrokerStateHashSink& f,
1073 const source::detail::IntentExitCancelBracket& v) {
1074 f.s(v.exit_id); f.s(v.from_entry); f.s(v.comment);
1075}
1076void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentCancel& v) {
1077 f.s(v.id);
1078}
1079void hash_intent_payload(BrokerStateHashSink&, const source::detail::IntentCancelAll&) {}
1080void hash_intent_payload(BrokerStateHashSink& f, const source::detail::IntentOrder& v) {
1081 f.s(v.id); f.b(v.is_long); f.d(v.qty); f.d(v.limit_price); f.d(v.stop_price);
1082 f.s(v.oca_name); f.i(v.oca_type);
1083}
1084void hash_intent_payload(BrokerStateHashSink& f,
1085 const source::detail::IntentRiskDirection& v) {
1086 f.i(v.direction);
1087}
1088void hash_intent_payload(BrokerStateHashSink& f,
1089 const source::detail::IntentRiskMaxConsLossDays& v) {
1090 f.i(v.value);
1091}
1092void hash_intent_payload(BrokerStateHashSink& f,
1093 const source::detail::IntentRiskMaxDrawdown& v) {
1094 f.d(v.value); f.b(v.percent);
1095}
1096void hash_intent_payload(BrokerStateHashSink& f,
1097 const source::detail::IntentRiskMaxIntradayLoss& v) {
1098 f.d(v.value); f.b(v.percent);
1099}
1100void hash_intent_payload(BrokerStateHashSink& f,
1101 const source::detail::IntentRiskMaxIntradayFilledOrders& v) {
1102 f.i(v.limit);
1103}
1104void hash_intent_payload(BrokerStateHashSink& f,
1105 const source::detail::IntentRiskMaxPositionSize& v) {
1106 f.d(v.value);
1107}
1108
1109void hash_selected_intents(BrokerStateHashSink& f, const source::detail::PineIntentState& state) {
1110 f.s(kSourceIntentDomain);
1111 // The native run follows the source identity normalization of a handle's run
1112 // (hash_source_run_identity): it protects a stale state and is not a
1113 // source-visible fact, so it is never a per-run nonce. The owner, the kind
1114 // tag, the bracket flag and the replay pointer are addresses or transient and
1115 // are not folded.
1116 f.u(0);
1117 f.b(state.has_evaluation); f.b(state.replayed); f.i(state.label_ms);
1118 hash_intent_config(f, state.start_config); hash_intent_config(f, state.end_config);
1119 hash_intent_risk(f, state.start_risk); hash_intent_risk(f, state.end_risk);
1120 hash_intent_point(f, state.terminal_point);
1121 f.u(state.rows.size());
1122 for (const source::detail::PineIntentRow& row : state.rows) {
1123 f.u(row.payload.index());
1124 std::visit([&f](const auto& payload) { hash_intent_payload(f, payload); }, row.payload);
1125 hash_intent_config(f, row.config);
1126 hash_intent_point(f, row.point);
1127 }
1128}
1129
1130} // namespace
1131
1134 if (!trade_scratch_)
1135 NativeExecutionConsumer::bound(*this).rethrow_released_command_scratch_failure(
1136 NativeExecutionConsumer::CommandScratchBinding::Trade);
1137 const auto released = NativeExecutionConsumer::bound(*this).released_command_scratch(
1138 NativeExecutionConsumer::CommandScratchBinding::Trade);
1139 if (!trade_scratch_ && released) {
1140 // Preserve the live store's exact presence/digest bytes after its
1141 // run-owned backing is released. This is no additional hash field.
1142 f.b(true);
1143 hash_scratch_digest(f, released->status, released->digest);
1144 } else {
1145 hash_scratch_store(f, trade_scratch_);
1146 }
1147 f.b(config_.process_orders_on_close); f.b(config_.calc_on_order_fills);
1148 f.d(config_.initial_capital); f.i(config_.default_qty_type); f.d(config_.default_qty_value);
1149 f.i(config_.pyramiding); f.d(config_.commission_value); f.i(config_.commission_type);
1150 f.i(config_.slippage); f.d(config_.margin_long); f.d(config_.margin_short);
1151 f.b(config_.close_entries_rule_any); f.b(config_.src_series_active);
1152 f.d(override_.initial_capital); f.d(override_.commission_value); f.d(override_.default_qty_value);
1153 f.i(override_.pyramiding); f.i(override_.slippage); f.i(override_.commission_type);
1154 f.i(override_.default_qty_type); f.i(override_.process_orders_on_close);
1155 f.i(override_.calc_on_order_fills); f.i(override_.close_entries_rule);
1158#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1159 // v4: the auxiliary feed and its chart ranges never change during a run;
1160 // their digest is taken when they are set (refresh_aux_security_digest:
1161 // aux_security_bars_, aux_security_input_tf_, aux_security_chart_begin_,
1162 // aux_security_chart_end_).
1166#endif
1167 // Per-site request.security semantics, in sec_id order. Folded under
1168 // their own domain and only when a site is registered, so a run without
1169 // request.security keeps the hash it had.
1170 if (!pine_security_states_.empty()) {
1172 f.u(pine_security_states_.size());
1173 for (const auto& [sec_id, pine] : pine_security_states_) {
1174 f.i(sec_id);
1175 f.b(pine.lookahead_on);
1176 f.b(pine.gaps_on);
1177 f.i(pine.publish_gate_tf_seconds);
1178 f.b(pine.calling_close_completes_partial);
1179 // Folded only when set: every site that loads bars keeps the
1180 // digest it had.
1181 if (pine.no_loaded_bars) f.b(pine.no_loaded_bars);
1182 f.b(pine.heikinashi);
1183 f.d(pine.ha_prev_open);
1184 f.d(pine.ha_prev_close);
1185 f.b(pine.ha_seeded);
1186 // The projection in hand, not the prepared list: later buckets
1187 // are a function of input the run has not consumed yet, and a
1188 // prefix run must hash like the full run up to its last bar.
1189 f.b(!pine.historical_projections.empty());
1190 if (!pine.historical_projections.empty()) {
1191 f.u(pine.historical_projection_cursor);
1192 f.b(pine.historical_projection_dispatched);
1193 const auto& projection = pine.historical_projections[std::min(
1194 pine.historical_projection_cursor,
1195 pine.historical_projections.size() - 1)];
1196 f.d(projection.bar.open); f.d(projection.bar.high);
1197 f.d(projection.bar.low); f.d(projection.bar.close);
1198 f.d(projection.bar.volume); f.i(projection.bar.timestamp);
1199 f.i(projection.first_child_ms);
1200 f.b(projection.is_complete);
1201 }
1202 f.b(pine.lower_tf_requested);
1203 f.b(pine.lower_tf_emulation);
1204 f.i(pine.lower_tf_ratio);
1205 f.i(pine.lower_tf_seconds);
1206 f.b(pine.lower_tf_array_requested);
1207 f.i(pine.lower_tf_sub_bar_index);
1208 f.b(pine.lower_tf_use_input);
1209 f.i(pine.lower_tf_input_aggregation_ratio);
1210 f.u(pine.lower_tf_input_buffer.size());
1211 for (const Bar& bar : pine.lower_tf_input_buffer) {
1212 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close);
1213 f.d(bar.volume); f.i(bar.timestamp);
1214 }
1215 f.b(pine.calling_open_latches_first);
1216 f.b(pine.first_bucket_published);
1217 f.i(pine.slice_open_label);
1218 f.i(pine.last_published_label);
1219 f.u(pine.deferred_aux.size());
1220 for (const auto& held : pine.deferred_aux) {
1221 f.d(held.bar.open); f.d(held.bar.high); f.d(held.bar.low);
1222 f.d(held.bar.close); f.d(held.bar.volume); f.i(held.bar.timestamp);
1223 f.i(held.next_input_ms);
1224 f.b(held.calling_bar_complete);
1225 }
1226 }
1227 }
1228 // Lane XSYM-D: the request sites of other symbols -- registration and
1229 // per-run cursors -- and the request data the run was handed (the feeds'
1230 // keys and sizes, the facts and the recorded series, through the digest
1231 // the setters keep; the feeds' bars are the projected spec's instrument
1232 // feeds, which the continuation digest folds). Under their own domain and
1233 // only when present, so every run without them keeps the hash it had.
1234 if (!foreign_security_sites_.empty() || !symbol_feeds_.empty()
1235 || !symbol_facts_.empty() || !recorded_series_.empty()) {
1237 f.u(symbol_feeds_.size());
1238 f.u(symbol_facts_.size());
1239 f.u(recorded_series_.size());
1242 f.u(foreign_security_series_.size());
1243 for (const int sec_id : foreign_security_series_) f.i(sec_id);
1244 f.u(foreign_security_sites_.size());
1245 for (const auto& [sec_id, site] : foreign_security_sites_) {
1246 f.i(sec_id);
1247 f.s(site.symbol);
1248 f.s(site.requested_tf);
1249 f.b(site.lookahead);
1250 f.b(site.gaps);
1251 f.b(site.ignore_invalid);
1252 f.s(site.tf);
1253 f.b(site.invalid);
1254 f.i(site.subscription);
1255 f.u(site.feed);
1256 f.i(site.delivered);
1257 f.i(site.close_ms);
1258 f.i(site.last_input);
1259 f.i(site.handed);
1260 }
1261 }
1262 // The selected window's pre-roll capture, only while a selected run parks it
1263 // (selected_intents allocates nothing and is null for every other run).
1264 if (const source::detail::PineIntentState* const intents = detail::selected_intents(*this))
1265 hash_selected_intents(f, *intents);
1266 adapter_.hash_state(f); scheduler_.hash_state(f);
1267}
1268
1269} // 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