PineForge v0.13.1-379-g9b50973
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
6#include <algorithm>
7#include <type_traits>
8#include <variant>
9
10namespace pineforge {
11namespace {
12
13// The admission journal is source-layer state (R5 lane N14): its reflected
14// fields fold here, through the source host's extension, exactly as they
15// did when this helper lived in the kernel-internal sink header.
16void hash_admission_field(BrokerStateHashSink& f, const admission::Field& field) {
17 f.s(field.path); f.u(field.value.index());
18 std::visit([&](const auto& value) {
19 using T = std::decay_t<decltype(value)>;
20 if constexpr (std::is_same_v<T, uint64_t>) f.u(value);
21 else if constexpr (std::is_same_v<T, int64_t>) f.i(value);
22 else if constexpr (std::is_same_v<T, double>) f.d(value);
23 else f.s(value);
24 }, field.value);
25}
26
27void hash_source_series(BrokerStateHashSink& f, const Series<double>& series) {
28 f.i(series.size());
29 for (int i = 0; i < series.size(); ++i) f.d(series[i]);
30}
31
32} // namespace
33
34namespace {
35
36void hash_source_run_identity(BrokerStateHashSink& f,
37 const native_order::RunIdentity& identity) {
38 f.s(identity.session_key);
39 // The monotonically increasing native generation protects stale handles;
40 // it does not change the source-visible state of a fresh run.
41 f.u(0);
42}
43void hash_native_handle(BrokerStateHashSink& f, const native_order::RequestHandle& handle) {
44 hash_source_run_identity(f, handle.run); f.u(handle.incarnation);
45}
46void hash_native_handle_vector(BrokerStateHashSink& f,
47 const std::vector<native_order::RequestHandle>& handles) {
48 f.u(handles.size());
49 for (const auto& handle : handles) hash_native_handle(f, handle);
50}
51void hash_placement(BrokerStateHashSink& f, const source::PlacementSnapshot& value) {
52 f.i(static_cast<std::int64_t>(value.family)); f.s(value.source_id); f.s(value.from_entry);
53 f.s(value.comment); f.s(value.oca_name); f.i(value.oca_type); f.i(value.qty_type);
54 f.d(value.requested_qty); f.d(value.projection_remaining_qty);
55 f.d(value.qty_percent); f.b(value.is_long); f.b(value.immediately);
56 f.b(value.opening); f.b(value.deferred_cohort);
57 f.b(value.reservation_deferred_to_pending_entry);
58 f.b(value.fixed_exit_reservation);
59 f.b(value.frozen_market_instruction);
60 f.d(value.frozen_market_own_units); f.d(value.frozen_market_transaction_units);
61 f.b(value.frozen_market_targeted_close); f.b(value.frozen_market_target_was_long);
62 f.b(value.direction_gate); f.b(value.affordability_policy_active);
63 f.b(value.affordability_close_only);
64 f.b(value.rounded_signal_cost_close_only);
65 f.b(value.affordability_keep_mc_close_surplus);
66 f.b(value.reverse_to); f.b(value.replaced_opening); f.b(value.replacement_predecessor_market);
67 f.b(value.terms_priced_reverse);
68 f.d(value.frozen_reversal_transaction);
69 f.i(value.placement_cycle); f.u(value.sequential_group); f.u(value.sequential_rank);
70 f.b(value.has_full_entry_bracket);
71 hash_source_run_identity(f, value.paired_reversal_parent.run);
72 f.u(value.paired_reversal_parent.incarnation);
73 hash_source_run_identity(f, value.preserved_by_close_all.run);
74 f.u(value.preserved_by_close_all.incarnation);
75 f.i(value.preserved_close_all_bar);
76 hash_source_run_identity(f, value.bracket_origin.run);
77 f.u(value.bracket_origin.incarnation);
78 f.u(value.source_sequence);
79 f.u(value.command_ordinal); f.u(value.placement_open_epoch);
80 f.u(value.command_sequence);
81 f.u(value.close_callsite_token); f.u(value.close_batch_calls);
82 f.s(value.close_first_id); f.d(value.close_first_target);
83 f.b(value.close_first_ledger_consumed); f.b(value.close_first_carry_valid);
84 f.d(value.close_first_carry_qty); f.b(value.close_retire_ledger_whole);
85 f.d(value.close_pending_later_qty);
86 f.i(value.placement_script_open_ms);
87 f.i(value.placement_sub_open_ms); f.i(value.projection_created_bar);
88 f.b(value.projection_created_bar_pinned);
89 f.b(value.post_parent_calc_level_fill);
90 f.i(value.projection_position_side); f.b(value.projection_after_close);
91 f.b(value.projection_over_pyramiding);
92 f.b(value.projection_opposite_market_predecessor);
93 f.u(value.projection_predecessor);
94 f.u(value.recreated_after_named_cancelled_entry_incarnation);
95 f.u(value.named_cancel_surviving_exit_incarnation);
96 f.b(value.retained_parent_topology);
97 f.b(value.defer_until_post_parent_calculation);
98 f.b(value.projection_predecessor_market); f.b(value.projection_predecessor_exit);
99 f.b(value.projection_created_during_coof); f.b(value.projection_coof_at_terminal);
100 f.b(value.projection_coof_mid_bar); f.d(value.forced_execution_price);
101 f.d(value.projection_tv_carry_qty);
102 f.d(value.projection_default_stop_equity);
103 f.d(value.projection_default_stop_signal_close);
104 f.d(value.projection_explicit_equity); f.d(value.projection_explicit_signal_close);
105 f.d(value.projection_affordability_equity);
106 f.d(value.projection_affordability_signal_price);
107 f.d(value.projection_affordability_held_qty);
108 f.d(value.sizing.equity); f.d(value.sizing.price);
109 f.d(value.sizing.fx); f.d(value.sizing.mark); f.d(value.sizing.frozen_units);
110 f.b(value.sizing.at_fill); f.d(value.exit_levels.limit); f.d(value.exit_levels.stop);
111 f.d(value.exit_levels.trail_points); f.d(value.exit_levels.trail_offset);
112 f.d(value.exit_levels.trail_price); f.d(value.exit_levels.profit_ticks);
113 f.d(value.exit_levels.loss_ticks);
114 f.d(value.trail_activation_level); f.d(value.retained_trail_best);
115 admission::reflect(value.market_admission, "placement.market_admission",
116 [&](const admission::Field& field) { hash_admission_field(f, field); });
117 f.i(static_cast<std::int64_t>(value.birth.cause())); f.i(value.birth.bar());
118 f.i(value.birth.timestamp()); f.i(static_cast<std::int64_t>(value.birth.cursor().domain()));
119 f.i(static_cast<std::int64_t>(value.birth.cursor().position()));
120 f.i(value.birth.cursor().index()); f.i(value.birth.cursor().count());
121 f.d(value.birth.cursor_price()); f.u(value.birth.first_fill()); f.u(value.birth.last_fill());
122 f.u(value.birth.evaluation_ordinal()); f.i(static_cast<std::int64_t>(value.birth_reach));
123 f.b(value.leg_activation.bounds().has_value());
124 if (value.leg_activation.bounds()) {
125 f.i(value.leg_activation.bounds()->position_cycle);
126 f.i(value.leg_activation.bounds()->stop_first_bar);
127 f.i(value.leg_activation.bounds()->limit_first_bar);
128 }
129 f.b(value.exit_activation.evidence().has_value());
130 if (value.exit_activation.evidence()) {
131 const auto& evidence = *value.exit_activation.evidence();
132 f.i(evidence.position_cycle); f.i(evidence.entry_bar); f.i(evidence.direction);
133 f.d(evidence.cursor_price); f.d(evidence.stop_level); f.d(evidence.limit_level);
134 f.b(evidence.limit_continuation.has_value());
135 if (evidence.limit_continuation) {
136 f.i(static_cast<std::int64_t>(evidence.limit_continuation->cause));
137 f.u(evidence.limit_continuation->observed_fill_sequence);
138 }
139 }
140 value.legs.visit(f);
141 f.b(value.restored_after_margin);
142 f.b(value.reservation_expansion.capture().has_value());
143 if (value.reservation_expansion.capture()) {
144 const auto& capture = *value.reservation_expansion.capture();
145 f.i(capture.position_cycle); f.i(static_cast<std::int64_t>(capture.side));
146 f.b(capture.first_later_admission.has_value());
147 if (capture.first_later_admission) f.u(*capture.first_later_admission);
148 }
149 f.b(value.reservation_growth_source.reservation_owner().has_value());
150 if (value.reservation_growth_source.reservation_owner())
151 f.u(*value.reservation_growth_source.reservation_owner());
152 f.u(value.reservation_growth_owner_incarnation);
153 f.b(value.stop_limit_activated); f.i(value.coof_cascade_seg_i);
154 f.b(value.coof_cascade_inflight_fires); f.b(value.paired_flat_market_candidate);
155 f.d(value.paired_flat_market_own_qty); f.d(value.paired_flat_market_signal_close);
156 f.d(value.paired_flat_market_signal_equity);
157 f.d(value.paired_flat_market_signal_margin_pct);
158 f.d(value.paired_flat_market_signal_pointvalue); f.d(value.paired_flat_market_signal_fx);
159 f.i(value.paired_flat_market_peer_seq); f.d(value.paired_flat_market_transaction_qty);
160 f.i(value.signal_close_mc_bar); f.u(value.signal_close_mc_entry_incarnation);
161 f.u(value.signal_close_mc_fill_seq); f.d(value.signal_close_mc_remaining_qty);
162 f.b(value.pooc_global_full_exit_dynamic_qty);
163 f.b(value.pooc_global_full_exit_tracks_bound_adds);
164 f.b(value.pooc_global_full_exit_bound_add);
165 f.i(static_cast<std::int32_t>(value.cancellation.cause)); f.i(value.cancellation.state);
166 f.i(value.cancellation.close_claim_release); f.u(value.cancellation.source_incarnation);
167 f.i(value.cancellation.source_sequence); f.u(value.cancellation.target_incarnation);
168 f.i(value.cancellation.target_owner); f.u(value.cancellation.target_revision);
169 f.d(value.cancellation.close_claim_consumed); f.d(value.cancellation.close_claim_retired);
170}
171
172void hash_short_seed_plan(BrokerStateHashSink& f, const source::ShortSeedPlan& value) {
173 hash_native_handle(f, value.long_entry); hash_native_handle(f, value.materialize_long);
174 hash_native_handle(f, value.final_short); f.s(value.seed_id); f.s(value.long_entry_id);
175 f.s(value.final_short_id); f.s(value.materialize_label); f.d(value.seed_qty);
176 f.i(value.seed_cycle); f.b(value.active); f.b(value.report_swap_pending);
177}
178
179void hash_native_request(BrokerStateHashSink& f, const native_order::Request& request) {
180 f.u(request.intent.index());
181 if (const auto* reduce = std::get_if<native_order::Reduce>(&request.intent)) {
182 f.u(reduce->size.index());
183 if (const auto* units = std::get_if<native_order::ExplicitUnits>(&reduce->size)) f.d(units->units);
184 } else if (const auto* transact = std::get_if<native_order::Transact>(&request.intent)) {
185 f.d(transact->signed_units);
186 } else if (const auto* reverse = std::get_if<native_order::ReverseTo>(&request.intent)) {
187 f.d(reverse->signed_units);
188 } else if (const auto* sized = std::get_if<native_order::HostSized>(&request.intent)) {
189 f.i(static_cast<std::int64_t>(sized->kind)); f.b(sized->side.has_value());
190 if (sized->side) f.i(static_cast<std::int64_t>(*sized->side));
191 } else if (const auto* core_sized = std::get_if<native_order::Sized>(&request.intent)) {
192 // A core-sized opening carries its whole quantity in the basis, so the
193 // source fingerprint must separate two commands that differ only there.
194 f.i(static_cast<std::int64_t>(core_sized->side));
195 f.u(core_sized->basis.index());
196 if (const auto* cash = std::get_if<native_order::CashValue>(&core_sized->basis)) {
197 f.d(cash->cash);
198 } else {
199 f.d(std::get<native_order::EquityFraction>(core_sized->basis).fraction);
200 }
201 f.i(static_cast<std::int64_t>(core_sized->time));
202 f.i(static_cast<std::int64_t>(core_sized->price));
203 f.i(static_cast<std::int64_t>(core_sized->grid_policy));
204 f.b(core_sized->reserve_percent_fee);
205 }
206 f.s(request.label); f.s(request.comment);
207 f.u(request.trigger.index());
208 if (const auto* limit = std::get_if<native_order::Limit>(&request.trigger)) f.d(limit->price);
209 else if (const auto* stop = std::get_if<native_order::Stop>(&request.trigger)) f.d(stop->price);
210 else if (const auto* stop_limit = std::get_if<native_order::StopLimit>(&request.trigger)) {
211 f.d(stop_limit->stop); f.d(stop_limit->limit);
212 } else if (const auto* trail = std::get_if<native_order::Trail>(&request.trigger)) {
213 f.d(trail->offset); f.b(trail->arm_price.has_value());
214 if (trail->arm_price) f.d(*trail->arm_price);
215 }
216 f.u(request.capacity.index());
217 if (const auto* budget = std::get_if<native_order::PointBudget>(&request.capacity)) f.d(budget->units);
218 f.u(request.owner.index());
219 if (const auto* wait = std::get_if<native_order::WaitForApplied>(&request.owner)) {
220 hash_native_handle(f, wait->parent);
221 } else if (const auto* opening = std::get_if<native_order::BindOpening>(&request.owner)) {
222 hash_native_handle(f, opening->opening); f.i(opening->cycle);
223 } else if (const auto* openings = std::get_if<native_order::BindOpenings>(&request.owner)) {
224 hash_native_handle_vector(f, openings->openings); f.i(openings->cycle);
225 } else if (const auto* cohort = std::get_if<native_order::BindCohort>(&request.owner)) {
226 f.u(cohort->cohort.value);
227 }
228 f.u(request.group.index());
229 if (const auto* group = std::get_if<native_order::Member>(&request.group)) {
230 f.u(group->group); f.i(group->cohort); f.i(static_cast<std::int64_t>(group->effect));
231 }
232}
233
234} // namespace
235
238 f.u(source_sequence_); f.u(command_ordinal_); f.u(broker_open_epoch_);
239 f.i(last_broker_open_ms_); f.u(source_command_sequence_); f.b(host_ != nullptr);
240 f.b(config_.process_orders_on_close); f.b(config_.calc_on_order_fills);
241 f.d(config_.initial_capital); f.i(config_.default_qty_type); f.d(config_.default_qty_value);
242 f.i(config_.pyramiding); f.d(config_.commission_value); f.i(config_.commission_type);
243 f.i(config_.slippage); f.d(config_.margin_long); f.d(config_.margin_short);
244 f.b(config_.close_entries_rule_any); f.b(config_.src_series_active);
245 f.i(static_cast<std::int64_t>(path_order_));
246 f.s(staged_.syminfo.ticker); f.s(staged_.syminfo.tickerid); f.s(staged_.syminfo.currency);
247 f.s(staged_.syminfo.basecurrency); f.s(staged_.syminfo.type); f.s(staged_.syminfo.timezone);
248 f.s(staged_.syminfo.session); f.s(staged_.syminfo.volumetype); f.s(staged_.syminfo.description);
249 f.d(staged_.syminfo.mintick); f.d(staged_.syminfo.pointvalue); f.d(staged_.syminfo.qty_step);
250 f.s(staged_.chart_timezone); f.d(staged_.account_fx);
251 f.u(staged_.account_fx_effective_from_ms.size());
252 for (const auto timestamp : staged_.account_fx_effective_from_ms) f.i(timestamp);
253 f.u(staged_.account_fx_per_quote.size());
254 for (const auto rate : staged_.account_fx_per_quote) f.d(rate);
255 f.b(staged_.quantity_grid.has_value()); if (staged_.quantity_grid) f.d(*staged_.quantity_grid);
256 std::vector<std::string> input_keys;
257 for (const auto& pair : staged_.inputs) input_keys.push_back(pair.first);
258 std::sort(input_keys.begin(), input_keys.end()); f.u(input_keys.size());
259 for (const auto& key : input_keys) { f.s(key); f.s(staged_.inputs.at(key)); }
260 std::vector<std::string> cohort_keys;
261 for (const auto& pair : cohorts_by_id_) cohort_keys.push_back(pair.first);
262 std::sort(cohort_keys.begin(), cohort_keys.end()); f.u(cohort_keys.size());
263 for (const auto& key : cohort_keys) {
264 const auto& cohort = cohorts_by_id_.at(key);
265 f.s(key); f.u(cohort.handle.value); f.i(cohort.cycle);
266 hash_native_handle_vector(f, cohort.origins.members()); hash_native_handle_vector(f, cohort.opened);
267 std::vector<std::uint64_t> live_origin_keys;
268 live_origin_keys.reserve(cohort.live_units_by_origin.size());
269 for (const auto& row : cohort.live_units_by_origin) live_origin_keys.push_back(row.first);
270 std::sort(live_origin_keys.begin(), live_origin_keys.end());
271 f.u(live_origin_keys.size());
272 for (const auto incarnation : live_origin_keys) {
273 f.u(incarnation); f.d(cohort.live_units_by_origin.at(incarnation));
274 }
275 }
276 f.u(cohort_order_.size());
277 for (const auto& key : cohort_order_) f.s(key);
278 std::vector<std::uint64_t> placement_keys;
279 for (const auto& pair : placement_) placement_keys.push_back(pair.first);
280 std::sort(placement_keys.begin(), placement_keys.end()); f.u(placement_keys.size());
281 for (const auto key : placement_keys) { f.u(key); hash_placement(f, placement_.at(key)); }
282 std::vector<std::uint64_t> live_keys;
283 for (const auto& pair : live_by_source_key_) live_keys.push_back(pair.first);
284 std::sort(live_keys.begin(), live_keys.end()); f.u(live_keys.size());
285 for (const auto key : live_keys) { f.u(key); hash_native_handle(f, live_by_source_key_.at(key)); }
286 std::vector<std::uint64_t> bracket_keys;
287 for (const auto& pair : bracket_families_) bracket_keys.push_back(pair.first);
288 std::sort(bracket_keys.begin(), bracket_keys.end()); f.u(bracket_keys.size());
289 for (const auto key : bracket_keys) { f.u(key); hash_native_handle_vector(f, bracket_families_.at(key).members()); }
290 f.u(pending_bracket_legs_.size());
291 for (const auto& leg : pending_bracket_legs_) {
292 hash_native_request(f, leg.request); hash_placement(f, leg.snapshot);
293 f.s(leg.replacement_key); f.u(leg.family_key);
294 }
295 f.u(pending_entries_.size());
296 for (const auto& entry : pending_entries_) {
297 hash_native_request(f, entry.request); hash_placement(f, entry.snapshot);
298 f.s(entry.replacement_key);
299 }
300 f.u(delayed_market_orders_.size());
301 for (const auto& order : delayed_market_orders_) {
302 hash_native_request(f, order.request); hash_placement(f, order.snapshot);
303 f.s(order.replacement_key); f.u(order.release_open_epoch);
304 f.b(order.execute_at_open);
305 }
306 f.u(deferred_open_marketable_sells_.size());
307 for (const auto& sell : deferred_open_marketable_sells_) {
308 hash_placement(f, sell.snapshot); f.s(sell.replacement_key); f.d(sell.fill_price);
309 f.d(sell.path_position); f.b(sell.open_marketable);
310 }
311 f.u(throttled_reopen_rearm_.size());
312 for (const auto& snapshot : throttled_reopen_rearm_) hash_placement(f, snapshot);
313 f.i(entry_openings_interval_index_); f.i(entry_openings_this_interval_);
314 f.u(pending_same_bar_commands_.size());
315 for (const auto& command : pending_same_bar_commands_) {
316 hash_native_request(f, command.request); hash_placement(f, command.snapshot);
317 f.s(command.replacement_key); f.b(command.opening);
318 }
319 f.u(source_shadow_pending_.size());
320 for (const auto& shadow : source_shadow_pending_) {
321 hash_placement(f, shadow.snapshot); f.s(shadow.label);
322 }
323 f.d(pending_same_bar_close_qty_);
324 f.u(pending_relative_exits_.size());
325 for (const auto& exit : pending_relative_exits_) {
326 f.s(exit.exit_id); f.s(exit.from_entry); f.d(exit.trail_points); f.d(exit.trail_offset);
327 f.d(exit.trail_price); f.d(exit.qty_percent); f.s(exit.comment); f.d(exit.qty);
328 f.s(exit.oca_name); f.d(exit.profit_ticks); f.d(exit.loss_ticks);
329 }
330 // R5 lane R4d: folded only while a leg is anchored, so every run that
331 // anchors none keeps its established hash.
332 if (!anchored_relative_legs_.empty() || anchored_relative_stats_.anchored != 0
333 || materializing_parent_.incarnation != 0) {
334 f.s("pineforge-source-anchored-legs/v1");
335 f.u(anchored_relative_stats_.anchored); f.u(anchored_relative_stats_.adopted);
336 f.u(anchored_relative_stats_.withdrawn); f.u(materializing_parent_.incarnation);
337 f.i(anchored_cohort_sequence_);
338 f.u(anchored_relative_legs_.size());
339 for (const auto& leg : anchored_relative_legs_) {
340 f.s(leg.exit_id); f.s(leg.from_entry); f.u(static_cast<std::uint64_t>(leg.family));
341 f.u(leg.handle.incarnation); f.u(leg.parent.incarnation); f.b(leg.parent_long);
342 f.d(leg.operand_ticks); f.d(leg.trail_offset);
343 hash_native_request(f, leg.request);
344 // hash_native_request predates anchors; an anchored leg's own
345 // spelling is folded here.
346 if (const auto* anchor = std::get_if<native_order::FromOwnerFill>(
347 &leg.request.anchor)) {
348 f.d(anchor->offset); f.b(anchor->ticks);
349 f.u(static_cast<std::uint64_t>(anchor->rounding));
350 }
351 f.b(leg.armed); f.d(leg.installed_level);
352 }
353 }
354 f.u(pending_coof_requests_.size());
355 for (const auto& pending : pending_coof_requests_) {
356 hash_native_request(f, pending.request); hash_placement(f, pending.snapshot);
357 f.s(pending.replacement_key); f.b(pending.opening); f.u(pending.family_key);
358 f.b(pending.next_open);
359 }
360 f.u(pending_margin_revivals_.size());
361 for (const auto& pending : pending_margin_revivals_) {
362 hash_placement(f, pending.snapshot); f.i(pending.decline_bar);
363 }
364 hash_native_handle_vector(f, live_handles_); hash_native_handle_vector(f, first_open_newborns_);
365 // PendingIntentView is a derived read-only alias of live_handles_. Keep
366 // its historical fingerprint position and bytes without copying the
367 // roster at every mutation boundary.
368 hash_native_handle_vector(f, live_handles_);
369 f.u(dropped_close_receipts_.size());
370 for (const auto& receipt : dropped_close_receipts_) {
371 f.s(receipt.source_id); f.s(receipt.comment); f.d(receipt.qty);
372 f.d(receipt.qty_percent); f.b(receipt.immediately); f.u(receipt.callsite_token);
373 f.u(receipt.command_ordinal);
374 }
375 f.u(open_entry_fees_.size());
376 for (const auto& fee : open_entry_fees_) {
377 hash_native_handle(f, fee.opening); f.s(fee.source_id); f.d(fee.units);
378 f.d(fee.nonpercent_fee);
379 }
380 f.u(trade_exit_phase_.size());
381 for (const auto phase : trade_exit_phase_) f.u(phase);
382 std::vector<std::uint64_t> current_debit_ordinals;
383 current_debit_ordinals.reserve(current_debited_applied_ordinals_.size());
384 for (const auto ordinal : current_debited_applied_ordinals_) current_debit_ordinals.push_back(ordinal);
385 std::sort(current_debit_ordinals.begin(), current_debit_ordinals.end());
386 f.u(current_debit_ordinals.size());
387 for (const auto ordinal : current_debit_ordinals) f.u(ordinal);
388 std::vector<std::uint64_t> intraday_relabel_ordinals;
389 intraday_relabel_ordinals.reserve(intraday_loss_relabel_ordinals_.size());
390 for (const auto ordinal : intraday_loss_relabel_ordinals_)
391 intraday_relabel_ordinals.push_back(ordinal);
392 std::sort(intraday_relabel_ordinals.begin(), intraday_relabel_ordinals.end());
393 f.u(intraday_relabel_ordinals.size());
394 for (const auto ordinal : intraday_relabel_ordinals) f.u(ordinal);
395 std::vector<std::string> consumed_partial_keys;
396 consumed_partial_keys.reserve(consumed_partial_exit_cycles_.size());
397 for (const auto& row : consumed_partial_exit_cycles_) consumed_partial_keys.push_back(row.first);
398 std::sort(consumed_partial_keys.begin(), consumed_partial_keys.end());
399 f.u(consumed_partial_keys.size());
400 for (const auto& key : consumed_partial_keys) {
401 f.s(key); f.i(consumed_partial_exit_cycles_.at(key));
402 }
403 std::vector<std::uint64_t> shadowed_openings(
404 bracket_shadowed_openings_.begin(), bracket_shadowed_openings_.end());
405 std::sort(shadowed_openings.begin(), shadowed_openings.end());
406 f.u(shadowed_openings.size());
407 for (const auto opening : shadowed_openings) f.u(opening);
408 std::vector<std::string> named_cancel_keys;
409 named_cancel_keys.reserve(named_entry_cancel_tokens_.size());
410 for (const auto& row : named_entry_cancel_tokens_) named_cancel_keys.push_back(row.first);
411 std::sort(named_cancel_keys.begin(), named_cancel_keys.end());
412 f.u(named_cancel_keys.size());
413 for (const auto& key : named_cancel_keys) {
414 const auto& token = named_entry_cancel_tokens_.at(key);
415 f.s(key); f.u(token.entry_incarnation); f.u(token.surviving_exit_incarnation);
416 }
417 const auto hash_close_units = [&](const auto& values) {
418 f.u(values.size());
419 for (const auto& row : values) { f.s(row.first); f.d(row.second); }
420 };
421 hash_close_units(close_logical_units_);
422 hash_close_units(close_reserved_units_);
423 hash_close_units(close_first_units_);
424 const auto hash_close_owners = [&](const auto& owners) {
425 f.u(owners.size());
426 for (const auto& owner : owners) {
427 f.u(owner.first); hash_close_units(owner.second);
428 }
429 };
430 hash_close_owners(close_callsite_reserved_units_);
431 hash_close_owners(close_callsite_first_units_);
432 f.u(close_batch_callsites_.size());
433 for (const auto& row : close_batch_callsites_) {
434 const auto& site = row.second;
435 f.u(row.first); f.b(site.active); f.u(site.token); f.i(site.calls);
436 f.s(site.first_id); f.d(site.first_target);
437 f.b(site.first_ledger_consumed); f.b(site.first_carry_valid);
438 f.d(site.first_carry_qty); f.s(site.id); f.s(site.comment);
439 f.d(site.target); f.b(site.retire_ledger_whole);
440 f.u(site.queue_sequence); f.u(site.deferred_cleanup_ids.size());
441 for (const auto& id : site.deferred_cleanup_ids) f.s(id);
442 }
443 f.i(close_batch_bar_); f.u(close_batch_queue_sequence_);
444 f.d(close_batch_pending_debt_); f.d(close_batch_admitted_total_);
445 f.u(receipt_cursor_);
446 f.u(last_applied_ordinal_);
447 f.u(terminal_receipt_cursor_);
448 f.i(entry_attempt_bar_); f.u(entry_attempts_on_bar_);
449 f.b(materializing_relative_);
450 f.i(current_position_cycle_);
451 f.i(current_position_sign_);
452 f.u(next_sequential_group_);
453 f.b(source_batch_mutated_);
454 f.b(coof_recalc_active_); f.b(coof_first_open_);
455 f.u(coof_market_entry_recalc_incarnation_);
456 f.u(coof_market_entry_recalc_fill_seq_); f.u(coof_current_fill_seq_);
457 // Live only inside a fill recalculation (NaN otherwise); folded then so
458 // the idle digest keeps its prior form.
459 if (coof_recalc_active_) f.d(coof_fill_cursor_t_);
460 const auto& coof_coord = coof_context_.coordinate;
461 f.u(coof_coord.ordinal); f.i(coof_coord.interval_index); f.i(coof_coord.open_ms);
462 f.i(coof_coord.eligible_open_ms); f.i(coof_coord.last_traded_close_ms);
463 f.i(coof_coord.next_period_open_ms); f.i(coof_coord.next_input_open_ms);
464 f.i(coof_coord.effective_time_ms); f.i(coof_coord.source_price_time_ms);
465 f.i(static_cast<std::int64_t>(coof_coord.provenance));
466 f.i(static_cast<std::int64_t>(coof_coord.path_phase));
467 f.i(static_cast<std::int64_t>(coof_coord.completion)); f.i(coof_context_.decision_floor_ms);
468 const auto hash_coof_interval = [&](const native_calendar::NativeInterval& interval) {
469 f.i(interval.open_ms); f.i(interval.eligible_open_ms); f.i(interval.last_traded_close_ms);
470 f.i(interval.next_period_open_ms); f.i(interval.next_input_open_ms);
471 };
472 hash_coof_interval(coof_context_.input_interval);
473 hash_coof_interval(coof_context_.script_interval);
474 f.i(coof_context_.sub_index); f.i(coof_context_.sub_count);
475 f.b(coof_context_.is_terminal_sub_bar); f.i(coof_context_.sub_bar_open_ms);
476 f.i(coof_context_.script_bar_open_ms);
477 f.b(coof_context_.driver_statistics.intrabar_path_enabled);
478 f.i(coof_context_.driver_statistics.sub_bars_per_script_bar);
479 f.i(coof_context_.driver_statistics.samples_per_sub_bar);
480 f.u(coof_context_.driver_statistics.sub_bars_processed);
481 f.u(coof_context_.driver_statistics.sample_ticks_processed);
482 f.d(coof_script_bar_.open); f.d(coof_script_bar_.high); f.d(coof_script_bar_.low);
483 f.d(coof_script_bar_.close); f.d(coof_script_bar_.volume); f.i(coof_script_bar_.timestamp);
484 f.b(coof_script_bar_valid_);
485 std::vector<std::int64_t> pooc_basis_keys;
486 for (const auto& pair : pooc_close_basis_by_script_bar_) pooc_basis_keys.push_back(pair.first);
487 std::sort(pooc_basis_keys.begin(), pooc_basis_keys.end()); f.u(pooc_basis_keys.size());
488 for (const auto key : pooc_basis_keys) { f.i(key); f.d(pooc_close_basis_by_script_bar_.at(key)); }
489 f.d(pooc_open_basis_); f.i(pooc_open_script_bar_); f.i(close_all_pending_script_bar_);
490 f.d(last_fx_rate_); f.i(position_open_script_bar_); f.u(position_open_epoch_); f.i(position_open_bar_index_);
491 f.u(static_cast<std::uint64_t>(position_open_phase_));
492 f.b(position_open_priced_);
493 f.i(last_margin_call_script_bar_); f.i(pooc_close_checkpoint_deferred_ms_);
494 f.u(last_margin_call_event_ordinal_);
495 f.u(last_margin_call_entry_incarnation_); f.i(last_margin_call_position_cycle_);
496 f.b(last_margin_call_at_script_close_);
497 f.d(last_margin_call_closed_units_); f.d(last_margin_call_remaining_units_);
498 f.i(signal_close_mc_event_bar_);
499 f.i(signal_close_mc_position_cycle_); f.u(signal_close_mc_entry_incarnation_);
500 f.u(signal_close_mc_fill_seq_); f.d(signal_close_mc_before_qty_);
501 f.d(signal_close_mc_remaining_qty_); f.i(risk_coof_direct_script_bar_);
502 f.u(cap_latest_fill_);
503 f.b(source_margin_call_enabled_);
504 f.d(policy_script_bar_.open); f.d(policy_script_bar_.high);
505 f.d(policy_script_bar_.low); f.d(policy_script_bar_.close);
506 f.d(policy_script_bar_.volume); f.i(policy_script_bar_.timestamp);
507 f.b(policy_script_bar_valid_);
508 // KI-62 market-add provenance (R5 lane L12, 2.ii j). It used to ride the
509 // generic lot as PyramidEntry::market_pyramid_add; it is adapter state
510 // now, so the source extension folds it. Unordered container: sort the
511 // keys, exactly like trail_state_at_open_ below.
512 std::vector<std::uint64_t> market_add_keys(market_pyramid_adds_.begin(),
513 market_pyramid_adds_.end());
514 std::sort(market_add_keys.begin(), market_add_keys.end());
515 f.u(market_add_keys.size());
516 for (const auto key : market_add_keys) f.u(key);
517 std::vector<std::uint64_t> trail_open_keys;
518 trail_open_keys.reserve(trail_state_at_open_.size());
519 for (const auto& row : trail_state_at_open_) trail_open_keys.push_back(row.first);
520 std::sort(trail_open_keys.begin(), trail_open_keys.end());
521 f.u(trail_open_keys.size());
522 for (const auto key : trail_open_keys) {
523 const auto& state = trail_state_at_open_.at(key);
524 f.u(key); f.b(state.activated); f.d(state.best_price);
525 f.d(state.current_level); f.u(state.activation_ordinal);
526 }
527 f.b(stream_mode_);
528 f.i(day_ledger_.current_day); f.i(day_ledger_.last_loss_day); f.i(day_ledger_.consecutive_loss_days);
529 f.i(day_ledger_.intraday_loss_day); f.d(day_ledger_.intraday_start_equity);
530 f.d(day_ledger_.intraday_realized); f.u(day_ledger_.observed_applied_ordinal);
531 f.i(risk_.direction); f.i(risk_.max_cons_loss_days); f.d(risk_.max_drawdown);
532 f.b(risk_.max_drawdown_percent); f.d(risk_.max_intraday_loss);
533 f.b(risk_.max_intraday_loss_percent); f.d(risk_.max_position_size); f.b(risk_.halted);
534 f.d(risk_.observed_peak_equity); f.d(risk_.observed_max_drawdown);
535 f.i(risk_.intraday_block_day); f.b(risk_.intraday_cancel_pending);
536 hash_short_seed_plan(f, short_seed_);
537 hash_short_seed_plan(f, pending_short_seed_.plan);
538 f.u(pending_short_seed_.expected_open_epoch); f.b(pending_short_seed_.ready);
539 hash_native_handle(f, short_seed_long_candidate_);
540 f.i(last_bar_dual_entry_path_); f.i(last_bar_dual_entry_script_open_ms_);
541 f.b(pending_view_.owner_ != nullptr);
542 f.i(static_cast<std::int64_t>(cap.attachment())); f.i(cap.configuration().limit);
543 f.b(cap.configuration().skip_noop_market); f.b(cap.configuration().defer_pooc_close);
544 f.b(cap.configuration().count_pooc_full_close);
545 const auto& cap_budget = cap.budget();
546 f.b(cap_budget.day().has_value());
547 if (cap_budget.day()) f.i(cap_budget.day()->key);
548 f.i(cap_budget.charged_slots()); f.b(cap_budget.latched());
549 f.b(cap_budget.transfer().has_value());
550 if (const auto& transfer = cap_budget.transfer()) {
551 f.i(transfer->day.key); f.u(transfer->close_fill);
552 f.i(transfer->source_bar); f.u(transfer->inheritor);
553 }
554 f.b(cap.due_cause().has_value());
555 if (const auto& due = cap.due_cause()) {
556 f.u(due->action_id); f.i(due->charged_day.key); f.i(due->charged_slots);
557 f.i(due->trigger_bar); f.u(due->trigger_order);
558 }
559 f.u(cap.next_action()); f.b(priority.attached());
560 f.b(priority.retained_parent_first());
561 admission_journal.reflect("journal", [&](const auto& field) { hash_admission_field(f, field); });
562}
563
565 // ab9714be test_live_state_hash_recording: a shorter run is a hash prefix
566 // of the same longer feed. Retained future input is provider transport,
567 // not broker continuation state, so fold only the consumed source prefix.
568 const std::size_t consumed = std::min(
569 retained_.bars.size(), static_cast<std::size_t>(std::max(source_bar_count_, 0)));
570 f.s("pineforge-pine-scheduler/v2"); f.u(consumed);
571 for (std::size_t index = 0; index < consumed; ++index) {
572 const auto& bar = retained_.bars[index];
573 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close); f.d(bar.volume); f.i(bar.timestamp);
574 }
575 f.s(retained_.input_tf); f.s(retained_.script_tf); f.b(retained_.bar_magnifier);
576 f.i(retained_.magnifier_samples); f.i(static_cast<std::int64_t>(retained_.distribution));
577 f.b(retained_.volume_weighted); f.i(retained_.volume_weighted_min_samples);
578 f.i(retained_.volume_weighted_max_samples); f.b(retained_.is_stream); f.i(retained_.warmup_n);
579 f.b(retained_.simple_run);
580 f.i(language_.pos_view_freeze_bar_); f.i(static_cast<std::int64_t>(language_.pos_view_frozen_side_));
581 f.d(language_.pos_view_frozen_qty_); hash_str_double_map(f, language_.pos_view_frozen_entry_qty_);
582 f.b(language_._src_series_active_); hash_source_series(f, language_._src_open_);
583 hash_source_series(f, language_._src_high_); hash_source_series(f, language_._src_low_);
584 hash_source_series(f, language_._src_close_); hash_source_series(f, language_._src_volume_);
585 hash_source_series(f, language_._src_hl2_); hash_source_series(f, language_._src_hlc3_);
586 hash_source_series(f, language_._src_ohlc4_); hash_source_series(f, language_._src_hlcc4_);
587 f.d(language_.prev_chart_close_); f.d(language_.last_chart_close_); f.i(language_.bar_index_offset_);
588 f.b(language_.is_first_tick_); f.b(language_.is_last_tick_); f.b(language_.history_slot_is_new_);
589 f.b(language_.coof_checkpoint_contains_current_bar_);
590 hash_source_series(f, language_.coof_checkpoint_src_open_);
591 hash_source_series(f, language_.coof_checkpoint_src_high_);
592 hash_source_series(f, language_.coof_checkpoint_src_low_);
593 hash_source_series(f, language_.coof_checkpoint_src_close_);
594 hash_source_series(f, language_.coof_checkpoint_src_volume_);
595 hash_source_series(f, language_.coof_checkpoint_src_hl2_);
596 hash_source_series(f, language_.coof_checkpoint_src_hlc3_);
597 hash_source_series(f, language_.coof_checkpoint_src_ohlc4_);
598 hash_source_series(f, language_.coof_checkpoint_src_hlcc4_);
599 f.d(language_.coof_checkpoint_prev_chart_close_); f.d(language_.coof_checkpoint_last_chart_close_);
600 f.i(current_script_open_ms_);
601 f.d(current_script_bar_.open); f.d(current_script_bar_.high); f.d(current_script_bar_.low);
602 f.d(current_script_bar_.close); f.d(current_script_bar_.volume); f.i(current_script_bar_.timestamp);
603 f.b(current_script_bar_valid_); f.b(saw_open_fill_); f.i(open_point_fills_); f.i(source_bar_count_);
604 f.b(expected_source_bars_ >= source_bar_count_);
605 f.u(applied_cursor_); f.i(coof_callback_script_open_);
606 f.i(last_published_script_open_ms_);
607 f.i(prior_input_script_open_ms_);
608 f.i(awaiting_legacy_script_open_ms_);
609 f.i(last_stream_input_open_ms_);
610 const std::size_t completion_prefix = std::min(input_script_completes_.size(), consumed);
611 f.u(completion_prefix);
612 for (std::size_t index = 0; index < completion_prefix; ++index)
613 f.u(input_script_completes_[index]);
614 const std::size_t boundary_prefix = std::min(input_script_boundary_completes_.size(), consumed);
615 f.u(boundary_prefix);
616 for (std::size_t index = 0; index < boundary_prefix; ++index)
617 f.u(input_script_boundary_completes_[index]);
618 f.b(uses_aux_security_feed_);
619 f.d(deferred_boundary_input_.bar.open); f.d(deferred_boundary_input_.bar.high);
620 f.d(deferred_boundary_input_.bar.low); f.d(deferred_boundary_input_.bar.close);
621 f.d(deferred_boundary_input_.bar.volume); f.i(deferred_boundary_input_.bar.timestamp);
622 f.i(deferred_boundary_input_.next_input_ms);
623 f.i(deferred_boundary_input_.prior_script_open_ms);
624 f.b(deferred_boundary_input_.calling_bar_complete);
625 f.b(deferred_boundary_input_.all_security_states);
626 f.b(deferred_boundary_input_.active);
627}
628
631 f.b(config_.process_orders_on_close); f.b(config_.calc_on_order_fills);
632 f.d(config_.initial_capital); f.i(config_.default_qty_type); f.d(config_.default_qty_value);
633 f.i(config_.pyramiding); f.d(config_.commission_value); f.i(config_.commission_type);
634 f.i(config_.slippage); f.d(config_.margin_long); f.d(config_.margin_short);
635 f.b(config_.close_entries_rule_any); f.b(config_.src_series_active);
636 f.d(override_.initial_capital); f.d(override_.commission_value); f.d(override_.default_qty_value);
637 f.i(override_.pyramiding); f.i(override_.slippage); f.i(override_.commission_type);
638 f.i(override_.default_qty_type); f.i(override_.process_orders_on_close);
639 f.i(override_.calc_on_order_fills); f.i(override_.close_entries_rule);
640 // Transient excursion-sampler cache (see pine_strategy_host.hpp): it is
641 // re-derived at every precommit, but it is next-decision visible to the
642 // excursion sampler inside an in-flight execution, so it is folded here
643 // rather than waived.
647#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
648 f.u(aux_security_bars_.size());
649 for (const auto& bar : aux_security_bars_) {
650 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close);
651 f.d(bar.volume); f.i(bar.timestamp);
652 }
655 for (const auto value : aux_security_chart_begin_) f.u(value);
656 f.u(aux_security_chart_end_.size());
657 for (const auto value : aux_security_chart_end_) f.u(value);
658#endif
659 // Per-site request.security semantics, in sec_id order. Folded under
660 // their own domain and only when a site is registered, so a run without
661 // request.security keeps the hash it had.
662 if (!pine_security_states_.empty()) {
664 f.u(pine_security_states_.size());
665 for (const auto& [sec_id, pine] : pine_security_states_) {
666 f.i(sec_id);
667 f.b(pine.lookahead_on);
668 f.b(pine.gaps_on);
669 f.i(pine.publish_gate_tf_seconds);
670 f.b(pine.calling_close_completes_partial);
671 f.b(pine.heikinashi);
672 f.d(pine.ha_prev_open);
673 f.d(pine.ha_prev_close);
674 f.b(pine.ha_seeded);
675 // The projection in hand, not the prepared list: later buckets
676 // are a function of input the run has not consumed yet, and a
677 // prefix run must hash like the full run up to its last bar.
678 f.b(!pine.historical_projections.empty());
679 if (!pine.historical_projections.empty()) {
680 f.u(pine.historical_projection_cursor);
681 f.b(pine.historical_projection_dispatched);
682 const auto& projection = pine.historical_projections[std::min(
683 pine.historical_projection_cursor,
684 pine.historical_projections.size() - 1)];
685 f.d(projection.bar.open); f.d(projection.bar.high);
686 f.d(projection.bar.low); f.d(projection.bar.close);
687 f.d(projection.bar.volume); f.i(projection.bar.timestamp);
688 f.i(projection.first_child_ms);
689 f.b(projection.is_complete);
690 }
691 f.b(pine.lower_tf_requested);
692 f.b(pine.lower_tf_emulation);
693 f.i(pine.lower_tf_ratio);
694 f.i(pine.lower_tf_seconds);
695 f.b(pine.lower_tf_array_requested);
696 f.i(pine.lower_tf_sub_bar_index);
697 f.b(pine.lower_tf_use_input);
698 f.i(pine.lower_tf_input_aggregation_ratio);
699 f.u(pine.lower_tf_input_buffer.size());
700 for (const Bar& bar : pine.lower_tf_input_buffer) {
701 f.d(bar.open); f.d(bar.high); f.d(bar.low); f.d(bar.close);
702 f.d(bar.volume); f.i(bar.timestamp);
703 }
704 f.b(pine.calling_open_latches_first);
705 f.b(pine.first_bucket_published);
706 f.i(pine.slice_open_label);
707 f.i(pine.last_published_label);
708 f.u(pine.deferred_aux.size());
709 for (const auto& held : pine.deferred_aux) {
710 f.d(held.bar.open); f.d(held.bar.high); f.d(held.bar.low);
711 f.d(held.bar.close); f.d(held.bar.volume); f.i(held.bar.timestamp);
712 f.i(held.next_input_ms);
713 f.b(held.calling_bar_complete);
714 }
715 }
716 }
717 // The margin slice's sampling chronology is resolved once per pending
718 // slice in the precommit pass and read back by the host's own excursion
719 // sampler at settlement, so it is folded rather than waived.
722 // The TRAIL peak basis is the precommit view's pre-slip matcher price,
723 // which no durable snapshot re-derives at settlement.
725 adapter_.hash_state(f); scheduler_.hash_state(f);
726}
727
728} // namespace pineforge
void hash_state(BrokerStateHashSink &) const
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)
void hash_state(BrokerStateHashSink &) const
void bar(const Bar &, const NativeDecisionContext &, PineStrategyHost &)
std::vector< std::size_t > aux_security_chart_begin_
void hash_host_extension(BrokerStateHashSink &) const override
std::map< int, PineSecurityEvalState > pine_security_states_
std::vector< std::size_t > aux_security_chart_end_
void reflect(const Draft &value, const std::string &path, const FieldVisitor &visit)
constexpr char kSourceSecurityDomain[]
constexpr char kSourceAdapterDomain[]
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
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...