PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pine_adapter.cpp
Go to the documentation of this file.
4
6
8
9#include "../engine_internal.hpp"
11#include "../timezone.hpp"
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include <cstdint>
17#include <ctime>
18#include <cstring>
19#include <limits>
20#include <map>
21#include <stdexcept>
22#include <string_view>
23#include <type_traits>
24#include <utility>
25
26namespace pineforge::source {
27namespace {
28
29constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
30
31bool same_double_bits(double left, double right) noexcept {
32 std::uint64_t left_bits = 0;
33 std::uint64_t right_bits = 0;
34 std::memcpy(&left_bits, &left, sizeof(left_bits));
35 std::memcpy(&right_bits, &right, sizeof(right_bits));
36 return left_bits == right_bits;
37}
38
39bool same_exit_levels(const PineExitLevels& left, const PineExitLevels& right) noexcept {
40 return same_double_bits(left.limit, right.limit)
41 && same_double_bits(left.stop, right.stop)
42 && same_double_bits(left.trail_points, right.trail_points)
43 && same_double_bits(left.trail_offset, right.trail_offset)
44 && same_double_bits(left.trail_price, right.trail_price)
45 && same_double_bits(left.profit_ticks, right.profit_ticks)
46 && same_double_bits(left.loss_ticks, right.loss_ticks);
47}
48
49bool finite_positive(double value) noexcept {
50 return std::isfinite(value) && value > 0.0;
51}
52
53bool finite_non_negative(double value) noexcept {
54 return std::isfinite(value) && value >= 0.0;
55}
56
57// ab9714be pine_fills.cpp:384 routes a priced exit's fill through
58// margin_call_slice_before_priced_exit, whose 1x-long arm
59// (pine_fills.cpp:2328-2456) takes the entry-bar opening slice THERE -- inside
60// process_pending_orders, strictly before that bar's update_per_trade_extremes
61// (pine_scheduler.cpp:257). In that chronology neither the split-off residual
62// nor the surviving main lot inherits the bar's H/L/C sample: the residual
63// keeps its entry seed (fav 0, adv = its own commission) and the main lot only
64// ever sees its exit fill. The end-of-bar opening branch is the one that
65// samples the complete bar. A priced exit leg still resting from an EARLIER
66// bar whose level this bar's range crosses is exactly the fill that preempts
67// the slice, so it is the discriminator between the two sampling points.
68template <typename Handles, typename Placement, typename FromEntryFilled>
69bool opening_slice_precedes_priced_exit_fill(const Handles& handles,
70 const Placement& placement,
71 const Bar& bar,
72 int interval_index,
73 bool is_long,
74 double mintick,
75 double avg_price,
76 std::int64_t position_cycle,
77 const FromEntryFilled& from_entry_filled) noexcept {
78 // The discriminator is CYCLE-scoped, exactly like the owner's bracket
79 // liveness: only a leg whose from_entry filled in THIS position cycle is
80 // evaluated (ab9714be pine_fills.cpp:7669-7673), its activation and leg
81 // ownership are bound to position_cycle_seq_ and unbound the moment the
82 // book goes flat (pine_orders.cpp:597-608, 614-627), and its priced legs
83 // are tested against the live position's own direction. A bracket left
84 // over from a finished cycle -- or one prearmed for the opposite side --
85 // never preempts the opening slice. A trail leg participates through its
86 // activation price, resolved from the live entry when the birth operand
87 // was points (pine_fills.cpp:7208-7230).
88 const auto expected_side = is_long
89 ? static_cast<std::int32_t>(PositionSide::LONG)
90 : static_cast<std::int32_t>(PositionSide::SHORT);
91 for (const auto& handle : handles) {
92 const auto found = placement.find(handle.incarnation);
93 if (found == placement.end()) continue;
94 const auto& row = found->second;
95 if (row.family != PineOrderFamily::ExitLimit
96 && row.family != PineOrderFamily::ExitStop
97 && row.family != PineOrderFamily::ExitTrail) {
98 continue;
99 }
100 if (position_cycle > 0 && row.placement_cycle != 0
101 && row.placement_cycle != position_cycle) {
102 continue;
103 }
104 if (row.projection_position_side != expected_side
105 && row.projection_position_side != static_cast<std::int32_t>(PositionSide::FLAT)) {
106 continue;
107 }
108 // ab9714be pine_fills.cpp:7671-7674 removes an exit whose from_entry
109 // has not filled in the live position cycle before it can fill, so
110 // a bracket the script keeps re-issuing for the other side's id
111 // (re-priced off the live position's average) cannot preempt.
112 if (!row.from_entry.empty() && !from_entry_filled(row.from_entry)) continue;
113 if (row.projection_created_bar < 0
114 || row.projection_created_bar > interval_index) {
115 continue;
116 }
117 if (row.family == PineOrderFamily::ExitStop && std::isfinite(row.exit_levels.stop)) {
118 if (is_long && bar.low <= row.exit_levels.stop) return true;
119 if (!is_long && bar.high >= row.exit_levels.stop) return true;
120 }
121 if (row.family == PineOrderFamily::ExitLimit && std::isfinite(row.exit_levels.limit)) {
122 if (is_long && bar.high >= row.exit_levels.limit) return true;
123 if (!is_long && bar.low <= row.exit_levels.limit) return true;
124 }
125 if (row.family == PineOrderFamily::ExitTrail) {
126 double trail_act = row.exit_levels.trail_price;
127 const double base_px = finite_positive(avg_price) ? avg_price : bar.open;
128 if (!finite_positive(trail_act) && finite_positive(row.exit_levels.trail_points)
129 && finite_positive(mintick) && finite_positive(base_px)) {
130 trail_act = base_px + (is_long ? 1.0 : -1.0)
131 * row.exit_levels.trail_points * mintick;
132 }
133 if (finite_positive(trail_act)) {
134 if (is_long && bar.high >= trail_act) return true;
135 if (!is_long && bar.low <= trail_act) return true;
136 }
137 }
138 }
139 return false;
140}
141
142// ab9714be pine_strategy_commands.cpp:533-537: a non-NaN limit/stop is a
143// present price level, including 0.0.
144bool price_present(double value) noexcept { return !std::isnan(value); }
145
146bool pure_stop_entry_marketable_at(const PlacementSnapshot& snapshot, double open) noexcept {
147 if (snapshot.family != PineOrderFamily::Entry) return false;
148 if (!finite_positive(snapshot.exit_levels.stop)) return false;
149 if (finite_positive(snapshot.exit_levels.limit)
150 || finite_positive(snapshot.exit_levels.trail_points)
151 || finite_positive(snapshot.exit_levels.trail_price)
152 || finite_positive(snapshot.exit_levels.trail_offset)) {
153 return false;
154 }
155 return snapshot.is_long ? open >= snapshot.exit_levels.stop
156 : open <= snapshot.exit_levels.stop;
157}
158
159bool source_path_high_first(const Bar& bar, NativePathOrder order) noexcept {
160 if (order == NativePathOrder::HighFirst) return true;
161 if (order == NativePathOrder::LowFirst) return false;
162 return std::abs(bar.high - bar.open) < std::abs(bar.open - bar.low);
163}
164
165double next_source_path_waypoint(const Bar& bar, NativePathPhase phase,
166 double current, NativePathOrder order,
167 double tick = 0.0, int slippage = 0) noexcept {
168 const bool high_first = source_path_high_first(bar, order);
169 const double tol = (slippage > 0 && tick > 0.0) ? (slippage + 0.5) * tick : 1e-6;
170 const auto at = [tol](double left, double right) {
171 return std::abs(left - right) <= tol;
172 };
173 switch (phase) {
174 case NativePathPhase::Open:
175 return high_first ? bar.high : bar.low;
176 case NativePathPhase::High:
177 if (!at(current, bar.high) && current < bar.high - tol) return bar.high;
178 return high_first ? bar.low : kNaN;
179 case NativePathPhase::Low:
180 if (!at(current, bar.low) && current > bar.low + tol) return bar.low;
181 return high_first ? kNaN : bar.high;
182 case NativePathPhase::Close:
183 return kNaN;
184 case NativePathPhase::None:
185 return kNaN;
186 }
187 return kNaN;
188}
189
190std::uint64_t fnv_append(std::uint64_t value, const void* bytes, std::size_t size) noexcept {
191 const auto* p = static_cast<const unsigned char*>(bytes);
192 for (std::size_t i = 0; i < size; ++i) {
193 value ^= p[i];
194 value *= 1099511628211ULL;
195 }
196 return value;
197}
198
199std::uint64_t fnv_string(std::string_view value) noexcept {
200 return fnv_append(1469598103934665603ULL, value.data(), value.size());
201}
202
203void copy_pending_string(std::string_view value, char* out, std::uint8_t* truncated,
204 std::uint64_t* hash) noexcept {
205 *hash = fnv_string(value);
206 const std::size_t size = std::min<std::size_t>(value.size(), 63U);
207 if (size != 0) std::memcpy(out, value.data(), size);
208 out[size] = '\0';
209 *truncated = value.size() > size ? 1U : 0U;
210}
211
212void copy_pending_prefixed_string(std::string_view prefix, std::string_view value,
213 char* out, std::uint8_t* truncated,
214 std::uint64_t* hash) noexcept {
215 // The public mirror is callable with allocation disabled. Hash and copy
216 // the synthetic close prefix in-place instead of materializing a string.
217 std::uint64_t digest = fnv_append(
218 1469598103934665603ULL, prefix.data(), prefix.size());
219 *hash = fnv_append(digest, value.data(), value.size());
220 const std::size_t total = prefix.size() + value.size();
221 const std::size_t prefix_size = std::min<std::size_t>(prefix.size(), 63U);
222 if (prefix_size != 0) std::memcpy(out, prefix.data(), prefix_size);
223 const std::size_t value_size = std::min<std::size_t>(
224 value.size(), 63U - prefix_size);
225 if (value_size != 0)
226 std::memcpy(out + prefix_size, value.data(), value_size);
227 out[prefix_size + value_size] = '\0';
228 *truncated = total > prefix_size + value_size ? 1U : 0U;
229}
230
231int mirror_order_type(PineOrderFamily family) noexcept {
232 switch (family) {
233 case PineOrderFamily::Entry: return 1;
234 case PineOrderFamily::Order: return 3;
239 case PineOrderFamily::ExitTrail: return 2;
241 case PineOrderFamily::Risk: return 0;
242 }
243 return 0;
244}
245
246std::uint64_t source_key(const SourceId& left, const SourceId& right) noexcept {
247 std::uint64_t value = fnv_string(left);
248 const char separator = '\0';
249 value = fnv_append(value, &separator, sizeof(separator));
250 return fnv_append(value, right.data(), right.size());
251}
252
253double nearest_tick(double value, double tick) noexcept {
254 if (!std::isfinite(value) || !finite_positive(tick)) return value;
255 return std::floor(value / tick + 0.5) * tick;
256}
257
258// The grid index k = floor(p / mintick + 0.5) re-derived as the decimal
259// quotient k / (1 / mintick), never the n * mintick product: a function of k
260// alone, so two prints on one grid point compare equal and a limit level on
261// the grid is never crossed by a ULP.
262double source_decimal_tick(double value, double tick) noexcept {
263 if (!std::isfinite(value) || !finite_positive(tick)) return value;
264 const double k = std::floor(value / tick + 0.5);
265 const double inverse = 1.0 / tick;
266 const double integral_inverse = std::floor(inverse + 0.5);
267 if (integral_inverse > 0.0
268 && std::abs(inverse - integral_inverse) <= 1e-6 * integral_inverse) {
269 return k / integral_inverse;
270 }
271 return k * tick;
272}
273
274double source_bar_fill_tick(double value, double tick) noexcept {
275 if (!std::isfinite(value) || !finite_positive(tick)) return value;
276 const double k = std::floor(value / tick + 0.5);
277 // ab9714be include/pineforge/engine.hpp:1207-1210 and 1258-1266: the
278 // legacy bar fill price is ``round_to_mintick`` = floor(p / mintick + 0.5)
279 // * mintick, and it is IDEMPOTENT — a price that is already on the tick
280 // grid comes back bit-identical (the comment there pins that the
281 // downstream directional snap is an identity on such a result, because
282 // n * mintick sits one binary64 ULP above the plain decimal quotient).
283 // Re-deriving an already rounded price as k / (1 / tick) dropped that ULP,
284 // so a same-bar reversal entry no longer booked on its own close's print.
285 if (k * tick == value) return value;
286 return source_decimal_tick(value, tick);
287}
288
289// ab9714be engine.hpp:1264-1266: a fill AT an on-grid raw print books
290// bar_fill_price(print) = floor(p / mintick + 0.5) * mintick, which can sit
291// one binary64 ULP off the print itself; both name the same path point.
292bool source_same_point(double booked, double raw, double tick) noexcept {
293 return booked == raw
294 || (finite_positive(tick) && source_bar_fill_tick(raw, tick) == raw
295 && nearest_tick(raw, tick) == booked);
296}
297
298double directional_tick(double value, double tick, bool upward) noexcept {
299 if (!std::isfinite(value) || !finite_positive(tick)) return value;
300 const double scaled = value / tick;
301 return (upward ? std::ceil(scaled - 1e-9) : std::floor(scaled + 1e-9)) * tick;
302}
303
304int price_grid_decimals(double tick) noexcept {
305 if (!finite_positive(tick)) return -1;
306 double scaled = tick;
307 for (int digits = 0; digits <= 10; ++digits) {
308 const double k = std::floor(scaled + 0.5);
309 if (k >= 1.0 && std::abs(scaled - k) <= 1e-6 * k) return digits;
310 scaled *= 10.0;
311 }
312 return -1;
313}
314
315double source_level_on_price_grid(double level, double tick) noexcept {
316 if (!std::isfinite(level)) return level;
317 const int digits = price_grid_decimals(tick);
318 if (digits < 0) return level;
319 double price_scale = 1.0;
320 for (int i = 0; i < digits; ++i) price_scale *= 10.0;
321 const double point_size = 1.0 / price_scale;
322 const double points = level / point_size;
323 const double grid = std::floor(points + 0.5);
324 if (std::abs(points - grid) <= 0.01 / price_scale)
325 return grid / price_scale;
326 return level;
327}
328
329double source_trigger_threshold(double level, double tick,
330 bool is_buy, bool is_limit) noexcept {
331 if (!std::isfinite(level) || !finite_positive(tick)) return level;
332 // ab9714be pine_strategy_commands.cpp:533-537: 0.0 is a valid stop level.
333 // The native matcher's finite_non_negative trigger validator refuses negative
334 // levels, so a non-positive trigger cannot be shifted half a tick lower.
335 if (level <= 0.0) return 0.0;
336 const bool upward = is_limit ? !is_buy : is_buy;
337 double scaled = level / tick;
338 const double inverse = 1.0 / tick;
339 const double integral_inverse = std::floor(inverse + 0.5);
340 if (integral_inverse > 0.0
341 && std::abs(inverse - integral_inverse) <= 1e-6 * integral_inverse) {
342 scaled = level * integral_inverse;
343 const double nearest_index = std::floor(scaled + 0.5);
344 if (source_bar_fill_tick(level, tick) == level)
345 scaled = nearest_index;
346 }
347 const double target_index = upward
348 ? std::ceil(scaled - 1e-12)
349 : std::floor(scaled + 1e-12);
350 const double grid = target_index * tick;
351 double threshold = grid + (upward ? -0.5 : 0.5) * tick;
352 // Materialize the exact binary64 boundary using the same half-up broker
353 // projection as the legacy trigger bar. Decimal half ticks can land one
354 // or two ULPs to either side depending on the literal and multiplication
355 // order (11.805 and 13.775 are the two pinned opposite cases).
356 const auto reaches_target = [&](double price) {
357 const double rounded_index = std::floor(price / tick + 0.5);
358 return upward ? rounded_index >= target_index
359 : rounded_index <= target_index;
360 };
361 for (int i = 0; i < 16 && !reaches_target(threshold); ++i) {
362 threshold = std::nextafter(threshold, upward
363 ? std::numeric_limits<double>::infinity()
364 : -std::numeric_limits<double>::infinity());
365 }
366 // Walk to the outermost representable value which still projects to the
367 // target grid. This handles literal half ticks whose binary value and the
368 // multiply-built boundary lie one ULP apart.
369 for (int i = 0; i < 16; ++i) {
370 const double candidate = std::nextafter(threshold, upward
371 ? -std::numeric_limits<double>::infinity()
372 : std::numeric_limits<double>::infinity());
373 if (!reaches_target(candidate)) break;
374 threshold = candidate;
375 }
376 return threshold;
377}
378
379int legacy_volume_weighted_max_samples(int samples) noexcept {
380 constexpr int kMaxSamples = 1 << 20;
381 const int nonnegative = std::max(samples, 0);
382 const int scaled = nonnegative > kMaxSamples / 4
383 ? kMaxSamples : nonnegative * 4;
384 return std::max(scaled, 8);
385}
386
387double floor_quantity_grid(double units, const std::optional<double>& grid) noexcept {
388 if (!std::isfinite(units) || units <= 0.0) return 0.0;
389 if (!grid || !std::isfinite(*grid) || *grid <= 0.0) return units;
390 const double floored = std::floor(units / *grid + 1e-6) * *grid;
391 // Preserve the caller's exact representation when quantization is a
392 // no-op. Reconstructing an already-on-grid value can move it by one ULP
393 // (ab9714be:engine.hpp:1547-1570).
394 return floored < units ? floored : units;
395}
396
397double source_money_round(double value) noexcept {
398 if (!std::isfinite(value) || value == 0.0) return value;
399 const double magnitude = std::floor(std::log10(std::abs(value)));
400 const double scale = std::pow(10.0, 9.0 - magnitude);
401 const double rounded = std::floor(std::abs(value) * scale + 0.5) / scale;
402 return value < 0.0 ? -rounded : rounded;
403}
404
405double source_money_floor_lot(double units, const std::optional<double>& grid) noexcept {
406 if (!grid || !std::isfinite(*grid) || *grid <= 0.0) return units;
407 if (!std::isfinite(units) || units <= 0.0) return units;
408 double floored = std::floor(units / *grid) * *grid;
409 if (*grid == 0.01) {
410 const double cent_candidate = std::floor(units * 100.0) * *grid;
411 if (cent_candidate > floored && cent_candidate <= units) floored = cent_candidate;
412 }
413 return floored < units ? floored : units;
414}
415
416NativeFeeKind fee_kind_for(int commission_type) noexcept {
417 switch (static_cast<CommissionType>(commission_type)) {
418 case CommissionType::CASH_PER_CONTRACT: return NativeFeeKind::CashPerUnit;
419 case CommissionType::CASH_PER_ORDER: return NativeFeeKind::CashPerExecution;
421 default: return NativeFeeKind::Percent;
422 }
423}
424
425NativeOpenDirections directions_for(int direction) noexcept {
426 if (direction > 0) return NativeOpenDirections::Long;
427 if (direction < 0) return NativeOpenDirections::Short;
428 return NativeOpenDirections::Both;
429}
430
431// R4-D L10z review fix 1: one throttled re-arm per refused source identity.
432// A throttled opening can be refused at two driver points within the same bar;
433// the bar-close re-arm must resubmit it exactly once, so the queue is deduped
434// on the placement identity (source id + source sequence) of the refused row.
435bool throttled_rearm_already_queued(
436 const std::vector<PlacementSnapshot>& queue,
437 const PlacementSnapshot& source) noexcept {
438 for (const auto& queued : queue) {
439 if (queued.source_id == source.source_id
440 && queued.source_sequence == source.source_sequence) {
441 return true;
442 }
443 }
444 return false;
445}
446
447} // namespace
448
449// ab9714be pine_fills.cpp:2009-2023: a margin slice born in a prefix-sampling
450// chronology (the POOC pre-script pass, or the 1x-long opening slice taken
451// inside process_pending_orders before a priced exit's fill) samples only the
452// traversed waypoint prefix (open-trigger must not inherit a later high).
453// Every other slice is the non-POOC end-of-bar opening trim, which
454// process_margin_call runs after the ordinary full-bar sample, so the closed
455// row inherits the complete bar.
456Bar margin_call_sample_bar(const Bar& bar, double fire_price, bool prefix_sample,
457 bool high_first, double mintick, int slippage) {
458 if (!prefix_sample || !std::isfinite(fire_price)) return bar;
459 // A slipped open waypoint does not bit-match bar.open, so the prefix walk
460 // below would run past it to the close; the tick-scaled tolerance keeps an
461 // open-fired slice at the open.
462 const double slip_tol = (slippage + 1) * (mintick > 0.0 ? mintick : 0.01) + 1e-7;
463 if (std::abs(fire_price - bar.open) <= slip_tol) {
464 Bar prefix = bar;
465 prefix.high = prefix.low = bar.open;
466 prefix.close = fire_price;
467 return prefix;
468 }
469 const double path[4] = {
470 bar.open,
471 high_first ? bar.high : bar.low,
472 high_first ? bar.low : bar.high,
473 bar.close,
474 };
475 // finding-446: a short's slice books at the nearest-tick rounded high,
476 // which need not bit-match the raw waypoint; without the tick fallback
477 // the walk would run past it and sample the whole bar.
478 int fire = -1;
479 for (int i = 0; i < 4 && fire < 0; ++i) {
480 if (same_double_bits(path[i], fire_price)) fire = i;
481 }
482 for (int i = 0; i < 4 && fire < 0 && mintick > 0.0; ++i) {
483 if (same_double_bits(nearest_tick(path[i], mintick), fire_price)) fire = i;
484 }
485 if (fire < 0) fire = 3;
486 Bar prefix = bar;
487 prefix.high = prefix.low = path[0];
488 for (int i = 1; i <= fire; ++i) {
489 prefix.high = std::max(prefix.high, path[i]);
490 prefix.low = std::min(prefix.low, path[i]);
491 }
492 prefix.close = fire_price;
493 return prefix;
494}
495
496// ab9714be pine_risk.cpp:256-292: sample the full bar H/L/C into every open
497// lot. Legacy runs this AFTER pending-order fills and BEFORE
498// process_margin_call, so a liquidation at the first extreme still owns the
499// rest of the bar. Native apply_excursion stops when the lot is closed, so
500// the adapter re-runs this walk at margin-call submit.
501void sample_open_trade_extremes(std::vector<PyramidEntry>& lots,
502 PositionSide side, int bar_index, const Bar& bar) {
503 if (side == PositionSide::FLAT || lots.empty()) return;
504 if (!std::isfinite(bar.high) || !std::isfinite(bar.low)
505 || !std::isfinite(bar.close)) {
506 return;
507 }
508 const bool is_long = (side == PositionSide::LONG);
509 for (auto& pe : lots) {
510 double pe_hi = bar.high;
511 double pe_lo = bar.low;
512 if (pe.entry_bar_index == bar_index) {
513 if (pe.skip_entry_bar_high) pe_hi = pe.price;
514 if (pe.skip_entry_bar_low) pe_lo = pe.price;
515 }
516 const double fav_px = is_long ? pe_hi : pe_lo;
517 const double adv_px = is_long ? pe_lo : pe_hi;
518 const double favorable = is_long ? (fav_px - pe.price) * pe.qty
519 : (pe.price - fav_px) * pe.qty;
520 const double adverse = is_long ? (pe.price - adv_px) * pe.qty
521 : (adv_px - pe.price) * pe.qty;
522 if (favorable > pe.max_runup) pe.max_runup = favorable;
523 if (adverse > pe.max_drawdown) pe.max_drawdown = adverse;
524 const double closing = is_long ? (bar.close - pe.price) * pe.qty
525 : (pe.price - bar.close) * pe.qty;
526 if (closing > pe.max_runup) pe.max_runup = closing;
527 const double closing_dd = -closing;
528 if (closing_dd > pe.max_drawdown) pe.max_drawdown = closing_dd;
529 }
530}
531
533 : cap(attachment) {
534 pending_view_.owner_ = this;
535}
536
542
543void PineExecutionAdapter::bind(NativeStrategyHost& host) noexcept { host_ = &host; }
544
545NativeStrategyHost& PineExecutionAdapter::require_host() const {
546 if (!host_) throw std::logic_error("Pine execution adapter is not bound to a native host");
547 return *host_;
548}
549
550OrderBirth PineExecutionAdapter::capture_order_birth() const {
551 const auto point = require_host().current_execution_point();
552 if (!point) return OrderBirth::direct_command(-1, require_host().native_decision_floor());
553 const int bar = point->decision.coordinate.interval_index;
554 const std::int64_t timestamp = point->decision.sub_bar_open_ms;
555 if (!coof_recalc_active_) return OrderBirth::chart_evaluation(bar, timestamp);
556
557 const bool magnified = point->decision.sub_count > 1;
558 const auto domain = magnified ? BirthCursorDomain::MagnifierTicks
560 const int count = magnified ? std::max(1, point->decision.sub_count) : 4;
561 int index = magnified ? point->decision.sub_index : 0;
562 if (!magnified) {
563 bool high_first = std::abs(coof_script_bar_.high - coof_script_bar_.open)
564 < std::abs(coof_script_bar_.open - coof_script_bar_.low);
565 if (const auto state = require_host().native_state(); state.spec) {
566 if (state.spec->path_order == NativePathOrder::HighFirst) high_first = true;
567 else if (state.spec->path_order == NativePathOrder::LowFirst) high_first = false;
568 }
569 switch (point->decision.coordinate.path_phase) {
570 case NativePathPhase::Open: index = 0; break;
571 case NativePathPhase::High: index = high_first ? 1 : 2; break;
572 case NativePathPhase::Low: index = high_first ? 2 : 1; break;
573 case NativePathPhase::Close: index = 3; break;
574 case NativePathPhase::None: index = 0; break;
575 }
576 }
577 index = std::max(0, std::min(index, count - 1));
578 const auto cursor = BirthCursor::point(domain, index, count);
579 const std::uint64_t ordinal = std::max<std::uint64_t>(1, last_applied_ordinal_);
580 return OrderBirth::fill_evaluation(bar, timestamp, cursor, point->price,
581 ordinal, ordinal, ordinal);
582}
583
584void PineExecutionAdapter::initialize_l4c_policy(PlacementSnapshot& snapshot,
585 native_order::RequestHandle handle) {
586 if (snapshot.birth.cause() == OrderBirthCause::Unattributed)
587 snapshot.birth = capture_order_birth();
588 const bool trailing = finite_positive(snapshot.exit_levels.trail_points)
589 || finite_positive(snapshot.exit_levels.trail_price)
590 || finite_positive(snapshot.exit_levels.trail_offset);
591 snapshot.birth_reach = compat::pine::select_historical_birth_reach(snapshot.birth, trailing);
592 if (coof_recalc_active_) {
593 snapshot.coof_cascade_seg_i = coof_context_.coordinate.interval_index;
594 snapshot.coof_cascade_inflight_fires = true;
595 }
596
597 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
598 || snapshot.family == PineOrderFamily::ExitStop
599 || snapshot.family == PineOrderFamily::ExitTrail;
600 if (!exit) return;
601
602 const exit_legs::Prices prices{snapshot.exit_levels.limit, snapshot.exit_levels.stop,
603 snapshot.exit_levels.trail_points, snapshot.exit_levels.trail_price,
604 snapshot.exit_levels.trail_offset, snapshot.exit_levels.profit_ticks,
605 snapshot.exit_levels.loss_ticks};
606 if (!snapshot.legs.target().incarnation && handle.incarnation != 0) {
607 snapshot.legs.set_prices(prices);
608 snapshot.legs.attach(handle.incarnation, std::max<std::int64_t>(0, snapshot.placement_cycle));
609 } else if (handle.incarnation != 0
610 && snapshot.legs.target().incarnation != handle.incarnation) {
611 snapshot.legs.fork(handle.incarnation, std::max<std::int64_t>(0, snapshot.placement_cycle));
612 snapshot.legs.set_prices(prices);
613 }
614
615 const auto physical = require_host().physical_position();
616 if (physical.signed_units == 0.0 || snapshot.projection_created_bar < 0) return;
617 const int direction = physical.signed_units > 0.0 ? 1 : -1;
618 const auto point = require_host().current_execution_point();
619 const Bar activation_bar = coof_script_bar_valid_ ? coof_script_bar_
620 : policy_script_bar_;
621 bool path_high_first = std::abs(activation_bar.high - activation_bar.open)
622 < std::abs(activation_bar.open - activation_bar.low);
623 if (const auto state = require_host().native_state(); state.spec) {
624 if (state.spec->path_order == NativePathOrder::HighFirst) path_high_first = true;
625 else if (state.spec->path_order == NativePathOrder::LowFirst) path_high_first = false;
626 }
627 int historical_point = 0;
628 double waypoint = activation_bar.open;
629 if (point) {
630 switch (point->decision.coordinate.path_phase) {
631 case NativePathPhase::Open:
632 historical_point = 0; waypoint = activation_bar.open; break;
633 case NativePathPhase::High:
634 historical_point = path_high_first ? 1 : 2; waypoint = activation_bar.high; break;
635 case NativePathPhase::Low:
636 historical_point = path_high_first ? 2 : 1; waypoint = activation_bar.low; break;
637 case NativePathPhase::Close:
638 historical_point = 3; waypoint = activation_bar.close; break;
639 case NativePathPhase::None:
640 historical_point = 0; waypoint = point->price; break;
641 }
642 }
643 const bool at_waypoint = point
644 && source_same_point(point->price, waypoint, staged_.syminfo.mintick);
645 const bool historical_segment = point && historical_point > 0 && !at_waypoint;
646 const int recalc_leg = historical_segment
647 ? std::max(0, historical_point - 1) : historical_point;
648
649 std::string_view first_lot_id;
650 std::uint64_t first_lot_incarnation = 0;
651 for (const auto& id : cohort_order_) {
652 const auto cohort = cohorts_by_id_.find(id);
653 if (cohort == cohorts_by_id_.end()) continue;
654 for (const auto& opening : cohort->second.opened) {
655 const auto units = cohort->second.live_units_by_origin.find(opening.incarnation);
656 if (units == cohort->second.live_units_by_origin.end()
657 || !(units->second > 0.0)) {
658 continue;
659 }
660 first_lot_id = id;
661 first_lot_incarnation = opening.incarnation;
662 break;
663 }
664 if (first_lot_incarnation != 0) break;
665 }
666
667 const double activation_quantity = std::isfinite(snapshot.projection_remaining_qty)
668 ? snapshot.projection_remaining_qty : snapshot.requested_qty;
669 const bool full_quantity = (!std::isfinite(snapshot.qty_percent)
670 || snapshot.qty_percent >= 100.0 - 1e-9)
671 && (!std::isfinite(activation_quantity)
672 || activation_quantity >= std::abs(physical.signed_units) - 1e-9);
673 compat::pine::ExitActivationContext context;
674 context.cycle = current_position_cycle_;
675 context.bar_index = snapshot.projection_created_bar;
676 context.position_open_bar = position_open_bar_index_;
677 context.direction = direction;
678 context.cursor_price = point ? point->price : snapshot.sizing.price;
679 context.fill_recalc = coof_recalc_active_;
680 context.scheduler = config_.calc_on_order_fills;
681 context.magnifier = point && point->decision.sub_count > 1;
682 context.process_on_close = config_.process_orders_on_close;
683 context.warmup = require_host().native_state().phase == NativeRunPhase::Warmup;
684 context.stream_idle = !stream_mode_;
685 context.after_first_open_fill = coof_recalc_active_ && !coof_first_open_
686 && point && point->decision.coordinate.path_phase == NativePathPhase::Open;
687 context.recalc_leg = recalc_leg;
688 context.historical_segment = historical_segment;
689 context.at_extreme = at_waypoint
690 && (historical_point == 1 || historical_point == 2);
691 context.historical_point = historical_point;
692 context.current_fill = coof_current_fill_seq_;
693 context.bar = activation_bar;
694 context.position_entry_count = static_cast<int>(physical.lot_count);
695 context.position_quantity = std::abs(physical.signed_units);
696 context.pyramiding = config_.pyramiding;
697 context.lot_count = physical.lot_count;
698 context.first_lot_id = first_lot_id;
699 context.first_lot_incarnation = first_lot_incarnation;
700 context.market_recalc_incarnation = coof_market_entry_recalc_incarnation_;
701 context.market_recalc_fill = coof_market_entry_recalc_fill_seq_;
702 context.pending_empty = live_handles_.empty() && pending_entries_.empty()
703 && pending_bracket_legs_.empty() && pending_same_bar_commands_.empty()
704 && pending_coof_requests_.empty();
705 context.slippage = config_.slippage;
706 context.pointvalue = staged_.syminfo.pointvalue;
707 context.account_fx = point ? active_staged_fx(point->decision.sub_bar_open_ms)
708 : staged_.account_fx;
709 context.fx_series_empty = staged_.account_fx_effective_from_ms.empty();
710 context.bar_path_high_first = path_high_first;
711 context.tick_high = source_bar_fill_tick(activation_bar.high, staged_.syminfo.mintick);
712 compat::pine::ExitActivationRequest request;
713 request.requested_trailing = trailing;
714 request.full_quantity = full_quantity;
715 request.from_fill = snapshot.birth.from_fill();
716 request.has_from_entry = !snapshot.from_entry.empty();
717 request.birth_reach = snapshot.birth_reach;
718 request.from_entry = snapshot.from_entry;
719 request.oca_name = snapshot.oca_name;
720 request.quantity = activation_quantity;
721 snapshot.exit_activation = compat::pine::select_exit_activation(
722 request, snapshot.exit_levels.stop, snapshot.exit_levels.limit, context);
723 if (snapshot.exit_activation.evidence()) {
724 snapshot.leg_activation.bind(snapshot.exit_activation.resolve(
725 current_position_cycle_, position_open_bar_index_));
726 }
727
728 // ab9714be reservation_expansion.cpp:7-27 and
729 // pine_strategy_commands.cpp:1891-1924: only an ordinary same-bar MARKET
730 // population can grow a global full POOC exit. The immutable source birth
731 // and placement facts select that population; the native cohort remains
732 // the execution authority.
733 const bool full_global = config_.process_orders_on_close && snapshot.from_entry.empty()
734 && !std::isfinite(snapshot.requested_qty)
735 && (!std::isfinite(snapshot.qty_percent) || snapshot.qty_percent >= 100.0)
736 && physical.signed_units != 0.0;
737 const bool side = physical.signed_units > 0.0;
738 const auto is_unpriced_market_add = [](const PlacementSnapshot& candidate) {
739 return !finite_positive(candidate.exit_levels.limit)
740 && !finite_positive(candidate.exit_levels.stop)
741 && !finite_positive(candidate.exit_levels.trail_offset)
742 && !finite_positive(candidate.exit_levels.trail_price);
743 };
744 const auto is_entry_like = [](const PlacementSnapshot& candidate) {
745 return candidate.opening && (candidate.family == PineOrderFamily::Entry
746 || candidate.family == PineOrderFamily::Order);
747 };
748 struct CandidateRef {
749 PlacementSnapshot* snapshot = nullptr;
750 std::uint64_t incarnation = 0;
751 };
752 std::vector<compat::pine::ReservationGrowthCandidate> candidates;
753 std::vector<CandidateRef> candidate_refs;
754 const auto collect = [&](PlacementSnapshot& candidate,
755 std::uint64_t incarnation) {
756 if (!is_entry_like(candidate)) return;
757 compat::pine::ReservationGrowthCandidate fact;
758 fact.incarnation = incarnation;
759 fact.source_id = candidate.source_id;
760 fact.market_entry = candidate.family == PineOrderFamily::Entry
761 && is_unpriced_market_add(candidate);
762 fact.from_fill = candidate.birth.from_fill();
763 fact.at_entry_capacity = candidate.projection_over_pyramiding;
764 fact.is_long = candidate.is_long;
765 fact.created_position_side =
766 static_cast<PositionSide>(candidate.projection_position_side);
767 fact.created_bar = candidate.projection_created_bar;
768 candidates.push_back(std::move(fact));
769 candidate_refs.push_back({&candidate, incarnation});
770 };
771 for (auto& pending : pending_entries_) collect(pending.snapshot, 0);
772 for (auto& pending : pending_same_bar_commands_) collect(pending.snapshot, 0);
773 for (const auto& live : live_handles_) {
774 const auto existing = placement_.find(live.incarnation);
775 if (existing != placement_.end()) collect(existing->second, live.incarnation);
776 }
777 const double percent = std::isfinite(snapshot.qty_percent)
778 ? snapshot.qty_percent : 100.0;
779 const auto selected = full_global
781 candidates, snapshot.from_entry, config_.process_orders_on_close,
782 physical.signed_units == 0.0, percent,
783 snapshot.projection_created_bar,
785 : std::vector<std::uint64_t>{};
786 const bool partial = percent < 100.0 - 1e-9
787 || (std::isfinite(snapshot.projection_remaining_qty)
788 && snapshot.projection_remaining_qty
789 < std::abs(physical.signed_units) - 1e-9);
790 const bool qualified_adds = compat::pine::admits_reservation_expansion(
791 selected, partial, snapshot.projection_remaining_qty,
792 std::abs(physical.signed_units));
793 snapshot.pooc_global_full_exit_dynamic_qty = qualified_adds;
794 snapshot.pooc_global_full_exit_tracks_bound_adds = qualified_adds;
795 if (qualified_adds && handle.incarnation != 0) {
796 try {
797 snapshot.reservation_expansion.capture(handle.incarnation, current_position_cycle_,
798 direction > 0 ? PositionSide::LONG
800 std::abs(physical.signed_units));
801 } catch (const std::invalid_argument&) {
802 // A replacement carries its existing immutable capture.
803 }
804 for (const auto& selected_source : candidate_refs) {
805 auto* candidate = selected_source.snapshot;
806 if (!candidate) continue;
807 candidate->reservation_growth_owner_incarnation = handle.incarnation;
808 if (selected_source.incarnation != 0
809 && selected_source.incarnation != handle.incarnation) {
810 try {
811 candidate->reservation_growth_source.assign_capture(
812 selected_source.incarnation, handle.incarnation);
813 candidate->pooc_global_full_exit_bound_add = true;
814 } catch (const std::invalid_argument&) {
815 // This exact source already owns the capture receipt.
816 }
817 }
818 }
819 }
820}
821
822void PineExecutionAdapter::update_l4c_priority() {
823 std::vector<compat::pine::OrderPriorityCandidate> candidates;
824 candidates.reserve(live_handles_.size());
825 for (const auto& handle : live_handles_) {
826 const auto found = placement_.find(handle.incarnation);
827 if (found == placement_.end()) continue;
828 const auto& snapshot = found->second;
829 compat::pine::OrderPriorityCandidate candidate;
830 candidate.handle = handle;
831 candidate.kind = snapshot.family == PineOrderFamily::Entry
833 : ((snapshot.family == PineOrderFamily::ExitLimit
834 || snapshot.family == PineOrderFamily::ExitStop
835 || snapshot.family == PineOrderFamily::ExitTrail)
838 candidate.id = snapshot.source_id;
839 candidate.from_entry = snapshot.from_entry;
840 candidate.created_bar = snapshot.projection_created_bar;
841 candidate.source_sequence = snapshot.source_sequence;
842 candidate.predecessor = snapshot.projection_predecessor;
843 candidate.recreated_after_named_cancelled =
844 snapshot.recreated_after_named_cancelled_entry_incarnation;
845 candidate.named_cancel_surviving_exit = snapshot.named_cancel_surviving_exit_incarnation;
846 candidate.created_flat = snapshot.projection_position_side
847 == static_cast<std::int32_t>(PositionSide::FLAT);
848 candidate.birth_from_fill = snapshot.birth.from_fill();
849 candidate.prior_close = snapshot.projection_after_close;
850 candidate.at_entry_capacity = snapshot.projection_over_pyramiding;
851 candidate.stop_limit_activated = snapshot.stop_limit_activated;
852 candidate.default_quantity = !std::isfinite(snapshot.requested_qty);
853 candidate.requested_qty = snapshot.requested_qty;
854 candidate.qty_percent = snapshot.qty_percent;
855 candidate.stop = snapshot.exit_levels.stop;
856 candidate.limit = snapshot.exit_levels.limit;
857 candidate.trail_points = snapshot.exit_levels.trail_points;
858 candidate.trail_price = snapshot.exit_levels.trail_price;
859 candidate.trail_offset = snapshot.exit_levels.trail_offset;
860 candidate.profit_ticks = snapshot.exit_levels.profit_ticks;
861 candidate.loss_ticks = snapshot.exit_levels.loss_ticks;
862 candidate.oca_name = snapshot.oca_name;
863 candidate.oca_type = snapshot.oca_type;
864 candidates.push_back(std::move(candidate));
865 }
866 const auto point = require_host().current_execution_point();
867 const compat::pine::OrderPriorityContext context{
868 require_host().physical_position().signed_units == 0.0,
869 config_.process_orders_on_close, config_.calc_on_order_fills,
870 coof_recalc_active_, point && point->decision.sub_count > 1, false, true,
871 point ? point->decision.coordinate.interval_index : -1};
872 const auto decision = priority.select(context, candidates);
873 if (!decision) return;
874 const auto rank = [&](const native_order::RequestHandle& handle) {
875 if (handle == decision->parent) return 0;
876 if (handle == decision->child) return 1;
877 return 2;
878 };
879 std::stable_sort(live_handles_.begin(), live_handles_.end(),
880 [&](const auto& left, const auto& right) { return rank(left) < rank(right); });
881}
882
883void PineExecutionAdapter::update_l4c_lifecycle(
884 const native_order::ExecutionAppliedEvent& event,
885 const NativeDecisionContext& context) {
886 const auto found = placement_.find(event.handle().incarnation);
887 if (found == placement_.end()) return;
888 auto& snapshot = found->second;
889 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
890 || snapshot.family == PineOrderFamily::ExitStop
891 || snapshot.family == PineOrderFamily::ExitTrail;
892 if (!exit || !snapshot.legs.target().incarnation) return;
893 const auto domain = context.sub_count > 1
894 ? (config_.calc_on_order_fills ? exit_legs::Domain::MagnifierFillRecalc
895 : exit_legs::Domain::Magnifier)
896 : (config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
897 : exit_legs::Domain::Ordinary);
898 const exit_legs::Frame cause{event.ordinal, context.coordinate.interval_index,
899 domain, exit_legs::Phase::AfterMargin};
900 if (const auto completion = compat::pine::select_exit_completion(snapshot.legs, cause)) {
901 const exit_legs::Action action{snapshot.legs.target(), snapshot.legs.revision(),
902 cause, *completion};
903 (void)snapshot.legs.apply(snapshot.legs.target(), action);
904 }
905 if (require_host().physical_position().signed_units == 0.0) snapshot.leg_activation.unbind();
906}
907
908bool PineExecutionAdapter::is_declined_market_reversal(
909 const native_order::MatchRejectedEvent& event) const noexcept {
910 if (event.reason != native_order::MatchRejectReason::HostPrecommit
911 && event.reason != native_order::MatchRejectReason::InitialMargin) {
912 return false;
913 }
914 const auto found = placement_.find(event.handle().incarnation);
915 if (found == placement_.end()) return false;
916 const auto& source = found->second;
917 const auto prior = static_cast<PositionSide>(source.projection_position_side);
918 return source.opening && source.family == PineOrderFamily::Entry
919 && source.reverse_to && std::holds_alternative<native_order::Market>(event.request().trigger)
920 && prior != PositionSide::FLAT
921 && ((prior == PositionSide::LONG) != source.is_long);
922}
923
924bool PineExecutionAdapter::follows_same_bar_declined_reversal(
925 const PlacementSnapshot& exit, const NativePrecommitView& view) const {
926 if (exit.from_entry.empty() || !exit.legs.target().incarnation) return false;
927 const auto rows = require_host().native_events(receipt_cursor_);
928 for (const auto& row : rows) {
929 if (!row.command) continue;
930 const auto* rejected = std::get_if<native_order::MatchRejectedEvent>(&*row.command);
931 if (!rejected || rejected->cursor.point.interval_index != view.cursor.point.interval_index
932 || !is_declined_market_reversal(*rejected)) {
933 continue;
934 }
935 const auto reversal = placement_.find(rejected->handle().incarnation);
936 if (reversal != placement_.end()
937 && bracket_belongs_to_reversal(exit, reversal->second)) {
938 return true;
939 }
940 }
941 return false;
942}
943
944bool PineExecutionAdapter::bracket_belongs_to_reversal(
945 const PlacementSnapshot& bracket,
946 const PlacementSnapshot& reversal) const noexcept {
947 if (bracket.projection_position_side == reversal.projection_position_side)
948 return true;
949 const auto cohort = cohorts_by_id_.find(bracket.from_entry);
950 if (cohort == cohorts_by_id_.end()) return false;
951 const bool prior_long = reversal.projection_position_side
952 == static_cast<std::int32_t>(PositionSide::LONG);
953 for (const auto& origin : cohort->second.origins) {
954 const auto opening = placement_.find(origin.incarnation);
955 if (opening != placement_.end() && opening->second.opening
956 && opening->second.is_long == prior_long) {
957 return true;
958 }
959 }
960 return false;
961}
962
963void PineExecutionAdapter::suspend_declined_reversal_brackets(
964 const native_order::MatchRejectedEvent& event) {
965 if (!is_declined_market_reversal(event)) return;
966 const auto reversal = placement_.find(event.handle().incarnation);
967 if (reversal == placement_.end()) return;
968 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
969 : exit_legs::Domain::Ordinary;
970 const exit_legs::Frame cause{event.ordinal, event.cursor.point.interval_index,
971 domain, exit_legs::Phase::Observation};
972 suspend_brackets_for_reversal(reversal->second, cause,
973 policy_script_bar_valid_ ? policy_script_bar_.open : reversal->second.sizing.mark);
974}
975
976void PineExecutionAdapter::suspend_brackets_for_reversal(
977 const PlacementSnapshot& reversal, const exit_legs::Frame& cause,
978 double open_price) {
979 const int direction = reversal.projection_position_side
980 == static_cast<std::int32_t>(PositionSide::LONG) ? 1 : -1;
981 // Off the reversal's side, bracket_belongs_to_reversal() answers from a
982 // walk of the bracket's from_entry origins, which this loop never changes
983 // (it only rewrites leg lifecycles): memoise that answer per from_entry
984 // instead of repeating the walk for every retained exit row.
985 std::unordered_map<SourceId, bool> cross_side_by_entry;
986 const auto belongs = [&](const PlacementSnapshot& bracket) {
987 if (bracket.projection_position_side == reversal.projection_position_side)
988 return true;
989 const auto cached = cross_side_by_entry.find(bracket.from_entry);
990 if (cached != cross_side_by_entry.end()) return cached->second;
991 const bool value = bracket_belongs_to_reversal(bracket, reversal);
992 cross_side_by_entry.emplace(bracket.from_entry, value);
993 return value;
994 };
995 for (auto row : placement_) {
996 auto& candidate = row.second;
997 const bool exit = candidate.family == PineOrderFamily::ExitLimit
998 || candidate.family == PineOrderFamily::ExitStop
999 || candidate.family == PineOrderFamily::ExitTrail;
1000 if (!exit || candidate.from_entry.empty() || candidate.legs.dormant()
1001 || !candidate.legs.target().incarnation
1002 || !belongs(candidate)) {
1003 continue;
1004 }
1005 const compat::pine::ExitSuspensionContext context{
1006 cause, direction, require_host().position_avg_price(), staged_.syminfo.mintick,
1007 open_price,
1008 candidate.legs.trail_best(), false, true};
1009 const auto operation = compat::pine::select_exit_suspension(candidate.legs, context);
1010 if (!operation) continue;
1011 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
1012 cause, *operation};
1013 (void)candidate.legs.apply(candidate.legs.target(), action);
1014 }
1015}
1016
1017void PineExecutionAdapter::suspend_coof_declined_reversal_at_open(
1018 const Bar& bar, const NativeDecisionContext& context) {
1019 const auto physical = require_host().physical_position();
1020 if (physical.signed_units == 0.0 || !finite_positive(bar.open)) return;
1021 const auto handles = live_handles_;
1022 for (const auto& handle : handles) {
1023 const auto found = placement_.find(handle.incarnation);
1024 if (found == placement_.end()) continue;
1025 const auto& reversal = found->second;
1026 const bool opposite = (physical.signed_units > 0.0) != reversal.is_long;
1027 const bool market_entry = !finite_positive(reversal.exit_levels.limit)
1028 && !finite_positive(reversal.exit_levels.stop)
1029 && !finite_positive(reversal.exit_levels.trail_offset)
1030 && !finite_positive(reversal.exit_levels.trail_price);
1031 if (!reversal.opening || reversal.family != PineOrderFamily::Entry
1032 || !reversal.reverse_to || !opposite || !market_entry) {
1033 continue;
1034 }
1035 const double units = finite_positive(reversal.sizing.frozen_units)
1036 ? reversal.sizing.frozen_units : reversal.requested_qty;
1037 const double margin = reversal.is_long ? config_.margin_long : config_.margin_short;
1038 const double fx = active_staged_fx(context.sub_bar_open_ms);
1039 const double required = units * bar.open * staged_.syminfo.pointvalue
1040 * fx * margin / 100.0;
1041 const double equity = std::isfinite(reversal.sizing.equity)
1042 ? reversal.sizing.equity : require_host().native_marked_equity(bar.open);
1043 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
1044 if (finite_positive(units) && finite_positive(margin) && std::isfinite(required)
1045 && std::isfinite(equity) && required > equity + guard) {
1046 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
1047 : exit_legs::Domain::Ordinary;
1048 const exit_legs::Frame cause{context.coordinate.ordinal,
1049 context.coordinate.interval_index, domain,
1050 exit_legs::Phase::Observation};
1051 suspend_brackets_for_reversal(reversal, cause, bar.open);
1052 }
1053 }
1054}
1055
1056void PineExecutionAdapter::hold_reversal_pair_brackets(const SourceId& from_entry) {
1057 const auto point = require_host().current_execution_point();
1058 if (!point) return;
1059 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
1060 : exit_legs::Domain::Ordinary;
1061 const exit_legs::Frame cause{point->decision.coordinate.ordinal,
1062 point->decision.coordinate.interval_index, domain, exit_legs::Phase::Observation};
1063 for (auto row : placement_) {
1064 auto& candidate = row.second;
1065 const bool exit = candidate.family == PineOrderFamily::ExitLimit
1066 || candidate.family == PineOrderFamily::ExitStop
1067 || candidate.family == PineOrderFamily::ExitTrail;
1068 if (!exit || candidate.from_entry != from_entry || candidate.legs.dormant()
1069 || !candidate.legs.target().incarnation) {
1070 continue;
1071 }
1072 const auto operation = compat::pine::select_pair_hold(candidate.legs, cause);
1073 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
1074 cause, operation};
1075 (void)candidate.legs.apply(candidate.legs.target(), action);
1076 }
1077}
1078
1079void PineExecutionAdapter::purge_brackets_after_applied_reversal(
1080 const PlacementSnapshot& reversal) {
1081 const bool prior_long = reversal.projection_position_side
1082 == static_cast<std::int32_t>(PositionSide::LONG);
1083 std::vector<native_order::RequestHandle> stale;
1084 for (const auto& handle : live_handles_) {
1085 const auto found = placement_.find(handle.incarnation);
1086 if (found == placement_.end()) continue;
1087 const auto& candidate = found->second;
1088 const bool exit = candidate.family == PineOrderFamily::ExitLimit
1089 || candidate.family == PineOrderFamily::ExitStop
1090 || candidate.family == PineOrderFamily::ExitTrail;
1091 const bool stale_margin = candidate.family == PineOrderFamily::Margin
1092 && candidate.projection_position_side == reversal.projection_position_side;
1093 bool targets_prior = false;
1094 if (const auto cohort = cohorts_by_id_.find(candidate.from_entry);
1095 cohort != cohorts_by_id_.end()) {
1096 for (const auto& origin : cohort->second.origins) {
1097 const auto opening = placement_.find(origin.incarnation);
1098 if (opening != placement_.end() && opening->second.opening
1099 && opening->second.is_long == prior_long) {
1100 targets_prior = true;
1101 break;
1102 }
1103 }
1104 }
1105 if ((exit && targets_prior) || stale_margin) stale.push_back(handle);
1106 }
1107 for (const auto& handle : stale) {
1108 (void)require_host().cancel(handle);
1109 retire(handle);
1110 }
1111 for (auto& cohort : cohorts_by_id_) {
1112 bool prior_side = false;
1113 for (const auto& origin : cohort.second.origins) {
1114 const auto opening = placement_.find(origin.incarnation);
1115 if (opening != placement_.end() && opening->second.opening
1116 && opening->second.is_long == prior_long) {
1117 prior_side = true;
1118 break;
1119 }
1120 }
1121 if (prior_side) {
1122 cohort.second.opened.clear();
1123 cohort.second.live_units_by_origin.clear();
1124 }
1125 }
1126}
1127
1128void PineExecutionAdapter::revive_brackets_after_margin(
1129 const native_order::ExecutionAppliedEvent& event,
1130 const NativeDecisionContext& context) {
1131 const auto physical = require_host().physical_position();
1132 if (physical.signed_units == 0.0) return;
1133 // A same-path declined reversal and its dormant bracket can both precede
1134 // this margin event before the next bar-open receipt sweep. Apply the
1135 // already-recorded rejection at this Applied boundary so the revival sees
1136 // the same lifecycle state as the legacy margin callback.
1137 for (const auto& row : require_host().native_events(receipt_cursor_)) {
1138 if (!row.command) continue;
1139 const auto* rejected = std::get_if<native_order::MatchRejectedEvent>(&*row.command);
1140 if (rejected
1141 && rejected->cursor.point.interval_index == context.coordinate.interval_index
1142 && is_declined_market_reversal(*rejected)) {
1143 suspend_declined_reversal_brackets(*rejected);
1144 }
1145 }
1146 const auto domain = context.sub_count > 1
1147 ? (config_.calc_on_order_fills ? exit_legs::Domain::MagnifierFillRecalc
1148 : exit_legs::Domain::Magnifier)
1149 : (config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
1150 : exit_legs::Domain::Ordinary);
1151 const exit_legs::Frame cause{event.ordinal, context.coordinate.interval_index,
1152 domain, exit_legs::Phase::AfterMargin};
1153 std::optional<PlacementSnapshot> marketable;
1154 native_order::RequestHandle marketable_handle{};
1155 for (auto row : placement_) {
1156 // ab9714be pine_orders.cpp:597-608: leg ownership is position-cycle
1157 // scoped, so only a bracket bound to the cycle this margin call is
1158 // settling may be revived; one parked by an earlier cycle stays
1159 // dormant until that cycle re-arms it.
1160 if (row.second.placement_cycle != current_position_cycle_) continue;
1161 auto& candidate = row.second;
1162 const bool exit = candidate.family == PineOrderFamily::ExitLimit
1163 || candidate.family == PineOrderFamily::ExitStop
1164 || candidate.family == PineOrderFamily::ExitTrail;
1165 if (!exit || !candidate.legs.dormant() || candidate.from_entry.empty()
1166 || !(cohort_exposure_for(candidate.from_entry) > 0.0)
1167 || !candidate.legs.target().incarnation) {
1168 continue;
1169 }
1170 // A same-id exit re-issued after the slice REPLACED this bracket: the
1171 // successor carries the cycle's legs, and reviving the superseded parent
1172 // here would fire the finished cycle's already-touched level against the
1173 // new lot (ab9714be pine_fills.cpp:7669-7673).
1174 const auto inc = row.first;
1175 const auto target_inc = candidate.legs.target().incarnation;
1176 bool superseded = false;
1177 for (const auto other : placement_) {
1178 if (other.second.placement_cycle == current_position_cycle_
1179 && other.second.projection_predecessor != 0
1180 && (other.second.projection_predecessor == inc
1181 || other.second.projection_predecessor == target_inc)) {
1182 superseded = true;
1183 break;
1184 }
1185 // The owner keeps ONE pending exit per (id, from_entry): a later
1186 // bar's same-id strategy.exit re-priced it in place, so the older
1187 // stop is gone and only that re-issue is a revival candidate
1188 // (ab9714be pine_fills.cpp:2069-2094 scans the live pending_orders_
1189 // only). A re-issue made over a dormant predecessor carries no
1190 // projection link, so match it by identity: same leg family, a
1191 // later script bar and a different stop. A same-bar re-issue
1192 // still revives the originally armed stop (ab9714be
1193 // pine_fills.cpp:2088-2094); a same-level re-materialization
1194 // changes nothing and keeps the predecessor.
1195 const auto& peer = other.second;
1196 if (other.first > inc && peer.placement_cycle == current_position_cycle_
1197 && peer.family == candidate.family
1198 && peer.source_id == candidate.source_id
1199 && peer.from_entry == candidate.from_entry
1200 && peer.placement_script_open_ms > candidate.placement_script_open_ms
1201 && !same_double_bits(peer.exit_levels.stop, candidate.exit_levels.stop)) {
1202 superseded = true;
1203 break;
1204 }
1205 }
1206 if (superseded) continue;
1207 const double revive_stop = compat::pine::select_margin_revival_stop(candidate.legs);
1208 const exit_legs::Action restore{candidate.legs.target(), candidate.legs.revision(),
1209 cause, exit_legs::Restore{{exit_legs::Leg::Stop, exit_legs::Leg::Limit,
1210 exit_legs::Leg::Trail}}};
1211 const auto restored = candidate.legs.apply(candidate.legs.target(), restore);
1212 if (restored != exit_legs::Result::Applied
1213 && restored != exit_legs::Result::Replay) {
1214 continue;
1215 }
1216 candidate.restored_after_margin = true;
1217 const double held = std::abs(physical.signed_units);
1218 // Full coverage is compared with a tolerance: the slice has already been
1219 // split off the physical lot, so the surviving quantity sits below the
1220 // pre-slice request (ab9714be spells full-position coverage as
1221 // `qty - kQtyEpsilon`, pine_fills.cpp:1618).
1222 const bool full = !std::isfinite(candidate.requested_qty)
1223 ? (!std::isfinite(candidate.qty_percent) || candidate.qty_percent >= 100.0 - 1e-5)
1224 : candidate.requested_qty >= held - 1e-9;
1225 const bool ready = !candidate.leg_activation.bounds()
1226 || candidate.leg_activation.stop_ready(
1227 current_position_cycle_, context.coordinate.interval_index);
1228 const double executable_stop = finite_positive(candidate.exit_levels.stop)
1229 ? candidate.exit_levels.stop : revive_stop;
1230 const bool reaches = std::isfinite(executable_stop)
1231 && (physical.signed_units < 0.0
1232 ? executable_stop <= event.resolved_price
1233 : executable_stop >= event.resolved_price);
1234 if (!marketable && full && ready && reaches) {
1235 marketable = candidate;
1236 marketable_handle = native_order::RequestHandle{
1237 event.handle().run, candidate.legs.target().incarnation};
1238 }
1239 }
1240 if (!marketable) return;
1241 if (marketable_handle.incarnation) {
1242 (void)require_host().cancel(marketable_handle);
1243 retire(marketable_handle);
1244 }
1245 native_order::Request request;
1246 request.intent = native_order::Flatten{};
1247 request.label = marketable->source_id;
1248 request.comment = marketable->comment;
1249 PlacementSnapshot snapshot = *marketable;
1250 snapshot.forced_execution_price = event.resolved_price;
1251 snapshot.requested_qty = std::numeric_limits<double>::quiet_NaN();
1252 snapshot.qty_percent = 100.0;
1253 snapshot.immediately = true;
1254 snapshot.legs = {};
1255 snapshot.leg_activation = {};
1256 snapshot.exit_activation = {};
1257 const auto accepted = submit_or_replace(
1258 std::move(request), std::move(snapshot), false,
1259 "__margin_revival__" + std::to_string(marketable->legs.target().incarnation));
1260 if (accepted) {
1261 (void)require_host().execute_current(
1262 {*accepted, NativeCurrentPriceRule::NearestTick});
1263 }
1264}
1265
1267 admission_journal.reset();
1268 cohorts_by_id_.clear();
1269 cohort_order_.clear();
1270 placement_.clear();
1271 live_by_source_key_.clear();
1272 bracket_families_.clear();
1273 pending_bracket_legs_.clear();
1274 pending_entries_.clear();
1275 delayed_market_orders_.clear();
1276 deferred_open_marketable_sells_.clear();
1277 throttled_reopen_rearm_.clear();
1278 entry_openings_interval_index_ = -1;
1279 entry_openings_this_interval_ = 0;
1280 pending_same_bar_commands_.clear();
1281 source_shadow_pending_.clear();
1282 pending_same_bar_close_qty_ = 0.0;
1283 pending_relative_exits_.clear();
1284 anchored_relative_legs_.clear();
1285 anchored_relative_stats_ = {};
1286 anchored_cohort_sequence_ = 0;
1287 pending_coof_requests_.clear();
1288 pending_margin_revivals_.clear();
1289 live_handles_.clear();
1290 first_open_newborns_.clear();
1291 dropped_close_receipts_.clear();
1292 open_entry_fees_.clear();
1293 trade_exit_phase_.clear();
1294 current_debited_applied_ordinals_.clear();
1295 intraday_loss_relabel_ordinals_.clear();
1296 consumed_partial_exit_cycles_.clear();
1297 bracket_shadowed_openings_.clear();
1298 named_entry_cancel_tokens_.clear();
1299 close_logical_units_.clear();
1300 close_reserved_units_.clear();
1301 close_first_units_.clear();
1302 close_callsite_reserved_units_.clear();
1303 close_callsite_first_units_.clear();
1304 close_batch_callsites_.clear();
1305 close_batch_bar_ = -1;
1306 close_batch_queue_sequence_ = 0;
1307 close_batch_pending_debt_ = 0.0;
1308 close_batch_admitted_total_ = 0.0;
1309 receipt_cursor_ = 0;
1310 last_applied_ordinal_ = 0;
1311 terminal_receipt_cursor_ = 0;
1312 materializing_relative_ = false;
1313 current_position_cycle_ = 0;
1314 current_position_sign_ = 0;
1315 next_sequential_group_ = 0;
1316 source_batch_mutated_ = false;
1317 coof_recalc_active_ = false;
1318 coof_first_open_ = false;
1319 coof_market_entry_recalc_incarnation_ = 0;
1320 coof_market_entry_recalc_fill_seq_ = 0;
1321 coof_current_fill_seq_ = 0;
1322 coof_fill_cursor_t_ = kNaN;
1323 coof_context_ = {};
1324 coof_script_bar_ = {};
1325 coof_script_bar_valid_ = false;
1326 pooc_close_basis_by_script_bar_.clear();
1327 pooc_open_basis_ = 0.0;
1328 pooc_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
1329 close_all_pending_script_bar_ = std::numeric_limits<std::int64_t>::min();
1330 last_fx_rate_ = kNaN;
1331 position_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
1332 position_open_epoch_ = 0;
1333 position_open_bar_index_ = -1;
1334 position_open_phase_ = NativePathPhase::None;
1335 position_open_priced_ = false;
1336 last_margin_call_script_bar_ = std::numeric_limits<std::int64_t>::min();
1337 kernel_margin_path_point_ = std::numeric_limits<std::uint64_t>::max();
1338 kernel_margin_resize_point_ = std::numeric_limits<std::uint64_t>::max();
1339 pooc_close_checkpoint_deferred_ms_ = std::numeric_limits<std::int64_t>::min();
1340 signal_close_mc_event_bar_ = -1;
1341 signal_close_mc_position_cycle_ = 0;
1342 signal_close_mc_entry_incarnation_ = 0;
1343 signal_close_mc_fill_seq_ = 0;
1344 signal_close_mc_before_qty_ = kNaN;
1345 signal_close_mc_remaining_qty_ = kNaN;
1346 last_margin_call_event_ordinal_ = 0;
1347 last_margin_call_entry_incarnation_ = 0;
1348 last_margin_call_position_cycle_ = 0;
1349 last_margin_call_at_script_close_ = false;
1350 last_margin_call_closed_units_ = 0.0;
1351 last_margin_call_remaining_units_ = 0.0;
1352 risk_coof_direct_script_bar_ = std::numeric_limits<std::int64_t>::min();
1353 cap_latest_fill_ = 0;
1354 day_ledger_ = {};
1355 risk_.observed_peak_equity = kNaN;
1356 risk_.observed_max_drawdown = 0.0;
1357 risk_.intraday_block_day = std::numeric_limits<std::int64_t>::min();
1358 risk_.intraday_cancel_pending = false;
1359 policy_script_bar_ = {};
1360 policy_script_bar_valid_ = false;
1361 market_pyramid_adds_.clear();
1362 trail_state_at_open_.clear();
1363 stream_mode_ = false;
1364 bar_magnifier_ = false;
1365 path_order_ = NativePathOrder::Auto;
1366 short_seed_ = {};
1367 pending_short_seed_ = {};
1368 short_seed_long_candidate_ = {};
1369 last_bar_dual_entry_path_ = 0;
1370 last_bar_dual_entry_script_open_ms_ =
1371 std::numeric_limits<std::int64_t>::min();
1372 source_sequence_ = 0;
1373 command_ordinal_ = 0;
1374 broker_open_epoch_ = 0;
1375 last_broker_open_ms_ = std::numeric_limits<std::int64_t>::min();
1376 source_command_sequence_ = 0;
1377 entry_attempt_bar_ = -1;
1378 entry_attempts_on_bar_ = 0;
1379 cap.reset_run();
1380 refresh_pending_view();
1381}
1382
1383void PineExecutionAdapter::set_configuration(const PineStrategyConfig& config) noexcept { config_ = config; }
1385void PineExecutionAdapter::set_begin_mode(bool is_stream, bool bar_magnifier) noexcept {
1386 stream_mode_ = is_stream;
1387 bar_magnifier_ = bar_magnifier;
1388}
1390 path_order_ = path_order;
1391}
1392void PineExecutionAdapter::set_receipt_high_water_readers(
1393 ReceiptHighWaterReader event_reader, ReceiptHighWaterReader terminal_reader) noexcept {
1394 event_high_water_reader_ = event_reader;
1395 terminal_receipt_high_water_reader_ = terminal_reader;
1396}
1397
1399 const StagedConfiguration& staged,
1400 const NativeBeginArgs& args,
1401 NativePathOrder path_order) const {
1402 NativeRunSpec spec;
1403 if (run_counter_ == std::numeric_limits<std::uint64_t>::max()) {
1404 throw std::overflow_error("Pine native run counter exhausted");
1405 }
1406 const std::string timezone = staged.syminfo.timezone.empty() ? "UTC" : staged.syminfo.timezone;
1407 const std::string session = staged.syminfo.session.empty() ? "24x7" : staged.syminfo.session;
1408 // Bind a stable identity only to staged session/timezone facts; labels and
1409 // source ids never cross this generic identity boundary.
1410 spec.identity = {session + "@" + timezone, ++run_counter_};
1411
1412 // A11: sub-two-bar public starts retain the explicit undetected state and
1413 // intentionally leave both string fields empty.
1414 // A historical one-bar run has no detectable timeframe, but a stream
1415 // begin carries an explicit provider timeframe even when its warmup has
1416 // only one bar. Preserve that public stream contract rather than
1417 // erasing the caller's labels into the undetected batch shape.
1418 // The simple begin has no timeframe argument to preserve when fewer than
1419 // two bars cannot establish one. A TF-aware public begin is explicit even
1420 // for one historical bar, and follows the legacy run_tf_impl path.
1421 spec.timeframe_undetected = args.n < 2 && !args.is_stream
1422 && args.input_tf.empty() && args.script_tf.empty();
1423 if (!spec.timeframe_undetected) {
1424 std::string effective_input = args.input_tf;
1425 if (effective_input.empty() && args.n >= 2 && args.bars != nullptr) {
1426 effective_input = detect_timeframe(args.bars, args.n);
1427 }
1428 spec.input_tf = std::move(effective_input);
1429 spec.script_tf = args.script_tf.empty() ? spec.input_tf : args.script_tf;
1430 // Legacy run_tf_impl used tf_ratio(), for which spelling aliases such
1431 // as D and 1D are passthrough. When the input spelling was detected
1432 // (and therefore was not an explicit public argument), retain the
1433 // caller's equal-duration script spelling so the generic calendar
1434 // also takes its raw-label passthrough path.
1435 if (args.input_tf.empty() && !spec.input_tf.empty() && !spec.script_tf.empty()
1436 && tf_ratio(spec.input_tf, spec.script_tf) == 1) {
1437 spec.input_tf = spec.script_tf;
1438 }
1439 }
1440 spec.ticker = staged.syminfo.ticker;
1441 spec.tickerid = staged.syminfo.tickerid;
1442 spec.type = staged.syminfo.type;
1443 spec.currency = staged.syminfo.currency;
1444 spec.basecurrency = staged.syminfo.basecurrency;
1445 spec.description = staged.syminfo.description;
1446 spec.volumetype = staged.syminfo.volumetype;
1447 spec.timezone = timezone;
1448 spec.session = session;
1449 spec.chart_timezone = staged.chart_timezone;
1450 spec.initial_capital = config.initial_capital;
1451 spec.point_value = staged.syminfo.pointvalue;
1452 spec.account_fx = staged.account_fx;
1453 spec.price_tick = staged.syminfo.mintick;
1454 spec.slippage_ticks = config.slippage < 0 ? 0U : static_cast<std::uint32_t>(config.slippage);
1455 spec.fee_kind = fee_kind_for(config.commission_type);
1456 spec.fee_value = config.commission_value;
1457 spec.quantity_grid = staged.quantity_grid;
1458 // A13: source hosts opt into the generic tolerant batch ingress.
1459 // Native-only hosts retain the strict Canonical/None defaults.
1460 spec.slot_label_policy = NativeSlotLabelPolicy::FeedTolerant;
1461 spec.legacy_tolerance = NativeFeedTolerance::BatchStructuralBars;
1462 if (args.is_stream) {
1463 // A36: legacy stream warmups permit zero-valued interim OHLC bars;
1464 // the final close is checked by the stream preflight boundary.
1465 spec.legacy_tolerance = static_cast<NativeFeedTolerance>(
1466 static_cast<std::uint32_t>(spec.legacy_tolerance)
1467 | static_cast<std::uint32_t>(NativeFeedTolerance::WarmupNonNegativeOHLC));
1468 }
1469 spec.path_order = path_order;
1471 ? NativeCloseExecution::AfterCalculation : NativeCloseExecution::NextEligiblePoint;
1472 // R6: calc_on_order_fills IS the kernel's fill-triggered cadence. The
1473 // consumer drives one recalculation at each applied execution's cursor
1474 // from its own notification drain (A.4 chronology), delivered to
1475 // PineStrategyHost::on_native_recalculate with reason OrderFill; Pine
1476 // keeps only its language-state rollback, its waypoint deferral and this
1477 // guard literal. The open-bar view stays Complete: TradingView's COOF
1478 // callback reads the whole script bar (CT4).
1479 if (config.calc_on_order_fills) {
1480 spec.calculation = NativeCalculationTrigger::BarCloseAndFills;
1482 }
1483 // Pine's request_abort surface reports a cooperative cancellation through
1484 // status, not through last_error(). Native-only hosts retain Error.
1485 spec.abort_reporting = NativeAbortReporting::Quiet;
1486 // RP3/RP9 (L2): the kernel records the equity curve and its extremes; the
1487 // source host keeps only the Pine cadence that marks where the points
1488 // fall (pine_strategy_host.cpp scheduler_mark_report_point). TradingView's
1489 // range-end report — which re-marks the curve's last point and re-folds
1490 // every extreme from it (pine_strategy_host.cpp scheduler_record_range_end)
1491 // — is report shape, not a mark-to-market row, so the kernel's own
1492 // range-end producer stays off and report_open_position_at_end with it.
1493 //
1494 // R5 audit lane Q6 measured what that costs. The ROWS are no longer
1495 // duplicated: scheduler_record_range_end calls the kernel's generic
1496 // producer (NativeExecutionConsumer::append_open_position_report_rows),
1497 // and tests/test_adapter_range_end_relower.cpp asserts both sides get
1498 // the identical rows out of it (RE1 pnl=500/300 with no fee; RE2
1499 // pnl=479.5 comm=20.5 and 279.30000000000001/20.700000000000003 at
1500 // 0.1 %). What this policy value keeps out is the SHAPE, and each piece
1501 // is a named divergence in docs/design/native-feature-parity.md §3.7:
1502 // the equity re-mark off the NET row P&L (RE2: the curve's last point
1503 // reads eq=100758.8 op=0 here against the kernel's eq=100800 op=800 —
1504 // 41.2 apart, the round-trip commission of both lots), the extreme
1505 // re-fold that follows it (RE3: maxru=1758.8000000000029 against 1800),
1506 // the same-bar bracket re-sort, and three marks on the terminal bar
1507 // against the kernel's one at run end. RE4, the flat control, agrees on
1508 // both sides, so every divergence above belongs to the range end.
1509 spec.report_policy = NativeReportPolicy::KernelRecordedAtHostMarks;
1510 // Contract P6: Pine pyramiding is adapter command policy. A resting source
1511 // entry must not consume a generic physical-lot cap before it fills, so
1512 // the projected native spec deliberately leaves max_open_lots unbounded.
1513 spec.allowed_open_directions = directions_for(risk_.direction);
1514 // Pine's frozen default sizing admits against its signal-time tuple. The
1515 // generic initial-margin gate only sees the later fill-time FX rate, so
1516 // source admission is reproduced in validate_precommit instead: every
1517 // opening verdict below is AdmitWithHostMargin, which is what keeps the
1518 // kernel's own initial-margin requirement out of the CANDIDATE decision.
1519 // The declared model is maintenance-only (zero per-side initial, below),
1520 // which is what keeps it out of L3b's PLACEMENT decision, where a
1521 // candidate verdict does not exist yet. The two together are the whole
1522 // statement "the source owns opening admission".
1523 // R5: the broker model itself is the kernel's. TradingView's two margin
1524 // percents ARE the maintenance fractions (strategy(margin_long=,
1525 // margin_short=) is the fraction of the position's value the account must
1526 // keep, and the emulator liquidates the moment it cannot), the check is
1527 // made at the remaining path's adverse mark and rests there, and the
1528 // whole model is present exactly when the call is enabled -- which is how
1529 // set_margin_call_enabled() keeps its C-ABI semantics without a second
1530 // switch. The money rules TradingView layers on top (its equity basis,
1531 // its ten-significant-digit requirement, its lot-floored 4x slice with
1532 // the whole-drop band, its scheduling) are host policy and live in the
1533 // three hooks below, never in this spec.
1534 const double margin_long_fraction = config.margin_long / 100.0;
1535 const double margin_short_fraction = config.margin_short / 100.0;
1536 if (source_margin_call_enabled_
1537 && std::isfinite(margin_long_fraction) && margin_long_fraction > 0.0
1538 && std::isfinite(margin_short_fraction) && margin_short_fraction > 0.0) {
1539 NativeMarginModel margin;
1540 // Wave-4 ruling: opening admission and liquidation are two different
1541 // broker functions, and this adapter takes only the second one from
1542 // the kernel. Its own pre-trade check is TradingView's
1543 // ten-significant-digit money admission against the signal-time
1544 // tuple, answered as AdmitWithHostMargin at every opening candidate
1545 // (validate_precommit below), so the model is declared
1546 // MAINTENANCE-ONLY: zero per-side initial means the kernel enforces
1547 // no opening requirement -- at the candidate gate and at L3b's
1548 // placement gate alike -- while the maintenance fractions keep the
1549 // kernel's liquidation. A positive initial here would re-run the
1550 // kernel's raw-double requirement over a quantity the source's money
1551 // rule has already admitted, and decline openings TradingView takes.
1552 margin.initial_long = 0.0;
1553 margin.initial_short = 0.0;
1554 margin.maintenance_long = margin_long_fraction;
1555 margin.maintenance_short = margin_short_fraction;
1556 // No sizing knob (R5 N11): resolve_margin_call_units answers every
1557 // call, so sizing / shortfall_multiple / liquidation_min_units would
1558 // be set only to be shadowed, and TradingView's slice (restore
1559 // lot-floored BEFORE the 4x, floored again, one-contract whole-drop
1560 // band) is no generic policy; check_adapter_spec_shadowing.py guards.
1561 // The slice belongs to the adverse waypoint it was measured at, not to
1562 // a solved level -- and a 1x long, whose slope solves no level at all,
1563 // still has to be checked.
1564 margin.check = NativeLiquidationCheck::PathAdverseExtremeMark;
1565 // TradingView's margin equity charges only a PERCENT entry commission
1566 // against the account; a cash or per-contract entry fee is added back
1567 // (percent_commission_live_equity).
1568 margin.basis =
1569 (config.commission_type == static_cast<int>(CommissionType::PERCENT)
1570 && config.commission_value > 0.0)
1571 ? NativeMarginEquityBasis::MarkedEquity
1572 : NativeMarginEquityBasis::MarkedEquityBeforeOpenCommission;
1573 // compute_liquidation_price() solves from closed money alone.
1574 margin.level_base = NativeLiquidationLevelBase::RealizedOnly;
1575 // TradingView's own liquidation ticket. The adapter's host classifies
1576 // a row as MARGIN_CALL from exactly this exit id (R5 lane L12, 2.ii l:
1577 // PineStrategyHost::on_native_applied states execution::CloseCause,
1578 // which pineforge.h pins as `3`), so the ticket the kernel books has
1579 // to be this one.
1580 margin.liquidation_label = "__margin_call__";
1581 margin.liquidation_comment = "Margin call";
1582 spec.margin = margin;
1583 }
1584 if (args.bar_magnifier) {
1585 // pine_scheduler.cpp:894-899/:1035-1041 supplied the legacy
1586 // volume-weighted bound. The native API's generic default remains
1587 // 64; the source provider owns this policy projection.
1588 const int volume_weighted_cap =
1589 legacy_volume_weighted_max_samples(args.magnifier_samples);
1590 const bool synthesized = spec.timeframe_undetected || spec.input_tf == spec.script_tf;
1591 if (synthesized) {
1592 IntrabarPath::synthesized path;
1593 path.samples = args.magnifier_samples;
1594 path.distribution = args.magnifier_distribution;
1595 path.volume_weighted = args.magnifier_volume_weighted;
1596 path.volume_weighted_min_samples = args.magnifier_volume_weighted_min_samples;
1597 path.volume_weighted_max_samples = volume_weighted_cap;
1598 spec.intrabar.value = std::move(path);
1599 } else {
1600 // A18: a genuinely finer supplied feed remains a retained
1601 // lower-timeframe path. The generic validator owns duration and
1602 // divisibility rejection for any non-finer malformed pairing.
1603 IntrabarPath::lower_tf path;
1604 if (args.bars && args.n > 0) path.bars.assign(args.bars, args.bars + args.n);
1605 path.tf = spec.input_tf;
1606 path.samples = args.magnifier_samples;
1607 path.distribution = args.magnifier_distribution;
1608 path.volume_weighted = args.magnifier_volume_weighted;
1609 path.volume_weighted_min_samples = args.magnifier_volume_weighted_min_samples;
1610 path.volume_weighted_max_samples = volume_weighted_cap;
1611 // Real lower-timeframe bars already carry the legacy four
1612 // turning points. Preserve the source broker's continuous
1613 // segment crossing over those points: a stop reached between the
1614 // open and an endpoint fills at its level, while synthesized
1615 // paths below retain the sampled one-price/gap semantics.
1617 spec.intrabar.value = std::move(path);
1618 }
1619 } else if (!spec.timeframe_undetected
1620 && (args.magnifier_samples != 4
1622 // Legacy TF-aware callers accept inactive sampler arguments. Preserve
1623 // that source-surface shape without relaxing the strict native public
1624 // API: an empty lower path is already defined to fall back to the
1625 // caller's confirmed script-bar path at delivery.
1626 IntrabarPath::lower_tf inert;
1627 inert.tf = spec.input_tf;
1628 inert.samples = 4;
1629 inert.distribution = MagnifierDistribution::ENDPOINTS;
1630 inert.volume_weighted = false;
1631 inert.volume_weighted_min_samples = 2;
1632 inert.volume_weighted_max_samples = 64;
1633 inert.sample_eligibility = IntrabarPath::SampleEligibility::ContinuousSegments;
1634 spec.intrabar.value = std::move(inert);
1635 }
1636 const auto validation = validate_native_run_spec(spec);
1637 if (!validation) {
1638 throw std::logic_error("Pine adapter produced invalid native run spec field "
1639 + std::to_string(static_cast<unsigned>(validation.field)));
1640 }
1641 return spec;
1642}
1643
1644std::uint64_t PineExecutionAdapter::key_for(const SourceId& left, const SourceId& right) const noexcept {
1645 return source_key(left, right);
1646}
1647
1648native_order::CohortHandle PineExecutionAdapter::cohort_for(const SourceId& id) {
1649 auto found = cohorts_by_id_.find(id);
1650 if (found != cohorts_by_id_.end()) return found->second.handle;
1651 CohortFacts facts;
1652 facts.handle = require_host().cohort_open();
1653 if (facts.handle.value == 0) throw std::logic_error("native cohort allocation refused");
1654 const auto result = facts.handle;
1655 cohorts_by_id_.emplace(id, std::move(facts));
1656 cohort_order_.push_back(id);
1657 return result;
1658}
1659
1660PineSizingSnapshot PineExecutionAdapter::sizing_snapshot() const {
1661 PineSizingSnapshot snapshot;
1662 const auto& host = require_host();
1663 if (const auto point = host.current_execution_point()) {
1664 // The source broker captures its signal tuple at the tick-built close,
1665 // never at the raw sub-tick callback print. Preserve that one basis
1666 // for frozen sizing, money-band admission and the paired FX fact.
1667 snapshot.mark = nearest_tick(point->price, staged_.syminfo.mintick);
1668 snapshot.price = snapshot.mark;
1669 snapshot.equity = percent_commission_live_equity(snapshot.mark);
1670 }
1671 snapshot.fx = staged_.account_fx;
1672 if (const auto point = host.current_execution_point())
1673 snapshot.fx = active_staged_fx(point->decision.sub_bar_open_ms);
1674 return snapshot;
1675}
1676
1677// The money a default-quantity declaration converts. CASH is the declared
1678// value itself; a percentage is taken of the equity the snapshot marked, on
1679// the ten-significant-digit money grid whenever the instrument has a lot grid.
1680// Which equity that is (ab9714be pine_fills.cpp:1411-1426, restated in
1681// percent_commission_live_equity) and this rounding are source policy the
1682// generic EquityFraction basis deliberately does not model, so the source
1683// hands the core the money and the core converts it. Any other declaration
1684// has no money of its own and returns NaN.
1685double PineExecutionAdapter::default_sizing_cash(
1686 const PineSizingSnapshot& sizing) const noexcept {
1687 if (config_.default_qty_type == static_cast<int>(QtyType::CASH)) {
1688 return config_.default_qty_value;
1689 }
1690 if (config_.default_qty_type != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
1691 || !finite_positive(sizing.equity)) {
1692 return kNaN;
1693 }
1694 const double equity = staged_.quantity_grid ? source_money_round(sizing.equity) : sizing.equity;
1695 return config_.default_qty_value / 100.0 * equity;
1696}
1697
1698// A percentage declaration reserves a percentage commission out of its own
1699// money before converting; a cash declaration does not. The divisor is the
1700// exact inverse of the charge, which is what native_order::Sized spells as
1701// reserve_percent_fee.
1702bool PineExecutionAdapter::default_sizing_reserves_percent_fee() const noexcept {
1703 return config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
1704 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
1705 && config_.commission_value > 0.0;
1706}
1707
1708// The source lot floor. The two declarations do not share one: a percentage
1709// quantity uses the money floor with its cent-grid case, a cash quantity the
1710// ordinary grid helper with its proportional epsilon. Neither is the core's
1711// generic "largest grid multiple at or below the quotient", so the core hands
1712// back the raw quotient (ExecutionGridPolicy::ExplicitUnits) and this runs on
1713// top of it in resolve_terms.
1714double PineExecutionAdapter::default_sizing_lot_floor(double units) const noexcept {
1715 if (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
1716 && staged_.quantity_grid) {
1717 return source_money_floor_lot(units, staged_.quantity_grid);
1718 }
1719 return floor_quantity_grid(units, staged_.quantity_grid);
1720}
1721
1722// The core intent a default-quantity declaration spells: the source money as
1723// the CashValue basis, the core's fee reserve, the raw quotient for the source
1724// lot floor, frozen at acceptance against the signal rule. No money, no shape.
1725std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_shape(
1726 const PineSizingSnapshot& sizing) const noexcept {
1727 const double cash = default_sizing_cash(sizing);
1728 if (!finite_positive(cash)) return std::nullopt;
1729 native_order::Sized sized;
1730 sized.basis = native_order::CashValue{cash};
1731 sized.time = native_order::SizeTime::AtAcceptance;
1732 sized.price = native_order::SizePrice::SignalOnTick;
1733 sized.grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
1734 sized.reserve_percent_fee = default_sizing_reserves_percent_fee();
1735 return sized;
1736}
1737
1738// The placement-time default quantity: the source's money band and
1739// affordability gates consume it before any request exists (entry(): "The TV
1740// money band is a source policy"), so the source asks the core for its own
1741// conversion and floors it -- the arithmetic exists once (R5 N11).
1742double PineExecutionAdapter::default_sizing_units(const PineSizingSnapshot& sizing) const {
1743 if (!finite_positive(sizing.price) || !finite_positive(sizing.fx)) return 0.0;
1744 const auto sized = default_sizing_shape(sizing);
1745 if (!sized) return 0.0;
1746 const auto quotient = require_host().native_sized_units(
1747 *sized, sizing.price, sizing.equity, sizing.fx);
1748 return quotient ? default_sizing_lot_floor(*quotient) : 0.0;
1749}
1750
1751std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_intent(
1752 const PineSizingSnapshot& sizing, bool is_long) const noexcept {
1753 // The core converts at the run's own point value and at the FX rate of the
1754 // acceptance coordinate; the source samples its rate at the sub-bar open.
1755 // The two are the same number only while the run carries no FX series, so
1756 // a run that declares one keeps its host-resolved sizing.
1757 if (!staged_.account_fx_effective_from_ms.empty()
1758 || !finite_positive(staged_.account_fx) || sizing.fx != staged_.account_fx
1759 || !finite_positive(sizing.price) || !finite_positive(staged_.syminfo.pointvalue)
1760 || !finite_positive(staged_.syminfo.mintick)) {
1761 return std::nullopt;
1762 }
1763 auto sized = default_sizing_shape(sizing);
1764 if (sized) sized->side = is_long ? native_order::Side::Long : native_order::Side::Short;
1765 return sized;
1766}
1767
1768// The sizing price of a default-sized MARKET entry: the signal mark carried
1769// to the expected fill by the side's slippage ticks and re-snapped onto the
1770// chart tick (ab9714be pine_strategy_commands.cpp:325-328). This is the rule
1771// native_order::SizePrice::SignalOnTick names generically.
1772double PineExecutionAdapter::default_market_sizing_price(
1773 double mark, bool is_long) const noexcept {
1774 const double tick = staged_.syminfo.mintick;
1775 return nearest_tick(mark + (is_long ? 1.0 : -1.0) * config_.slippage * tick, tick);
1776}
1777
1779 PineSizingSnapshot sizing = sizing_snapshot();
1780 if (!finite_positive(sizing.mark)) return false;
1781 sizing.price = default_market_sizing_price(sizing.mark, is_long);
1782 sizing.equity = percent_commission_live_equity(sizing.mark);
1783 return finite_positive(default_sizing_units(sizing))
1784 && default_sizing_intent(sizing, is_long).has_value()
1785 && core_sizing_price_matches(sizing, is_long);
1786}
1787
1788bool PineExecutionAdapter::core_sizing_price_matches(
1789 const PineSizingSnapshot& sizing, bool is_long) const {
1790 const double tick = staged_.syminfo.mintick;
1791 if (!finite_positive(tick) || !finite_positive(sizing.price)) return false;
1792 const auto point = require_host().current_execution_point();
1793 if (!point || !finite_positive(point->price)) return false;
1794 // SizePrice::SignalOnTick in the core's own arithmetic -- its nearest tick
1795 // is std::round with ties away from zero, not this file's
1796 // floor(v / tick + 0.5) -- so this is a comparison against the core and
1797 // not a restatement of the source rule. The two agree on every price a
1798 // tick ladder can present; where they would not, the command keeps its
1799 // host-resolved intent and its own frozen quantity.
1800 const auto on_tick = [tick](double value) { return std::round(value / tick) * tick; };
1801 const double ticks = config_.slippage < 0 ? 0.0 : static_cast<double>(config_.slippage);
1802 const double slipped = is_long ? on_tick(point->price) + ticks * tick
1803 : on_tick(point->price) - ticks * tick;
1804 return on_tick(slipped) == sizing.price;
1805}
1806
1807bool PineExecutionAdapter::same_bar_market_tx_scope() const {
1808 const bool all_in_percent = config_.default_qty_type
1809 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
1810 && config_.default_qty_value >= 100.0;
1811 const bool fixed_default = config_.default_qty_type == static_cast<int>(QtyType::FIXED);
1812 const bool variable_default = config_.default_qty_type == static_cast<int>(QtyType::CASH)
1813 || (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
1814 && config_.default_qty_value < 100.0);
1815 const bool variable_short_seed = variable_default && short_seed_context_is_live();
1816 if (!host_ || config_.process_orders_on_close || config_.calc_on_order_fills
1817 || coof_recalc_active_ || config_.close_entries_rule_any
1818 || config_.pyramiding > 1 || all_in_percent
1819 || (!fixed_default && !variable_short_seed)
1820 || config_.slippage != 0 || config_.commission_value != 0.0
1821 || risk_.direction != 0 || risk_.max_cons_loss_days != 0
1822 || risk_.max_drawdown > 0.0 || risk_.max_intraday_loss > 0.0
1823 || risk_.max_position_size > 0.0 || risk_.halted || cap.active()) {
1824 return false;
1825 }
1826 const auto state = require_host().native_state();
1827 const auto* inert = state.spec ? state.spec->intrabar.lower() : nullptr;
1828 const bool inactive_sampler_path = inert != nullptr && inert->bars.empty()
1829 && inert->tf == state.spec->input_tf && inert->samples == 4
1830 && inert->distribution == MagnifierDistribution::ENDPOINTS
1831 && !inert->volume_weighted && inert->volume_weighted_min_samples == 2
1832 && inert->volume_weighted_max_samples == 64
1833 && inert->sample_eligibility == IntrabarPath::SampleEligibility::ContinuousSegments;
1834 return state.phase == NativeRunPhase::Batch && state.spec != nullptr
1835 && (state.spec->intrabar.is_none() || inactive_sampler_path);
1836}
1837
1838// Entry-family triggers only: every trailing shape the source language can
1839// ask for is built in exit(), on the kernel's own Trail / TrailTicks
1840// spelling, so this helper never carried a live trail case.
1841native_order::Trigger PineExecutionAdapter::trigger_for(double limit_price,
1842 double stop_price) const {
1843 if (price_present(limit_price) && price_present(stop_price))
1844 return native_order::StopLimit{stop_price, limit_price};
1845 if (price_present(limit_price)) return native_order::Limit{limit_price};
1846 if (price_present(stop_price)) return native_order::Stop{stop_price};
1847 return native_order::Market{};
1848}
1849
1850native_order::Group PineExecutionAdapter::group_for(const std::string& name, int type,
1851 std::int64_t cohort) const {
1852 if (name.empty() || type == 0) return native_order::NoGroup{};
1853 const auto group = fnv_string(name);
1854 return native_order::Member{group == 0 ? 1 : group, cohort,
1855 type == 1 ? native_order::GroupEffect::Cancel : native_order::GroupEffect::Reduce};
1856}
1857
1858void PineExecutionAdapter::remember(const native_order::RequestHandle& handle,
1859 PlacementSnapshot snapshot) {
1860 if (snapshot.reservation_growth_owner_incarnation != 0
1861 && snapshot.reservation_growth_owner_incarnation != handle.incarnation) {
1862 snapshot.reservation_growth_source.assign_capture(
1863 handle.incarnation, snapshot.reservation_growth_owner_incarnation);
1864 snapshot.pooc_global_full_exit_bound_add = true;
1865 }
1866 initialize_l4c_policy(snapshot, handle);
1867 // Every accepted request receives a fresh incarnation. Construct its
1868 // immutable placement evidence directly in the hash table rather than
1869 // default-constructing a string-bearing snapshot and move-assigning it.
1870 const auto inserted = placement_.try_emplace(handle.incarnation, std::move(snapshot));
1871 if (!inserted.second) placement_.replace(handle.incarnation, std::move(snapshot));
1872 if (std::find(live_handles_.begin(), live_handles_.end(), handle) == live_handles_.end())
1873 live_handles_.push_back(handle);
1874 update_l4c_priority();
1875 refresh_pending_view();
1876}
1877
1878void PineExecutionAdapter::retire(native_order::RequestHandle handle) noexcept {
1879 live_handles_.erase(std::remove(live_handles_.begin(), live_handles_.end(), handle),
1880 live_handles_.end());
1881 first_open_newborns_.erase(std::remove(first_open_newborns_.begin(),
1882 first_open_newborns_.end(), handle), first_open_newborns_.end());
1883 for (auto it = live_by_source_key_.begin(); it != live_by_source_key_.end();) {
1884 if (it->second == handle) it = live_by_source_key_.erase(it); else ++it;
1885 }
1886 if (pending_short_seed_.ready && (handle == pending_short_seed_.plan.long_entry
1887 || handle == pending_short_seed_.plan.materialize_long
1888 || handle == pending_short_seed_.plan.final_short)) {
1889 pending_short_seed_ = {};
1890 }
1891 if (handle == short_seed_long_candidate_) short_seed_long_candidate_ = {};
1892 if (short_seed_.active && (handle == short_seed_.long_entry
1893 || handle == short_seed_.materialize_long || handle == short_seed_.final_short)) {
1894 short_seed_.active = false;
1895 }
1896 refresh_pending_view();
1897}
1898
1899void PineExecutionAdapter::maybe_activate_short_seed_plan() {
1900 // The legacy sort qualifies at the broker boundary, never midway through
1901 // the source callback that created the three objects. on_bar_open owns
1902 // the final activation when the live next-bar predicate is available.
1903}
1904
1905bool PineExecutionAdapter::short_seed_context_is_live() const noexcept {
1906 if (!host_) return false;
1907 const auto physical = host_->physical_position();
1908 if (!(physical.signed_units < 0.0) || physical.lot_count != 1U) return false;
1909 const double held = std::abs(physical.signed_units);
1910 for (const auto& id : cohort_order_) {
1911 const auto cohort = cohorts_by_id_.find(id);
1912 if (cohort == cohorts_by_id_.end()) continue;
1913 if (cohort_exposure_for(id) == held) return true;
1914 }
1915 return false;
1916}
1917
1918bool PineExecutionAdapter::qualify_short_seed_plan(const ShortSeedPlan& plan) const {
1919 if (!host_ || plan.long_entry.incarnation == 0 || plan.materialize_long.incarnation == 0
1920 || plan.final_short.incarnation == 0 || plan.seed_id.empty()) {
1921 return false;
1922 }
1923 const auto long_it = placement_.find(plan.long_entry.incarnation);
1924 const auto materialize_it = placement_.find(plan.materialize_long.incarnation);
1925 const auto final_it = placement_.find(plan.final_short.incarnation);
1926 if (long_it == placement_.end() || materialize_it == placement_.end()
1927 || final_it == placement_.end()) {
1928 return false;
1929 }
1930 const PlacementSnapshot& long_entry = long_it->second;
1931 const PlacementSnapshot& materialize = materialize_it->second;
1932 const PlacementSnapshot& final_short = final_it->second;
1933 const bool bar_magnifier = bar_magnifier_;
1934 const auto is_live = [&](const native_order::RequestHandle& handle) {
1935 return std::find(live_handles_.begin(), live_handles_.end(), handle) != live_handles_.end();
1936 };
1937 const auto no_level = [](const PineExitLevels& levels) {
1938 return std::isnan(levels.limit) && std::isnan(levels.stop)
1939 && std::isnan(levels.trail_points) && std::isnan(levels.trail_offset)
1940 && std::isnan(levels.trail_price) && std::isnan(levels.profit_ticks)
1941 && std::isnan(levels.loss_ticks);
1942 };
1943 const auto fresh_plain = [&](const PlacementSnapshot& row) {
1944 return row.placement_open_epoch + 1U == broker_open_epoch_
1945 && row.projection_position_side == static_cast<std::int32_t>(PositionSide::SHORT)
1946 && !row.replaced_opening && row.projection_predecessor == 0
1947 && row.recreated_after_named_cancelled_entry_incarnation == 0
1948 && row.named_cancel_surviving_exit_incarnation == 0
1949 && !row.birth.from_fill() && !row.birth.at_terminal_fill()
1950 && !compat::pine::historical_cascade_reach(row.birth_reach)
1951 && !row.projection_created_during_coof
1952 && !row.reservation_expansion.capture()
1953 && row.oca_name.empty() && row.oca_type == 0;
1954 };
1955 const bool fixed_default = config_.default_qty_type == static_cast<int>(QtyType::FIXED);
1956 const auto pure_default_market_entry = [&](const PlacementSnapshot& row) {
1957 const bool sizing_shape = fixed_default ? std::isnan(row.sizing.frozen_units)
1958 : finite_positive(row.sizing.frozen_units) && finite_positive(row.sizing.equity)
1959 && finite_positive(row.sizing.price) && finite_positive(row.sizing.mark)
1960 && finite_positive(row.sizing.fx);
1961 return row.family == PineOrderFamily::Entry && row.deferred_cohort
1962 && std::isnan(row.requested_qty) && row.qty_type == -1 && sizing_shape
1963 && no_level(row.exit_levels) && !row.projection_after_close;
1964 };
1965 const auto exact_full_fifo_close_short = [&]() {
1966 return materialize.family == PineOrderFamily::Close
1967 && materialize.source_id == plan.seed_id && materialize.from_entry == plan.seed_id
1968 && !materialize.is_long && finite_positive(materialize.requested_qty)
1969 && materialize.qty_percent == 100.0 && no_level(materialize.exit_levels)
1970 && materialize.frozen_market_instruction && materialize.frozen_market_targeted_close
1971 && finite_positive(materialize.frozen_market_transaction_units)
1972 && !materialize.reservation_expansion.capture()
1973 && std::abs(materialize.frozen_market_transaction_units - plan.seed_qty) <= 1e-12
1974 && std::abs(materialize.requested_qty - plan.seed_qty) <= 1e-12;
1975 };
1976 std::array<std::uint64_t, 3> incarnations{
1977 plan.long_entry.incarnation,
1978 plan.materialize_long.incarnation,
1979 plan.final_short.incarnation,
1980 };
1981 std::sort(incarnations.begin(), incarnations.end());
1982 const bool consecutive_incarnations = incarnations[0] != 0
1983 && incarnations[0] != std::numeric_limits<std::uint64_t>::max()
1984 && incarnations[0] + 1U == incarnations[1]
1985 && incarnations[1] != std::numeric_limits<std::uint64_t>::max()
1986 && incarnations[1] + 1U == incarnations[2];
1987 const auto physical = host_->physical_position();
1988 const auto point = host_->current_execution_point();
1989 const double open = point ? point->price : kNaN;
1990 const double tick = staged_.syminfo.mintick;
1991 const double admit = finite_positive(tick) ? nearest_tick(open, tick) : open;
1992 const double entry_qty = fixed_default ? config_.default_qty_value : long_entry.sizing.frozen_units;
1993 const double final_qty = fixed_default ? config_.default_qty_value : final_short.sizing.frozen_units;
1994 const double fx = point ? active_staged_fx(point->decision.sub_bar_open_ms) : staged_.account_fx;
1995 const double notional = staged_.syminfo.pointvalue * fx;
1996 const double marked = percent_commission_live_equity(open);
1997 const double projected_long = entry_qty + std::min(plan.seed_qty, entry_qty);
1998 const double projected_free = marked - projected_long * open * notional;
1999 const double projected_required = (projected_long + final_qty) * admit * notional;
2000 const double epsilon = std::max(1e-9, std::abs(marked) * 1e-12);
2001 const bool projected_final_short_admission_is_safe = finite_positive(admit)
2002 && finite_positive(entry_qty) && finite_positive(final_qty) && finite_positive(notional)
2003 && std::isfinite(projected_free) && std::isfinite(projected_required)
2004 && projected_required <= projected_free + epsilon;
2005 bool percent_rechecks_safe = true;
2006 if (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
2007 && config_.default_qty_value <= 100.0) {
2008 for (const PlacementSnapshot* row : {&long_entry, &final_short}) {
2009 const double required = row->sizing.frozen_units * admit * staged_.syminfo.pointvalue
2010 * row->sizing.fx;
2011 if (!(finite_positive(required) && required <= row->sizing.equity)) {
2012 percent_rechecks_safe = false;
2013 break;
2014 }
2015 }
2016 }
2017 const int source_bar = long_entry.projection_created_bar;
2018 return !config_.close_entries_rule_any && !config_.process_orders_on_close
2019 && !config_.calc_on_order_fills && !coof_recalc_active_
2020 && !bar_magnifier && !stream_mode_ && !risk_.halted
2021 && risk_.direction == 0 && risk_.max_cons_loss_days == 0 && risk_.max_drawdown <= 0.0
2022 && risk_.max_intraday_loss <= 0.0 && risk_.max_position_size <= 0.0 && !cap.active()
2023 && config_.pyramiding == 1 && config_.slippage == 0 && config_.commission_value == 0.0
2024 && std::abs(config_.margin_long - 100.0) < 1e-12
2025 && std::abs(config_.margin_short - 100.0) < 1e-12
2026 && physical.signed_units < 0.0 && physical.lot_count == 1U && current_position_cycle_ > 0
2027 && is_live(plan.long_entry) && is_live(plan.materialize_long) && is_live(plan.final_short)
2028 && source_bar >= 0
2030 && long_entry.command_ordinal + 1U == final_short.command_ordinal
2031 && final_short.command_ordinal + 1U == materialize.command_ordinal
2032 && consecutive_incarnations
2033 && fresh_plain(long_entry) && fresh_plain(final_short) && fresh_plain(materialize)
2034 && pure_default_market_entry(long_entry) && pure_default_market_entry(final_short)
2035 && !long_entry.source_id.empty() && long_entry.is_long
2036 && !long_entry.projection_over_pyramiding && !final_short.source_id.empty()
2037 && long_entry.source_id != final_short.source_id
2038 && long_entry.source_id != materialize.source_id && !final_short.is_long
2039 && final_short.projection_over_pyramiding
2040 && long_entry.placement_cycle == plan.seed_cycle
2041 && final_short.placement_cycle == plan.seed_cycle
2042 && std::abs(long_entry.projection_tv_carry_qty - plan.seed_qty) <= 1e-12
2043 && std::abs(final_short.projection_tv_carry_qty - plan.seed_qty) <= 1e-12
2044 && exact_full_fifo_close_short() && plan.seed_id == final_short.source_id
2045 && position_open_epoch_ < broker_open_epoch_
2046 && finite_positive(plan.seed_qty) && std::abs(std::abs(physical.signed_units) - plan.seed_qty) <= 1e-12
2047 && (fixed_default ? std::abs(config_.default_qty_value - plan.seed_qty) <= 1e-12
2048 : std::abs(long_entry.sizing.frozen_units - final_short.sizing.frozen_units) <= 1e-12)
2049 && std::abs(materialize.projection_tv_carry_qty - plan.seed_qty) <= 1e-12
2050 && projected_final_short_admission_is_safe && percent_rechecks_safe;
2051}
2052
2053void PineExecutionAdapter::activate_short_seed_plan_at_open(const NativeDecisionContext&) {
2054 if (!pending_short_seed_.ready) return;
2055 if (broker_open_epoch_ < pending_short_seed_.expected_open_epoch) return;
2056 if (broker_open_epoch_ == pending_short_seed_.expected_open_epoch
2057 && qualify_short_seed_plan(pending_short_seed_.plan)) {
2058 short_seed_ = pending_short_seed_.plan;
2059 short_seed_.active = true;
2060 } else if (short_seed_.long_entry == pending_short_seed_.plan.long_entry
2061 && short_seed_.materialize_long == pending_short_seed_.plan.materialize_long
2062 && short_seed_.final_short == pending_short_seed_.plan.final_short) {
2063 short_seed_ = {};
2064 }
2065 pending_short_seed_ = {};
2066}
2067
2068std::optional<native_order::RequestHandle> PineExecutionAdapter::submit_or_replace(
2069 native_order::Request request, PlacementSnapshot snapshot, bool opening,
2070 const SourceId& replacement_key) {
2071 auto& host = require_host();
2072 // A core-sized opening freezes its basis at the execution point the CORE
2073 // sees when it accepts the command; the source froze the same rule when
2074 // the command was written. A command queued for a later point, or one
2075 // the source repriced after writing it, has moved the two apart: it keeps
2076 // its host-resolved intent and its own frozen quantity rather than
2077 // silently resizing at the submission point.
2078 if (std::holds_alternative<native_order::Sized>(request.intent)
2079 && !core_sizing_price_matches(snapshot.sizing, snapshot.is_long)) {
2080 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
2081 snapshot.is_long ? native_order::Side::Long : native_order::Side::Short};
2082 }
2083 const NativePhysicalPosition physical = host.physical_position();
2084 snapshot.projection_position_side = physical.signed_units > 0.0
2085 ? static_cast<std::int32_t>(PositionSide::LONG)
2086 : (physical.signed_units < 0.0 ? static_cast<std::int32_t>(PositionSide::SHORT)
2087 : static_cast<std::int32_t>(PositionSide::FLAT));
2088 snapshot.projection_after_close = snapshot.projection_after_close
2089 || pending_same_bar_close_qty_ > 0.0;
2090 snapshot.projection_over_pyramiding = opening
2091 && !(config_.process_orders_on_close && snapshot.projection_after_close)
2092 && config_.pyramiding > 0
2093 && ((physical.signed_units > 0.0) == snapshot.is_long)
2094 && physical.signed_units != 0.0
2095 && physical.lot_count >= static_cast<std::size_t>(config_.pyramiding);
2096 snapshot.projection_created_during_coof =
2097 snapshot.projection_created_during_coof || coof_recalc_active_;
2098 snapshot.projection_coof_at_terminal = snapshot.projection_coof_at_terminal
2099 || (coof_recalc_active_ && coof_context_.is_terminal_sub_bar);
2100 snapshot.projection_coof_mid_bar = snapshot.projection_coof_mid_bar
2101 || (coof_recalc_active_ && !coof_context_.is_terminal_sub_bar);
2102 snapshot.projection_tv_carry_qty = opening
2103 ? std::max(0.0, std::abs(physical.signed_units)
2104 - pending_same_bar_close_qty_)
2105 : std::abs(physical.signed_units);
2106 snapshot.projection_default_stop_equity = snapshot.sizing.equity;
2107 snapshot.projection_default_stop_signal_close = snapshot.sizing.mark;
2108 snapshot.projection_explicit_equity = std::isfinite(snapshot.requested_qty)
2109 ? snapshot.sizing.equity : kNaN;
2110 snapshot.projection_explicit_signal_close = std::isfinite(snapshot.requested_qty)
2111 ? snapshot.sizing.price : kNaN;
2112 if (snapshot.affordability_policy_active) {
2113 snapshot.projection_affordability_equity = snapshot.sizing.equity;
2114 snapshot.projection_affordability_signal_price = snapshot.sizing.price;
2115 snapshot.projection_affordability_held_qty = std::abs(physical.signed_units);
2116 } else {
2117 snapshot.projection_affordability_equity = kNaN;
2118 snapshot.projection_affordability_signal_price = kNaN;
2119 snapshot.projection_affordability_held_qty = kNaN;
2120 }
2121 const bool fill_time_any_percentage = snapshot.family == PineOrderFamily::Close
2122 && config_.close_entries_rule_any && !std::isfinite(snapshot.requested_qty)
2123 && std::isfinite(snapshot.qty_percent);
2124 if (!opening && !snapshot.reservation_deferred_to_pending_entry
2125 && !fill_time_any_percentage
2126 && !std::isfinite(snapshot.projection_remaining_qty)
2127 && snapshot.deferred_cohort && !std::isfinite(snapshot.requested_qty)
2128 && physical.signed_units != 0.0) {
2129 const double percent = std::isfinite(snapshot.qty_percent)
2130 ? snapshot.qty_percent : 100.0;
2131 snapshot.projection_remaining_qty = quantize_close_units(
2132 std::abs(physical.signed_units), percent);
2133 }
2134 if (opening && !snapshot.source_id.empty()) {
2135 if (const auto token = named_entry_cancel_tokens_.find(snapshot.source_id);
2136 token != named_entry_cancel_tokens_.end()) {
2137 snapshot.recreated_after_named_cancelled_entry_incarnation =
2138 token->second.entry_incarnation;
2139 snapshot.named_cancel_surviving_exit_incarnation =
2140 token->second.surviving_exit_incarnation;
2141 named_entry_cancel_tokens_.erase(token);
2142 }
2143 }
2144 // ab9714be pine_strategy_commands.cpp:497: order records created_position_cycle_seq from current cycle
2145 if (snapshot.placement_cycle == 0)
2146 snapshot.placement_cycle = current_position_cycle_;
2147 if (snapshot.birth.cause() == OrderBirthCause::Unattributed)
2148 snapshot.birth = capture_order_birth();
2149 snapshot.coof_cascade_seg_i = coof_recalc_active_
2150 ? coof_context_.coordinate.interval_index : -1;
2151 snapshot.coof_cascade_inflight_fires = coof_recalc_active_;
2152 if (!snapshot.paired_flat_market_candidate) {
2153 snapshot.paired_flat_market_candidate = snapshot.frozen_market_instruction
2154 && physical.signed_units == 0.0;
2155 snapshot.paired_flat_market_own_qty = snapshot.frozen_market_own_units;
2156 snapshot.paired_flat_market_transaction_qty =
2157 snapshot.frozen_market_transaction_units;
2158 }
2159 snapshot.paired_flat_market_signal_close = snapshot.sizing.price;
2160 snapshot.paired_flat_market_signal_equity = snapshot.sizing.equity;
2161 snapshot.paired_flat_market_signal_margin_pct = snapshot.is_long
2162 ? config_.margin_long : config_.margin_short;
2163 snapshot.paired_flat_market_signal_pointvalue = staged_.syminfo.pointvalue;
2164 snapshot.paired_flat_market_signal_fx = snapshot.sizing.fx;
2165 snapshot.pooc_global_full_exit_dynamic_qty = config_.process_orders_on_close
2166 && !opening && !std::isfinite(snapshot.requested_qty)
2167 && (!std::isfinite(snapshot.qty_percent) || snapshot.qty_percent >= 100.0);
2168 snapshot.pooc_global_full_exit_tracks_bound_adds = snapshot.pooc_global_full_exit_dynamic_qty;
2169 if (const auto point = host.current_execution_point()) {
2170 // ab9714be src/source/pine_strategy_commands.cpp:481 and
2171 // src/source/pine_strategy_commands.cpp:1959 stamp an order's
2172 // created_bar at the bar of the source command, and a bracket leg the
2173 // pass skipped while flat stays in that same book slot
2174 // (src/source/pine_fills.cpp:7461-7468), so only the
2175 // process_orders_on_close gate of src/source/pine_fills.cpp:7620-7648
2176 // compares created_bar with the current bar. Submitting the staged leg
2177 // when its parent entry applies must not re-stamp the bar.
2178 if (!snapshot.projection_created_bar_pinned) {
2179 snapshot.projection_created_bar = point->decision.coordinate.interval_index;
2180 }
2181 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
2182 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
2183 }
2184 if (opening && snapshot.family == PineOrderFamily::Entry
2185 && !snapshot.is_long && snapshot.deferred_cohort
2186 && snapshot.affordability_close_only
2187 && !std::isfinite(snapshot.requested_qty)
2188 && !finite_positive(snapshot.exit_levels.limit)
2189 && !finite_positive(snapshot.exit_levels.stop)
2190 && !snapshot.projection_after_close
2191 && !config_.process_orders_on_close && !config_.calc_on_order_fills
2192 && !coof_recalc_active_ && !stream_mode_
2193 && physical.signed_units > 0.0 && physical.lot_count == 1U
2194 && last_margin_call_script_bar_ == snapshot.placement_script_open_ms
2195 && last_margin_call_event_ordinal_ != 0
2196 && last_margin_call_event_ordinal_ == last_applied_ordinal_
2197 && last_margin_call_entry_incarnation_ != 0
2198 && last_margin_call_position_cycle_ == current_position_cycle_
2199 && last_margin_call_at_script_close_
2200 && last_margin_call_closed_units_ == 1.0
2201 && last_margin_call_remaining_units_ == std::abs(physical.signed_units)) {
2202 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&host);
2203 const auto state = host.native_state();
2204 const bool magnifier = pine_host
2205 && pine_host->scheduler_.bar_magnifier_enabled();
2206 const double carried = last_margin_call_remaining_units_
2207 + last_margin_call_closed_units_;
2208 if (pine_host && !magnifier && state.phase == NativeRunPhase::Batch
2209 && std::isfinite(carried)
2210 && std::abs(carried - std::abs(physical.signed_units) - 1.0) < 1e-6) {
2211 // The native pre-open/path callback has already applied the
2212 // signal-close margin event before this source command is
2213 // published. Preserve the exact event receipt the legacy pending
2214 // owner wrote after command publication (pine_fills.cpp:2049-52).
2215 snapshot.projection_tv_carry_qty = carried;
2216 snapshot.signal_close_mc_bar = snapshot.projection_created_bar;
2217 snapshot.signal_close_mc_entry_incarnation =
2218 last_margin_call_entry_incarnation_;
2219 snapshot.signal_close_mc_fill_seq =
2220 pine_host->adapter_broker_fill_event_sequence();
2221 snapshot.signal_close_mc_remaining_qty =
2222 last_margin_call_remaining_units_;
2223 snapshot.affordability_keep_mc_close_surplus = true;
2224 }
2225 }
2226 if (snapshot.opening && snapshot.family == PineOrderFamily::Entry
2227 && !snapshot.is_long && snapshot.rounded_signal_cost_close_only
2228 && snapshot.affordability_close_only
2229 && signal_close_mc_event_bar_ == snapshot.projection_created_bar
2230 && signal_close_mc_position_cycle_ == snapshot.placement_cycle
2231 && signal_close_mc_entry_incarnation_ != 0
2232 && signal_close_mc_fill_seq_ != 0) {
2233 snapshot.signal_close_mc_bar = signal_close_mc_event_bar_;
2234 snapshot.signal_close_mc_entry_incarnation =
2235 signal_close_mc_entry_incarnation_;
2236 snapshot.signal_close_mc_fill_seq = signal_close_mc_fill_seq_;
2237 snapshot.signal_close_mc_remaining_qty = signal_close_mc_remaining_qty_;
2238 snapshot.projection_tv_carry_qty = signal_close_mc_before_qty_;
2239 }
2240 snapshot.placement_open_epoch = broker_open_epoch_;
2241 if (snapshot.command_ordinal == 0) snapshot.command_ordinal = ++command_ordinal_;
2242 if (snapshot.command_sequence == 0) {
2243 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
2244 throw std::overflow_error("Pine source command sequence exhausted");
2245 }
2246 snapshot.command_sequence = ++source_command_sequence_;
2247 }
2248 std::optional<admission::Allocation> admission_allocation;
2249 std::shared_ptr<const admission::CommandObservation> admission_observation;
2250 if (snapshot.family == PineOrderFamily::Entry
2251 || snapshot.family == PineOrderFamily::Order) {
2252 admission_allocation.emplace(admission_journal.reserve());
2253 auto observed = std::make_shared<admission::CommandObservation>();
2254 observed->command = admission_allocation->sequence();
2255 observed->kind = snapshot.family == PineOrderFamily::Entry
2256 ? admission::CommandKind::Entry : admission::CommandKind::Raw;
2257 observed->birth = snapshot.birth;
2258 observed->id = snapshot.source_id;
2259 observed->requested_quantity = snapshot.requested_qty;
2260 observed->quantity_type = snapshot.qty_type;
2261 observed->buy = snapshot.is_long;
2262 observed->prices = {snapshot.exit_levels.limit, snapshot.exit_levels.stop};
2263 observed->oca_name = snapshot.oca_name;
2264 observed->oca_type = snapshot.oca_type;
2265 const auto native = host.native_state();
2266 auto& configuration = observed->configuration;
2267 configuration.process_on_close = config_.process_orders_on_close;
2268 configuration.calc_on_fills = config_.calc_on_order_fills;
2269 configuration.magnifier = native.spec && !native.spec->intrabar.is_none();
2270 configuration.fill_recalculation = coof_recalc_active_;
2271 configuration.scheduler = config_.calc_on_order_fills;
2272 configuration.slippage = config_.slippage;
2273 configuration.pyramiding = config_.pyramiding;
2274 configuration.default_quantity_type = config_.default_qty_type;
2275 configuration.default_quantity_value = config_.default_qty_value;
2276 configuration.long_margin = config_.margin_long;
2277 configuration.short_margin = config_.margin_short;
2278 configuration.commission_value = config_.commission_value;
2279 configuration.commission_type = config_.commission_type;
2280 configuration.pointvalue = staged_.syminfo.pointvalue;
2281 configuration.fx = snapshot.sizing.fx;
2282 configuration.quantity_step = staged_.quantity_grid.value_or(0.0);
2283 configuration.mintick = staged_.syminfo.mintick;
2284 configuration.risk_direction = risk_.direction;
2285 configuration.loss_days_limit = risk_.max_cons_loss_days;
2286 configuration.drawdown_limit = risk_.max_drawdown;
2287 configuration.intraday_loss_limit = risk_.max_intraday_loss;
2288 configuration.position_limit = risk_.max_position_size;
2289 configuration.fill_cap_active = cap.active();
2290 configuration.risk_halted = risk_.halted;
2291 observed->bar = snapshot.projection_created_bar;
2292 observed->placement_side = snapshot.projection_position_side;
2293 observed->placement_cycle = snapshot.placement_cycle;
2294 observed->prior_close_quantity = snapshot.projection_after_close
2295 ? snapshot.projection_tv_carry_qty : 0.0;
2296 observed->held_quantity = snapshot.projection_tv_carry_qty;
2297 observed->held_entries = static_cast<int>(physical.lot_count);
2298 observed->realized_equity = snapshot.sizing.equity;
2299 observed->placement_equity = snapshot.sizing.equity;
2300 observed->signal_close = snapshot.sizing.mark;
2301 observed->quantized_fixed_quantity = std::isfinite(snapshot.requested_qty)
2302 ? snapshot.requested_qty : kNaN;
2303 if (!std::isfinite(snapshot.requested_qty)
2304 && std::holds_alternative<native_order::Market>(request.trigger)
2305 && (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
2306 || config_.default_qty_type == static_cast<int>(QtyType::CASH))) {
2307 observed->original_sizing = admission::SizingObservation{
2308 snapshot.sizing.frozen_units, snapshot.sizing.equity,
2309 snapshot.sizing.price, snapshot.sizing.mark, snapshot.sizing.fx};
2310 }
2311 observed->explicit_equity = snapshot.projection_explicit_equity;
2312 observed->explicit_price = snapshot.projection_explicit_signal_close;
2313 admission_observation = std::move(observed);
2314 snapshot.market_admission.bind(admission_observation);
2315 }
2316 if (auto* member = std::get_if<native_order::Member>(&request.group)) {
2317 if (source_sequence_ >= static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
2318 throw std::overflow_error("Pine OCA member sequence exhausted");
2319 }
2320 // The generic group member's cohort distinguishes siblings. A source
2321 // OCA name identifies the group; every accepted source instruction is
2322 // a distinct member of it, including replacement incarnations.
2323 member->cohort = static_cast<std::int64_t>(source_sequence_ + 1U);
2324 }
2325 const auto key = replacement_key.empty() ? 0 : key_for(replacement_key);
2326 std::optional<native_order::RequestHandle> accepted;
2327 std::optional<PlacementSnapshot> predecessor_snapshot;
2328 bool predecessor_exit = false;
2329 bool predecessor_market = false;
2330 const auto unchanged_dynamic_exit = [&](const PlacementSnapshot& prior) {
2331 const bool preserve_coof_birth = prior.projection_created_during_coof
2332 && !coof_recalc_active_;
2333 if (opening || materializing_relative_
2334 || ((coof_recalc_active_ || config_.calc_on_order_fills)
2335 && !preserve_coof_birth)
2336 || !snapshot.deferred_cohort
2337 || !prior.deferred_cohort || snapshot.family != prior.family
2338 || (snapshot.family != PineOrderFamily::ExitLimit
2339 && snapshot.family != PineOrderFamily::ExitStop
2340 && snapshot.family != PineOrderFamily::ExitTrail)
2341 || !std::isnan(snapshot.requested_qty) || !std::isnan(prior.requested_qty)
2342 || snapshot.source_id != prior.source_id || snapshot.from_entry != prior.from_entry
2343 || snapshot.comment != prior.comment || snapshot.oca_name != prior.oca_name
2344 || snapshot.oca_type != prior.oca_type
2345 || !same_double_bits(snapshot.qty_percent, prior.qty_percent)
2346 || snapshot.bracket_origin != prior.bracket_origin
2347 || !same_exit_levels(snapshot.exit_levels, prior.exit_levels)) {
2348 return false;
2349 }
2350 const auto* sized = std::get_if<native_order::HostSized>(&request.intent);
2351 const auto* owner = std::get_if<native_order::BindCohort>(&request.owner);
2352 const auto cohort = cohorts_by_id_.find(snapshot.from_entry);
2353 const bool fifo_live_owner = !config_.close_entries_rule_any
2354 && std::holds_alternative<native_order::Independent>(request.owner)
2355 && cohort != cohorts_by_id_.end()
2356 && cohort_exposure_for(snapshot.from_entry) > 0.0;
2357 if (!sized || sized->kind != native_order::HostSizedKind::Close || sized->side
2358 || ((!owner || cohort == cohorts_by_id_.end()
2359 || owner->cohort != cohort->second.handle)
2360 && !fifo_live_owner)) {
2361 return false;
2362 }
2363 if (const auto* limit = std::get_if<native_order::Limit>(&request.trigger)) {
2364 if (preserve_coof_birth)
2365 return snapshot.family == PineOrderFamily::ExitLimit;
2366 return snapshot.family == PineOrderFamily::ExitLimit
2367 && same_double_bits(limit->price, prior.exit_levels.limit);
2368 }
2369 if (const auto* stop = std::get_if<native_order::Stop>(&request.trigger)) {
2370 if (preserve_coof_birth)
2371 return snapshot.family == PineOrderFamily::ExitStop;
2372 return snapshot.family == PineOrderFamily::ExitStop
2373 && same_double_bits(stop->price, prior.exit_levels.stop);
2374 }
2375 if (const auto* trail = std::get_if<native_order::Trail>(&request.trigger)) {
2376 // An attempted request still carries the kernel's tick spelling;
2377 // acceptance resolves it against the same mintick, so read the
2378 // price distance this leg will be stored with.
2379 const double trail_offset = trail->ticks
2380 ? trail->ticks->ticks * staged_.syminfo.mintick : trail->offset;
2381 return snapshot.family == PineOrderFamily::ExitTrail
2382 && same_double_bits(trail_offset, prior.exit_levels.trail_offset)
2383 && trail->arm_price.has_value() == std::isfinite(prior.exit_levels.trail_price)
2384 && (!trail->arm_price || same_double_bits(*trail->arm_price,
2385 prior.exit_levels.trail_price));
2386 }
2387 return false;
2388 };
2389 std::optional<std::uint64_t> retained_source_sequence;
2390 if (key != 0) {
2391 std::optional<native_order::RequestHandle> existing_handle;
2392 if (const auto existing = live_by_source_key_.find(key);
2393 existing != live_by_source_key_.end()) {
2394 // `retire` removes this key from live_by_source_key_. Keep the
2395 // handle independent of the map node before either operation.
2396 existing_handle = existing->second;
2397 if (const auto previous = placement_.find(existing_handle->incarnation);
2398 previous != placement_.end()) {
2399 predecessor_snapshot = previous->second;
2400 // The legacy pending book leaves a same-definition bracket
2401 // untouched. Its source cohort remains live and its dynamic
2402 // close quantity is resolved at fill time, so a normal-bar
2403 // reissue has no new executable fact to record.
2404 if (unchanged_dynamic_exit(previous->second)) return existing_handle;
2405 const auto family = previous->second.family;
2406 predecessor_exit = family == PineOrderFamily::ExitLimit
2407 || family == PineOrderFamily::ExitStop || family == PineOrderFamily::ExitTrail;
2408 predecessor_market = family == PineOrderFamily::Entry
2409 && !std::isfinite(previous->second.exit_levels.limit)
2410 && !std::isfinite(previous->second.exit_levels.stop)
2411 && !std::isfinite(previous->second.exit_levels.trail_offset);
2412 }
2413 }
2414 if (existing_handle) {
2415 // A close-time re-issue whose activation is unchanged but whose
2416 // offset alone moves keeps the running trail extreme. The
2417 // native Trail owns that evolving generic state, so replacing it
2418 // would incorrectly reset the track at every script close. The
2419 // source projection still records the latest offset operand.
2420 if (predecessor_snapshot
2421 && predecessor_snapshot->family == PineOrderFamily::ExitTrail
2422 && snapshot.family == PineOrderFamily::ExitTrail) {
2423 const auto same = [](double left, double right) {
2424 return (std::isnan(left) && std::isnan(right)) || left == right;
2425 };
2426 const bool same_activation = same(predecessor_snapshot->exit_levels.trail_points,
2427 snapshot.exit_levels.trail_points)
2428 && same(predecessor_snapshot->exit_levels.trail_price,
2429 snapshot.exit_levels.trail_price);
2430 const bool offset_changed = !same(predecessor_snapshot->exit_levels.trail_offset,
2431 snapshot.exit_levels.trail_offset);
2432 if (same_activation) {
2433 const auto live = placement_.find(existing_handle->incarnation);
2434 if (live != placement_.end()) {
2435 if (offset_changed) {
2436 if (const auto trail = host.trail_state(*existing_handle);
2437 trail && trail->activated
2438 && std::isfinite(trail->best_price)) {
2439 live->second.retained_trail_best = trail->best_price;
2440 }
2441 }
2442 live->second.exit_levels.trail_offset = snapshot.exit_levels.trail_offset;
2443 live->second.trail_activation_level = snapshot.trail_activation_level;
2444 live->second.requested_qty = snapshot.requested_qty;
2445 live->second.projection_remaining_qty = snapshot.projection_remaining_qty;
2446 live->second.qty_percent = snapshot.qty_percent;
2447 live->second.comment = snapshot.comment;
2448 live->second.sizing = snapshot.sizing;
2449 }
2450 refresh_pending_view();
2451 return existing_handle;
2452 }
2453 if (std::isfinite(snapshot.sizing.price))
2454 snapshot.retained_trail_best = snapshot.sizing.price;
2455 }
2456 const auto result = host.replace(*existing_handle, request);
2457 if (result.status == native_order::ReplaceStatus::Replaced && result.successor) {
2458 // ab9714be pine_strategy_host.cpp:239-241 + 270-275: the flatten
2459 // unbinds every exit's leg activation and the fresh open rebinds
2460 // only RETAINED activations, so a same-id exit that replaces a
2461 // bracket across a stop-out starts its successor with clean legs
2462 // instead of inheriting the finished cycle's touched stop / trail
2463 // state and firing it against the new lot.
2464 if (const auto found_p = placement_.find(existing_handle->incarnation);
2465 found_p != placement_.end()) {
2466 found_p->second.legs = {};
2467 }
2468 snapshot.projection_predecessor = existing_handle->incarnation;
2469 if (predecessor_snapshot) {
2470 const auto family = predecessor_snapshot->family;
2471 snapshot.projection_predecessor_exit = family == PineOrderFamily::ExitLimit
2472 || family == PineOrderFamily::ExitStop || family == PineOrderFamily::ExitTrail;
2473 snapshot.projection_predecessor_market = family == PineOrderFamily::Entry
2474 && !std::isfinite(predecessor_snapshot->exit_levels.limit)
2475 && !std::isfinite(predecessor_snapshot->exit_levels.stop)
2476 && !std::isfinite(predecessor_snapshot->exit_levels.trail_offset);
2477 const bool fresh_after_dormant = predecessor_snapshot->legs.dormant()
2478 && (snapshot.family == PineOrderFamily::ExitLimit
2479 || snapshot.family == PineOrderFamily::ExitStop
2480 || snapshot.family == PineOrderFamily::ExitTrail);
2481 // pine_execution_lifecycle.cpp's same-(id, from_entry)
2482 // reissue replaces a dormant bracket wholesale. Carrying
2483 // its suspension into the successor would leave the fresh
2484 // source prices permanently unmatchable.
2485 if (!fresh_after_dormant) {
2486 snapshot.legs = predecessor_snapshot->legs;
2487 snapshot.leg_activation = predecessor_snapshot->leg_activation;
2488 snapshot.exit_activation = predecessor_snapshot->exit_activation;
2489 snapshot.restored_after_margin =
2490 predecessor_snapshot->restored_after_margin;
2491 }
2492 snapshot.reservation_expansion = predecessor_snapshot->reservation_expansion;
2493 snapshot.reservation_growth_source = predecessor_snapshot->reservation_growth_source;
2494 snapshot.cancellation = {PineCancellationCause::Replacement, 1, 0,
2495 existing_handle->incarnation,
2496 static_cast<std::int64_t>(predecessor_snapshot->source_sequence),
2497 existing_handle->incarnation, predecessor_snapshot->placement_cycle,
2498 predecessor_snapshot->legs.revision(),
2499 predecessor_snapshot->requested_qty, kNaN};
2500 if ((family == PineOrderFamily::ExitLimit
2501 || family == PineOrderFamily::ExitStop
2502 || family == PineOrderFamily::ExitTrail)
2503 && !snapshot.from_entry.empty()) {
2504 const auto token = named_entry_cancel_tokens_.find(snapshot.from_entry);
2505 bool retained_child = token != named_entry_cancel_tokens_.end()
2506 && token->second.surviving_exit_incarnation
2507 == existing_handle->incarnation;
2508 for (const auto& live : live_handles_) {
2509 const auto parent = placement_.find(live.incarnation);
2510 if (parent != placement_.end() && parent->second.opening
2511 && parent->second.family == PineOrderFamily::Entry
2512 && parent->second.source_id == snapshot.from_entry
2513 && (parent->second.named_cancel_surviving_exit_incarnation
2514 == existing_handle->incarnation
2515 || predecessor_snapshot->source_sequence
2516 < parent->second.source_sequence)) {
2517 retained_child = true;
2518 break;
2519 }
2520 }
2521 if (retained_child) {
2522 retained_source_sequence = predecessor_snapshot->source_sequence;
2523 }
2524 }
2525 // ab9714be pine_strategy_commands.cpp:482: same-id entry replacement preserves created_seq (preserved_seq > 0)
2526 if ((family == PineOrderFamily::Entry && snapshot.family == PineOrderFamily::Entry)
2527 || (family == PineOrderFamily::Order && snapshot.family == PineOrderFamily::Order)) {
2528 retained_source_sequence = predecessor_snapshot->source_sequence;
2529 }
2530 }
2531 snapshot.projection_predecessor_exit = predecessor_exit;
2532 snapshot.projection_predecessor_market = predecessor_market;
2533 retire(*existing_handle);
2534 accepted = *result.successor;
2535 }
2536 }
2537 }
2538 if (auto* member = std::get_if<native_order::Member>(&request.group)) {
2539 if (source_sequence_ >= static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
2540 throw std::overflow_error("Pine OCA member sequence exhausted");
2541 }
2542 // The generic group member's cohort distinguishes siblings. A source
2543 // OCA name identifies the group; every accepted source instruction is
2544 // a distinct member of it, including replacement incarnations.
2545 member->cohort = static_cast<std::int64_t>(source_sequence_ + 1U);
2546 }
2547 if (!accepted && materializing_relative_ && !opening) {
2548 // R5 lane R4d: the kernel already armed this leg at the parent's
2549 // fill. When the request this fill point builds is that very trigger
2550 // on that very group, the armed child IS the accepted request and
2551 // nothing is submitted; every source fact of the snapshot stays the
2552 // fill point's own.
2553 const auto armed = std::find_if(
2554 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
2555 [&](const AnchoredRelativeLeg& leg) {
2556 return leg.armed && leg.parent == materializing_parent_
2557 && leg.family == snapshot.family
2558 && leg.exit_id == snapshot.source_id
2559 && leg.from_entry == snapshot.from_entry;
2560 });
2561 if (armed != anchored_relative_legs_.end()) {
2562 const auto same_trigger = [&] {
2563 const auto& mine = armed->request.trigger;
2564 if (mine.index() != request.trigger.index()) return false;
2565 if (const auto* limit = std::get_if<native_order::Limit>(&request.trigger)) {
2566 return same_double_bits(limit->price, armed->installed_level)
2567 && limit->fill_through
2568 == std::get<native_order::Limit>(mine).fill_through;
2569 }
2570 if (const auto* stop = std::get_if<native_order::Stop>(&request.trigger))
2571 return same_double_bits(stop->price, armed->installed_level);
2572 if (const auto* trail = std::get_if<native_order::Trail>(&request.trigger)) {
2573 const auto& anchored = std::get<native_order::Trail>(mine);
2574 return trail->arm_price && trail->ticks && anchored.ticks
2575 && same_double_bits(*trail->arm_price, armed->installed_level)
2576 && same_double_bits(trail->ticks->ticks, anchored.ticks->ticks)
2577 && trail->offset == 0.0;
2578 }
2579 return false;
2580 }();
2581 const auto* mine_group = std::get_if<native_order::Member>(&armed->request.group);
2582 const auto* group = std::get_if<native_order::Member>(&request.group);
2583 const bool same_group = mine_group && group && mine_group->group == group->group
2584 && mine_group->effect == group->effect;
2585 // The armed child is a host-sized close of the book (its arm
2586 // scope); so must the request be, or it is another request.
2587 const auto* sized = std::get_if<native_order::HostSized>(&request.intent);
2588 const bool same_close = sized && sized->kind == native_order::HostSizedKind::Close
2589 && !sized->side
2590 && std::holds_alternative<native_order::Independent>(request.owner)
2591 && std::holds_alternative<native_order::ImmediateRemaining>(request.capacity);
2592 if (same_trigger && same_group && same_close) {
2593 accepted = armed->handle;
2594 anchored_relative_legs_.erase(armed);
2595 ++anchored_relative_stats_.adopted;
2596 }
2597 }
2598 }
2599 if (!accepted) {
2600 const auto result = host.submit(request);
2601 if (result.status != native_order::SubmitStatus::Accepted || !result.handle) return std::nullopt;
2602 accepted = *result.handle;
2603 }
2604 snapshot.opening = opening;
2605 if (!std::isfinite(snapshot.projection_remaining_qty)
2606 && std::isfinite(snapshot.requested_qty)) {
2607 snapshot.projection_remaining_qty = snapshot.requested_qty;
2608 }
2609 const auto next_source_sequence = ++source_sequence_;
2610 const bool staged_replacement_sequence = snapshot.projection_predecessor != 0
2611 && snapshot.source_sequence != 0;
2612 snapshot.source_sequence = retained_source_sequence
2613 ? *retained_source_sequence
2614 : (staged_replacement_sequence ? snapshot.source_sequence : next_source_sequence);
2615 if (admission_observation) {
2616 admission::CommandEvent event;
2617 event.observation = admission_observation;
2618 event.outcome = admission::Outcome::Admitted;
2619 event.admitted_incarnation = accepted->incarnation;
2620 admission_journal.append(std::move(event));
2621 }
2622 remember(*accepted, std::move(snapshot));
2623 // ab9714be pine_strategy_commands.cpp:445-446 (entry/order) and
2624 // 1694-1703 (exit): an accepted same-id instruction removes the prior
2625 // pending order, including one still queued here for a later submission.
2626 if (!replacement_key.empty()) {
2627 pending_coof_requests_.erase(
2628 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
2629 [&](const PendingCoofRequest& row) { return row.replacement_key == replacement_key; }),
2630 pending_coof_requests_.end());
2631 pending_bracket_legs_.erase(
2632 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
2633 [&](const PendingBracketLeg& row) { return row.replacement_key == replacement_key; }),
2634 pending_bracket_legs_.end());
2635 delayed_market_orders_.erase(
2636 std::remove_if(delayed_market_orders_.begin(), delayed_market_orders_.end(),
2637 [&](const DelayedMarketOrder& row) { return row.replacement_key == replacement_key; }),
2638 delayed_market_orders_.end());
2639 }
2640 if (key != 0) live_by_source_key_[key] = *accepted;
2641 if (opening) {
2642 const auto source = placement_.at(accepted->incarnation).source_id;
2643 const auto cohort = cohort_for(source);
2644 host.cohort_add(cohort, *accepted);
2645 cohorts_by_id_.at(source).origins.push_back(*accepted);
2646 }
2647 const auto& accepted_snapshot = placement_.at(accepted->incarnation);
2648 if (coof_recalc_active_
2649 && ((std::holds_alternative<native_order::Market>(request.trigger)
2650 && (coof_first_open_
2651 || finite_positive(accepted_snapshot.forced_execution_price)))
2652 || (coof_first_open_
2653 && finite_positive(accepted_snapshot.forced_execution_price)))
2654 && std::holds_alternative<native_order::ImmediateRemaining>(request.capacity)
2655 && host.current_execution_point()) {
2656 first_open_newborns_.push_back(*accepted);
2657 }
2658 return accepted;
2659}
2660
2661std::vector<native_order::RequestHandle> PineExecutionAdapter::openings_for(const SourceId& id) const {
2662 const auto found = cohorts_by_id_.find(id);
2663 return found == cohorts_by_id_.end() ? std::vector<native_order::RequestHandle>{}
2664 : found->second.opened;
2665}
2666
2667bool PineExecutionAdapter::from_entry_filled_this_cycle(const SourceId& id) const noexcept {
2668 // The source analogue of ab9714be cycle_filled_entry_ids_: the id opened
2669 // in the live position cycle (reset when the book goes flat).
2670 const auto found = cohorts_by_id_.find(id);
2671 return found != cohorts_by_id_.end() && current_position_cycle_ > 0
2672 && found->second.cycle == current_position_cycle_;
2673}
2674
2675double PineExecutionAdapter::cohort_exposure_for(const SourceId& id) const noexcept {
2676 const auto found = cohorts_by_id_.find(id);
2677 if (found == cohorts_by_id_.end()) return 0.0;
2678 double total = 0.0;
2679 // `live_units_by_origin` is a lookup table. Quantities must follow the
2680 // cohort's deterministic source insertion order, never its hash buckets.
2681 // Only roster positions whose incarnation has a unit row can contribute:
2682 // step through just those, ascending, rather than the id's whole entry
2683 // history. Allocation-free (the pending-order readback calls this).
2684 const auto& facts = found->second;
2685 bool have_previous = false;
2686 std::size_t previous = 0;
2687 for (;;) {
2688 bool have_next = false;
2689 std::size_t next = 0;
2690 double units = 0.0;
2691 for (const auto& unit : facts.live_units_by_origin) {
2692 const auto* positions = facts.origins.positions_of(unit.first);
2693 if (!positions) continue;
2694 for (const auto position : *positions) {
2695 if (have_previous && position <= previous) continue;
2696 if (!have_next || position < next) {
2697 have_next = true;
2698 next = position;
2699 units = unit.second;
2700 }
2701 break;
2702 }
2703 }
2704 if (!have_next) break;
2705 if (std::isfinite(units) && units > 0.0) total += units;
2706 have_previous = true;
2707 previous = next;
2708 }
2709 return std::isfinite(total) && total > 0.0 ? total : 0.0;
2710}
2711
2713 std::size_t total = 0;
2714 for (const auto& id : cohort_order_) {
2715 const auto found = cohorts_by_id_.find(id);
2716 if (found != cohorts_by_id_.end()) total += found->second.opened.size();
2717 }
2718 return total > static_cast<std::size_t>(std::numeric_limits<int>::max())
2719 ? std::numeric_limits<int>::max() : static_cast<int>(total);
2720}
2721
2722double PineExecutionAdapter::percent_commission_live_equity(double mark) const noexcept {
2723 if (!host_) return std::numeric_limits<double>::quiet_NaN();
2724 // ab9714be pine_fills.cpp:1411-1426: entry-bar affordability marks open positions
2725 // from closed equity (initial capital + realized net profit) minus the open
2726 // entries' percentage commission, plus the lot's open PnL.
2727 if (auto* pine = dynamic_cast<PineStrategyHost*>(host_)) {
2728 if (config_.commission_type == static_cast<int>(CommissionType::PERCENT)
2729 && config_.commission_value > 0.0
2730 && pine->position_side_ != PositionSide::FLAT) {
2731 double paid_open_commission = 0.0;
2732 for (const auto& pe : pine->pyramid_entries_) {
2733 if (pe.qty <= internal::kQtyEpsilon) continue;
2734 const double fee = pine->open_entry_commission(pe);
2735 if (!std::isfinite(fee)) return std::numeric_limits<double>::quiet_NaN();
2736 paid_open_commission += fee;
2737 }
2738 return (pine->current_equity() + pine->open_profit(mark)) - paid_open_commission;
2739 }
2740 }
2741 const double marked = host_->native_marked_equity(mark);
2742 if (!std::isfinite(marked)) return marked;
2743 // `marked_equity()` accounts for every open entry fee. Pine's sizing
2744 // basis subtracts only surviving PERCENT entry commissions, so restore the
2745 // adapter-recorded cash-per-order/contract fees without changing generic
2746 // accounting or marked equity itself.
2747 double restored = 0.0;
2748 for (const auto& fact : open_entry_fees_) {
2749 if (!std::isfinite(fact.nonpercent_fee))
2750 return std::numeric_limits<double>::quiet_NaN();
2751 restored += fact.nonpercent_fee;
2752 }
2753 return std::isfinite(restored) ? marked + restored
2754 : std::numeric_limits<double>::quiet_NaN();
2755}
2756
2757void PineExecutionAdapter::record_opening_fee(
2758 const PlacementSnapshot& source, const native_order::ExecutionAppliedEvent& event) {
2759 const double opened = std::abs(event.opened_units);
2760 if (!(opened > 0.0) || !std::isfinite(opened)
2761 || config_.commission_type == static_cast<int>(CommissionType::PERCENT)) {
2762 return;
2763 }
2764 double fee = 0.0;
2765 if (config_.commission_type == static_cast<int>(CommissionType::CASH_PER_CONTRACT)) {
2766 fee = config_.commission_value * opened;
2767 } else if (config_.commission_type == static_cast<int>(CommissionType::CASH_PER_ORDER)) {
2768 const double total = opened + std::abs(event.closed_units);
2769 fee = total > 0.0 ? config_.commission_value * opened / total : 0.0;
2770 }
2771 if (!std::isfinite(fee)) return;
2772 open_entry_fees_.push_back({event.handle(), source.source_id, opened, fee});
2773}
2774
2775void PineExecutionAdapter::consume_opening_fees(
2776 const native_order::ExecutionAppliedEvent& event, const SourceId* source_id) {
2777 if (!(event.closed_units > 0.0) || !std::isfinite(event.closed_units)) return;
2778 std::vector<std::uint64_t> selected;
2779 if (const auto* opening = std::get_if<execution::OpeningExposure>(&event.scope)) {
2780 selected.push_back(opening->incarnation);
2781 } else if (const auto* openings = std::get_if<native_order::SelectedExposure>(&event.scope)) {
2782 selected = openings->incarnations;
2783 }
2784 double remaining = event.closed_units;
2785 for (auto it = open_entry_fees_.begin(); it != open_entry_fees_.end() && remaining > 0.0;) {
2786 const bool selected_origin = selected.empty()
2787 || std::find(selected.begin(), selected.end(), it->opening.incarnation) != selected.end();
2788 if (!selected_origin || (source_id && it->source_id != *source_id)
2789 || !(it->units > 0.0) || !std::isfinite(it->units)) {
2790 ++it;
2791 continue;
2792 }
2793 const double consumed = std::min(it->units, remaining);
2794 const double fraction = consumed / it->units;
2795 it->units -= consumed;
2796 it->nonpercent_fee -= it->nonpercent_fee * fraction;
2797 remaining -= consumed;
2798 if (!(it->units > 0.0)) it = open_entry_fees_.erase(it); else ++it;
2799 }
2800}
2801
2802void PineExecutionAdapter::record_dropped_close(
2803 const SourceId& id, const std::string& comment, double qty, double qty_percent,
2804 bool immediately, std::uint64_t callsite_token) {
2805 dropped_close_receipts_.push_back(
2806 {id, comment, qty, qty_percent, immediately, callsite_token, command_ordinal_});
2807}
2808
2809double PineExecutionAdapter::quantize_close_units(double basis, double percent) const noexcept {
2810 if (!std::isfinite(basis) || basis <= 0.0 || !std::isfinite(percent) || percent <= 0.0)
2811 return 0.0;
2812 // ab9714be pine_fills.cpp:6893-6896: `is_partial` is decided by
2813 // `qp < 100.0 - kFullPercentEps`, so a reservation percentage that only
2814 // misses 100 by the binary64 rounding of `reserved / live_basis * 100`
2815 // (pine_fills.cpp:7124) is a FULL exit and books the whole live basis.
2816 // Sizing it through the multiply/grid floor instead leaves a sub-lot dust
2817 // remainder that never flattens (ab9714be pine_fills.cpp:1618 spells the
2818 // same whole-position coverage as `qty - kQtyEpsilon`).
2819 if (percent >= 100.0 - internal::kFullPercentEps) {
2820 if (staged_.quantity_grid && *staged_.quantity_grid > 0.0) {
2821 const double nearest = std::round(basis / *staged_.quantity_grid) * *staged_.quantity_grid;
2822 if (std::abs(basis - nearest) <= internal::kQtyEpsilon) return nearest;
2823 }
2824 return basis;
2825 }
2826 double units = basis * percent / 100.0;
2827 if (!std::isfinite(units) || units <= 0.0) return 0.0;
2828 return quantize_percent_exit_units(units, basis);
2829}
2830
2831double PineExecutionAdapter::quantize_percent_exit_units(
2832 double requested, double available) const noexcept {
2833 if (!std::isfinite(requested) || requested <= 0.0) return 0.0;
2834 if (!staged_.quantity_grid || !std::isfinite(*staged_.quantity_grid)
2835 || *staged_.quantity_grid <= 0.0) {
2836 return requested;
2837 }
2838 const double step = *staged_.quantity_grid;
2839 const double floored = std::floor(requested / step + 1e-6) * step;
2840 double result = floored < requested ? floored : requested;
2841 // ab9714be:engine.hpp:1587-1598. An integer-lot percentage exit keeps
2842 // one minimum unit while one full step of reservation capacity remains;
2843 // fractional grids retain the ordinary floor-to-zero behaviour.
2844 if (step >= 1.0 && requested < step && available >= step) result = step;
2845 return result;
2846}
2847
2848bool PineExecutionAdapter::compute_exit_reservation(
2849 const SourceId& exit_id, const SourceId& from_entry,
2850 double requested_qty, double& qty_percent, double live_basis,
2851 double& reserved_qty) const {
2852 constexpr double kQuantityEpsilon = 1e-10;
2853 constexpr double kFullPercentEpsilon = 1e-9;
2854 qty_percent = std::isfinite(qty_percent)
2855 ? std::clamp(qty_percent, 0.0, 100.0) : 100.0;
2856 reserved_qty = kNaN;
2857 if (!(live_basis > kQuantityEpsilon)) {
2858 if (std::isfinite(requested_qty)) {
2859 reserved_qty = std::abs(requested_qty);
2860 return reserved_qty > kQuantityEpsilon;
2861 }
2862 return true;
2863 }
2864
2865 struct Reservation {
2866 std::uint64_t family = 0;
2867 double units = kNaN;
2868 double percent = 100.0;
2869 bool explicit_units = false;
2870 std::vector<std::uint64_t> origins;
2871 };
2872 std::vector<Reservation> reservations;
2873 auto observe = [&](const PlacementSnapshot& snapshot) {
2874 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
2875 || snapshot.family == PineOrderFamily::ExitStop
2876 || snapshot.family == PineOrderFamily::ExitTrail
2877 || snapshot.family == PineOrderFamily::Close;
2878 if (!exit || snapshot.from_entry != from_entry) return;
2879 // ab9714be pine_orders.cpp:599-602: close reservations only apply to positions matching active position_cycle_seq_
2880 if (snapshot.family == PineOrderFamily::Close
2881 && snapshot.placement_cycle != 0
2882 && snapshot.placement_cycle < current_position_cycle_) {
2883 return;
2884 }
2885 const auto family = key_for(snapshot.source_id, snapshot.from_entry);
2886 auto row = std::find_if(reservations.begin(), reservations.end(),
2887 [&](const Reservation& value) { return value.family == family; });
2888 const double percent = std::isfinite(snapshot.qty_percent)
2889 ? std::clamp(snapshot.qty_percent, 0.0, 100.0) : 100.0;
2890 const double units = std::isfinite(snapshot.projection_remaining_qty)
2891 ? std::max(0.0, snapshot.projection_remaining_qty)
2892 : (std::isfinite(snapshot.requested_qty)
2893 ? std::max(0.0, std::abs(snapshot.requested_qty))
2894 // ab9714be src/source/pine_strategy_commands.cpp:2761 and
2895 // src/source/pine_fills.cpp:7094 both reserve a sibling leg's
2896 // share as ``live_pos * (oqp / 100.0)``, percent divided first.
2897 : live_basis * (percent / 100.0));
2898 const bool explicit_units = std::isfinite(snapshot.requested_qty);
2899 const auto origin = snapshot.bracket_origin.incarnation;
2900 if (row == reservations.end()) {
2901 Reservation next;
2902 next.family = family;
2903 next.units = units;
2904 next.percent = percent;
2905 next.explicit_units = explicit_units;
2906 if (explicit_units) next.origins.push_back(origin);
2907 reservations.push_back(std::move(next));
2908 } else {
2909 if (explicit_units && row->explicit_units
2910 && std::find(row->origins.begin(), row->origins.end(), origin)
2911 == row->origins.end()) {
2912 row->units += units;
2913 row->origins.push_back(origin);
2914 } else {
2915 row->units = std::max(row->units, units);
2916 }
2917 row->explicit_units = row->explicit_units || explicit_units;
2918 row->percent = std::max(row->percent, percent);
2919 }
2920 };
2921 for (const auto& handle : live_handles_) {
2922 const auto found = placement_.find(handle.incarnation);
2923 if (found != placement_.end()) observe(found->second);
2924 }
2925 for (const auto& pending : pending_bracket_legs_) observe(pending.snapshot);
2926 for (const auto& pending : pending_coof_requests_) observe(pending.snapshot);
2927 for (const auto& delayed : delayed_market_orders_) observe(delayed.snapshot);
2928
2929 const auto this_family = key_for(exit_id, from_entry);
2930 double already_reserved = 0.0;
2931 double preserved_reserved = kNaN;
2932 bool other_full_exit = false;
2933 for (const auto& reservation : reservations) {
2934 if (reservation.family == this_family) {
2935 if (std::isfinite(reservation.units)) {
2936 preserved_reserved = std::isfinite(preserved_reserved)
2937 ? std::max(preserved_reserved, reservation.units)
2938 : reservation.units;
2939 }
2940 continue;
2941 }
2942 if (std::isfinite(reservation.units)) already_reserved += reservation.units;
2943 if (reservation.percent >= 100.0 - kFullPercentEpsilon) other_full_exit = true;
2944 }
2945 const double available = std::max(0.0, live_basis - already_reserved);
2946 if (std::isfinite(requested_qty)) {
2947 reserved_qty = std::min(std::abs(requested_qty), available);
2948 } else if (qty_percent < 100.0 - kFullPercentEpsilon
2949 && std::isfinite(preserved_reserved)) {
2950 reserved_qty = std::min(preserved_reserved, live_basis);
2951 } else {
2952 // A full source bracket owns the exact live exposure. Multiplying
2953 // that exposure by 100/100 can round down by one binary64 step and
2954 // turn the legacy execute_market_exit branch into a dust reduction
2955 // (ab9714be:src/source/pine_fills.cpp:6893-6932; A33).
2956 double requested = qty_percent >= 100.0 - kFullPercentEpsilon
2957 ? live_basis : live_basis * qty_percent / 100.0;
2958 if (qty_percent < 100.0 - kFullPercentEpsilon) {
2959 requested = quantize_percent_exit_units(requested, available);
2960 }
2961 reserved_qty = std::min(requested, available);
2962 }
2963 if (!(reserved_qty > kQuantityEpsilon)) return false;
2964 qty_percent = reserved_qty / live_basis * 100.0;
2965 const bool partial = reserved_qty < live_basis - 1e-9;
2966 if (partial && other_full_exit) return false;
2967 return true;
2968}
2969
2970void PineExecutionAdapter::reconcile_deferred_exit_reservations(
2971 const SourceId& from_entry, double live_basis) {
2972 constexpr double kQuantityEpsilon = 1e-10;
2973 constexpr double kFullPercentEpsilon = 1e-9;
2974 if (!(live_basis > kQuantityEpsilon)) return;
2975
2976 struct Family {
2977 std::uint64_t key = 0;
2978 std::uint64_t command_sequence = 0;
2979 double percent = 100.0;
2980 double existing = kNaN;
2981 double explicit_requested = kNaN;
2982 std::vector<native_order::RequestHandle> handles;
2983 std::vector<std::size_t> queued;
2984 std::vector<std::size_t> delayed;
2985 std::vector<std::uint64_t> origins;
2986 };
2987 std::vector<Family> families;
2988 for (const auto& handle : live_handles_) {
2989 const auto found = placement_.find(handle.incarnation);
2990 if (found == placement_.end()) continue;
2991 const auto& snapshot = found->second;
2992 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
2993 || snapshot.family == PineOrderFamily::ExitStop
2994 || snapshot.family == PineOrderFamily::ExitTrail;
2995 if (!exit || snapshot.from_entry != from_entry) continue;
2996 const auto key = key_for(snapshot.source_id, snapshot.from_entry);
2997 auto family = std::find_if(families.begin(), families.end(),
2998 [&](const Family& value) { return value.key == key; });
2999 if (family == families.end()) {
3000 Family next;
3001 next.key = key;
3002 next.command_sequence = snapshot.command_sequence;
3003 next.percent = std::isfinite(snapshot.qty_percent)
3004 ? std::clamp(snapshot.qty_percent, 0.0, 100.0) : 100.0;
3005 next.existing = snapshot.projection_remaining_qty;
3006 next.explicit_requested = snapshot.requested_qty;
3007 next.handles.push_back(handle);
3008 next.origins.push_back(snapshot.bracket_origin.incarnation);
3009 families.push_back(std::move(next));
3010 } else {
3011 family->command_sequence = std::min(family->command_sequence,
3012 snapshot.command_sequence);
3013 family->handles.push_back(handle);
3014 if (std::isfinite(snapshot.projection_remaining_qty)) {
3015 family->existing = std::isfinite(family->existing)
3016 ? std::max(family->existing, snapshot.projection_remaining_qty)
3017 : snapshot.projection_remaining_qty;
3018 }
3019 if (std::isfinite(snapshot.requested_qty))
3020 family->explicit_requested = snapshot.requested_qty;
3021 if (std::find(family->origins.begin(), family->origins.end(),
3022 snapshot.bracket_origin.incarnation) == family->origins.end()) {
3023 family->origins.push_back(snapshot.bracket_origin.incarnation);
3024 }
3025 }
3026 }
3027 for (std::size_t index = 0; index < pending_bracket_legs_.size(); ++index) {
3028 const auto& snapshot = pending_bracket_legs_[index].snapshot;
3029 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
3030 || snapshot.family == PineOrderFamily::ExitStop
3031 || snapshot.family == PineOrderFamily::ExitTrail;
3032 if (!exit || snapshot.from_entry != from_entry) continue;
3033 const auto key = key_for(snapshot.source_id, snapshot.from_entry);
3034 auto family = std::find_if(families.begin(), families.end(),
3035 [&](const Family& value) { return value.key == key; });
3036 if (family == families.end()) {
3037 Family next;
3038 next.key = key;
3039 next.command_sequence = snapshot.command_sequence;
3040 next.percent = std::isfinite(snapshot.qty_percent)
3041 ? std::clamp(snapshot.qty_percent, 0.0, 100.0) : 100.0;
3042 next.existing = snapshot.projection_remaining_qty;
3043 next.explicit_requested = snapshot.requested_qty;
3044 next.queued.push_back(index);
3045 next.origins.push_back(snapshot.bracket_origin.incarnation);
3046 families.push_back(std::move(next));
3047 } else {
3048 family->command_sequence = std::min(family->command_sequence,
3049 snapshot.command_sequence);
3050 family->queued.push_back(index);
3051 if (std::isfinite(snapshot.projection_remaining_qty)) {
3052 family->existing = std::isfinite(family->existing)
3053 ? std::max(family->existing, snapshot.projection_remaining_qty)
3054 : snapshot.projection_remaining_qty;
3055 }
3056 if (std::isfinite(snapshot.requested_qty))
3057 family->explicit_requested = snapshot.requested_qty;
3058 if (std::find(family->origins.begin(), family->origins.end(),
3059 snapshot.bracket_origin.incarnation) == family->origins.end()) {
3060 family->origins.push_back(snapshot.bracket_origin.incarnation);
3061 }
3062 }
3063 }
3064 for (std::size_t index = 0; index < delayed_market_orders_.size(); ++index) {
3065 const auto& snapshot = delayed_market_orders_[index].snapshot;
3066 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
3067 || snapshot.family == PineOrderFamily::ExitStop
3068 || snapshot.family == PineOrderFamily::ExitTrail;
3069 if (!exit || snapshot.from_entry != from_entry) continue;
3070 const auto key = key_for(snapshot.source_id, snapshot.from_entry);
3071 auto family = std::find_if(families.begin(), families.end(),
3072 [&](const Family& value) { return value.key == key; });
3073 if (family == families.end()) {
3074 Family next;
3075 next.key = key;
3076 next.command_sequence = snapshot.command_sequence;
3077 next.percent = std::isfinite(snapshot.qty_percent)
3078 ? std::clamp(snapshot.qty_percent, 0.0, 100.0) : 100.0;
3079 next.existing = snapshot.projection_remaining_qty;
3080 next.explicit_requested = snapshot.requested_qty;
3081 next.delayed.push_back(index);
3082 next.origins.push_back(snapshot.bracket_origin.incarnation);
3083 families.push_back(std::move(next));
3084 } else {
3085 family->command_sequence = std::min(family->command_sequence,
3086 snapshot.command_sequence);
3087 family->delayed.push_back(index);
3088 if (std::isfinite(snapshot.projection_remaining_qty)) {
3089 family->existing = std::isfinite(family->existing)
3090 ? std::max(family->existing, snapshot.projection_remaining_qty)
3091 : snapshot.projection_remaining_qty;
3092 }
3093 if (std::isfinite(snapshot.requested_qty))
3094 family->explicit_requested = snapshot.requested_qty;
3095 if (std::find(family->origins.begin(), family->origins.end(),
3096 snapshot.bracket_origin.incarnation) == family->origins.end()) {
3097 family->origins.push_back(snapshot.bracket_origin.incarnation);
3098 }
3099 }
3100 }
3101 std::stable_sort(families.begin(), families.end(),
3102 [](const Family& left, const Family& right) {
3103 return left.command_sequence < right.command_sequence;
3104 });
3105
3106 double reserved = 0.0;
3107 std::vector<native_order::RequestHandle> cancel;
3108 for (const auto& family : families) {
3109 const double available = std::max(0.0, live_basis - reserved);
3110 double units = 0.0;
3111 if (std::isfinite(family.explicit_requested)) {
3112 // An explicit strategy.exit quantity belongs to each bound entry
3113 // instance. Opening a later same-id parent adds another leg; it
3114 // must not turn every existing one-unit leg into a percentage of
3115 // the enlarged net position (ab9714be:pine_strategy_commands.cpp
3116 // :2739-2811 and test_exit_bracket_pending_entry_leg).
3117 units = std::min(std::abs(family.explicit_requested), available);
3118 } else if (family.percent < 100.0 - kFullPercentEpsilon
3119 && std::isfinite(family.existing)) {
3120 units = std::min(family.existing, available);
3121 } else {
3122 // ab9714be pine_fills.cpp:6893-6896: a reservation percentage
3123 // inside kFullPercentEps of 100 is a FULL exit, so it owns the
3124 // exact live exposure. Recomputing `live_basis * 100 / 100` can
3125 // land one binary64 step low and strand a sub-lot dust remainder
3126 // that never flattens (ab9714be pine_fills.cpp:1618 spells the
3127 // same whole-position coverage as `qty - kQtyEpsilon`).
3128 double requested = family.percent >= 100.0 - kFullPercentEpsilon
3129 ? live_basis : live_basis * family.percent / 100.0;
3130 if (family.percent < 100.0 - kFullPercentEpsilon) {
3131 requested = quantize_percent_exit_units(requested, available);
3132 }
3133 units = std::min(requested, available);
3134 }
3135 if (!(units > kQuantityEpsilon)) {
3136 cancel.insert(cancel.end(), family.handles.begin(), family.handles.end());
3137 for (const auto index : family.queued) {
3138 if (index < pending_bracket_legs_.size())
3139 pending_bracket_legs_[index].snapshot.qty_percent = 0.0;
3140 }
3141 for (const auto index : family.delayed) {
3142 if (index < delayed_market_orders_.size())
3143 delayed_market_orders_[index].snapshot.qty_percent = 0.0;
3144 }
3145 continue;
3146 }
3147 const double normalized_percent = units / live_basis * 100.0;
3148 for (const auto& handle : family.handles) {
3149 const auto found = placement_.find(handle.incarnation);
3150 if (found == placement_.end()) continue;
3151 found->second.projection_remaining_qty = units;
3152 found->second.qty_percent = normalized_percent;
3153 found->second.fixed_exit_reservation =
3154 std::isfinite(family.explicit_requested)
3155 || family.percent < 100.0 - kFullPercentEpsilon
3156 || std::isfinite(family.existing);
3157 found->second.reservation_deferred_to_pending_entry = false;
3158 }
3159 for (const auto index : family.queued) {
3160 if (index >= pending_bracket_legs_.size()) continue;
3161 auto& snapshot = pending_bracket_legs_[index].snapshot;
3162 snapshot.projection_remaining_qty = units;
3163 snapshot.qty_percent = normalized_percent;
3164 snapshot.fixed_exit_reservation =
3165 std::isfinite(family.explicit_requested)
3166 || family.percent < 100.0 - kFullPercentEpsilon
3167 || std::isfinite(family.existing);
3168 snapshot.reservation_deferred_to_pending_entry = false;
3169 }
3170 for (const auto index : family.delayed) {
3171 if (index >= delayed_market_orders_.size()) continue;
3172 auto& snapshot = delayed_market_orders_[index].snapshot;
3173 snapshot.projection_remaining_qty = units;
3174 snapshot.qty_percent = normalized_percent;
3175 snapshot.fixed_exit_reservation =
3176 std::isfinite(family.explicit_requested)
3177 || family.percent < 100.0 - kFullPercentEpsilon
3178 || std::isfinite(family.existing);
3179 snapshot.reservation_deferred_to_pending_entry = false;
3180 }
3181 // A limit/stop/trail OCA set for one opening consumes one reservation,
3182 // even though it has multiple native request handles. Count distinct
3183 // bound entry origins, not executable sibling legs.
3184 const std::size_t multiplicity = std::max<std::size_t>(1, family.origins.size());
3185 reserved += std::isfinite(family.explicit_requested)
3186 ? std::min(available, units * static_cast<double>(multiplicity))
3187 : units;
3188 }
3189 for (const auto& handle : cancel) {
3190 const auto result = require_host().cancel(handle);
3191 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
3192 }
3193 pending_bracket_legs_.erase(
3194 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
3195 [](const PendingBracketLeg& leg) {
3196 return leg.snapshot.qty_percent == 0.0;
3197 }),
3198 pending_bracket_legs_.end());
3199}
3200
3201double PineExecutionAdapter::active_staged_fx(std::int64_t timestamp_ms) const noexcept {
3202 double rate = staged_.account_fx;
3203 const std::size_t count = std::min(staged_.account_fx_effective_from_ms.size(),
3204 staged_.account_fx_per_quote.size());
3205 for (std::size_t i = 0; i < count; ++i) {
3206 if (staged_.account_fx_effective_from_ms[i] > timestamp_ms) break;
3207 rate = staged_.account_fx_per_quote[i];
3208 }
3209 return std::isfinite(rate) && rate > 0.0 ? rate : 1.0;
3210}
3211
3212void PineExecutionAdapter::submit_fx_margin_slice(
3213 const Bar& bar, const NativeDecisionContext& context, double rate, bool execute_at_current) {
3214 const auto position = require_host().physical_position();
3215 const double held = std::abs(position.signed_units);
3216 const double margin = position.signed_units > 0.0 ? config_.margin_long : config_.margin_short;
3217 if (!source_margin_call_enabled_ || !(held > 0.0) || !finite_positive(margin) || margin != 100.0
3218 || !finite_positive(bar.open) || !finite_positive(staged_.syminfo.pointvalue)) return;
3219 const double required = held * bar.open * staged_.syminfo.pointvalue * rate;
3220 const double equity = require_host().native_marked_equity(bar.open);
3221 if (!(required > equity) || !std::isfinite(equity)) return;
3222 const double raw_minimum = (required - equity)
3223 / (bar.open * staged_.syminfo.pointvalue * rate);
3224 if (!(raw_minimum > 0.0) || !std::isfinite(raw_minimum)) return;
3225 double minimum = raw_minimum;
3226 if (staged_.quantity_grid) minimum = floor_quantity_grid(minimum, staged_.quantity_grid);
3227 double units = 0.0;
3228 if (minimum > 0.0) {
3229 // The source broker floors the restore quantity before applying its
3230 // fourfold liquidation multiplier, then floors the executable result.
3231 units = 4.0 * minimum;
3232 if (staged_.quantity_grid) units = floor_quantity_grid(units, staged_.quantity_grid);
3233 } else if (staged_.quantity_grid && *staged_.quantity_grid <= 1.0
3234 && raw_minimum > 1e-12 && raw_minimum < 1.0) {
3235 // A positive deficit which floors below one lot is discontinuous in
3236 // the legacy FX rollover path: it closes one whole contract (G2).
3237 const double candidate = std::min(1.0, held);
3238 const double gridded = floor_quantity_grid(candidate, staged_.quantity_grid);
3239 const double guard = std::max(1e-12, std::abs(candidate) * 1e-12);
3240 if (candidate >= held - 1e-12 || std::abs(gridded - candidate) <= guard)
3241 units = candidate;
3242 }
3243 units = std::min(held, units);
3244 if (!(units > 0.0) || !std::isfinite(units)) return;
3245 native_order::Request request;
3246 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
3247 request.label = kMarginCallLabel;
3248 request.comment = "Margin call";
3249 PlacementSnapshot snapshot;
3250 snapshot.family = PineOrderFamily::Margin;
3251 snapshot.source_id = request.label;
3252 snapshot.requested_qty = units;
3253 snapshot.sizing = sizing_snapshot();
3254 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
3256 if (accepted && execute_at_current) {
3257 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
3258 }
3259}
3260
3261void PineExecutionAdapter::apply_fx_open_margin_slice(
3262 const Bar& bar, const NativeDecisionContext& context) {
3263 const double rate = active_staged_fx(context.sub_bar_open_ms);
3264 const double prior = last_fx_rate_;
3265 last_fx_rate_ = rate;
3266 if (!std::isfinite(prior) || prior == rate) return;
3267 const auto position = require_host().physical_position();
3268 if (position.signed_units == 0.0
3269 || position_open_script_bar_ >= context.script_bar_open_ms) return;
3270 const double margin = position.signed_units > 0.0 ? config_.margin_long : config_.margin_short;
3271 if (source_margin_call_enabled_ && finite_positive(margin) && margin != 100.0) {
3272 throw std::runtime_error(
3273 "timestamped account-currency FX broker-open rollover supports "
3274 "only carried 1x full-margin positions");
3275 }
3276 submit_fx_margin_slice(bar, context, rate, true);
3277}
3278
3279void PineExecutionAdapter::apply_fx_opening_margin_slice(
3280 const native_order::ExecutionAppliedEvent& event,
3281 const NativeDecisionContext& context) {
3282 std::optional<PlacementSnapshot> opening_snapshot;
3283 if (const auto found = placement_.find(event.handle().incarnation);
3284 found != placement_.end()) {
3285 opening_snapshot = found->second;
3286 }
3287 if (!opening_snapshot || !opening_snapshot->opening
3288 || opening_snapshot->family != PineOrderFamily::Entry
3289 || !finite_positive(opening_snapshot->sizing.frozen_units)
3290 || config_.default_qty_type != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
3291 || config_.commission_type != static_cast<int>(CommissionType::PERCENT)
3292 || !(config_.commission_value > 0.0)) {
3293 return;
3294 }
3295 const double rate = active_staged_fx(context.sub_bar_open_ms);
3296 if (!std::isfinite(opening_snapshot->sizing.fx) || opening_snapshot->sizing.fx == rate) return;
3297 Bar opening;
3298 opening.open = event.resolved_price;
3299 opening.high = event.resolved_price;
3300 opening.low = event.resolved_price;
3301 opening.close = event.resolved_price;
3302 opening.timestamp = context.sub_bar_open_ms;
3303 // A31(b): the generic applied callback keeps the same current coordinate
3304 // live, so this just-observed opening correction settles before the next
3305 // candidate without a source-specific execution path.
3306 submit_fx_margin_slice(opening, context, rate, true);
3307}
3308
3309void PineExecutionAdapter::schedule_preopen_margin_slice(
3310 const Bar& bar, const NativeDecisionContext& context) {
3311 // The legacy broker checks the adverse excursion of a default-percent
3312 // stop entry's fill bar after the opening is admitted. Submit its
3313 // source-owned close leg at the preceding open: BindCohort resolves only
3314 // after that opening applies, then the ordinary native path matches the
3315 // adverse waypoint in the same bar. This keeps the liquidation policy
3316 // entirely above the generic driver.
3317 if (!source_margin_call_enabled_
3318 || require_host().physical_position().signed_units != 0.0
3319 || config_.default_qty_type != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
3320 || !finite_positive(staged_.syminfo.pointvalue)
3321 || !finite_positive(staged_.syminfo.mintick)
3322 || !finite_positive(bar.open)) {
3323 return;
3324 }
3325
3326 // A pre-open margin submission can append to live_handles_ and insert
3327 // into placement_; scan value copies rather than retaining either
3328 // container's elements across submit_or_replace.
3329 const auto live_handles = live_handles_;
3330 for (const auto& handle : live_handles) {
3331 std::optional<PlacementSnapshot> opening_copy;
3332 if (const auto found = placement_.find(handle.incarnation);
3333 found != placement_.end()) {
3334 opening_copy = found->second;
3335 }
3336 if (!opening_copy) continue;
3337 const PlacementSnapshot& opening = *opening_copy;
3338 if (!opening.opening || opening.family != PineOrderFamily::Entry
3339 || !finite_positive(opening.sizing.frozen_units)
3340 || !finite_positive(opening.exit_levels.stop)) {
3341 continue;
3342 }
3343 const bool marketable = opening.is_long
3344 ? opening.exit_levels.stop <= bar.open
3345 : opening.exit_levels.stop >= bar.open;
3346 if (!marketable) continue;
3347
3348 const double entry = nearest_tick(bar.open, staged_.syminfo.mintick);
3349 const double adverse_raw = opening.is_long ? bar.low : bar.high;
3350 const double adverse = nearest_tick(adverse_raw, staged_.syminfo.mintick);
3351 if (!finite_positive(entry) || !finite_positive(adverse)
3352 || (opening.is_long ? !(adverse < entry) : !(adverse > entry))) {
3353 continue;
3354 }
3355 const double fx = active_staged_fx(context.sub_bar_open_ms);
3356 const double margin = opening.is_long ? config_.margin_long : config_.margin_short;
3357 if (!finite_positive(fx) || !finite_positive(margin)) continue;
3358 const double units = opening.sizing.frozen_units;
3359 // Owner sequencing: the source broker evaluates the adverse excursion
3360 // only AFTER the opening is admitted, and an all-in marketable stop
3361 // entry that cannot be funded at its own fill price is declined there
3362 // (ab9714be src/source/pine_fills.cpp:4618
3363 // stop_entry_margin_admission_declines consumes the fill at :4741:
3364 // required = qty * round_to_mintick(fill) * pointvalue * fx * margin,
3365 // refused when it exceeds realized equity plus the same
3366 // max(1e-9, |equity|*1e-12) guard; a gap-through fill price is the
3367 // rounded open). Arming the deferred close for such a candidate
3368 // leaves a stale Reduce that a LATER bar's different opening matches:
3369 // OANDA:XAUUSD 2025-06-01 22:00 sizes 3.1872003125 units against
3370 // equity 10525.244578, costs 3.1872003125 * 3303.415 = 10528.64 at
3371 // the open, so the owner never opens there -- its all-in long fills on
3372 // the 22:15 open (3.18228862 * 3307.24 = 10524.59) as ONE lot. The
3373 // ETH feed's admitted all-in short (4.08281952 * 2463.00 against
3374 // equity 10055.984463, cost 10055.984463) keeps its legitimate
3375 // same-bar slice.
3376 const double admission_cost = units * entry * staged_.syminfo.pointvalue
3377 * fx * margin / 100.0;
3378 const double admission_guard = std::max(
3379 1e-9, std::abs(opening.sizing.equity) * 1e-12);
3380 if (!std::isfinite(admission_cost)
3381 || admission_cost > opening.sizing.equity + admission_guard) {
3382 continue;
3383 }
3384 const double unrealized = (opening.is_long ? adverse - entry : entry - adverse)
3385 * units * staged_.syminfo.pointvalue * fx;
3386 const double marked_equity = opening.sizing.equity + unrealized;
3387 const double required = units * adverse * staged_.syminfo.pointvalue * fx
3388 * margin / 100.0;
3389 const double unit_margin = adverse * staged_.syminfo.pointvalue * fx
3390 * margin / 100.0;
3391 if (!std::isfinite(marked_equity) || !std::isfinite(required)
3392 || !finite_positive(unit_margin) || !(required > marked_equity)) {
3393 continue;
3394 }
3395 double restore = floor_quantity_grid((required - marked_equity) / unit_margin,
3396 staged_.quantity_grid);
3397 double slice = floor_quantity_grid(4.0 * restore, staged_.quantity_grid);
3398 slice = std::min(units, slice);
3399 if (!finite_positive(slice)) continue;
3400
3401 native_order::Request request;
3402 request.intent = native_order::HostSized{native_order::HostSizedKind::Close, std::nullopt};
3403 request.label = "__margin_preopen__" + opening.source_id;
3404 request.comment = "Margin call";
3405 // Preserve the exact source adverse waypoint as the generic trigger;
3406 // terms rounds its resulting fill by the source tick rule.
3407 request.trigger = native_order::Stop{adverse_raw};
3408 request.owner = native_order::BindCohort{cohort_for(opening.source_id)};
3409 PlacementSnapshot snapshot;
3410 snapshot.family = PineOrderFamily::Margin;
3411 snapshot.source_id = request.label;
3412 snapshot.from_entry = opening.source_id;
3413 snapshot.comment = request.comment;
3414 snapshot.requested_qty = slice;
3415 snapshot.sizing = opening.sizing;
3416 (void)submit_or_replace(std::move(request), std::move(snapshot), false,
3417 "__margin_preopen__" + opening.source_id);
3418 // The source stop-entry row is unique under this pre-open condition;
3419 // a second candidate belongs to a later source evaluation.
3420 return;
3421 }
3422}
3423
3424void PineExecutionAdapter::consume_cohort_units(
3425 const SourceId& id, const native_order::ExecutionAppliedEvent& event) {
3426 if (!(event.closed_units > 0.0) || !std::isfinite(event.closed_units)) return;
3427 const auto found = cohorts_by_id_.find(id);
3428 if (found == cohorts_by_id_.end()) return;
3429 auto& facts = found->second;
3430 std::vector<std::uint64_t> selected;
3431 if (const auto* opening = std::get_if<execution::OpeningExposure>(&event.scope)) {
3432 selected.push_back(opening->incarnation);
3433 } else if (const auto* openings = std::get_if<native_order::SelectedExposure>(&event.scope)) {
3434 selected = openings->incarnations;
3435 } else {
3436 for (const auto& handle : facts.opened) selected.push_back(handle.incarnation);
3437 }
3438 double remaining = event.closed_units;
3439 for (const auto incarnation : selected) {
3440 auto unit = facts.live_units_by_origin.find(incarnation);
3441 if (unit == facts.live_units_by_origin.end() || !(unit->second > 0.0)) continue;
3442 const double deduction = std::min(unit->second, remaining);
3443 unit->second -= deduction;
3444 remaining -= deduction;
3445 if (unit->second == 0.0) facts.live_units_by_origin.erase(unit);
3446 if (!(remaining > 0.0)) break;
3447 }
3448}
3449
3450void PineExecutionAdapter::consume_closed_trade_rows(
3451 const native_order::ExecutionAppliedEvent& event,
3452 const PlacementSnapshot* cause) {
3453 auto& host = require_host();
3454 const auto settle_slot = [&](CohortFacts& cohort, const Trade& trade,
3455 bool drained) {
3456 if (!cause) return;
3457 const bool bracket = cause->family == PineOrderFamily::ExitLimit
3458 || cause->family == PineOrderFamily::ExitStop
3459 || cause->family == PineOrderFamily::ExitTrail;
3460 const bool close_path = cause->family == PineOrderFamily::Close
3461 || cause->family == PineOrderFamily::CloseAll
3462 || cause->family == PineOrderFamily::Risk
3463 || cause->family == PineOrderFamily::Margin
3464 || cause->family == PineOrderFamily::Entry
3465 || cause->family == PineOrderFamily::Order;
3466 const bool exact_bracket_owner = cause->bracket_origin.incarnation != 0
3467 ? cause->bracket_origin.incarnation == trade.entry_incarnation
3468 : cause->from_entry == trade.entry_id;
3469 if (bracket && !exact_bracket_owner)
3470 bracket_shadowed_openings_.insert(trade.entry_incarnation);
3471 if (!drained) return;
3472 const bool owned_bracket = bracket && exact_bracket_owner
3473 && bracket_shadowed_openings_.find(trade.entry_incarnation)
3474 == bracket_shadowed_openings_.end();
3475 if (!close_path && !owned_bracket) return;
3476 cohort.opened.erase(
3477 std::remove_if(cohort.opened.begin(), cohort.opened.end(),
3478 [&](const auto& opening) {
3479 return opening.incarnation == trade.entry_incarnation;
3480 }),
3481 cohort.opened.end());
3482 bracket_shadowed_openings_.erase(trade.entry_incarnation);
3483 };
3484 for (std::size_t row = 0; row < event.closed_trade_count; ++row) {
3485 const std::size_t index = event.first_trade_index + row;
3486 if (index >= static_cast<std::size_t>(host.trade_count())) continue;
3487 const Trade& trade = host.get_trade(static_cast<int>(index));
3488 double remaining = trade.qty;
3489 bool matched = false;
3490 for (auto& cohort : cohorts_by_id_) {
3491 auto units = cohort.second.live_units_by_origin.find(
3492 trade.entry_incarnation);
3493 if (units == cohort.second.live_units_by_origin.end()) continue;
3494 const double consumed = std::min(units->second, remaining);
3495 units->second -= consumed;
3496 remaining -= consumed;
3497 const bool drained = !(units->second > 1e-10);
3498 if (drained)
3499 cohort.second.live_units_by_origin.erase(units);
3500 settle_slot(cohort.second, trade, drained);
3501 matched = true;
3502 break;
3503 }
3504 if (matched || !(remaining > 0.0)) continue;
3505 const auto cohort = cohorts_by_id_.find(trade.entry_id);
3506 if (cohort == cohorts_by_id_.end()) continue;
3507 for (const auto& origin : cohort->second.origins) {
3508 auto units = cohort->second.live_units_by_origin.find(origin.incarnation);
3509 if (units == cohort->second.live_units_by_origin.end()) continue;
3510 const double consumed = std::min(units->second, remaining);
3511 units->second -= consumed;
3512 remaining -= consumed;
3513 const bool drained = !(units->second > 1e-10);
3514 if (drained)
3515 cohort->second.live_units_by_origin.erase(units);
3516 settle_slot(cohort->second, trade, drained);
3517 if (!(remaining > 0.0)) break;
3518 }
3519 }
3520}
3521
3522bool PineExecutionAdapter::origin_is_pending(
3523 const native_order::RequestHandle& origin) const noexcept {
3524 if (origin.incarnation == 0) return false;
3525 const auto placement = placement_.find(origin.incarnation);
3526 if (placement == placement_.end() || !placement->second.opening) return false;
3527 return std::find(live_handles_.begin(), live_handles_.end(), origin) != live_handles_.end();
3528}
3529
3530std::vector<std::size_t> PineExecutionAdapter::live_origin_positions(
3531 const CohortFacts& cohort) const {
3532 std::vector<std::size_t> positions;
3533 for (const auto& handle : live_handles_) cohort.origins.append_positions_of(handle, positions);
3534 std::sort(positions.rbegin(), positions.rend());
3535 positions.erase(std::unique(positions.begin(), positions.end()), positions.end());
3536 return positions;
3537}
3538
3539void PineExecutionAdapter::cancel_bracket_origin(native_order::RequestHandle origin) {
3540 pending_bracket_legs_.erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
3541 [&](const PendingBracketLeg& leg) { return leg.snapshot.bracket_origin == origin; }),
3542 pending_bracket_legs_.end());
3543 std::vector<native_order::RequestHandle> matches;
3544 for (const auto& handle : live_handles_) {
3545 const auto placement = placement_.find(handle.incarnation);
3546 if (placement != placement_.end() && placement->second.bracket_origin == origin)
3547 matches.push_back(handle);
3548 }
3549 for (const auto& handle : matches) {
3550 const auto result = require_host().cancel(handle);
3551 if (result.status == native_order::CancelStatus::Cancelled) {
3552 if (const auto placement = placement_.find(handle.incarnation);
3553 placement != placement_.end()) {
3554 placement->second.cancellation = {PineCancellationCause::Explicit, 1, 0,
3555 handle.incarnation, static_cast<std::int64_t>(placement->second.source_sequence),
3556 handle.incarnation, placement->second.placement_cycle,
3557 placement->second.legs.revision(), placement->second.requested_qty, kNaN};
3558 }
3559 retire(handle);
3560 }
3561 }
3562}
3563
3564void PineExecutionAdapter::cancel_bracket_siblings(native_order::RequestHandle handle) {
3565 std::optional<PlacementSnapshot> snapshot_copy;
3566 if (const auto source = placement_.find(handle.incarnation);
3567 source != placement_.end()) {
3568 snapshot_copy = source->second;
3569 }
3570 if (!snapshot_copy) return;
3571 const PlacementSnapshot& snapshot = *snapshot_copy;
3572 if (snapshot.family != PineOrderFamily::ExitLimit && snapshot.family != PineOrderFamily::ExitStop
3573 && snapshot.family != PineOrderFamily::ExitTrail) return;
3574 std::vector<native_order::RequestHandle> matches;
3575 for (const auto& candidate : live_handles_) {
3576 if (candidate == handle) continue;
3577 const auto placement = placement_.find(candidate.incarnation);
3578 if (placement == placement_.end()) continue;
3579 const auto& sibling = placement->second;
3580 if (sibling.source_id == snapshot.source_id && sibling.from_entry == snapshot.from_entry
3581 && sibling.bracket_origin == snapshot.bracket_origin
3582 && (sibling.family == PineOrderFamily::ExitLimit || sibling.family == PineOrderFamily::ExitStop
3583 || sibling.family == PineOrderFamily::ExitTrail)) {
3584 matches.push_back(candidate);
3585 }
3586 }
3587 for (const auto& sibling : matches) {
3588 const auto result = require_host().cancel(sibling);
3589 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
3590 }
3591}
3592
3593void PineExecutionAdapter::cancel_exit_orders_for_full_close(
3594 const SourceId& from_entry) {
3595 const auto matches = [&](const PlacementSnapshot& snapshot) {
3596 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
3597 || snapshot.family == PineOrderFamily::ExitStop
3598 || snapshot.family == PineOrderFamily::ExitTrail;
3599 return exit && (from_entry.empty() ? snapshot.from_entry.empty()
3600 : snapshot.from_entry == from_entry);
3601 };
3602 pending_bracket_legs_.erase(
3603 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
3604 [&](const PendingBracketLeg& row) { return matches(row.snapshot); }),
3605 pending_bracket_legs_.end());
3606 pending_coof_requests_.erase(
3607 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
3608 [&](const PendingCoofRequest& row) { return matches(row.snapshot); }),
3609 pending_coof_requests_.end());
3610 source_shadow_pending_.erase(
3611 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
3612 [&](const SourceShadowPending& row) { return matches(row.snapshot); }),
3613 source_shadow_pending_.end());
3614 pending_relative_exits_.erase(
3615 std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
3616 [&](const PendingRelativeExit& row) {
3617 return from_entry.empty() ? row.from_entry.empty()
3618 : row.from_entry == from_entry;
3619 }),
3620 pending_relative_exits_.end());
3621 if (!from_entry.empty()) withdraw_anchored_relative_legs(nullptr, &from_entry);
3622
3623 std::vector<native_order::RequestHandle> handles;
3624 for (const auto& handle : live_handles_) {
3625 const auto found = placement_.find(handle.incarnation);
3626 if (found != placement_.end() && matches(found->second)) handles.push_back(handle);
3627 }
3628 for (const auto& handle : handles) {
3629 const auto result = require_host().cancel(handle);
3630 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
3631 }
3632 for (auto family = bracket_families_.begin(); family != bracket_families_.end();) {
3633 family->second.remove_all(handles);
3634 if (family->second.empty()) family = bracket_families_.erase(family);
3635 else ++family;
3636 }
3637 refresh_pending_view();
3638}
3639
3640// ab9714be pine_fills.cpp:7461-7468: while the book is flat, an EXIT whose
3641// ``created_position_side`` is not FLAT is Removed outright and never Skipped
3642// — a bracket armed against a position that has since closed does not survive
3643// the flat. pine_fills.cpp:7667-7675 gives the reason and the second half of
3644// the rule: an EXIT whose ``from_entry`` has not filled in the CURRENT
3645// position cycle is Removed as well, and ``cycle_filled_entry_ids_`` is
3646// cleared the moment the book goes flat, so a stale bracket must not fire
3647// later against a FUTURE position reusing the same entry id. A retired
3648// candidate row keeps its lifecycle, so ``revive_brackets_after_margin`` can
3649// resurrect the dormant leg the moment the reused id regains exposure, and a
3650// still-live leg can fill against the reversal's fresh lot on the same bar.
3651// Removing both the book entry and the lifecycle restores the legacy rule.
3652// The lifecycle half is the position-cycle filter at the top of
3653// ``revive_brackets_after_margin`` (pine_orders.cpp:597-608): a row parked by
3654// a finished cycle is never revived, so no walk over the whole placement
3655// history is needed here -- one per flat made a long run quadratic.
3656//
3657// Both legacy Removes are LAZY per-broker-point eligibility checks that walk
3658// the pending queue once in ``created_seq`` order, not an eager sweep. An
3659// exit placed on the SAME script bar as the flattening close and BEHIND it in
3660// command order — the close's paired reversal entry sits between the two
3661// (pine_fills.cpp:8013-8018) — is therefore only reached once that entry has
3662// filled and repopulated ``cycle_filled_entry_ids_`` for the new cycle, so
3663// neither Remove applies to it and it stays armed for the fresh lot. A
3664// bracket placed on any EARLIER bar was reached while the retiring cycle was
3665// still the live one, with its ``from_entry`` absent from that cycle's filled
3666// set, and was Removed there. ``paired_close`` carries the same-bar close
3667// that defines the boundary; a null pointer keeps the unrestricted
3668// preservation the transient-flat cleanup at the end of a close relies on.
3669void PineExecutionAdapter::retire_in_position_exits_at_flat(
3670 bool preserve_pending_parents, bool dormant_rows_only,
3671 const PlacementSnapshot* paired_close) {
3672 // A transient flat between two same-point reversal transactions does not
3673 // end the pending parent's lifecycle, so the parent's own brackets are
3674 // kept (the same exclusion the flat cleanup below applies per owner).
3675 const auto pending_parent = [&](const PlacementSnapshot& row) {
3676 if (!preserve_pending_parents || row.from_entry.empty()) return false;
3677 if (paired_close != nullptr
3678 && (row.projection_created_bar
3679 != paired_close->projection_created_bar
3680 || row.command_sequence <= paired_close->command_sequence)) {
3681 return false;
3682 }
3683 const SourceId& owner = row.from_entry;
3684 for (const auto& handle : live_handles_) {
3685 const auto found = placement_.find(handle.incarnation);
3686 if (found != placement_.end() && found->second.opening
3687 && found->second.family == PineOrderFamily::Entry
3688 && found->second.source_id == owner) {
3689 return true;
3690 }
3691 }
3692 return std::any_of(pending_entries_.begin(), pending_entries_.end(),
3693 [&](const PendingEntry& entry) {
3694 return entry.snapshot.opening && entry.snapshot.source_id == owner;
3695 });
3696 };
3697 const auto matches = [&](const PlacementSnapshot& snapshot) {
3698 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
3699 || snapshot.family == PineOrderFamily::ExitStop
3700 || snapshot.family == PineOrderFamily::ExitTrail;
3701 return exit
3702 && static_cast<PositionSide>(snapshot.projection_position_side)
3704 && !pending_parent(snapshot);
3705 };
3706 if (!dormant_rows_only) {
3707 pending_bracket_legs_.erase(
3708 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
3709 [&](const PendingBracketLeg& row) { return matches(row.snapshot); }),
3710 pending_bracket_legs_.end());
3711 pending_coof_requests_.erase(
3712 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
3713 [&](const PendingCoofRequest& row) { return matches(row.snapshot); }),
3714 pending_coof_requests_.end());
3715 source_shadow_pending_.erase(
3716 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
3717 [&](const SourceShadowPending& row) { return matches(row.snapshot); }),
3718 source_shadow_pending_.end());
3719 }
3720
3721 std::vector<native_order::RequestHandle> handles;
3722 if (!dormant_rows_only) {
3723 for (const auto& handle : live_handles_) {
3724 const auto found = placement_.find(handle.incarnation);
3725 if (found != placement_.end() && matches(found->second))
3726 handles.push_back(handle);
3727 }
3728 for (const auto& handle : handles) {
3729 const auto result = require_host().cancel(handle);
3730 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
3731 }
3732 for (auto family = bracket_families_.begin(); family != bracket_families_.end();) {
3733 family->second.remove_all(handles);
3734 if (family->second.empty()) family = bracket_families_.erase(family);
3735 else ++family;
3736 }
3737 }
3738 refresh_pending_view();
3739}
3740
3741// R4-D L10z review fix 6: the exit's placement row is selected by the
3742// (exit id, from_entry) pair, so two exits sharing a source id over different
3743// entries no longer tie-break on the wrong command sequence. The pair-less
3744// minimum is kept as a documented fallback for a re-issued exit whose row was
3745// bound to no entry (it preserves the historical ordering of those rows);
3746// UINT64_MAX is returned only when the exit id has no placement row at all.
3747// ab9714be pine_fills.cpp:664-670: candidate exit fills tie-break by order creation sequence created_seq
3749 const SourceId& exit_id, const SourceId& from_entry) const noexcept {
3750 // The same-bar exit sort asks this on every script bar while its tail
3751 // group persists; the table answers from its per-id index instead of a
3752 // scan over every placement ever retained (quadratic over a long run).
3753 return placement_.command_sequence_for(exit_id, from_entry);
3754}
3755
3756bool PineExecutionAdapter::is_open_phase_exit(std::size_t trade_index) const noexcept {
3757 return trade_index < trade_exit_phase_.size()
3758 && trade_exit_phase_[trade_index] == static_cast<std::uint8_t>(NativePathPhase::Open);
3759}
3760
3762 const std::vector<std::size_t>& indices) {
3763 const auto none = static_cast<std::uint8_t>(NativePathPhase::None);
3764 std::vector<std::uint8_t> permuted;
3765 permuted.reserve(indices.size());
3766 for (const std::size_t index : indices) {
3767 permuted.push_back(index < trade_exit_phase_.size() ? trade_exit_phase_[index] : none);
3768 }
3769 for (std::size_t i = 0; i < permuted.size(); ++i) {
3770 const std::size_t target = start + i;
3771 if (target >= trade_exit_phase_.size()) {
3772 if (permuted[i] == none) continue;
3773 trade_exit_phase_.resize(target + 1, none);
3774 }
3775 trade_exit_phase_[target] = permuted[i];
3776 }
3777}
3778
3780 auto& host = require_host();
3781 const auto state = host.native_state();
3782 std::optional<std::uint64_t> terminal_high_water;
3783 if (event_high_water_reader_ && terminal_receipt_high_water_reader_
3784 && (!state.spec || state.spec->intrabar.is_none())) {
3785 terminal_high_water = terminal_receipt_high_water_reader_(host);
3786 const std::uint64_t event_high_water = event_high_water_reader_(host);
3787 if (event_high_water <= receipt_cursor_) {
3788 terminal_receipt_cursor_ = std::max(
3789 terminal_receipt_cursor_, *terminal_high_water);
3790 return;
3791 }
3792 }
3793 const auto rows = host.native_events(receipt_cursor_);
3794 for (const auto& row : rows) {
3795 receipt_cursor_ = std::max(receipt_cursor_, row.ordinal);
3796 if (!row.command) continue;
3797 std::visit([&](const auto& event) {
3798 using Event = std::decay_t<decltype(event)>;
3799 // ab9714be pine_fills.cpp:7464-7468 and 355-359: a stale exit whose
3800 // position was already closed has no effect and is removed/retired.
3801 if constexpr (std::is_same_v<Event, native_order::MatchRejectedEvent>
3802 || std::is_same_v<Event, native_order::CancelledEvent>
3803 || std::is_same_v<Event, native_order::NoEffectEvent>) {
3804 // An anchored relative leg whose parent ended without a fill
3805 // left with it (OwnerGone); its queued definition waits for
3806 // the next parent of that id exactly as before.
3807 const auto ended = std::remove_if(
3808 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
3809 [&](const AnchoredRelativeLeg& leg) {
3810 return leg.handle == event.handle();
3811 });
3812 anchored_relative_stats_.withdrawn += static_cast<std::uint64_t>(
3813 std::distance(ended, anchored_relative_legs_.end()));
3814 anchored_relative_legs_.erase(ended, anchored_relative_legs_.end());
3815 const auto placement = placement_.find(event.handle().incarnation);
3816 if (placement != placement_.end()) {
3817 if constexpr (std::is_same_v<Event, native_order::MatchRejectedEvent>) {
3818 suspend_declined_reversal_brackets(event);
3819 }
3820 const auto handle = event.handle();
3821 const bool opening = placement->second.opening;
3822 placement->second.cancellation = {
3823 std::is_same_v<Event, native_order::CancelledEvent>
3825 1, 0, handle.incarnation,
3826 static_cast<std::int64_t>(placement->second.source_sequence),
3827 handle.incarnation, placement->second.placement_cycle,
3828 placement->second.legs.revision(),
3829 placement->second.requested_qty, kNaN};
3830 retire(handle);
3831 if (opening) cancel_bracket_origin(handle);
3832 }
3833 } else if constexpr (std::is_same_v<Event, native_order::ActivatedEvent>) {
3834 if (std::holds_alternative<native_order::StopLimitLive>(event.after)) {
3835 const auto placement = placement_.find(event.definition->handle.incarnation);
3836 if (placement != placement_.end()) placement->second.stop_limit_activated = true;
3837 }
3838 } else if constexpr (std::is_same_v<Event, native_order::ReservationReducedEvent>) {
3839 const auto placement = placement_.find(event.recipient.incarnation);
3840 if (placement != placement_.end()) {
3841 if (const auto* remaining = std::get_if<native_order::RemainingProjectionUnits>(
3842 &event.after)) {
3843 placement->second.projection_remaining_qty = remaining->q;
3844 }
3845 }
3846 } else if constexpr (std::is_same_v<Event, native_order::ExecutionAppliedEvent>) {
3847 if (event.terminal) cancel_bracket_siblings(event.handle());
3848 }
3849 }, *row.command);
3850 }
3851 if (terminal_high_water) {
3852 terminal_receipt_cursor_ = std::max(
3853 terminal_receipt_cursor_, *terminal_high_water);
3854 }
3855}
3856
3857native_order::Owner PineExecutionAdapter::owner_for_close(const SourceId& id, bool dynamic) const {
3858 // A global strategy.exit has no source-id cohort. Its source policy is
3859 // HostSized, but the generic book authority remains the whole physical
3860 // position rather than a synthetic empty cohort.
3861 if (id.empty()) return native_order::Independent{};
3862 // pine_fills.cpp:7669-7675 uses from_entry as a position-cycle existence
3863 // gate under FIFO; the actual reduction still consumes the global physical
3864 // roster. ANY alone selects the named cohort as the settlement scope.
3865 const auto found = cohorts_by_id_.find(id);
3866 if (!config_.close_entries_rule_any) {
3867 // Before the named parent has opened, retain the generic cohort's
3868 // NoTarget deferral. Once that parent is live, FIFO settles against
3869 // the global book rather than the named cohort.
3870 if (found == cohorts_by_id_.end() || !(cohort_exposure_for(id) > 0.0)) {
3872 found == cohorts_by_id_.end()
3873 ? const_cast<PineExecutionAdapter*>(this)->cohort_for(id)
3874 : found->second.handle};
3875 }
3876 return native_order::Independent{};
3877 }
3878 // A bracket born by the first-open COOF callback already has one durable
3879 // opening receipt. Bind that exact roster at the callback boundary so its
3880 // next real magnifier tick can consume it; later/deferred source commands
3881 // retain the growing cohort owner.
3882 if (dynamic && coof_recalc_active_ && coof_first_open_
3883 && found != cohorts_by_id_.end() && !found->second.opened.empty()) {
3884 return native_order::BindOpenings{found->second.opened, found->second.cycle};
3885 }
3886 if (dynamic || found == cohorts_by_id_.end()) {
3887 if (found == cohorts_by_id_.end())
3888 return native_order::BindCohort{const_cast<PineExecutionAdapter*>(this)->cohort_for(id)};
3889 return native_order::BindCohort{found->second.handle};
3890 }
3891 return native_order::BindOpenings{found->second.opened, found->second.cycle};
3892}
3893
3894void PineExecutionAdapter::stage_flat_children_before_parent(
3895 const SourceId& parent_id, std::int32_t created_bar,
3896 std::int64_t script_open_ms) {
3897 std::vector<native_order::RequestHandle> children;
3898 for (const auto& handle : live_handles_) {
3899 const auto found = placement_.find(handle.incarnation);
3900 if (found == placement_.end()) continue;
3901 const auto& child = found->second;
3902 const bool bracket = child.family == PineOrderFamily::ExitLimit
3903 || child.family == PineOrderFamily::ExitStop;
3904 if (bracket && child.from_entry == parent_id
3905 && child.projection_position_side
3906 == static_cast<std::int32_t>(PositionSide::FLAT)
3907 && child.projection_created_bar == created_bar
3908 && child.placement_script_open_ms == script_open_ms) {
3909 children.push_back(handle);
3910 }
3911 }
3912 for (const auto& handle : children) {
3913 const auto found = placement_.find(handle.incarnation);
3914 if (found == placement_.end()) continue;
3915 PlacementSnapshot snapshot = found->second;
3916 native_order::Request request;
3917 request.intent = native_order::HostSized{
3918 native_order::HostSizedKind::Close, std::nullopt};
3919 request.label = snapshot.source_id;
3920 request.comment = snapshot.comment;
3921 if (snapshot.family == PineOrderFamily::ExitLimit) {
3922 request.trigger = native_order::Limit{snapshot.exit_levels.limit};
3923 } else {
3924 request.trigger = native_order::Stop{snapshot.exit_levels.stop};
3925 }
3926 request.owner = owner_for_close(snapshot.from_entry, true);
3927 const std::string group_name = snapshot.oca_name.empty()
3928 ? snapshot.source_id + "\x1f" + snapshot.from_entry
3929 : snapshot.oca_name;
3930 request.group = group_for(group_name, 1);
3931 snapshot.defer_until_post_parent_calculation = true;
3932 const SourceId replacement_key = snapshot.source_id + "\x1f"
3933 + snapshot.from_entry + std::to_string(static_cast<int>(snapshot.family));
3934 const auto cancelled = require_host().cancel(handle);
3935 if (cancelled.status != native_order::CancelStatus::Cancelled) continue;
3936 retire(handle);
3937 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
3938 [&](const PendingBracketLeg& row) {
3939 return row.replacement_key == replacement_key;
3940 });
3941 PendingBracketLeg staged{std::move(request), std::move(snapshot),
3942 replacement_key,
3943 key_for(found->second.source_id,
3944 found->second.from_entry)};
3945 if (queued == pending_bracket_legs_.end()) {
3946 pending_bracket_legs_.push_back(std::move(staged));
3947 } else {
3948 *queued = std::move(staged);
3949 }
3950 }
3951 refresh_pending_view();
3952}
3953
3956 const NativeDecisionContext& context, bool first_open,
3957 std::uint64_t source_fill_sequence) {
3958 coof_recalc_active_ = true;
3959 coof_first_open_ = first_open;
3960 coof_current_fill_seq_ = source_fill_sequence;
3961 coof_fill_cursor_t_ = event.cursor.t;
3962 coof_market_entry_recalc_fill_seq_ = source_fill_sequence;
3963 coof_market_entry_recalc_incarnation_ = 0;
3964 if (event.opened_units != 0.0
3965 && std::holds_alternative<native_order::Market>(event.request().trigger)) {
3966 const auto placement = placement_.find(event.handle().incarnation);
3967 if (placement != placement_.end()
3968 && placement->second.family == PineOrderFamily::Entry) {
3969 coof_market_entry_recalc_incarnation_ = event.handle().incarnation;
3970 }
3971 }
3972 coof_context_ = context;
3973}
3974
3976 coof_recalc_active_ = false;
3977 coof_first_open_ = false;
3978 coof_market_entry_recalc_incarnation_ = 0;
3979 coof_market_entry_recalc_fill_seq_ = 0;
3980 coof_current_fill_seq_ = 0;
3981 coof_fill_cursor_t_ = kNaN;
3982 coof_context_ = {};
3983}
3984
3987 const NativeDecisionContext& context) const noexcept {
3988 if (!host_ || !config_.calc_on_order_fills || config_.process_orders_on_close
3989 || stream_mode_ || config_.pyramiding != 0 || config_.close_entries_rule_any
3990 || config_.slippage != 0 || config_.commission_value != 0.0
3991 || staged_.account_fx != 1.0 || !staged_.account_fx_effective_from_ms.empty()
3992 || risk_.max_intraday_loss != 0.0 || risk_.max_drawdown != 0.0
3993 || risk_.max_cons_loss_days != 0 || cap.active()
3994 || context.coordinate.path_phase != NativePathPhase::Low
3995 || !policy_script_bar_valid_ || event.closed_units <= 0.0
3996 || host_->physical_position().signed_units <= 0.0) {
3997 return false;
3998 }
3999 const auto state = host_->native_state();
4000 if (state.spec && !state.spec->intrabar.is_none()) return false;
4001 const auto filled = placement_.find(event.handle().incarnation);
4002 if (filled == placement_.end()) return false;
4003 const auto eligible = [&](const PlacementSnapshot& row) {
4004 return row.family == PineOrderFamily::ExitStop
4005 && row.projection_created_bar < context.coordinate.interval_index
4006 && row.from_entry == filled->second.from_entry && !row.from_entry.empty()
4007 && std::isfinite(row.requested_qty) && row.requested_qty > 0.0
4008 && row.oca_name.empty() && std::isnan(row.exit_levels.trail_points)
4009 && std::isnan(row.exit_levels.trail_price)
4010 && std::isfinite(row.exit_levels.stop)
4011 && row.exit_levels.stop <= event.resolved_price
4012 && row.exit_levels.stop >= policy_script_bar_.low;
4013 };
4014 if (!eligible(filled->second)) return false;
4015 return std::any_of(live_handles_.begin(), live_handles_.end(),
4016 [&](const native_order::RequestHandle& handle) {
4017 if (handle == event.handle()) return false;
4018 const auto sibling = placement_.find(handle.incarnation);
4019 return sibling != placement_.end() && eligible(sibling->second);
4020 });
4021}
4022
4023bool PineExecutionAdapter::defer_coof_tail() const noexcept {
4024 if (!coof_recalc_active_ || coof_first_open_) return false;
4025 const auto state = require_host().native_state();
4026 if (state.spec && state.spec->intrabar.lower()) return false;
4027 const auto phase = coof_context_.coordinate.path_phase;
4028 if (phase == NativePathPhase::Close || phase == NativePathPhase::None)
4029 return true;
4030 if (!coof_script_bar_valid_) return false;
4031 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4032 const NativePathPhase second = high_first
4033 ? NativePathPhase::Low : NativePathPhase::High;
4034 const double endpoint = high_first
4035 ? coof_script_bar_.low : coof_script_bar_.high;
4036 return phase == second && coof_fill_at_path_point(endpoint);
4037}
4038
4039bool PineExecutionAdapter::coof_fill_on_path_point() const noexcept {
4040 // The recalculating fill's matcher cursor sits at a leg end (t 0 or 1),
4041 // i.e. on an O/H/L/C point, rather than inside a leg.
4042 return coof_recalc_active_
4043 && (coof_fill_cursor_t_ == 0.0 || coof_fill_cursor_t_ == 1.0);
4044}
4045
4046bool PineExecutionAdapter::coof_fill_at_path_point(double waypoint) const noexcept {
4047 const auto point = require_host().current_execution_point();
4048 if (!point) return false;
4049 // ab9714be engine.hpp:1264-1266: an on-grid print books one ULP-exact
4050 // tick. An OFF-grid waypoint books bar_fill_price(waypoint)
4051 // (pine_fills.cpp:7962-7966), a tick a mid-leg fill can also book, so
4052 // that tick names the waypoint only for a fill placed on a path point.
4053 const double tick = staged_.syminfo.mintick;
4054 if (source_same_point(point->price, waypoint, tick)) return true;
4055 return finite_positive(tick) && source_bar_fill_tick(waypoint, tick) != waypoint
4056 && source_decimal_tick(point->price, tick) == source_decimal_tick(waypoint, tick)
4057 && coof_fill_on_path_point();
4058}
4059
4060bool PineExecutionAdapter::source_path_uses_high_first(const Bar& bar) const noexcept {
4061 return source_path_high_first(bar, path_order_);
4062}
4063
4064bool PineExecutionAdapter::coof_current_fill_was_forced_waypoint() const noexcept {
4065 // L6b encodes a later COOF MARKET fill as a priced source waypoint. At
4066 // the Applied callback its raw coordinate still names the segment on
4067 // which it was born; advance from the forced waypoint rather than walking
4068 // back to that segment's endpoint (ab9714be pine_scheduler.cpp:398-619).
4069 if (!coof_recalc_active_) return false;
4070 const auto point = require_host().current_execution_point();
4071 if (!point) return false;
4072 for (const auto& id : cohort_order_) {
4073 const auto cohort = cohorts_by_id_.find(id);
4074 if (cohort == cohorts_by_id_.end()) continue;
4075 for (const auto& opening : cohort->second.opened) {
4076 const auto units = cohort->second.live_units_by_origin.find(
4077 opening.incarnation);
4078 if (units == cohort->second.live_units_by_origin.end()
4079 || !(units->second > 0.0)) {
4080 continue;
4081 }
4082 const auto snapshot = placement_.find(opening.incarnation);
4083 if (snapshot != placement_.end() && snapshot->second.opening
4084 && snapshot->second.projection_created_during_coof
4085 && snapshot->second.placement_script_open_ms
4086 == coof_context_.script_bar_open_ms
4087 && same_double_bits(snapshot->second.forced_execution_price,
4088 point->price)) {
4089 return true;
4090 }
4091 }
4092 }
4093 return false;
4094}
4095
4096double PineExecutionAdapter::coof_next_waypoint(int* path_index) const noexcept {
4097 // path_index (optional) receives the returned waypoint's historical path
4098 // index (1/2 = extreme W1/W2, 3 = C), or -1 when it is not an O/H/L/C
4099 // point of the chart bar (lower-timeframe path, no waypoint).
4100 if (path_index) *path_index = -1;
4101 if (!coof_recalc_active_ || !coof_script_bar_valid_) return kNaN;
4102 const auto state = require_host().native_state();
4103 if (state.spec && state.spec->intrabar.lower()) {
4104 if (const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host())) {
4105 const auto point = require_host().current_execution_point();
4106 const auto next = pine_host->scheduler_.next_input_waypoint(
4107 coof_context_, point ? point->price : kNaN,
4108 state.spec->path_order);
4109 if (next) return *next;
4110 }
4111 }
4112 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4113 const NativePathPhase path_phase[] = {
4114 NativePathPhase::Open,
4115 high_first ? NativePathPhase::High : NativePathPhase::Low,
4116 high_first ? NativePathPhase::Low : NativePathPhase::High,
4117 NativePathPhase::Close,
4118 };
4119 const double path_price[] = {
4120 coof_script_bar_.open,
4121 high_first ? coof_script_bar_.high : coof_script_bar_.low,
4122 high_first ? coof_script_bar_.low : coof_script_bar_.high,
4123 coof_script_bar_.close,
4124 };
4125 const bool forced_waypoint = coof_current_fill_was_forced_waypoint();
4126 for (int index = 0; index < 4; ++index) {
4127 if (path_phase[index] != coof_context_.coordinate.path_phase) continue;
4128 const auto point = require_host().current_execution_point();
4129 // ab9714be pine_fills.cpp:7962-7966 and pine_scheduler.cpp:548-563: a
4130 // fill at a waypoint POINT books bar_fill_price(waypoint), the
4131 // nearest-tick rounding of the raw OHLC price, so the applied price
4132 // identifies that point only on the tick grid. source_bar_fill_tick
4133 // keeps an on-grid n * mintick booking one ULP off the waypoint's
4134 // decimal print, so compare the decimal grid forms.
4135 // An OFF-grid waypoint's booked tick is also reachable mid-leg (a
4136 // limit at 13.26 on the H->L 13.255 leg), so it names the point
4137 // only for a fill the matcher placed on a path point.
4138 const double tick = staged_.syminfo.mintick;
4139 const bool on_grid_waypoint = finite_positive(tick)
4140 && source_bar_fill_tick(path_price[index], tick) == path_price[index];
4141 const bool at_waypoint = point && finite_positive(tick)
4142 ? source_decimal_tick(point->price, tick)
4143 == source_decimal_tick(path_price[index], tick)
4144 && (on_grid_waypoint || coof_fill_on_path_point())
4145 : point && point->price == path_price[index];
4146 if (index > 0 && point && finite_positive(point->price)
4147 && !at_waypoint && !forced_waypoint) {
4148 if (path_index) *path_index = index;
4149 return path_price[index];
4150 }
4151 if (path_index && index < 3) *path_index = index + 1;
4152 return index < 3 ? path_price[index + 1] : kNaN;
4153 }
4154 return kNaN;
4155}
4156
4157double PineExecutionAdapter::next_coof_waypoint_price() const noexcept {
4158 if (!coof_recalc_active_ || !coof_script_bar_valid_) return kNaN;
4159 const auto state = require_host().native_state();
4160 // A35's generic remaining-path cursor already owns real lower-timeframe
4161 // geometry. Keep the newborn MARKET request unpriced so it advances to
4162 // the next retained sub-bar point instead of collapsing that path onto
4163 // the enclosing script bar's four OHLC waypoints.
4164 if (state.spec && state.spec->intrabar.lower()) return kNaN;
4165 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4166 const NativePathPhase first_extreme = high_first
4167 ? NativePathPhase::High : NativePathPhase::Low;
4168 const auto phase = coof_context_.coordinate.path_phase;
4169 if (phase == NativePathPhase::Open)
4170 return high_first ? coof_script_bar_.high : coof_script_bar_.low;
4171 if (phase == first_extreme)
4172 return high_first ? coof_script_bar_.low : coof_script_bar_.high;
4173 return kNaN;
4174}
4175
4176bool PineExecutionAdapter::coof_remaining_recrosses(
4177 double level, bool long_position) const noexcept {
4178 if (!finite_positive(level) || !coof_recalc_active_ || !coof_script_bar_valid_)
4179 return false;
4180 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4181 const NativePathPhase path_phase[] = {
4182 NativePathPhase::Open,
4183 high_first ? NativePathPhase::High : NativePathPhase::Low,
4184 high_first ? NativePathPhase::Low : NativePathPhase::High,
4185 NativePathPhase::Close,
4186 };
4187 const double path_price[] = {
4188 coof_script_bar_.open,
4189 high_first ? coof_script_bar_.high : coof_script_bar_.low,
4190 high_first ? coof_script_bar_.low : coof_script_bar_.high,
4191 coof_script_bar_.close,
4192 };
4193 const bool forced_waypoint = coof_current_fill_was_forced_waypoint();
4194 for (int index = 0; index < 4; ++index) {
4195 if (path_phase[index] != coof_context_.coordinate.path_phase) continue;
4196 const auto point = require_host().current_execution_point();
4197 int first = index + 1;
4198 if (index > 0 && point && finite_positive(point->price)
4199 && !source_same_point(point->price, path_price[index], staged_.syminfo.mintick)
4200 && !forced_waypoint) {
4201 first = index;
4202 }
4203 bool crossed_adverse = false;
4204 for (int cursor = first; cursor < 4; ++cursor) {
4205 if (long_position) {
4206 if (path_price[cursor] < level) crossed_adverse = true;
4207 else if (crossed_adverse && path_price[cursor] >= level) return true;
4208 } else {
4209 if (path_price[cursor] > level) crossed_adverse = true;
4210 else if (crossed_adverse && path_price[cursor] <= level) return true;
4211 }
4212 }
4213 return false;
4214 }
4215 return false;
4216}
4217
4218void PineExecutionAdapter::flush_coof_tail(
4219 bool openings_only, bool include_next_open) {
4220 auto queued = std::move(pending_coof_requests_);
4221 pending_coof_requests_.clear();
4222 for (auto& pending : queued) {
4223 if ((pending.next_open && !include_next_open)
4224 || (openings_only && (!pending.opening
4225 || std::holds_alternative<native_order::Market>(
4226 pending.request.trigger)))) {
4227 pending_coof_requests_.push_back(std::move(pending));
4228 continue;
4229 }
4230 const auto accepted = submit_or_replace(std::move(pending.request), std::move(pending.snapshot),
4231 pending.opening, pending.replacement_key);
4232 if (accepted && pending.family_key != 0)
4233 bracket_families_[pending.family_key].push_back(*accepted);
4234 }
4235}
4236
4237void PineExecutionAdapter::entry(const SourceId& id, bool is_long, double limit_price,
4238 double stop_price, double qty, const std::string& comment,
4239 const std::string& oca_name, int oca_type, int qty_type) {
4240 native_order::Request request;
4241 const bool default_sized = std::isnan(qty);
4242 const bool priced = !std::isnan(limit_price) || !std::isnan(stop_price);
4243 const bool explicit_fixed = !default_sized
4244 && (qty_type < 0 || qty_type == static_cast<int>(QtyType::FIXED));
4245 const double normalized_qty = explicit_fixed
4246 ? floor_quantity_grid(std::abs(qty), staged_.quantity_grid) : qty;
4247 const double signed_target = is_long ? normalized_qty : -normalized_qty;
4248 const double current = require_host().physical_position().signed_units;
4249 const auto source_point = require_host().current_execution_point();
4250 double flat_pending_opposite_market_units = 0.0;
4251 if (!source_point
4252 && require_host().native_state().kind == NativeLifecycleKind::Unconfigured) {
4253 // The historical source host allowed constructor-time commands to be
4254 // inspected before a run. They have no native decision coordinate
4255 // yet, so retain only their source command projection; the native
4256 // request core is not invoked outside an admitted begin.
4257 request.intent = default_sized
4259 native_order::HostSizedKind::Open,
4260 is_long ? native_order::Side::Long : native_order::Side::Short}}
4262 request.label = id;
4263 request.comment = comment;
4264 request.trigger = trigger_for(limit_price, stop_price);
4265 request.group = group_for(oca_name, oca_type);
4266 PlacementSnapshot snapshot;
4267 snapshot.family = PineOrderFamily::Entry;
4268 snapshot.source_id = id;
4269 snapshot.comment = comment;
4270 snapshot.oca_name = oca_name;
4271 snapshot.oca_type = oca_type;
4272 snapshot.qty_type = qty_type;
4273 snapshot.requested_qty = normalized_qty;
4274 snapshot.is_long = is_long;
4275 snapshot.opening = true;
4276 snapshot.deferred_cohort = default_sized;
4277 snapshot.command_ordinal = ++command_ordinal_;
4278 snapshot.command_sequence = ++source_command_sequence_;
4279 snapshot.source_sequence = ++source_sequence_;
4280 snapshot.exit_levels.limit = limit_price;
4281 snapshot.exit_levels.stop = stop_price;
4282 snapshot.sizing = sizing_snapshot();
4283 pending_entries_.push_back(
4284 {std::move(request), std::move(snapshot), id});
4285 return;
4286 }
4287 if (source_point) {
4288 const int bar = source_point->decision.coordinate.interval_index;
4289 if (entry_attempt_bar_ != bar) {
4290 entry_attempt_bar_ = bar;
4291 entry_attempts_on_bar_ = 0;
4292 }
4293 if (entry_attempts_on_bar_ != std::numeric_limits<std::uint32_t>::max())
4294 ++entry_attempts_on_bar_;
4295 }
4296 bool close_precedes_entry = pending_same_bar_close_qty_ > 0.0;
4297 const double preceding_close_qty = pending_same_bar_close_qty_;
4298 native_order::RequestHandle preceding_close_request{};
4299 if (source_point) {
4300 for (auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
4301 const auto& handle = *it;
4302 const auto prior = placement_.find(handle.incarnation);
4303 if (prior == placement_.end()) continue;
4304 const auto& row = prior->second;
4305 if ((row.family == PineOrderFamily::Close
4306 || row.family == PineOrderFamily::CloseAll)
4307 && !row.immediately
4308 && row.placement_script_open_ms
4309 == source_point->decision.script_bar_open_ms) {
4310 close_precedes_entry = true;
4311 preceding_close_request = handle;
4312 break;
4313 }
4314 }
4315 }
4316 limit_price = source_level_on_price_grid(limit_price, staged_.syminfo.mintick);
4317 stop_price = source_level_on_price_grid(stop_price, staged_.syminfo.mintick);
4318 const bool pure_stop_entry = std::isnan(limit_price)
4319 && finite_positive(stop_price);
4320 if (intraday_loss_orders_blocked()
4321 || (source_point && cap_placement_denied(source_point->decision))) {
4322 return;
4323 }
4324 if (pure_stop_entry && preceding_close_qty > 0.0
4325 && !pending_same_bar_commands_.empty()) {
4326 // The fixed-default batching path had retained the earlier close
4327 // outside the core. Publish it before the later priced entry so the
4328 // native book receives the legacy source statement order.
4329 flush_pending_same_bar_commands();
4330 if (source_point) {
4331 for (auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
4332 const auto prior = placement_.find(it->incarnation);
4333 if (prior == placement_.end()
4334 || prior->second.family != PineOrderFamily::Close
4335 || prior->second.placement_script_open_ms
4336 != source_point->decision.script_bar_open_ms) {
4337 continue;
4338 }
4339 preceding_close_request = *it;
4340 break;
4341 }
4342 }
4343 }
4344 if (config_.calc_on_order_fills && priced && !coof_recalc_active_
4345 && source_point && current != 0.0 && ((current > 0.0) == is_long)) {
4346 std::vector<native_order::RequestHandle> stale_recalc_entries;
4347 for (const auto& handle : live_handles_) {
4348 const auto prior = placement_.find(handle.incarnation);
4349 if (prior == placement_.end()) continue;
4350 const auto& row = prior->second;
4351 if (row.opening && row.family == PineOrderFamily::Entry
4352 && row.is_long == is_long && row.projection_created_during_coof
4353 && row.projection_created_bar
4354 == source_point->decision.coordinate.interval_index) {
4355 stale_recalc_entries.push_back(handle);
4356 }
4357 }
4358 for (const auto& handle : stale_recalc_entries) {
4359 const auto cancelled = require_host().cancel(handle);
4360 if (cancelled.status == native_order::CancelStatus::Cancelled)
4361 retire(handle);
4362 }
4363 }
4364 // Explicit entry quantities have a source placement-time admission
4365 // boundary. In particular, non-finite units and finite values whose
4366 // required margin overflows must never become a live generic request that
4367 // waits until a later matching point to be rejected.
4368 if (!default_sized) {
4369 if (!std::isfinite(qty)) return;
4370 const bool opposite_live = current != 0.0 && ((current > 0.0) != is_long);
4371 if (!opposite_live) {
4372 // Admission is a source command fact at the signal mark; a
4373 // priced entry's later trigger/gap check remains at fill time.
4374 const double mark = source_point ? source_point->price : kNaN;
4375 const double margin = is_long ? config_.margin_long : config_.margin_short;
4376 const double fx = source_point
4377 ? active_staged_fx(source_point->decision.sub_bar_open_ms) : staged_.account_fx;
4378 const double equity = source_point
4379 ? require_host().native_marked_equity(source_point->price) : kNaN;
4380 const double required = std::abs(normalized_qty) * mark
4381 * staged_.syminfo.pointvalue * fx * margin / 100.0;
4382 // R4-D L10ad: ab9714be pine_strategy_commands.cpp:344-346 gates the
4383 // whole placement affordability half on margin_pct > 0.0 ("margin_pct
4384 // == 0 disables the check, as it does in TradingView"). A strategy
4385 // declared with margin_long=0 / margin_short=0 therefore keeps taking
4386 // explicit-qty entries after its equity has gone negative; without the
4387 // gate required == 0 > equity rejected every later entry once the
4388 // account traded below zero (NQ1 ORB probe: flat after trade #73).
4389 if (finite_positive(margin) && margin <= 100.0
4390 && (!std::isfinite(required) || !std::isfinite(equity)
4391 || required > equity)) {
4392 if (pure_stop_entry) {
4393 std::optional<native_order::RequestHandle> prior_handle;
4394 if (const auto prior = live_by_source_key_.find(key_for(id));
4395 prior != live_by_source_key_.end()) {
4396 prior_handle = prior->second;
4397 }
4398 if (prior_handle) {
4399 const auto result = require_host().cancel(*prior_handle);
4400 if (result.status == native_order::CancelStatus::Cancelled)
4401 retire(*prior_handle);
4402 }
4403 }
4404 return;
4405 }
4406 }
4407 }
4408 if (explicit_fixed && normalized_qty == 0.0 && current == 0.0 && !priced) {
4409 PlacementSnapshot shadow;
4411 shadow.source_id = id;
4412 shadow.comment = comment;
4413 shadow.oca_name = oca_name;
4414 shadow.oca_type = oca_type;
4415 shadow.qty_type = qty_type;
4416 shadow.requested_qty = 0.0;
4417 shadow.is_long = is_long;
4418 shadow.opening = true;
4419 shadow.command_ordinal = ++command_ordinal_;
4420 shadow.command_sequence = ++source_command_sequence_;
4421 shadow.source_sequence = ++source_sequence_;
4422 shadow.projection_created_bar = source_point
4423 ? source_point->decision.coordinate.interval_index : -1;
4424 shadow.sizing = sizing_snapshot();
4425 source_shadow_pending_.push_back({std::move(shadow), id});
4426 return;
4427 }
4428 if (current == 0.0 && priced && explicit_fixed) {
4429 for (const auto& pending : pending_same_bar_commands_) {
4430 const auto& candidate = pending.snapshot;
4431 if (!pending.opening || candidate.family != PineOrderFamily::Entry
4432 || candidate.is_long == is_long
4433 || !candidate.frozen_market_instruction
4434 || !finite_positive(candidate.frozen_market_own_units)) {
4435 continue;
4436 }
4437 flat_pending_opposite_market_units += candidate.frozen_market_own_units;
4438 }
4439 if (flat_pending_opposite_market_units > 0.0
4440 && pending_same_bar_commands_.size() == 1U)
4441 flush_pending_same_bar_commands();
4442 else if (pending_same_bar_commands_.size() != 1U)
4443 flat_pending_opposite_market_units = 0.0;
4444 }
4445 // A later source entry is outside a previously captured POOC global-exit
4446 // population. Keep the native close live, but stop advertising it as a
4447 // full-live dynamic reservation.
4448 for (const auto& handle : live_handles_) {
4449 const auto existing = placement_.find(handle.incarnation);
4450 if (existing != placement_.end()
4451 && existing->second.pooc_global_full_exit_dynamic_qty) {
4452 existing->second.pooc_global_full_exit_dynamic_qty = false;
4453 existing->second.pooc_global_full_exit_tracks_bound_adds = false;
4454 }
4455 }
4456 const bool short_seed_final_candidate = current < 0.0 && !is_long
4457 && short_seed_long_candidate_.incarnation != 0;
4458 const bool opposite_opening_pending = std::any_of(live_handles_.begin(), live_handles_.end(),
4459 [&](const native_order::RequestHandle& handle) {
4460 const auto existing = placement_.find(handle.incarnation);
4461 return existing != placement_.end() && existing->second.opening
4462 && existing->second.family == PineOrderFamily::Entry
4463 && existing->second.is_long != is_long;
4464 });
4465 // ab9714be src/compat/pine/market_admission.cpp:33-37: the explicit
4466 // flat-pair scope (explicit_pair_scope) carries no commission term;
4467 // a commissioned pyramiding=2 pair keeps the gross transaction.
4468 const bool p2_flat_market_candidate = current == 0.0
4469 && config_.pyramiding == 2 && !config_.process_orders_on_close
4470 && !config_.calc_on_order_fills && !coof_recalc_active_
4471 && config_.default_qty_type == static_cast<int>(QtyType::FIXED)
4472 && config_.slippage == 0
4473 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
4474 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
4475 && risk_.max_position_size <= 0.0 && !risk_.halted && !cap.active()
4476 && explicit_fixed && !priced && oca_name.empty();
4477 const bool same_bar_market_candidate = (same_bar_market_tx_scope()
4478 || p2_flat_market_candidate)
4479 && !priced && oca_name.empty()
4480 && (qty_type < 0 || qty_type == static_cast<int>(QtyType::FIXED))
4481 && (default_sized || finite_positive(qty));
4482 if (!same_bar_market_candidate && config_.pyramiding == 2
4483 && !pending_same_bar_commands_.empty()) {
4484 source_batch_mutated_ = true;
4485 flush_pending_same_bar_commands();
4486 }
4487 const bool all_in_percent = default_sized && !priced && oca_name.empty()
4488 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
4489 && config_.default_qty_value >= 100.0;
4490 // ab9714be pine_fills.cpp:2789-3020: an all-in default MARKET emitted
4491 // while its side is already at the Pine pyramiding cap remains a broker
4492 // book row until the next opening. A later opposite sibling can make
4493 // that row executable before its turn; otherwise the open-boundary
4494 // adapter retires it without exposing a generic fill.
4495 const bool default_gross_over_cap_candidate = all_in_percent
4496 && std::abs(config_.default_qty_value - 100.0) < 1e-12
4497 && config_.pyramiding == 1 && !config_.process_orders_on_close
4498 && !config_.calc_on_order_fills && config_.slippage == 0
4499 && config_.commission_value == 0.0
4500 && std::abs(config_.margin_long - 100.0) < 1e-12
4501 && std::abs(config_.margin_short - 100.0) < 1e-12
4502 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
4503 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
4504 && risk_.max_position_size <= 0.0 && !risk_.halted && !cap.active();
4505 bool paired_all_in_reentry = false;
4506 if (all_in_percent && current != 0.0 && ((current > 0.0) == is_long) && source_point) {
4507 for (const auto& handle : live_handles_) {
4508 const auto prior = placement_.find(handle.incarnation);
4509 if (prior == placement_.end() || !prior->second.opening
4510 || prior->second.family != PineOrderFamily::Entry
4511 || prior->second.is_long == is_long) {
4512 continue;
4513 }
4514 paired_all_in_reentry = true;
4515 break;
4516 }
4517 }
4518 if (!same_bar_market_candidate && current != 0.0
4519 && ((current > 0.0) == is_long) && config_.pyramiding == 0
4520 && !(priced && config_.process_orders_on_close)) {
4521 // A zero pyramiding setting permits the flat opening but makes a
4522 // same-direction MARKET reissue a source no-op. It must be dropped
4523 // before native matching so IntradayCap's factor-A policy observes no
4524 // fabricated physical fill.
4525 if (source_point) observe_intraday_cap_noop(is_long, source_point->decision);
4526 return;
4527 }
4528 if (!same_bar_market_candidate && config_.pyramiding > 0 && current != 0.0
4529 && ((current > 0.0) == is_long)
4530 && !(priced && config_.process_orders_on_close)) {
4531 std::size_t accepted_in_cycle = 0;
4532 std::vector<SourceId> cohort_ids;
4533 cohort_ids.reserve(cohorts_by_id_.size());
4534 for (const auto& row : cohorts_by_id_) cohort_ids.push_back(row.first);
4535 std::sort(cohort_ids.begin(), cohort_ids.end());
4536 for (const auto& cohort_id : cohort_ids) {
4537 const auto cohort = cohorts_by_id_.find(cohort_id);
4538 if (cohort == cohorts_by_id_.end()) continue;
4539 for (const auto& origin : cohort->second.opened) {
4540 const auto placement = placement_.find(origin.incarnation);
4541 if (placement != placement_.end() && placement->second.is_long == is_long)
4542 ++accepted_in_cycle;
4543 }
4544 }
4545 for (const auto& handle : live_handles_) {
4546 const auto placement = placement_.find(handle.incarnation);
4547 // ab9714be pine_strategy_commands.cpp:446-464: remove_same_id_pending_orders excludes replaced id before pyramiding check
4548 if (placement != placement_.end() && placement->second.opening
4549 && placement->second.source_id != id
4550 && placement->second.is_long == is_long) ++accepted_in_cycle;
4551 }
4552 // Pine's cap is a monotone entry-incarnation count for the current
4553 // position cycle; a partial close does not free a pyramiding slot.
4554 if (!(config_.process_orders_on_close && close_precedes_entry)
4555 && accepted_in_cycle >= static_cast<std::size_t>(config_.pyramiding)
4556 && !short_seed_final_candidate && !paired_all_in_reentry
4557 && !default_gross_over_cap_candidate
4558 && !(config_.process_orders_on_close && opposite_opening_pending)) {
4559 // Source replacement erases the older same-id priced entry before
4560 // judging the replacement's pyramiding admission. A rejected
4561 // over-cap reissue therefore leaves neither the old nor the new
4562 // trigger live (ab9714be:pine_strategy_commands.cpp:302-367).
4563 if (priced) {
4564 const auto prior = live_by_source_key_.find(key_for(id));
4565 if (prior != live_by_source_key_.end()) {
4566 const auto handle = prior->second;
4567 const auto result = require_host().cancel(handle);
4568 if (result.status == native_order::CancelStatus::Cancelled)
4569 retire(handle);
4570 }
4571 }
4572 return;
4573 }
4574 }
4575 const auto current_point = source_point;
4576 const bool close_all_precedes = current_point
4577 && close_all_pending_script_bar_ == current_point->decision.script_bar_open_ms;
4578 native_order::RequestHandle preceding_close_all{};
4579 const bool reverses = current != 0.0 && ((current > 0.0) != is_long) && !close_all_precedes;
4580 if (default_sized
4581 && config_.default_qty_type == static_cast<int>(QtyType::FIXED)
4582 && reverses && !priced && config_.process_orders_on_close
4583 && !close_batch_callsites_.empty()) {
4584 // A later opposite MARKET entry owns the same source transaction.
4585 // The queued close remains a command-boundary observation only; the
4586 // native ReverseTo supplies the one physical close/open settlement.
4587 close_batch_callsites_.clear();
4588 close_batch_bar_ = -1;
4589 close_batch_queue_sequence_ = 0;
4590 close_batch_pending_debt_ = 0.0;
4591 close_batch_admitted_total_ = 0.0;
4592 pending_same_bar_close_qty_ = 0.0;
4593 close_precedes_entry = false;
4594 }
4595 // Default-sized reversal requests are HostSized already; they can carry a
4596 // fill-time close-only shape without changing explicit F7/F8 transaction
4597 // intent. Explicit affordability variants keep their established native
4598 // request shape unless a separately-qualified source family lowers them.
4599 const bool affordability_reversal_candidate = reverses && !priced
4600 && (default_sized
4601 ? (config_.default_qty_type == static_cast<int>(QtyType::FIXED)
4602 || config_.default_qty_type == static_cast<int>(QtyType::CASH)
4603 || (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
4604 && config_.default_qty_value > 100.0))
4605 // A per-call explicit unit quantity still resolves at the terms
4606 // boundary when it reverses: the source affordability check may
4607 // retain only the closing leg regardless of the configured
4608 // default quantity type (ab9714be pine_fills.cpp:5529-5660).
4609 : true);
4610 const bool direction_blocked = (risk_.direction > 0 && !is_long)
4611 || (risk_.direction < 0 && is_long);
4612 if (default_sized && reverses && current_point) {
4613 for (const auto& handle : live_handles_) {
4614 const auto pending = placement_.find(handle.incarnation);
4615 if (pending == placement_.end()) continue;
4616 const auto& prior = pending->second;
4617 if (prior.family == PineOrderFamily::Entry && prior.opening
4618 && !prior.is_long && prior.is_long == is_long && prior.replaced_opening
4619 && prior.replacement_predecessor_market
4620 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
4621 // The replacement's transaction owns this source pass; a
4622 // later same-side default market command remains unfilled.
4623 return;
4624 }
4625 }
4626 }
4627 if (default_sized && reverses && is_long && current_point) {
4628 std::vector<native_order::RequestHandle> superseded_buy_replacements;
4629 for (const auto& handle : live_handles_) {
4630 const auto pending = placement_.find(handle.incarnation);
4631 if (pending == placement_.end()) continue;
4632 const auto& prior = pending->second;
4633 if (prior.family == PineOrderFamily::Entry && prior.opening && prior.is_long
4634 && prior.replaced_opening && prior.replacement_predecessor_market
4635 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
4636 superseded_buy_replacements.push_back(handle);
4637 }
4638 }
4639 for (const auto& handle : superseded_buy_replacements) {
4640 cancel_bracket_origin(handle);
4641 const auto result = require_host().cancel(handle);
4642 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
4643 }
4644 }
4645 const bool cash_sized = qty_type == static_cast<int>(QtyType::CASH);
4646 const bool percent_sized = qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY);
4647 const bool typed_sized = !default_sized && (cash_sized || percent_sized);
4648 const bool fixed_priced_reverse = reverses && !default_sized && priced && !cash_sized;
4649 const bool cash_priced_reverse = reverses && !default_sized && priced && cash_sized;
4650 const bool default_stop_scope = default_sized && pure_stop_entry
4651 && finite_positive(stop_price)
4652 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
4653 && config_.default_qty_value <= 100.0;
4654 double default_stop_sizing_price = kNaN;
4655 if (default_stop_scope && finite_positive(staged_.syminfo.mintick)) {
4656 stop_price = directional_tick(stop_price, staged_.syminfo.mintick, is_long);
4657 const double signal = source_point
4658 ? nearest_tick(source_point->price, staged_.syminfo.mintick) : kNaN;
4659 const bool marketable = finite_positive(signal)
4660 && (is_long ? stop_price <= signal : stop_price >= signal);
4661 // ab9714be pine_strategy_commands.cpp:325-328: pure stop placement sizing price includes directional slippage ticks
4662 const double slip = (is_long ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
4663 default_stop_sizing_price = (marketable ? signal : stop_price) + slip;
4664 }
4665 if (default_sized || typed_sized || direction_blocked || affordability_reversal_candidate) {
4666 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
4667 is_long ? native_order::Side::Long : native_order::Side::Short};
4668 } else if (fixed_priced_reverse || cash_priced_reverse) {
4669 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
4670 is_long ? native_order::Side::Long : native_order::Side::Short};
4671 } else if (reverses) {
4672 request.intent = native_order::ReverseTo{signed_target};
4673 } else {
4674 request.intent = native_order::Transact{signed_target};
4675 }
4676 if (flat_pending_opposite_market_units > 0.0) {
4677 const double transaction = normalized_qty + flat_pending_opposite_market_units;
4678 request.intent = native_order::Transact{is_long ? transaction : -transaction};
4679 }
4680 request.label = id; request.comment = comment;
4681 const auto coof_native_state = require_host().native_state();
4682 const bool coof_lower_path = coof_native_state.spec
4683 && coof_native_state.spec->intrabar.lower();
4684 bool coof_market_next_open = false;
4685 if (coof_recalc_active_ && !coof_first_open_ && !coof_lower_path && !priced) {
4686 bool high_first = std::abs(coof_script_bar_.high - coof_script_bar_.open)
4687 < std::abs(coof_script_bar_.open - coof_script_bar_.low);
4688 if (coof_native_state.spec) {
4689 if (coof_native_state.spec->path_order == NativePathOrder::HighFirst)
4690 high_first = true;
4691 else if (coof_native_state.spec->path_order == NativePathOrder::LowFirst)
4692 high_first = false;
4693 }
4694 const NativePathPhase second = high_first
4695 ? NativePathPhase::Low : NativePathPhase::High;
4696 const double endpoint = high_first ? coof_script_bar_.low : coof_script_bar_.high;
4697 coof_market_next_open = coof_context_.coordinate.path_phase == second
4698 && coof_fill_at_path_point(endpoint);
4699 }
4700 double native_limit = limit_price;
4701 double native_stop = stop_price;
4702 if (finite_positive(limit_price) && !finite_positive(stop_price)) {
4703 // ab9714be pine_policy_members.cpp:11-17 + engine.hpp:1374-1381: every
4704 // entry limit, explicit-qty included, is tested against the
4705 // tick-quantized bar, so an off-grid level triggers on the half-up
4706 // rounded print (NYSE:F ORB 10.845 / 11.425 / 11.89 limits).
4707 native_limit = source_trigger_threshold(
4708 limit_price, staged_.syminfo.mintick, is_long, true);
4709 } else if (finite_positive(stop_price) && !finite_positive(limit_price)
4710 && !config_.calc_on_order_fills) {
4711 native_stop = source_trigger_threshold(
4712 stop_price, staged_.syminfo.mintick, is_long, false);
4713 }
4714 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
4715 const bool nonpositive_priced = priced && pine_host
4716 && pine_host->scheduler_uses_aux_security_feed()
4717 && !finite_positive(native_limit) && !finite_positive(native_stop);
4718 request.trigger = nonpositive_priced
4720 is_long ? std::numeric_limits<double>::min()
4721 : std::numeric_limits<double>::max()}}
4722 : trigger_for(native_limit, native_stop);
4723 double coof_market_fill = kNaN;
4724 if (coof_recalc_active_ && !coof_first_open_ && !coof_market_next_open
4725 && coof_script_bar_valid_
4726 && std::holds_alternative<native_order::Market>(request.trigger)
4727 && !defer_coof_tail()) {
4728 // ab9714be pine_scheduler.cpp:398-619: a MARKET request born by a
4729 // non-first-open fill recalc waits for the next unconsumed waypoint.
4730 // A mid-segment fill retains that segment's endpoint; an endpoint
4731 // fill advances to the following waypoint.
4732 int next_extreme_index = -1;
4733 const double next_extreme = coof_next_waypoint(&next_extreme_index);
4734 const auto point = require_host().current_execution_point();
4735 const double current_quote = point ? point->price : kNaN;
4736 coof_market_fill = source_bar_fill_tick(
4737 next_extreme, staged_.syminfo.mintick)
4738 + (is_long ? 1.0 : -1.0) * config_.slippage
4739 * staged_.syminfo.mintick;
4740 if (finite_positive(coof_market_fill) && finite_positive(current_quote)
4741 && !source_same_point(current_quote, coof_market_fill, staged_.syminfo.mintick)) {
4742 const bool falling = coof_market_fill < current_quote;
4743 // An extreme (W1/W2) target arms at the raw waypoint, so the fill
4744 // lands AT that point (pine_scheduler.cpp:548-563) even when its
4745 // booked tick lies beyond an off-grid extreme or short of it (L
4746 // 11.995 booked 12.00 would otherwise fill mid-leg).
4747 // The booked tick may lie on the wrong side of that raw level,
4748 // so the limit is a touch trigger (fill-through).
4749 const bool extreme_target = next_extreme_index == 1
4750 || next_extreme_index == 2;
4751 const native_order::Limit limit = extreme_target
4752 ? native_order::Limit{next_extreme, coof_market_fill != next_extreme}
4753 : native_order::Limit{coof_market_fill};
4754 if (is_long) {
4755 request.trigger = falling
4756 ? native_order::Trigger{limit}
4758 } else {
4759 request.trigger = falling
4761 : native_order::Trigger{limit};
4762 }
4763 }
4764 // ab9714be pine_fills.cpp:7962-7966 + engine.hpp:1207-1210: the
4765 // waypoint POINT fill books bar_fill_price(waypoint); the trigger
4766 // above keeps the reachable grid level.
4767 coof_market_fill = nearest_tick(next_extreme, staged_.syminfo.mintick)
4768 + (is_long ? 1.0 : -1.0) * config_.slippage
4769 * staged_.syminfo.mintick;
4770 }
4771 if (pure_stop_entry && explicit_fixed && current != 0.0
4772 && ((current > 0.0) != is_long) && source_point
4773 && !config_.process_orders_on_close && !config_.calc_on_order_fills) {
4774 auto* pine_host = dynamic_cast<PineStrategyHost*>(&require_host());
4775 const auto next = pine_host
4776 ? pine_host->scheduler_.next_source_bar(
4777 source_point->decision.coordinate.interval_index)
4778 : std::optional<Bar>{};
4779 if (next) {
4780 for (const auto& handle : live_handles_) {
4781 const auto found = placement_.find(handle.incarnation);
4782 if (found == placement_.end()) continue;
4783 const auto& prior = found->second;
4784 const bool current_exit_limit =
4785 prior.family == PineOrderFamily::ExitLimit
4786 && prior.projection_created_bar
4787 == source_point->decision.coordinate.interval_index
4788 && finite_positive(prior.exit_levels.limit);
4789 const bool both_reached = current > 0.0
4790 ? next->high >= prior.exit_levels.limit && next->low <= stop_price
4791 : next->low <= prior.exit_levels.limit && next->high >= stop_price;
4792 if (!current_exit_limit || !both_reached) continue;
4793 request.intent = native_order::Transact{signed_target};
4794 request.owner = native_order::WaitForApplied{handle};
4795 break;
4796 }
4797 }
4798 }
4799 request.group = group_for(oca_name, oca_type);
4800 if (close_all_precedes && !priced && current_point
4801 && !config_.process_orders_on_close) {
4802 for (auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
4803 const auto close = placement_.find(it->incarnation);
4804 if (close == placement_.end()
4805 || close->second.family != PineOrderFamily::CloseAll
4806 || close->second.placement_script_open_ms
4807 != current_point->decision.script_bar_open_ms) {
4808 continue;
4809 }
4810 preceding_close_all = *it;
4811 break;
4812 }
4813 }
4814 const bool sequenced_close_entry = pure_stop_entry
4815 || (!priced && (!staged_.quantity_grid || *staged_.quantity_grid < 1.0));
4816 if (preceding_close_all.incarnation == 0 && sequenced_close_entry
4817 && current != 0.0 && ((current > 0.0) != is_long))
4818 preceding_close_all = preceding_close_request;
4819 PlacementSnapshot snapshot;
4820 snapshot.family = PineOrderFamily::Entry;
4821 snapshot.source_id = id;
4822 snapshot.comment = comment;
4823 snapshot.oca_name = oca_name;
4824 snapshot.oca_type = oca_type; snapshot.qty_type = qty_type;
4825 snapshot.requested_qty = normalized_qty; snapshot.is_long = is_long;
4826 snapshot.opening = true;
4827 snapshot.paired_reversal_parent = preceding_close_all;
4828 snapshot.command_ordinal = ++command_ordinal_;
4829 snapshot.direction_gate = direction_blocked;
4830 snapshot.deferred_cohort = default_sized;
4831 if (reverses && source_point) {
4832 for (const auto& handle : live_handles_) {
4833 const auto prior = placement_.find(handle.incarnation);
4834 if (prior != placement_.end()
4835 && prior->second.family == PineOrderFamily::Close
4836 && !prior->second.from_entry.empty()
4837 && prior->second.projection_created_bar
4838 == source_point->decision.coordinate.interval_index) {
4839 snapshot.projection_after_close = true;
4840 break;
4841 }
4842 }
4843 }
4844 if (flat_pending_opposite_market_units > 0.0) {
4845 snapshot.paired_flat_market_candidate = true;
4846 snapshot.paired_flat_market_own_qty = normalized_qty;
4848 normalized_qty + flat_pending_opposite_market_units;
4849 }
4850 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
4851 throw std::overflow_error("Pine source command sequence exhausted");
4852 }
4853 snapshot.command_sequence = ++source_command_sequence_;
4854 // Reuse the durable level tuple for the parent trigger facts. A deferred
4855 // relative exit may safely arm from a non-gap LIMIT parent's known entry
4856 // level before that parent is applied.
4857 snapshot.exit_levels.limit = limit_price;
4858 snapshot.exit_levels.stop = stop_price;
4859 snapshot.birth = capture_order_birth();
4861 snapshot.birth, false);
4862 snapshot.reverse_to = reverses || paired_all_in_reentry;
4863 snapshot.projection_after_close = close_precedes_entry;
4864 snapshot.sizing = sizing_snapshot();
4865 if (finite_positive(coof_market_fill))
4866 snapshot.forced_execution_price = coof_market_fill;
4867 if (coof_recalc_active_ && !coof_first_open_ && !coof_lower_path && priced) {
4868 const auto point = require_host().current_execution_point();
4869 const double birth = point ? point->price : kNaN;
4870 const double waypoint = coof_next_waypoint();
4871 bool reached = false;
4872 bool limit_route = false;
4873 if (finite_positive(stop_price)) {
4874 reached = is_long ? (waypoint >= stop_price && birth < stop_price)
4875 : (waypoint <= stop_price && birth > stop_price);
4876 } else if (finite_positive(limit_price)) {
4877 limit_route = true;
4878 reached = is_long ? (waypoint <= limit_price && birth > limit_price)
4879 : (waypoint >= limit_price && birth < limit_price);
4880 }
4881 if (reached) {
4882 const double slipped = waypoint + (limit_route ? 0.0
4883 : (is_long ? 1.0 : -1.0) * config_.slippage
4884 * staged_.syminfo.mintick);
4885 snapshot.forced_execution_price = nearest_tick(
4886 slipped, staged_.syminfo.mintick);
4887 }
4888 }
4889 if (current == 0.0 && priced && current_point) {
4890 const auto is_opposite_market_predecessor = [&](const PlacementSnapshot& prior) {
4891 return prior.opening && prior.family == PineOrderFamily::Entry
4892 && prior.is_long != is_long
4893 && prior.placement_script_open_ms
4894 == current_point->decision.script_bar_open_ms
4895 && !finite_positive(prior.exit_levels.limit)
4896 && !finite_positive(prior.exit_levels.stop)
4897 && !finite_positive(prior.exit_levels.trail_points)
4898 && !finite_positive(prior.exit_levels.trail_price)
4899 && !finite_positive(prior.exit_levels.trail_offset);
4900 };
4901 snapshot.projection_opposite_market_predecessor = std::any_of(
4902 pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
4903 [&](const PendingSameBarCommand& prior) {
4904 return is_opposite_market_predecessor(prior.snapshot);
4905 }) || std::any_of(pending_entries_.begin(), pending_entries_.end(),
4906 [&](const PendingEntry& prior) {
4907 return is_opposite_market_predecessor(prior.snapshot);
4908 }) || std::any_of(live_handles_.begin(), live_handles_.end(),
4909 [&](const native_order::RequestHandle& handle) {
4910 const auto prior = placement_.find(handle.incarnation);
4911 return prior != placement_.end()
4912 && is_opposite_market_predecessor(prior->second);
4913 });
4914 // ab9714be pine_pending_intent.hpp:507-537: placement_has_opposite_market_predecessor reverses against same-bar predecessor
4916 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
4917 is_long ? native_order::Side::Long : native_order::Side::Short};
4918 snapshot.terms_priced_reverse = true;
4919 }
4920 }
4921 if (default_sized && !priced && finite_positive(snapshot.sizing.mark)) {
4922 snapshot.sizing.price = default_market_sizing_price(snapshot.sizing.mark, is_long);
4923 snapshot.sizing.equity = percent_commission_live_equity(snapshot.sizing.mark);
4924 }
4925 const auto predecessor = live_by_source_key_.find(key_for(id));
4926 snapshot.replaced_opening = predecessor != live_by_source_key_.end();
4927 if (snapshot.replaced_opening) {
4928 const auto prior = placement_.find(predecessor->second.incarnation);
4929 snapshot.replacement_predecessor_market = prior != placement_.end()
4930 && !finite_positive(prior->second.exit_levels.limit)
4931 && !finite_positive(prior->second.exit_levels.stop);
4932 }
4933 const bool special_sell_replacement = default_sized && reverses && !is_long
4934 && snapshot.replaced_opening && snapshot.replacement_predecessor_market;
4935 if (special_sell_replacement && current_point) {
4936 std::vector<native_order::RequestHandle> superseded_siblings;
4937 for (const auto& handle : live_handles_) {
4938 const auto pending = placement_.find(handle.incarnation);
4939 if (pending == placement_.end()) continue;
4940 const auto& prior = pending->second;
4941 if (prior.family == PineOrderFamily::Entry && prior.opening
4942 && prior.source_id != id && prior.is_long == is_long
4943 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
4944 superseded_siblings.push_back(handle);
4945 }
4946 }
4947 for (const auto& handle : superseded_siblings) {
4948 cancel_bracket_origin(handle);
4949 const auto result = require_host().cancel(handle);
4950 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
4951 }
4952 std::vector<native_order::RequestHandle> carried_brackets;
4953 std::vector<SourceId> carried_ids;
4954 std::vector<SourceId> cohort_ids;
4955 cohort_ids.reserve(cohorts_by_id_.size());
4956 for (const auto& row : cohorts_by_id_) cohort_ids.push_back(row.first);
4957 std::sort(cohort_ids.begin(), cohort_ids.end());
4958 for (const auto& cohort_id : cohort_ids) {
4959 const auto cohort = cohorts_by_id_.find(cohort_id);
4960 if (cohort == cohorts_by_id_.end()) continue;
4961 for (const auto& origin : cohort->second.opened) {
4962 const auto prior = placement_.find(origin.incarnation);
4963 if (prior != placement_.end() && prior->second.is_long != is_long) {
4964 carried_brackets.push_back(origin);
4965 carried_ids.push_back(cohort_id);
4966 }
4967 }
4968 }
4969 for (const auto& origin : carried_brackets) cancel_bracket_origin(origin);
4970 std::vector<native_order::RequestHandle> dynamic_carried_legs;
4971 for (const auto& handle : live_handles_) {
4972 const auto leg = placement_.find(handle.incarnation);
4973 if (leg == placement_.end()) continue;
4974 const auto family = leg->second.family;
4975 if ((family == PineOrderFamily::ExitLimit || family == PineOrderFamily::ExitStop
4976 || family == PineOrderFamily::ExitTrail)
4977 && std::find(carried_ids.begin(), carried_ids.end(), leg->second.from_entry)
4978 != carried_ids.end()) {
4979 dynamic_carried_legs.push_back(handle);
4980 }
4981 }
4982 for (const auto& handle : dynamic_carried_legs) {
4983 const auto result = require_host().cancel(handle);
4984 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
4985 }
4986 }
4987 snapshot.terms_priced_reverse = fixed_priced_reverse || cash_priced_reverse
4988 || (reverses && typed_sized);
4989 snapshot.placement_cycle = current_position_cycle_;
4990 if (fixed_priced_reverse) {
4991 snapshot.frozen_reversal_transaction = std::abs(current) + normalized_qty;
4992 }
4993 if (default_stop_scope && finite_positive(default_stop_sizing_price)) {
4994 // A default-sized stop entry freezes its quantity against the
4995 // directionally snapped level, except an already-marketable stop
4996 // which is a next-open market order and therefore freezes at the
4997 // source close. Neither path re-sizes at its later fill quote.
4998 snapshot.sizing.price = default_stop_sizing_price;
4999 }
5000 if (default_sized && finite_positive(snapshot.sizing.price)) {
5001 if (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
5002 || config_.default_qty_type == static_cast<int>(QtyType::CASH)) {
5003 snapshot.sizing.frozen_units = default_sizing_units(snapshot.sizing);
5004 }
5005 // ab9714be pine_fills.cpp:7139: non-pure-stop priced entries size at fill time using calc_qty(fill_price)
5006 snapshot.sizing.at_fill = (config_.calc_on_order_fills && coof_recalc_active_)
5007 || (priced && !default_stop_scope);
5008 }
5009 // R5 R2: a declaration-level default quantity whose sizing price IS the
5010 // signal rule and whose quantity is frozen at the command is exactly what
5011 // the core's Sized intent names, so it is lowered onto it here. The
5012 // fill-time paths (at_fill) and the pure-stop path size at a price the
5013 // core cannot name -- the source's own resolved fill quote, and the
5014 // directionally snapped stop level -- and keep their host-resolved shape,
5015 // as does a direction the run's own opening gate would refuse at
5016 // placement. The source keeps its money, its lot floor and its
5017 // placement admission; the core owns the units that settle.
5018 if (default_sized && !priced && !snapshot.sizing.at_fill && !direction_blocked
5019 && finite_positive(snapshot.sizing.frozen_units)) {
5020 const auto* host_shape = std::get_if<native_order::HostSized>(&request.intent);
5021 if (host_shape && host_shape->kind == native_order::HostSizedKind::Open) {
5022 if (auto sized = default_sizing_intent(snapshot.sizing, is_long)) {
5023 request.intent = *sized;
5024 }
5025 }
5026 }
5027 // The TV money band is a source policy, not a generic margin rule. Its
5028 // all-in source tuple is judged at placement on ten-significant-digit
5029 // money: a true-flat order is dropped, while a real reversal retains only
5030 // its closing leg. A later price-scale failure drops the whole command.
5031 // This is the direct lowering of pine_fills.cpp:5054-5139 at ab9714be.
5032 const double entry_margin = is_long ? config_.margin_long : config_.margin_short;
5033 const bool tv_money_scope = default_sized && !priced
5034 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
5035 && std::abs(config_.default_qty_value - 100.0) < 1e-12
5036 && std::abs(entry_margin - 100.0) < 1e-12
5037 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
5038 && finite_positive(snapshot.sizing.frozen_units)
5039 && finite_positive(snapshot.sizing.price)
5040 && finite_positive(snapshot.sizing.equity)
5041 && finite_positive(snapshot.sizing.fx)
5042 && finite_positive(staged_.syminfo.pointvalue)
5043 && (*staged_.quantity_grid * snapshot.sizing.price * staged_.syminfo.pointvalue
5044 * snapshot.sizing.fx < 1.0);
5045 // The POOC flat-money family is a fill-boundary decision because its
5046 // threshold includes the slipped signal price. validate_precommit owns
5047 // that exact comparison; applying the ordinary signal-price gate here
5048 // drops the tight-but-admitted POOC controls before the candidate exists.
5049 if (tv_money_scope && !config_.process_orders_on_close) {
5050 const double notional_per_price = snapshot.sizing.frozen_units
5051 * staged_.syminfo.pointvalue * snapshot.sizing.fx;
5052 const double rounded_cost = source_money_round(notional_per_price * snapshot.sizing.price);
5053 if (snapshot.sizing.equity + 1e-9 < rounded_cost) {
5054 if (reverses && !snapshot.projection_after_close) {
5055 snapshot.affordability_close_only = true;
5056 snapshot.rounded_signal_cost_close_only = true;
5057 } else if (reverses)
5058 snapshot.affordability_close_only = false;
5059 else return;
5060 } else if (!snapshot.projection_after_close) {
5061 const double affordable_price = source_money_round(
5062 source_money_round(snapshot.sizing.equity) / notional_per_price);
5063 if (std::isfinite(affordable_price) && affordable_price < snapshot.sizing.price) return;
5064 }
5065 }
5066 // pine_strategy_commands.cpp:284-426 placement half. A reversal whose
5067 // proposed opening cannot be funded retains a close-only source request;
5068 // flat/same-side rejection remains owned by their ordinary admission path.
5069 const bool affordability_scope = (!priced || pure_stop_entry) && (default_sized
5070 ? (config_.default_qty_type == static_cast<int>(QtyType::FIXED)
5071 || config_.default_qty_type == static_cast<int>(QtyType::CASH)
5072 || (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
5073 && config_.default_qty_value > 100.0))
5074 : true);
5075 snapshot.affordability_policy_active = affordability_scope;
5076 if (affordability_scope && finite_positive(snapshot.sizing.mark)) {
5077 const double margin = is_long ? config_.margin_long : config_.margin_short;
5078 const double signal = nearest_tick(snapshot.sizing.mark, staged_.syminfo.mintick);
5079 double own_units = normalized_qty;
5080 if (default_sized) {
5081 own_units = finite_positive(snapshot.sizing.frozen_units)
5082 ? snapshot.sizing.frozen_units : config_.default_qty_value;
5083 } else if (qty_type == static_cast<int>(QtyType::CASH)) {
5084 const double denominator = signal * staged_.syminfo.pointvalue * snapshot.sizing.fx;
5085 own_units = finite_positive(denominator) ? normalized_qty / denominator : 0.0;
5086 } else if (qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)) {
5087 const double denominator = signal * staged_.syminfo.pointvalue * snapshot.sizing.fx;
5088 own_units = finite_positive(denominator)
5089 ? snapshot.sizing.equity * normalized_qty / 100.0 / denominator : 0.0;
5090 }
5091 const double held = reverses ? 0.0
5092 : std::max(0.0, std::abs(current) - preceding_close_qty);
5093 const double required = (held + std::abs(own_units)) * signal * staged_.syminfo.pointvalue
5094 * snapshot.sizing.fx * margin / 100.0;
5095 const double epsilon = std::max(1e-9, std::abs(snapshot.sizing.equity) * 1e-12);
5096 snapshot.projection_affordability_equity = snapshot.sizing.equity;
5097 snapshot.projection_affordability_signal_price = signal;
5098 snapshot.projection_affordability_held_qty = held;
5099 if (reverses && margin > 0.0 && std::isfinite(required)
5100 && std::isfinite(snapshot.sizing.equity)
5101 && required > snapshot.sizing.equity + epsilon) {
5102 snapshot.affordability_close_only = true;
5103 } else if (!reverses && margin > 0.0
5104 && (!std::isfinite(required) || !std::isfinite(snapshot.sizing.equity)
5105 || required > snapshot.sizing.equity + epsilon)) {
5106 // The same placement-time rule drops an unaffordable flat or
5107 // same-side entry before it reaches the native request core.
5108 // ab9714be pine_strategy_commands.cpp:148-159, :343-426: a
5109 // rejected same-id pure STOP reissue also removes the older
5110 // resting request; a MARKET has no comparable carry.
5111 if (pure_stop_entry) {
5112 std::optional<native_order::RequestHandle> prior_handle;
5113 if (const auto prior = live_by_source_key_.find(key_for(id));
5114 prior != live_by_source_key_.end()) {
5115 prior_handle = prior->second;
5116 }
5117 if (prior_handle) {
5118 const auto result = require_host().cancel(*prior_handle);
5119 if (result.status == native_order::CancelStatus::Cancelled)
5120 retire(*prior_handle);
5121 }
5122 }
5123 return;
5124 }
5125 }
5126 if (default_stop_scope && finite_positive(snapshot.sizing.frozen_units)
5127 && finite_positive(snapshot.sizing.mark)) {
5128 const double margin = is_long ? config_.margin_long : config_.margin_short;
5129 const double required = snapshot.sizing.frozen_units * snapshot.sizing.mark
5130 * staged_.syminfo.pointvalue * snapshot.sizing.fx * margin / 100.0;
5131 // ab9714be pine_strategy_commands.cpp:343-426 prices the default
5132 // percent_of_equity <= 100 pure STOP against placement equity with the
5133 // SAME float guard the explicit/FIXED/CASH/>100 arm uses; an all-in
5134 // stop quantity is floored against tick(close) so its cost lands inside
5135 // one double-rounding of the equity snapshot and must not be dropped.
5136 const double stop_epsilon = std::max(
5137 1e-9, std::abs(snapshot.sizing.equity) * 1e-12);
5138 if (margin > 0.0 && (!std::isfinite(required) || !std::isfinite(snapshot.sizing.equity)
5139 || required > snapshot.sizing.equity + stop_epsilon)) {
5140 // Legacy replacement first removes the prior same-id resting
5141 // stop, then leaves the rejected re-issue absent from the book.
5142 std::optional<native_order::RequestHandle> prior_handle;
5143 if (const auto prior = live_by_source_key_.find(key_for(id));
5144 prior != live_by_source_key_.end()) {
5145 prior_handle = prior->second;
5146 }
5147 if (prior_handle) {
5148 const auto result = require_host().cancel(*prior_handle);
5149 if (result.status == native_order::CancelStatus::Cancelled) retire(*prior_handle);
5150 }
5151 return;
5152 }
5153 }
5154 if (const auto point = require_host().current_execution_point()) {
5155 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
5156 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
5157 if (default_sized && reverses && !snapshot.replaced_opening) {
5158 for (auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
5159 const auto prior = placement_.find(it->incarnation);
5160 if (prior == placement_.end()) continue;
5161 auto& prior_snapshot = prior->second;
5162 if (!prior_snapshot.opening || prior_snapshot.family != PineOrderFamily::Entry
5163 || prior_snapshot.is_long != is_long
5164 || prior_snapshot.placement_script_open_ms != point->decision.script_bar_open_ms) {
5165 continue;
5166 }
5167 const std::uint64_t group = prior_snapshot.sequential_group != 0
5168 ? prior_snapshot.sequential_group : ++next_sequential_group_;
5169 prior_snapshot.sequential_group = group;
5170 if (prior_snapshot.sequential_rank == 0) prior_snapshot.sequential_rank = 1;
5171 snapshot.sequential_group = group;
5172 snapshot.sequential_rank = 2;
5173 break;
5174 }
5175 }
5176 }
5177 if (current == 0.0 && config_.process_orders_on_close
5178 && !config_.calc_on_order_fills && default_sized
5179 && std::holds_alternative<native_order::Stop>(request.trigger)
5180 && !finite_positive(limit_price) && oca_name.empty() && source_point) {
5181 stage_flat_children_before_parent(
5182 id, source_point->decision.coordinate.interval_index,
5183 source_point->decision.script_bar_open_ms);
5184 }
5185 if (same_bar_market_candidate) {
5186 // Default percent/cash commands are already frozen at their source
5187 // call boundary. The batch's topology must use that physical own
5188 // size, never the public percentage/cash scalar, just as the fixed
5189 // branch uses its explicit unit value.
5190 const double default_own = finite_positive(snapshot.sizing.frozen_units)
5191 ? snapshot.sizing.frozen_units : config_.default_qty_value;
5192 const double own_units = floor_quantity_grid(default_sized
5193 ? default_own : std::abs(qty), staged_.quantity_grid);
5194 bool opposite_market_pending = false;
5195 bool opposite_entry_pending = false;
5196 double opposite_pending_own = 0.0;
5197 const auto inspect_pending = [&](const PlacementSnapshot& prior) {
5198 if (!prior.opening || prior.source_id == id || prior.is_long == is_long) {
5199 return;
5200 }
5201 if (prior.frozen_market_instruction
5202 && finite_positive(prior.frozen_market_own_units)) {
5203 opposite_market_pending = true;
5204 opposite_pending_own += prior.frozen_market_own_units;
5205 } else {
5206 opposite_entry_pending = true;
5207 }
5208 };
5209 for (const auto& pending : pending_same_bar_commands_) inspect_pending(pending.snapshot);
5210 for (const auto& pending : pending_entries_) inspect_pending(pending.snapshot);
5211 if (const auto point = require_host().current_execution_point()) {
5212 for (const auto& handle : live_handles_) {
5213 const auto prior = placement_.find(handle.incarnation);
5214 if (prior == placement_.end()
5215 || prior->second.placement_script_open_ms
5216 != point->decision.script_bar_open_ms) {
5217 continue;
5218 }
5219 inspect_pending(prior->second);
5220 }
5221 }
5222 std::size_t same_side_pending = 0;
5223 for (const auto& pending : pending_same_bar_commands_) {
5224 if (pending.opening && pending.snapshot.is_long == is_long
5225 && pending.snapshot.source_id != id
5226 && !pending.snapshot.projection_over_pyramiding) {
5227 ++same_side_pending;
5228 }
5229 }
5230 for (const auto& pending : pending_entries_) {
5231 if (pending.snapshot.opening && pending.snapshot.is_long == is_long
5232 && pending.snapshot.source_id != id
5233 && !pending.snapshot.projection_over_pyramiding) {
5234 ++same_side_pending;
5235 }
5236 }
5237 if (const auto point = require_host().current_execution_point()) {
5238 for (const auto& handle : live_handles_) {
5239 const auto prior = placement_.find(handle.incarnation);
5240 if (prior == placement_.end()
5241 || prior->second.placement_script_open_ms
5242 != point->decision.script_bar_open_ms) {
5243 continue;
5244 }
5245 // R4-D L10z review fix 2: a live row whose cancellation was
5246 // already recorded no longer competes for the pyramiding cap.
5247 if (prior->second.opening && prior->second.is_long == is_long
5248 && prior->second.source_id != id
5249 && !prior->second.projection_over_pyramiding
5250 && prior->second.cancellation.cause
5252 ++same_side_pending;
5253 }
5254 }
5255 }
5256 const bool same_side = (current != 0.0 && ((current > 0.0) == is_long))
5257 || (current == 0.0 && same_side_pending > 0);
5258 const std::size_t current_lots =
5259 (current != 0.0 && ((current > 0.0) == is_long))
5260 ? require_host().physical_position().lot_count
5261 : 0U;
5262 // ab9714be pine_orders.cpp:737-740: add_to_pyramid_market rejects entry when position_entry_count_ >= pyramiding
5263 const std::size_t total_entries = current_lots + same_side_pending;
5264 const bool over_cap = same_side
5265 && ((config_.pyramiding == 0 && total_entries >= 1U)
5266 || (config_.pyramiding > 0
5267 && total_entries >= static_cast<std::size_t>(config_.pyramiding)));
5268 snapshot.projection_over_pyramiding = over_cap;
5269 if (current == 0.0) {
5270 for (const auto& handle : live_handles_) {
5271 const auto found = placement_.find(handle.incarnation);
5272 if (found == placement_.end()) continue;
5273 auto& candidate = found->second;
5274 const bool exit = candidate.family == PineOrderFamily::ExitLimit
5275 || candidate.family == PineOrderFamily::ExitStop
5276 || candidate.family == PineOrderFamily::ExitTrail;
5277 if (exit && candidate.from_entry == id
5278 && !(cohort_exposure_for(id) > 0.0)) {
5279 candidate.reservation_deferred_to_pending_entry = true;
5280 candidate.projection_remaining_qty = kNaN;
5281 }
5282 }
5283 }
5284 if (over_cap && !opposite_market_pending && !opposite_entry_pending) return;
5285 if (!(over_cap && !opposite_market_pending)
5286 && finite_positive(own_units)) {
5287 const double held_opposite = current != 0.0 && ((current > 0.0) != is_long)
5288 ? std::max(0.0, std::abs(current) - preceding_close_qty) : 0.0;
5289 const double transaction = own_units + held_opposite + opposite_pending_own;
5290 if (finite_positive(transaction)) {
5291 if (over_cap && opposite_market_pending
5292 && config_.default_qty_type == static_cast<int>(QtyType::FIXED)) {
5293 // The kept over-cap member is admitted at its source
5294 // call as the whole frozen broker movement: held side,
5295 // this member's own leg, and every opposite pending
5296 // MARKET leg. The eventual net position is smaller,
5297 // but using it here would incorrectly admit famS's
5298 // 3-lot ES/NQ census rows.
5299 const double gross_units = std::abs(current) + own_units
5300 + opposite_pending_own;
5301 const double margin = is_long ? config_.margin_long : config_.margin_short;
5302 const double required = gross_units * snapshot.sizing.price
5303 * staged_.syminfo.pointvalue * snapshot.sizing.fx * margin / 100.0;
5304 if (!std::isfinite(required) || !std::isfinite(snapshot.sizing.equity)
5305 || required > snapshot.sizing.equity) {
5306 return;
5307 }
5308 }
5309 snapshot.opening = true;
5310 snapshot.frozen_market_instruction = true;
5311 snapshot.frozen_market_own_units = own_units;
5312 snapshot.frozen_market_transaction_units = transaction;
5313 auto existing = std::find_if(pending_same_bar_commands_.begin(),
5314 pending_same_bar_commands_.end(), [&](const PendingSameBarCommand& row) {
5315 return !row.snapshot.frozen_market_targeted_close
5316 && row.replacement_key == id;
5317 });
5318 PendingSameBarCommand pending{std::move(request), std::move(snapshot), id, true};
5319 if (existing == pending_same_bar_commands_.end()) {
5320 pending_same_bar_commands_.push_back(std::move(pending));
5321 } else {
5322 source_batch_mutated_ = true;
5323 *existing = std::move(pending);
5324 }
5325 return;
5326 }
5327 }
5328 }
5329 if (preceding_close_all.incarnation != 0) {
5330 pending_entries_.push_back({std::move(request), std::move(snapshot), id});
5331 return;
5332 }
5333 const bool source_same_side_market_add = default_sized
5334 && config_.default_qty_type == static_cast<int>(QtyType::FIXED)
5335 && !config_.calc_on_order_fills
5336 && (config_.process_orders_on_close || !opposite_opening_pending)
5337 && current != 0.0
5338 && ((current > 0.0) == is_long)
5339 && std::holds_alternative<native_order::Market>(request.trigger);
5340 const bool close_first_percent_add = default_sized
5341 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
5342 && std::abs(config_.default_qty_value - 100.0) < 1e-12
5343 && !config_.calc_on_order_fills && !config_.process_orders_on_close
5344 && config_.pyramiding > 1 && current != 0.0
5345 && ((current > 0.0) == is_long)
5346 && std::holds_alternative<native_order::Market>(request.trigger);
5347 if (source_same_side_market_add || close_first_percent_add) {
5348 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
5349 [&](const PendingEntry& value) { return value.replacement_key == id; });
5350 PendingEntry pending{std::move(request), std::move(snapshot), id};
5351 if (queued == pending_entries_.end()) pending_entries_.push_back(std::move(pending));
5352 else *queued = std::move(pending);
5353 return;
5354 }
5355 if (current == 0.0 && config_.process_orders_on_close && default_sized
5356 && std::holds_alternative<native_order::Stop>(request.trigger)
5357 && !finite_positive(limit_price) && oca_name.empty()) {
5358 const auto token = named_entry_cancel_tokens_.find(id);
5359 if (token != named_entry_cancel_tokens_.end()
5360 && token->second.entry_incarnation != 0
5361 && token->second.surviving_exit_incarnation != 0) {
5362 std::vector<std::uint64_t> child_families;
5363 for (const auto& handle : live_handles_) {
5364 const auto child = placement_.find(handle.incarnation);
5365 if (child == placement_.end() || child->second.from_entry != id) continue;
5366 const auto family = child->second.family;
5367 if (family != PineOrderFamily::ExitLimit
5368 && family != PineOrderFamily::ExitStop
5369 && family != PineOrderFamily::ExitTrail) {
5370 continue;
5371 }
5372 const auto key = key_for(child->second.source_id, child->second.from_entry);
5373 if (std::find(child_families.begin(), child_families.end(), key)
5374 == child_families.end()) {
5375 child_families.push_back(key);
5376 }
5377 }
5378 if (child_families.size() == 1) {
5380 token->second.entry_incarnation;
5382 token->second.surviving_exit_incarnation;
5383 }
5384 snapshot.retained_parent_topology = true;
5385 if (const auto point = require_host().current_execution_point()) {
5386 snapshot.projection_created_bar = point->decision.coordinate.interval_index;
5387 snapshot.projection_position_side = static_cast<std::int32_t>(PositionSide::FLAT);
5388 }
5389 snapshot.source_sequence = source_sequence_ + 1;
5390 if (child_families.size() != 1) named_entry_cancel_tokens_.erase(token);
5391 pending_entries_.push_back({std::move(request), std::move(snapshot), id});
5392 return;
5393 }
5394 }
5395 if (!priced && reverses) {
5396 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
5397 [&](const PendingEntry& value) { return value.replacement_key == id; });
5398 if (queued != pending_entries_.end()) {
5399 // A same-callback MARKET reissue replaces the staged priced
5400 // reversal before either definition reaches the generic core.
5401 // Flushing the old priced row after submitting this one reverses
5402 // the replacement and leaves the market instruction unreachable.
5403 *queued = PendingEntry{std::move(request), std::move(snapshot), id};
5404 return;
5405 }
5406 }
5407 if (priced && reverses && !config_.process_orders_on_close
5408 && !config_.calc_on_order_fills) {
5409 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
5410 [&](const PendingEntry& value) { return value.replacement_key == id; });
5411 PendingEntry pending{std::move(request), std::move(snapshot), id};
5412 if (queued == pending_entries_.end()) pending_entries_.push_back(std::move(pending));
5413 else *queued = std::move(pending);
5414 return;
5415 }
5416 if (config_.process_orders_on_close && !close_batch_callsites_.empty()
5417 && !priced && !coof_recalc_active_) {
5418 pending_entries_.push_back({std::move(request), std::move(snapshot), id});
5419 return;
5420 }
5421 // The legacy source selector orders a flat COOF book by the first
5422 // reachable priced trigger, not by statement insertion. Queue only this
5423 // bounded source shape until the enclosing source evaluation ends, then
5424 // materialize it in source-policy order before the generic core receives
5425 // any request. Recalc-born entries retain their existing callback path.
5426 const bool coof_flat_priced = config_.calc_on_order_fills && current == 0.0
5427 && priced && !coof_recalc_active_ && oca_name.empty();
5428 const bool ordinary_flat_pure_stop = [&]() {
5429 if (config_.process_orders_on_close || config_.calc_on_order_fills
5430 || current != 0.0 || !pure_stop_entry || !oca_name.empty()
5431 || !source_point) {
5432 return false;
5433 }
5434 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
5435 const auto next = pine_host
5436 ? pine_host->scheduler_.next_source_bar(
5437 source_point->decision.coordinate.interval_index)
5438 : std::optional<Bar>{};
5439 return next && source_bar_fill_tick(next->open, staged_.syminfo.mintick)
5440 == stop_price;
5441 }();
5442 if (coof_flat_priced || ordinary_flat_pure_stop) {
5443 pending_entries_.push_back({std::move(request), std::move(snapshot), id});
5444 return;
5445 }
5446 if (coof_market_next_open) {
5447 pending_coof_requests_.push_back(
5448 {std::move(request), std::move(snapshot), id, true, 0, true});
5449 return;
5450 }
5451 if (defer_coof_tail()) {
5452 pending_coof_requests_.push_back({std::move(request), std::move(snapshot), id, true, 0});
5453 return;
5454 }
5455 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), true, id);
5456 if (accepted) {
5457 if (current < 0.0 && is_long) short_seed_long_candidate_ = *accepted;
5458 const auto entry = placement_.find(accepted->incarnation);
5459 if (entry != placement_.end()) {
5460 const std::uint64_t entry_sequence = entry->second.command_sequence;
5461 // A source entry accepted after a captured POOC global exit is
5462 // outside that exit's live population. Keep any already-bound
5463 // pre-exit add eligible to grow the finite reservation at fill,
5464 // but close the open-population marker now so later entries never
5465 // acquire that reservation merely by arriving before the match.
5466 for (const auto& handle : live_handles_) {
5467 if (handle == *accepted) continue;
5468 const auto existing = placement_.find(handle.incarnation);
5469 if (existing == placement_.end()) continue;
5470 auto& prior = existing->second;
5471 if (!prior.reservation_expansion.capture()
5472 || prior.command_sequence >= entry_sequence) {
5473 continue;
5474 }
5475 prior.pooc_global_full_exit_dynamic_qty = false;
5476 prior.pooc_global_full_exit_tracks_bound_adds = false;
5477 prior.reservation_expansion.close_population(accepted->incarnation);
5478 }
5479 }
5480 if (default_sized && !priced && reverses && source_point
5481 && config_.default_qty_type
5482 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
5483 && config_.default_qty_value <= 100.0) {
5484 if (auto* pine_host = dynamic_cast<PineStrategyHost*>(&require_host())) {
5485 if (const auto next = pine_host->scheduler_.next_source_bar(
5486 source_point->decision.coordinate.interval_index)) {
5487 NativeDecisionContext next_context = source_point->decision;
5488 ++next_context.coordinate.interval_index;
5489 next_context.coordinate.path_phase = NativePathPhase::Open;
5490 next_context.script_bar_open_ms = next->timestamp;
5491 next_context.sub_bar_open_ms = next->timestamp;
5492 apply_reversal_gap_bracket_policy(
5493 *next, next_context, /*defer_trails=*/true);
5494 }
5495 }
5496 }
5497 } else if (paired_all_in_reentry) {
5498 // The source call is still observable in its current script pass,
5499 // although native max-lot admission has already terminally refused
5500 // it. Preserve that truthful source observer row until the next
5501 // broker open, without retaining a second executable order.
5502 // (The copy is made from the source snapshot before the next call.)
5503 SourceShadowPending shadow;
5504 shadow.snapshot.family = PineOrderFamily::Entry;
5505 shadow.snapshot.source_id = id;
5506 shadow.snapshot.is_long = is_long;
5507 shadow.snapshot.opening = true;
5508 shadow.snapshot.sizing = sizing_snapshot();
5509 shadow.label = id;
5510 source_shadow_pending_.push_back(std::move(shadow));
5511 }
5512}
5513
5514double PineExecutionAdapter::close_reserved_other_units(
5515 const SourceId& id, std::uint64_t) const noexcept {
5516 std::map<SourceId, double> backing_by_id = close_reserved_units_;
5517 for (const auto& owner : close_callsite_reserved_units_) {
5518 for (const auto& claim : owner.second) {
5519 auto& backing = backing_by_id[claim.first];
5520 backing = std::max(backing, claim.second);
5521 }
5522 }
5523 double total = 0.0;
5524 for (const auto& backing : backing_by_id) {
5525 if (backing.first != id) total += backing.second;
5526 }
5527 return total;
5528}
5529
5530bool PineExecutionAdapter::enqueue_pooc_fifo_close(
5531 const SourceId& id, const std::string& comment,
5532 std::uint64_t token, std::uint64_t) {
5533 constexpr double epsilon = 1e-10;
5534 const auto point = require_host().current_execution_point();
5535 const int bar = point ? point->decision.coordinate.interval_index : -1;
5536 if (close_batch_bar_ != bar) {
5537 close_batch_bar_ = bar;
5538 close_batch_queue_sequence_ = 0;
5539 close_batch_callsites_.clear();
5540 close_batch_pending_debt_ = 0.0;
5541 close_batch_admitted_total_ = 0.0;
5542 }
5543
5544 auto existing = close_batch_callsites_.find(token);
5545 const CloseCallsiteState* prior = existing == close_batch_callsites_.end()
5546 ? nullptr : &existing->second;
5547 const auto logical = close_logical_units_.find(id);
5548 const double unclosed = logical == close_logical_units_.end()
5549 ? 0.0 : logical->second;
5550
5551 double pending_reserved = 0.0;
5552 for (const auto& row : close_batch_callsites_) {
5553 if (row.second.active) pending_reserved += row.second.target;
5554 }
5555 const bool same_id_reissue = prior && prior->active && prior->id == id;
5556 bool replacement_can_reuse_own_claim = false;
5557 if (prior && prior->active && prior->id != id) {
5558 const bool another_targets_prior = std::any_of(
5559 close_batch_callsites_.begin(), close_batch_callsites_.end(),
5560 [&](const auto& row) {
5561 return row.first != token && row.second.active
5562 && row.second.id == prior->id;
5563 });
5564 replacement_can_reuse_own_claim = !another_targets_prior;
5565 }
5566 if (same_id_reissue || replacement_can_reuse_own_claim)
5567 pending_reserved -= prior->target;
5568
5569 const double held = std::abs(require_host().physical_position().signed_units);
5570 double persistent_other = close_reserved_other_units(id, token);
5571 if (prior && prior->first_ledger_consumed && prior->first_id != id) {
5572 double current_claim = 0.0;
5573 const auto owner = close_callsite_reserved_units_.find(token);
5574 if (owner != close_callsite_reserved_units_.end()) {
5575 const auto claim = owner->second.find(prior->first_id);
5576 if (claim != owner->second.end()) current_claim = claim->second;
5577 }
5578 double competing_claim = 0.0;
5579 const auto legacy = close_reserved_units_.find(prior->first_id);
5580 if (legacy != close_reserved_units_.end()) competing_claim = legacy->second;
5581 for (const auto& candidate : close_callsite_reserved_units_) {
5582 if (candidate.first == token) continue;
5583 const auto claim = candidate.second.find(prior->first_id);
5584 if (claim != candidate.second.end())
5585 competing_claim = std::max(competing_claim, claim->second);
5586 }
5587 persistent_other -= std::max(0.0, current_claim - competing_claim);
5588 persistent_other = std::max(0.0, persistent_other);
5589 }
5590 const double persistent_available = std::max(0.0, held - persistent_other);
5591 const double available = std::max(0.0, persistent_available - pending_reserved);
5592 const double target = std::min(unclosed, available);
5593 if (!(target > epsilon)) {
5594 if (unclosed > epsilon && !(persistent_available > epsilon)) {
5595 if (token == 0) {
5596 close_logical_units_.erase(id);
5597 close_reserved_units_.erase(id);
5598 close_first_units_.erase(id);
5599 } else {
5600 auto& site = close_batch_callsites_[token];
5601 if (std::find(site.deferred_cleanup_ids.begin(),
5602 site.deferred_cleanup_ids.end(), id)
5603 == site.deferred_cleanup_ids.end()) {
5604 site.deferred_cleanup_ids.push_back(id);
5605 }
5606 }
5607 }
5608 return false;
5609 }
5610
5611 const double replaced_target = prior && prior->active ? prior->target : 0.0;
5612 close_batch_pending_debt_ += target;
5613 pending_same_bar_close_qty_ += target;
5614 if (token != 0) close_batch_admitted_total_ += target - replaced_target;
5615 const bool retire_whole = unclosed > persistent_available + epsilon;
5616
5617 auto& site = close_batch_callsites_[token];
5618 if (!site.active) {
5619 site.active = true;
5620 site.token = token;
5621 site.calls = 1;
5622 site.first_id = id;
5623 site.first_target = target;
5624 site.id = id;
5625 site.comment = comment;
5626 site.target = target;
5627 site.retire_ledger_whole = retire_whole;
5628 site.queue_sequence = ++close_batch_queue_sequence_;
5629 return true;
5630 }
5631 if (site.id == id) {
5632 site.comment = comment;
5633 site.target = target;
5634 site.retire_ledger_whole = retire_whole;
5635 return true;
5636 }
5637
5638 ++site.calls;
5639 if (site.calls == 2) {
5640 const auto& reservations = token == 0
5641 ? close_reserved_units_
5642 : close_callsite_reserved_units_[token];
5643 const auto& provenance = token == 0
5644 ? close_first_units_
5645 : close_callsite_first_units_[token];
5646 const auto reserved = reservations.find(site.first_id);
5647 const auto first = provenance.find(site.first_id);
5648 if (reserved != reservations.end() && first != provenance.end()) {
5649 site.first_carry_valid = true;
5650 site.first_carry_qty = first->second;
5651 }
5652 site.first_ledger_consumed = true;
5653 } else if (site.calls == 3) {
5654 site.first_carry_valid = false;
5655 site.first_carry_qty = 0.0;
5656 }
5657 site.id = id;
5658 site.comment = comment;
5659 site.target = target;
5660 site.retire_ledger_whole = retire_whole;
5661 return true;
5662}
5663
5665 if (close_batch_callsites_.empty()) return;
5666 for (const auto& row : close_batch_callsites_) {
5667 const auto& site = row.second;
5668 for (const auto& id : site.deferred_cleanup_ids) {
5669 close_logical_units_.erase(id);
5670 if (site.token == 0) {
5671 close_reserved_units_.erase(id);
5672 close_first_units_.erase(id);
5673 } else {
5674 close_callsite_reserved_units_[site.token].erase(id);
5675 close_callsite_first_units_[site.token].erase(id);
5676 }
5677 }
5678 }
5679 std::vector<CloseCallsiteState> sites;
5680 sites.reserve(close_batch_callsites_.size());
5681 for (const auto& row : close_batch_callsites_)
5682 if (row.second.active) sites.push_back(row.second);
5683 std::stable_sort(sites.begin(), sites.end(),
5684 [](const CloseCallsiteState& left, const CloseCallsiteState& right) {
5685 return left.queue_sequence < right.queue_sequence;
5686 });
5687
5688 double remaining = 0.0;
5689 for (const auto& site : sites) remaining += site.target;
5690 std::vector<std::pair<std::uint64_t, double>> fifo_lots;
5691 if (const auto* pine = dynamic_cast<const PineStrategyHost*>(&require_host())) {
5692 fifo_lots.reserve(pine->pyramid_entries_.size());
5693 for (const auto& lot : pine->pyramid_entries_)
5694 fifo_lots.emplace_back(lot.entry_incarnation, lot.qty);
5695 }
5696 std::size_t fifo_prefix_size = 0;
5697 // ab9714be pine_orders.cpp:29-70 source_fifo_prefix_membership, with its
5698 // accumulation and endpoint ordering; openings resolve to their receipts.
5699 const auto source_fifo_prefix_openings = [&](
5700 const std::vector<std::pair<std::uint64_t, double>>& lots,
5701 double qty_limit) -> std::optional<native_order::BindOpenings> {
5702 fifo_prefix_size = 0;
5703 if (current_position_cycle_ <= 0 || !std::isfinite(qty_limit) || qty_limit <= 0.0)
5704 return std::nullopt;
5705 double qty_closed = 0.0;
5706 std::size_t prefix_size = 0;
5707 for (const auto& lot : lots) {
5708 if (qty_closed >= qty_limit - internal::kQtyEpsilon) break;
5709 if (!std::isfinite(lot.second) || lot.second <= 0.0) return std::nullopt;
5710 const double close_qty = std::min(lot.second, qty_limit - qty_closed);
5711 if (lot.second - close_qty > internal::kQtyEpsilon) return std::nullopt;
5712 ++prefix_size;
5713 qty_closed += close_qty;
5714 }
5715 if (prefix_size == 0 || prefix_size == lots.size()) return std::nullopt;
5716 // The selection is resolved at submission, while the owner resolves
5717 // it at the fill. Keep the late-bound FIFO Reduce whenever it drains
5718 // exactly that prefix anyway; only a drain that would keep a
5719 // sub-epsilon fragment or take one from the next lot needs the
5720 // explicit whole-lot selection.
5721 {
5722 double closed = 0.0;
5723 double left = qty_limit;
5724 bool exact = true;
5725 for (std::size_t index = 0; exact && index < prefix_size; ++index) {
5726 const double amount = std::min(lots[index].second, left);
5727 exact = amount == lots[index].second;
5728 closed += amount;
5729 left = qty_limit - closed;
5730 }
5731 if (exact && left == 0.0) return std::nullopt;
5732 }
5733 native_order::BindOpenings selection{{}, current_position_cycle_};
5734 std::unordered_set<std::uint64_t> included;
5735 for (std::size_t index = 0; index < prefix_size; ++index) {
5736 const auto incarnation = lots[index].first;
5737 if (incarnation == 0) return std::nullopt;
5738 if (!included.insert(incarnation).second) continue;
5739 const native_order::RequestHandle* receipt = nullptr;
5740 for (const auto& cohort : cohorts_by_id_) {
5741 for (const auto& opened : cohort.second.opened)
5742 if (opened.incarnation == incarnation) receipt = &opened;
5743 }
5744 if (!receipt) return std::nullopt;
5745 selection.openings.push_back(*receipt);
5746 }
5747 // Every live fragment of a selected opening must belong to the
5748 // prefix; otherwise the owner settles the close as a Reduce.
5749 for (std::size_t index = prefix_size; index < lots.size(); ++index)
5750 if (included.count(lots[index].first) != 0) return std::nullopt;
5751 fifo_prefix_size = prefix_size;
5752 return selection;
5753 };
5754 // Settlement FIFO arithmetic of one queued close over the local lot view.
5755 const auto drain_source_fifo_lots = [&](
5756 std::vector<std::pair<std::uint64_t, double>>& lots, double qty) {
5757 if (fifo_prefix_size != 0) {
5758 lots.erase(lots.begin(), lots.begin()
5759 + static_cast<std::ptrdiff_t>(fifo_prefix_size));
5760 return;
5761 }
5762 double closed = 0.0;
5763 double left = qty;
5764 std::size_t consumed = 0;
5765 for (auto& lot : lots) {
5766 if (!(left > 0.0)) break;
5767 const double amount = std::min(lot.second, left);
5768 lot.second -= amount;
5769 closed += amount;
5770 left = qty - closed;
5771 if (lot.second == 0.0) ++consumed;
5772 }
5773 lots.erase(lots.begin(), lots.begin() + static_cast<std::ptrdiff_t>(consumed));
5774 // The host drops sub-epsilon survivors after every applied fill.
5775 lots.erase(std::remove_if(lots.begin(), lots.end(), [](const auto& lot) {
5776 return lot.second <= internal::kQtyEpsilon;
5777 }), lots.end());
5778 };
5779 for (const auto& site : sites) {
5780 remaining = std::max(0.0, remaining - site.target);
5781 if (site.first_ledger_consumed) {
5782 close_logical_units_.erase(site.first_id);
5783 if (site.token == 0) {
5784 close_reserved_units_.erase(site.first_id);
5785 close_first_units_.erase(site.first_id);
5786 } else {
5787 close_callsite_reserved_units_[site.token].erase(site.first_id);
5788 close_callsite_first_units_[site.token].erase(site.first_id);
5789 }
5790 }
5791 for (const auto& id : site.deferred_cleanup_ids) {
5792 close_logical_units_.erase(id);
5793 if (site.token == 0) {
5794 close_reserved_units_.erase(id);
5795 close_first_units_.erase(id);
5796 } else {
5797 close_callsite_reserved_units_[site.token].erase(id);
5798 close_callsite_first_units_[site.token].erase(id);
5799 }
5800 }
5801
5802 const auto physical = require_host().physical_position();
5803 // ab9714be pine_strategy_commands.cpp:694-696: strategy_close returns immediately when position is flat (<= kQtyEpsilon)
5804 if (std::abs(physical.signed_units) <= internal::kQtyEpsilon) continue;
5805 const double available = std::abs(physical.signed_units);
5806 const double target = std::min(site.target, available);
5807 // ab9714be pine_orders.cpp:344: close within kQtyEpsilon of held position executes Flatten action
5808 const bool closes_full = target >= available - internal::kQtyEpsilon;
5809 if (closes_full) {
5810 cancel_exit_orders_for_full_close(site.id);
5811 const bool held_long = physical.signed_units > 0.0;
5812 pending_entries_.erase(std::remove_if(
5813 pending_entries_.begin(), pending_entries_.end(),
5814 [&](const PendingEntry& pending) {
5815 const auto& entry = pending.snapshot;
5816 return entry.opening && entry.family == PineOrderFamily::Entry
5817 && entry.is_long == held_long
5818 && !finite_positive(entry.exit_levels.limit)
5819 && !finite_positive(entry.exit_levels.stop);
5820 }), pending_entries_.end());
5821 }
5822
5823 // ab9714be pine_orders.cpp:29-70 / :340-347: a partial close whose
5824 // FIFO drain consumes only whole lots settles as a Flatten of exactly
5825 // that lot prefix. A binary64 Reduce of the same target can keep a
5826 // one-ULP fragment of the last lot, which moves every later binary64
5827 // availability/ledger target off the owner's arithmetic.
5828 std::optional<native_order::BindOpenings> prefix;
5829 fifo_prefix_size = 0;
5830 if (!closes_full) prefix = source_fifo_prefix_openings(fifo_lots, target);
5831
5832 // A partial target is the owner's binary64 ledger/availability
5833 // arithmetic (ab9714be pine_strategy_commands.cpp:1397-1408), which
5834 // execute_partial_exit_qty drains without re-quantizing. An off-grid
5835 // target cannot be an ExplicitUnits request, so resolve_terms books
5836 // it as explicit units at the fill instead.
5837 const bool off_grid = staged_.quantity_grid
5838 && !native_order::quantity_on_grid(target, *staged_.quantity_grid);
5839 native_order::Request request;
5840 request.intent = closes_full || prefix
5842 : off_grid
5844 native_order::HostSizedKind::Close, std::nullopt}}
5846 request.label = "__close__" + site.id;
5847 request.comment = site.comment;
5848 request.owner = native_order::Independent{};
5849 if (prefix) request.owner = std::move(*prefix);
5850 // Earlier sites of this flush settle first (queue order), so a later
5851 // site's prefix is judged against the lots that survive them.
5852 if (closes_full) fifo_lots.clear();
5853 else drain_source_fifo_lots(fifo_lots, target);
5854 PlacementSnapshot snapshot;
5855 snapshot.family = PineOrderFamily::Close;
5856 snapshot.source_id = site.id;
5857 snapshot.comment = site.comment;
5858 snapshot.requested_qty = closes_full ? kNaN : target;
5859 snapshot.projection_remaining_qty = target;
5860 snapshot.qty_percent = closes_full ? 100.0 : (target / available * 100.0);
5861 snapshot.is_long = false;
5862 snapshot.sizing = sizing_snapshot();
5863 snapshot.close_callsite_token = site.token;
5864 snapshot.close_batch_calls = static_cast<std::uint32_t>(site.calls);
5865 snapshot.close_first_id = site.first_id;
5866 snapshot.close_first_target = site.first_target;
5867 snapshot.close_first_ledger_consumed = site.first_ledger_consumed;
5868 snapshot.close_first_carry_valid = site.first_carry_valid;
5869 snapshot.close_first_carry_qty = site.first_carry_qty;
5870 snapshot.close_retire_ledger_whole = site.retire_ledger_whole;
5871 snapshot.close_pending_later_qty = remaining;
5872 const SourceId key = "__pine_close_flush__" + std::to_string(site.token);
5873 (void)submit_or_replace(
5874 std::move(request), std::move(snapshot), false, key);
5875 }
5876 close_batch_callsites_.clear();
5877 close_batch_bar_ = -1;
5878 close_batch_queue_sequence_ = 0;
5879 close_batch_pending_debt_ = 0.0;
5880 close_batch_admitted_total_ = 0.0;
5881}
5882
5883void PineExecutionAdapter::observe_close_policy(
5885 const PlacementSnapshot& snapshot) {
5886 if (snapshot.close_batch_calls == 0 || !(event.closed_units > 0.0)) return;
5887 const double remaining_position = std::abs(
5888 require_host().physical_position().signed_units);
5889 // ab9714be pine_strategy_commands.cpp:1575: the reservation is bounded by
5890 // the binary64 position difference qty_before - position_qty_, not by the
5891 // settled close units; the two can differ by a few ULPs.
5892 double actual_fill = event.closed_units;
5893 if (const auto* pine = dynamic_cast<const PineStrategyHost*>(&require_host());
5894 pine && std::isfinite(pine->precommit_held_units_)) {
5895 actual_fill = std::max(0.0, pine->precommit_held_units_ - remaining_position);
5896 }
5897 const auto erase_owner = [&](auto& owners, std::uint64_t token,
5898 const SourceId& id) {
5899 auto owner = owners.find(token);
5900 if (owner == owners.end()) return;
5901 owner->second.erase(id);
5902 if (owner->second.empty()) owners.erase(owner);
5903 };
5904
5905 if (snapshot.close_batch_calls == 1) {
5906 close_logical_units_.erase(snapshot.source_id);
5907 if (snapshot.close_callsite_token == 0) {
5908 close_reserved_units_.erase(snapshot.source_id);
5909 close_first_units_.erase(snapshot.source_id);
5910 } else {
5911 erase_owner(close_callsite_reserved_units_,
5912 snapshot.close_callsite_token, snapshot.source_id);
5913 erase_owner(close_callsite_first_units_,
5914 snapshot.close_callsite_token, snapshot.source_id);
5915 }
5916 } else if (remaining_position > 0.0) {
5917 if (snapshot.close_batch_calls == 2
5918 && snapshot.close_first_carry_valid
5919 && snapshot.close_first_carry_qty > 0.0) {
5920 close_logical_units_[snapshot.close_first_id] =
5921 snapshot.close_first_carry_qty;
5922 }
5923 const double reserved_other = close_reserved_other_units(
5924 snapshot.source_id, snapshot.close_callsite_token);
5925 const double capacity = std::max(0.0,
5926 remaining_position - reserved_other - snapshot.close_pending_later_qty);
5927 const double reserve = std::min(actual_fill, capacity);
5928 if (reserve > 0.0) {
5929 // The owner keeps the id's established ledger here (ab9714be
5930 // pine_strategy_commands.cpp:1557-1575); its floor stays the
5931 // settled close units, never the ULP-wider position difference.
5932 auto& logical = close_logical_units_[snapshot.source_id];
5933 logical = std::max(logical, std::min(event.closed_units, capacity));
5934 }
5935 if (snapshot.close_callsite_token == 0) {
5936 if (reserve > 0.0) close_reserved_units_[snapshot.source_id] = reserve;
5937 else {
5938 close_logical_units_.erase(snapshot.source_id);
5939 close_reserved_units_.erase(snapshot.source_id);
5940 }
5941 if (snapshot.close_batch_calls == 2 && reserve >= actual_fill)
5942 close_first_units_[snapshot.source_id] = snapshot.close_first_target;
5943 else
5944 close_first_units_.erase(snapshot.source_id);
5945 } else {
5946 if (reserve > 0.0) {
5947 close_callsite_reserved_units_[snapshot.close_callsite_token]
5948 [snapshot.source_id] = reserve;
5949 } else {
5950 close_logical_units_.erase(snapshot.source_id);
5951 erase_owner(close_callsite_reserved_units_,
5952 snapshot.close_callsite_token, snapshot.source_id);
5953 }
5954 if (snapshot.close_batch_calls == 2 && reserve >= actual_fill) {
5955 close_callsite_first_units_[snapshot.close_callsite_token]
5956 [snapshot.source_id] = snapshot.close_first_target;
5957 } else {
5958 erase_owner(close_callsite_first_units_,
5959 snapshot.close_callsite_token, snapshot.source_id);
5960 }
5961 }
5962 }
5963
5964 if (remaining_position == 0.0) {
5965 close_logical_units_.clear();
5966 close_reserved_units_.clear();
5967 close_first_units_.clear();
5968 close_callsite_reserved_units_.clear();
5969 close_callsite_first_units_.clear();
5970 }
5971}
5972
5973void PineExecutionAdapter::close(const SourceId& id, const std::string& comment, double qty,
5974 double qty_percent, bool immediately, std::uint64_t callsite_token) {
5975 if (intraday_loss_orders_blocked()) return;
5976 if (const auto point = require_host().current_execution_point();
5977 point && cap_placement_denied(point->decision)) {
5978 return;
5979 }
5980 // The public empty-id spelling is the source route's full-position
5981 // strategy.close form. It is not a cohort lookup (there is no empty
5982 // entry-id cohort), and it retains its caller-supplied report comment.
5983 if (id.empty()) {
5984 // ab9714be pine_strategy_commands.cpp:694-696: strategy_close returns immediately when physical position is flat
5985 if (require_host().physical_position().signed_units == 0.0) return;
5986 const std::uint64_t command_ordinal = ++command_ordinal_;
5987 if (const auto point = require_host().current_execution_point()) {
5988 close_all_pending_script_bar_ = point->decision.script_bar_open_ms;
5989 }
5990 bool empty_entry = false;
5991 bool opposite_entry = false;
5992 bool empty_is_long = false;
5993 for (const auto& pending : pending_same_bar_commands_) {
5994 if (!pending.opening || pending.snapshot.family != PineOrderFamily::Entry) continue;
5995 if (pending.snapshot.source_id.empty()) {
5996 empty_entry = true;
5997 empty_is_long = pending.snapshot.is_long;
5998 }
5999 }
6000 if (empty_entry) {
6001 for (const auto& pending : pending_same_bar_commands_) {
6002 if (!pending.opening || pending.snapshot.family != PineOrderFamily::Entry) continue;
6003 if (pending.snapshot.is_long != empty_is_long) {
6004 opposite_entry = true;
6005 break;
6006 }
6007 }
6008 }
6009 if (empty_entry && opposite_entry) {
6010 SourceShadowPending shadow;
6011 shadow.snapshot.family = PineOrderFamily::CloseAll;
6012 shadow.snapshot.source_id = "__pine_close_all";
6013 shadow.snapshot.sizing = sizing_snapshot();
6014 shadow.label = shadow.snapshot.source_id;
6015 source_shadow_pending_.push_back(std::move(shadow));
6016 return;
6017 }
6018 native_order::Request request;
6019 request.intent = native_order::Flatten{};
6020 request.label = "__pine_close_all";
6021 request.comment = comment;
6022 double coof_close_all_fill = kNaN;
6023 if (coof_recalc_active_ && !coof_first_open_ && !immediately
6024 && coof_script_bar_valid_) {
6025 const double next_extreme = coof_next_waypoint();
6026 const auto point = require_host().current_execution_point();
6027 const double current_quote = point ? point->price : kNaN;
6028 const bool buy = require_host().physical_position().signed_units < 0.0;
6029 coof_close_all_fill = source_bar_fill_tick(
6030 next_extreme, staged_.syminfo.mintick)
6031 + (buy ? 1.0 : -1.0) * config_.slippage
6032 * staged_.syminfo.mintick;
6033 if (finite_positive(coof_close_all_fill)
6034 && finite_positive(current_quote)
6035 && !source_same_point(current_quote, coof_close_all_fill,
6036 staged_.syminfo.mintick)) {
6037 const bool falling = coof_close_all_fill < current_quote;
6038 if (buy) {
6039 request.trigger = falling
6041 coof_close_all_fill}}
6043 coof_close_all_fill}};
6044 } else {
6045 request.trigger = falling
6047 coof_close_all_fill}}
6049 coof_close_all_fill}};
6050 }
6051 }
6052 }
6053 PlacementSnapshot snapshot;
6055 snapshot.source_id = request.label;
6056 snapshot.comment = comment;
6057 snapshot.command_ordinal = command_ordinal;
6058 snapshot.immediately = immediately;
6059 snapshot.sizing = sizing_snapshot();
6060 snapshot.forced_execution_price = coof_close_all_fill;
6061 (void)qty;
6062 (void)qty_percent;
6063 (void)callsite_token;
6064 const auto accepted = submit_or_replace(
6065 std::move(request), std::move(snapshot), false, "__pine_close_all");
6066 // ab9714be pine_strategy_commands.cpp:789-801: an ordinary POOC
6067 // close_all fills at the call (execute_immediate_close), ahead of any
6068 // same-bar market entry the script placed before it; an under-cap add
6069 // issued earlier in the pass therefore survives the flat
6070 // (pine_strategy_commands.cpp:2437-2446) instead of being flattened
6071 // with the book. The script keeps the pre-close position view (KI-64).
6072 const bool pooc_ordinary_close_all = config_.process_orders_on_close
6073 && !immediately && !config_.calc_on_order_fills && !coof_recalc_active_;
6074 if (pooc_ordinary_close_all && accepted) {
6075 if (auto* pine_host = dynamic_cast<PineStrategyHost*>(&require_host()))
6076 pine_host->freeze_script_position_view();
6077 }
6078 if ((immediately || pooc_ordinary_close_all) && accepted) {
6079 (void)require_host().execute_current(
6080 {*accepted, NativeCurrentPriceRule::NearestTick});
6081 }
6082 if (!accepted || config_.process_orders_on_close) return;
6083 const auto close = placement_.find(accepted->incarnation);
6084 if (close == placement_.end()) return;
6085 const auto side = static_cast<PositionSide>(close->second.projection_position_side);
6086 for (const auto& handle : live_handles_) {
6087 if (handle == *accepted) continue;
6088 const auto found = placement_.find(handle.incarnation);
6089 if (found == placement_.end()) continue;
6090 auto& pending = found->second;
6091 const bool pure_prior_stop = pending.opening
6092 && pending.family == PineOrderFamily::Entry
6093 && finite_positive(pending.exit_levels.stop)
6094 && !finite_positive(pending.exit_levels.limit)
6095 && !finite_positive(pending.exit_levels.trail_points)
6096 && !finite_positive(pending.exit_levels.trail_price)
6097 && !finite_positive(pending.exit_levels.trail_offset)
6098 && !pending.stop_limit_activated
6099 && pending.projection_created_bar < close->second.projection_created_bar
6100 && pending.projection_position_side == static_cast<std::int32_t>(side)
6101 && pending.is_long == (side == PositionSide::LONG)
6102 && !pending.projection_over_pyramiding;
6103 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
6104 const bool has_physical_id = pine_host
6105 && pine_host->adapter_has_open_entry_id(pending.source_id);
6106 if (!pure_prior_stop || !has_physical_id) continue;
6107 pending.preserved_by_close_all = *accepted;
6108 pending.preserved_close_all_bar = close->second.projection_created_bar;
6109 }
6110 const double cur_pos = require_host().physical_position().signed_units;
6111 const auto cur_pt = require_host().current_execution_point();
6112 if (cur_pos != 0.0 && cur_pt) {
6113 std::vector<native_order::RequestHandle> opposite_entries;
6114 for (const auto& handle : live_handles_) {
6115 if (handle == *accepted) continue;
6116 const auto found = placement_.find(handle.incarnation);
6117 if (found == placement_.end()) continue;
6118 const auto& pend = found->second;
6119 const bool unpriced_market = !finite_positive(pend.exit_levels.limit)
6120 && !finite_positive(pend.exit_levels.stop)
6121 && !finite_positive(pend.exit_levels.trail_points)
6122 && !finite_positive(pend.exit_levels.trail_price)
6123 && !finite_positive(pend.exit_levels.trail_offset);
6124 if (pend.opening && pend.family == PineOrderFamily::Entry
6125 && unpriced_market
6126 && !pend.projection_over_pyramiding
6127 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6128 opposite_entries.push_back(handle);
6129 }
6130 }
6131 for (const auto& handle : opposite_entries) {
6132 const auto found = placement_.find(handle.incarnation);
6133 if (found == placement_.end()) continue;
6134 PlacementSnapshot entry_snapshot = found->second;
6135 // ab9714be pine_fills.cpp:3848-3856: full market close processes before opposite entry so entry executes after close
6136 entry_snapshot.paired_reversal_parent = *accepted;
6137 entry_snapshot.market_admission = {};
6138 const auto result = require_host().cancel(handle);
6139 if (result.status == native_order::CancelStatus::Cancelled) {
6140 retire(handle);
6142 const double target = entry_snapshot.is_long
6143 ? entry_snapshot.requested_qty : -entry_snapshot.requested_qty;
6144 req.intent = std::isnan(target)
6145 ? native_order::OrderIntent{native_order::HostSized{native_order::HostSizedKind::Open,
6146 entry_snapshot.is_long ? native_order::Side::Long : native_order::Side::Short}}
6148 req.label = entry_snapshot.source_id;
6149 req.comment = entry_snapshot.comment;
6150 pending_entries_.push_back({std::move(req), std::move(entry_snapshot), entry_snapshot.source_id});
6151 }
6152 }
6153 for (auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
6154 if (it->opening && it->snapshot.family == PineOrderFamily::Entry
6155 && !it->snapshot.projection_over_pyramiding
6156 && it->snapshot.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6157 it->snapshot.paired_reversal_parent = *accepted;
6158 it->snapshot.market_admission = {};
6159 pending_entries_.push_back({std::move(it->request), std::move(it->snapshot), it->replacement_key});
6160 it = pending_same_bar_commands_.erase(it);
6161 } else {
6162 ++it;
6163 }
6164 }
6165 }
6166 return;
6167 }
6168 const auto openings = openings_for(id);
6169 const bool logical_pooc_fifo = config_.process_orders_on_close
6170 && !config_.close_entries_rule_any && !immediately
6171 && std::isnan(qty) && std::isnan(qty_percent)
6172 && fixture_close_logical_units(id) > 0.0;
6173 // P-DA3: strategy.close against an empty cohort is dropped at the command.
6174 if (openings.empty() && !logical_pooc_fifo) {
6175 record_dropped_close(id, comment, qty, qty_percent, immediately, callsite_token);
6176 return;
6177 }
6178 const bool has_pending_entry = std::any_of(
6179 pending_entries_.begin(), pending_entries_.end(),
6180 [&](const PendingEntry& pe) { return pe.snapshot.source_id == id; })
6181 || std::any_of(
6182 pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
6183 [&](const PendingSameBarCommand& pc) { return pc.snapshot.source_id == id; });
6184 // ab9714be pine_strategy_commands.cpp:2254-2256: compute_close_target_qty drops close when target unclosed quantity is zero
6185 if (!config_.process_orders_on_close && !id.empty() && !(cohort_exposure_for(id) > 0.0)
6186 && !has_pending_entry) {
6187 record_dropped_close(id, comment, qty, qty_percent, immediately, callsite_token);
6188 return;
6189 }
6190 const std::uint64_t command_ordinal = ++command_ordinal_;
6191 // A same-side fixed market add is tentatively held until a later
6192 // strategy.exit can contribute its priced legs to the source-priority
6193 // batch. A named strategy.close terminates that command shape instead:
6194 // publish the already-issued add before placing the close, exactly as the
6195 // legacy broker book did. Leaving it staged would make the public
6196 // command-boundary projection lose one of the two surviving rows and
6197 // would incorrectly make the close race an unsubmitted add.
6198 if (!config_.calc_on_order_fills && !config_.process_orders_on_close
6199 && !pending_entries_.empty()) {
6200 const double live = require_host().physical_position().signed_units;
6201 const bool has_opposite = std::any_of(
6202 pending_entries_.begin(), pending_entries_.end(), [&](const PendingEntry& entry) {
6203 return live != 0.0 && entry.snapshot.is_long != (live > 0.0);
6204 });
6205 const bool close_first_percent_add =
6206 config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
6207 && std::abs(config_.default_qty_value - 100.0) < 1e-12
6208 && std::all_of(pending_entries_.begin(), pending_entries_.end(),
6209 [](const PendingEntry& entry) {
6210 return entry.snapshot.family == PineOrderFamily::Entry
6211 && !std::isfinite(entry.snapshot.requested_qty)
6212 && !finite_positive(entry.snapshot.exit_levels.limit)
6213 && !finite_positive(entry.snapshot.exit_levels.stop);
6214 });
6215 if (!has_opposite && !close_first_percent_add) flush_pending_entries();
6216 }
6217 double requested_percent = std::isnan(qty_percent) ? 100.0 : qty_percent;
6218 double effective_qty = qty;
6219 const double current = require_host().physical_position().signed_units;
6220 if (config_.close_entries_rule_any && !immediately && std::isfinite(qty)) {
6221 const double matching = cohort_exposure_for(id);
6222 requested_percent = matching > 1e-10
6223 ? std::clamp(std::abs(qty) / matching * 100.0, 0.0, 100.0)
6224 : 100.0;
6225 // ab9714be:pine_strategy_commands.cpp:2522-2590. An ANY close keeps
6226 // the already-resolved claim as a percentage-bound deferred order;
6227 // its public pending qty remains NaN and the live cohort is resolved
6228 // at the eventual candidate.
6229 effective_qty = kNaN;
6230 }
6231 const bool default_fifo_close = !config_.close_entries_rule_any
6232 && std::isnan(qty) && std::isnan(qty_percent);
6233 bool paired_reversal_close = false;
6234 bool paired_reversal_whole_drop = false;
6235 std::optional<native_order::RequestHandle> paired_reversal_parent;
6236 if (default_fifo_close && current != 0.0) {
6237 const auto point = require_host().current_execution_point();
6238 for (const auto& handle : live_handles_) {
6239 const auto pending = placement_.find(handle.incarnation);
6240 if (pending == placement_.end()) continue;
6241 const auto& row = pending->second;
6242 if (row.opening && row.family == PineOrderFamily::Entry
6243 && row.is_long != (current > 0.0)
6244 && (!point || row.placement_script_open_ms
6245 == point->decision.script_bar_open_ms)) {
6246 paired_reversal_close = true;
6247 paired_reversal_parent = handle;
6248 const bool rule5_scope =
6249 config_.default_qty_type
6250 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
6251 && std::abs(config_.default_qty_value - 100.0) < 1e-12
6252 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
6253 && *staged_.quantity_grid < 1.0
6254 && staged_.syminfo.pointvalue == 1.0
6255 && row.sizing.fx == 1.0
6256 && staged_.account_fx_effective_from_ms.empty()
6257 && config_.commission_value == 0.0 && config_.slippage == 0
6258 && !config_.process_orders_on_close
6259 && !config_.calc_on_order_fills
6260 && finite_positive(row.sizing.frozen_units)
6261 && finite_positive(row.sizing.equity)
6262 && finite_positive(row.sizing.price);
6263 if (rule5_scope) {
6264 const double notional_per_price = row.sizing.frozen_units
6265 * staged_.syminfo.pointvalue * row.sizing.fx;
6266 const double rounded_cost = source_money_round(
6267 notional_per_price * row.sizing.price);
6268 const double affordable_price = source_money_round(
6269 source_money_round(row.sizing.equity)
6270 / notional_per_price);
6271 paired_reversal_whole_drop =
6272 row.sizing.equity + 1e-9 >= rounded_cost
6273 && std::isfinite(affordable_price)
6274 && affordable_price < row.sizing.price;
6275 }
6276 break;
6277 }
6278 }
6279 }
6280 if (immediately) {
6281 const double current = require_host().physical_position().signed_units;
6282 if (current != 0.0) {
6283 pending_entries_.erase(std::remove_if(pending_entries_.begin(), pending_entries_.end(),
6284 [&](const PendingEntry& entry) {
6285 return (current > 0.0) == entry.snapshot.is_long;
6286 }), pending_entries_.end());
6287 }
6288 }
6289 // P-DA2: an explicitly percentage-sized deferred ANY close retains
6290 // HostSized and resolves from the live selected cohort at its candidate.
6291 // FIFO's logical id ledger retains its command-time claim; so does the
6292 // separately specified same-bar market transaction artifact.
6293 const bool frozen_same_bar_close = same_bar_market_tx_scope() && !immediately
6294 && current != 0.0;
6295 const bool deferred_percentage = config_.close_entries_rule_any
6296 && std::isnan(effective_qty) && std::isfinite(requested_percent)
6297 && !immediately;
6298 const bool pooc_close_basis = config_.process_orders_on_close;
6299 double frozen_qty = effective_qty;
6300 if (std::isnan(effective_qty) && (!deferred_percentage || immediately || frozen_same_bar_close
6301 || pooc_close_basis)) {
6302 const auto point = require_host().current_execution_point();
6303 const std::int64_t bar_key = point ? point->decision.script_bar_open_ms
6304 : require_host().native_decision_floor();
6305 const double source_basis = cohort_exposure_for(id);
6306 const double fallback_basis = std::abs(require_host().physical_position().signed_units);
6307 const double script_basis = source_basis > 0.0 ? source_basis : fallback_basis;
6308 pooc_close_basis_by_script_bar_.emplace(bar_key, script_basis);
6309 frozen_qty = quantize_close_units(script_basis, requested_percent);
6310 }
6311 const double close_basis = cohort_exposure_for(id) > 0.0
6312 ? cohort_exposure_for(id) : std::abs(current);
6313 const bool closes_full_position = close_basis > 0.0
6314 && ((std::isfinite(frozen_qty) && frozen_qty >= close_basis - 1e-10)
6315 || (std::isnan(effective_qty) && requested_percent >= 100.0 - 1e-9));
6316 bool reversal_pair = false;
6317 if (closes_full_position && current != 0.0) {
6318 const auto opposite_entry = [&](const PlacementSnapshot& candidate) {
6319 return candidate.opening && candidate.family == PineOrderFamily::Entry
6320 && candidate.is_long != (current > 0.0)
6321 && (!finite_positive(candidate.exit_levels.limit)
6322 && !finite_positive(candidate.exit_levels.stop));
6323 };
6324 for (const auto& pending : pending_same_bar_commands_)
6325 reversal_pair = reversal_pair || opposite_entry(pending.snapshot);
6326 for (const auto& handle : live_handles_) {
6327 const auto found = placement_.find(handle.incarnation);
6328 if (found != placement_.end())
6329 reversal_pair = reversal_pair || opposite_entry(found->second);
6330 }
6331 }
6332 const bool batched_pooc_fifo = default_fifo_close
6333 && !immediately && config_.process_orders_on_close
6334 && !config_.calc_on_order_fills
6335 && !coof_recalc_active_ && !stream_mode_ && !cap.active()
6336 && !reversal_pair;
6337 if (batched_pooc_fifo) {
6338 const bool enqueued = enqueue_pooc_fifo_close(
6339 id, comment, callsite_token, command_ordinal);
6340 if (enqueued && closes_full_position)
6341 cancel_exit_orders_for_full_close(id);
6342 return;
6343 }
6344 if (closes_full_position && !reversal_pair) {
6345 cancel_exit_orders_for_full_close(id);
6346 }
6347 // A partial source close breaks the exact ShortSeed transaction book.
6348 // Its legacy effect is to leave the two frozen reversal commands on the
6349 // ordinary broker pass; the stale close itself owns no surviving broker
6350 // object. Detect that complete paired batch by source facts rather than
6351 // a generic id heuristic.
6352 if (same_bar_market_tx_scope() && !immediately && current != 0.0
6353 && (!std::isnan(qty) || !std::isnan(qty_percent))) {
6354 const bool variable_default = config_.default_qty_type
6355 != static_cast<int>(QtyType::FIXED);
6356 if (variable_default) {
6357 bool held_reentry = false;
6358 bool opposite_reversal = false;
6359 const bool held_long = current > 0.0;
6360 for (const auto& pending : pending_same_bar_commands_) {
6361 if (!pending.opening || pending.snapshot.family != PineOrderFamily::Entry) continue;
6362 if (pending.snapshot.source_id == id && pending.snapshot.is_long == held_long)
6363 held_reentry = true;
6364 if (pending.snapshot.is_long != held_long) opposite_reversal = true;
6365 }
6366 if (held_reentry && opposite_reversal) {
6367 // The partial close has no executable artifact in the source
6368 // transaction pass. Retain an accepted, dormant source
6369 // handle so the ShortSeed role projection remains truthful;
6370 // its unreachable buy limit prevents it from changing the
6371 // ordinary two-reversal outcome.
6372 if (short_seed_context_is_live() && current < 0.0) {
6373 native_order::Request placeholder;
6374 placeholder.intent = native_order::HostSized{
6375 native_order::HostSizedKind::Close, std::nullopt};
6376 placeholder.label = "__close__" + id;
6377 placeholder.trigger = native_order::Limit{
6378 std::numeric_limits<double>::min()};
6379 placeholder.owner = owner_for_close(id, true);
6380 PlacementSnapshot placeholder_snapshot;
6381 placeholder_snapshot.family = PineOrderFamily::Close;
6382 placeholder_snapshot.source_id = id;
6383 placeholder_snapshot.from_entry = id;
6384 placeholder_snapshot.requested_qty = frozen_qty;
6385 placeholder_snapshot.qty_percent = requested_percent;
6386 placeholder_snapshot.is_long = false;
6387 placeholder_snapshot.deferred_cohort = true;
6388 placeholder_snapshot.command_ordinal = command_ordinal;
6389 placeholder_snapshot.sizing = sizing_snapshot();
6390 (void)submit_or_replace(std::move(placeholder), std::move(placeholder_snapshot),
6391 false, "__short_seed_partial_hold__" + id);
6392 }
6393 return;
6394 }
6395 }
6396 }
6397 if (same_bar_market_tx_scope() && !immediately && id.size() != 0
6398 && std::isnan(qty) && std::isnan(qty_percent) && current != 0.0
6399 && finite_positive(frozen_qty)) {
6400 native_order::Request request;
6401 request.label = "__close__" + id;
6402 request.comment = comment;
6403 PlacementSnapshot snapshot;
6404 snapshot.family = PineOrderFamily::Close;
6405 snapshot.source_id = id;
6406 snapshot.from_entry = id;
6407 snapshot.comment = comment;
6408 snapshot.requested_qty = frozen_qty;
6409 snapshot.qty_percent = requested_percent;
6410 snapshot.command_ordinal = command_ordinal;
6411 snapshot.is_long = false;
6412 snapshot.frozen_market_instruction = true;
6413 snapshot.frozen_market_transaction_units = frozen_qty;
6414 snapshot.frozen_market_targeted_close = true;
6415 snapshot.frozen_market_target_was_long = current > 0.0;
6416 snapshot.birth = capture_order_birth();
6417 snapshot.sizing = sizing_snapshot();
6418 // ab9714be pine_orders.cpp:599-601: exit/close orders bind owner to active position_cycle_seq_
6419 snapshot.placement_cycle = current_position_cycle_;
6420 if (const auto point = require_host().current_execution_point()) {
6421 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
6422 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
6423 }
6424 const SourceId replacement_key = callsite_token == 0
6425 ? SourceId{}
6426 : "__pine_close_site__" + std::to_string(callsite_token);
6427 pending_same_bar_commands_.push_back(
6428 {std::move(request), std::move(snapshot), replacement_key, false});
6429 pending_same_bar_close_qty_ += frozen_qty;
6430 return;
6431 }
6432 // P-DA4: an immediate close has a live cohort at the command boundary;
6433 // materialize its percentage quantity and bind that fixed roster before
6434 // invoking execute_current. Deferred exits retain HostSized/BindCohort.
6435 const bool host_sized = std::isnan(frozen_qty) && !immediately && !default_fifo_close;
6436 const bool default_full_any = config_.close_entries_rule_any
6437 && std::isnan(qty) && std::isnan(qty_percent) && !immediately;
6438 const bool pooc_cap_full_close = default_fifo_close
6439 && config_.process_orders_on_close && cap.active();
6440 native_order::Request request;
6441 request.intent = host_sized
6442 ? native_order::OrderIntent{native_order::HostSized{native_order::HostSizedKind::Close, std::nullopt}}
6444 // The generic request label is the legacy close transaction signal while
6445 // PlacementSnapshot keeps the public source id for cohorts/readback.
6446 request.label = "__close__" + id;
6447 request.comment = comment;
6448 double coof_close_fill = kNaN;
6449 bool coof_close_next_open = false;
6450 if (coof_recalc_active_ && !coof_first_open_ && !immediately) {
6451 const auto state = require_host().native_state();
6452 const bool lower_path = state.spec && state.spec->intrabar.lower();
6453 if (!lower_path) {
6454 bool high_first = std::abs(coof_script_bar_.high - coof_script_bar_.open)
6455 < std::abs(coof_script_bar_.open - coof_script_bar_.low);
6456 if (state.spec) {
6457 if (state.spec->path_order == NativePathOrder::HighFirst) high_first = true;
6458 else if (state.spec->path_order == NativePathOrder::LowFirst) high_first = false;
6459 }
6460 const NativePathPhase second = high_first
6461 ? NativePathPhase::Low : NativePathPhase::High;
6462 const double endpoint = high_first
6463 ? coof_script_bar_.low : coof_script_bar_.high;
6464 coof_close_next_open = coof_context_.coordinate.path_phase == second
6465 && coof_fill_at_path_point(endpoint);
6466 }
6467 }
6468 if (coof_recalc_active_ && !coof_first_open_ && !immediately
6469 && !coof_close_next_open
6470 && coof_script_bar_valid_) {
6471 int next_waypoint_index = -1;
6472 const double next_waypoint = coof_next_waypoint(&next_waypoint_index);
6473 const auto point = require_host().current_execution_point();
6474 const double current_quote = point ? point->price : kNaN;
6475 const bool buy = current < 0.0;
6476 coof_close_fill = source_bar_fill_tick(
6477 next_waypoint, staged_.syminfo.mintick)
6478 + (buy ? 1.0 : -1.0) * config_.slippage
6479 * staged_.syminfo.mintick;
6480 if (finite_positive(coof_close_fill) && finite_positive(current_quote)
6481 && !source_same_point(current_quote, coof_close_fill, staged_.syminfo.mintick)) {
6482 // ab9714be pine_scheduler.cpp:541-563 fills the recalc's market
6483 // close AT the next EXTREME waypoint POINT (W1/W2) and books
6484 // bar_fill_price(waypoint) (pine_fills.cpp:7962-7966). Arm that
6485 // trigger at the raw extreme the path actually reaches; the
6486 // booked tick-grid price rides forced_execution_price. An
6487 // off-grid extreme (H 11.445 booked 11.45) is otherwise never
6488 // touched on this bar and the close rolls to the next open.
6489 // The C point admits no cascade order (pine_scheduler.cpp:
6490 // 541-544, 616-620), so a C target keeps the booked level.
6491 // The booked tick may lie on the wrong side of that raw level,
6492 // so an extreme limit is a touch trigger (fill-through).
6493 const bool extreme_target = next_waypoint_index == 1
6494 || next_waypoint_index == 2;
6495 const double level = extreme_target ? next_waypoint : coof_close_fill;
6496 const native_order::Limit limit{level,
6497 extreme_target && coof_close_fill != level};
6498 const bool falling = coof_close_fill < current_quote;
6499 if (buy) {
6500 request.trigger = falling
6501 ? native_order::Trigger{limit}
6503 } else {
6504 request.trigger = falling
6506 : native_order::Trigger{limit};
6507 }
6508 }
6509 }
6510 // ab9714be pine_strategy_commands.cpp:2222-2278 and :2522-2590:
6511 // the default close-entries rule freezes the id's logical quantity, then
6512 // drains the physical book in FIFO order. It is deliberately not bound
6513 // to that id's opening cohort (the ANY rule below is).
6514 request.owner = pooc_cap_full_close
6516 : default_fifo_close
6517 ? (paired_reversal_close && !paired_reversal_whole_drop
6518 && paired_reversal_parent
6520 *paired_reversal_parent}}
6522 : owner_for_close(id, host_sized && !default_full_any);
6523 PlacementSnapshot snapshot;
6524 snapshot.family = PineOrderFamily::Close; snapshot.source_id = id;
6525 snapshot.from_entry = default_fifo_close
6526 && (!paired_reversal_close || paired_reversal_whole_drop) ? SourceId{} : id;
6527 const bool exact_full_dynamic_close = host_sized && std::isnan(qty)
6528 && requested_percent >= 100.0;
6529 snapshot.comment = comment;
6530 snapshot.requested_qty = exact_full_dynamic_close ? kNaN : frozen_qty;
6531 snapshot.qty_percent = requested_percent;
6532 snapshot.projection_remaining_qty = frozen_qty;
6533 snapshot.command_ordinal = command_ordinal;
6534 snapshot.is_long = false;
6535 snapshot.forced_execution_price = coof_close_fill;
6536 snapshot.immediately = immediately; snapshot.deferred_cohort = host_sized; snapshot.sizing = sizing_snapshot();
6537 if (paired_reversal_parent && !paired_reversal_whole_drop)
6538 snapshot.paired_reversal_parent = *paired_reversal_parent;
6539 // The all-in source collision retains a same-side re-entry which may be
6540 // rejected only at the next opening. Its close must be a child of that
6541 // candidate: if the re-entry is refused, the legacy close is suppressed
6542 // rather than flattening the carried seed on its own.
6543 const bool all_in_percent = std::isnan(effective_qty)
6544 && requested_percent >= 100.0 - 1e-9
6545 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
6546 && config_.default_qty_value >= 100.0;
6547 bool opposite_reversal_pair = false;
6548 if (all_in_percent && current != 0.0 && !staged_.quantity_grid) {
6549 const bool held_long = current > 0.0;
6550 const auto point = require_host().current_execution_point();
6551 const auto same_bar = [&](const PlacementSnapshot& candidate) {
6552 return candidate.opening && candidate.family == PineOrderFamily::Entry
6553 && candidate.is_long != held_long
6554 && (!point || candidate.placement_script_open_ms
6555 == point->decision.script_bar_open_ms);
6556 };
6557 for (const auto& pending : pending_same_bar_commands_)
6558 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
6559 for (const auto& pending : pending_entries_)
6560 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
6561 for (const auto& pending : pending_coof_requests_)
6562 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
6563 for (const auto& handle : live_handles_) {
6564 const auto found = placement_.find(handle.incarnation);
6565 if (found != placement_.end())
6566 opposite_reversal_pair = opposite_reversal_pair || same_bar(found->second);
6567 }
6568 }
6569 if (opposite_reversal_pair) {
6570 // pine_fills.cpp:5509-5519 treats the entry/strategy.close pair as one
6571 // reversal decision. The entry's native ReverseTo owns the admitted
6572 // close; if it is declined, the paired close must not flatten the held
6573 // cohort independently. Keep only the source observation and place
6574 // the standing bracket behind the pair's lifecycle barrier.
6575 hold_reversal_pair_brackets(id);
6576 source_shadow_pending_.push_back({snapshot, "__close__" + id});
6577 return;
6578 }
6579 bool all_in_dependent_close = false;
6580 if (all_in_percent) {
6581 const auto point = require_host().current_execution_point();
6582 if (point) {
6583 std::optional<native_order::RequestHandle> reentry;
6584 for (const auto& handle : live_handles_) {
6585 const auto found = placement_.find(handle.incarnation);
6586 if (found == placement_.end() || !found->second.opening
6587 || found->second.family != PineOrderFamily::Entry
6588 || found->second.placement_script_open_ms
6589 != point->decision.script_bar_open_ms
6590 || found->second.source_id != id) {
6591 continue;
6592 }
6593 reentry = handle;
6594 }
6595 // ab9714be pine_fills.cpp:2991-3009: a same-side call placed at
6596 // the pyramiding cap moves nothing at the next opening, and the
6597 // open-boundary admission retires it unless an earlier opposite
6598 // same-bar command can move the account first. A close bound to
6599 // that doomed row would be cancelled with it, while the owner's
6600 // close (an ordinary exit order) still flattens the held side.
6601 if (reentry) {
6602 const auto found = placement_.find(reentry->incarnation);
6603 if (found != placement_.end() && found->second.projection_over_pyramiding) {
6604 const auto& held = found->second;
6605 bool earlier_opposite = false;
6606 for (const auto& handle : live_handles_) {
6607 const auto prior = placement_.find(handle.incarnation);
6608 if (prior == placement_.end()) continue;
6609 const auto& row = prior->second;
6610 if (row.opening && row.family == PineOrderFamily::Entry
6611 && row.is_long != held.is_long
6612 && row.placement_script_open_ms == held.placement_script_open_ms
6613 && row.source_sequence < held.source_sequence) {
6614 earlier_opposite = true;
6615 break;
6616 }
6617 }
6618 if (!earlier_opposite) reentry.reset();
6619 }
6620 }
6621 if (reentry) {
6622 request.owner = native_order::WaitForApplied{*reentry};
6623 all_in_dependent_close = true;
6624 hold_reversal_pair_brackets(id);
6625 }
6626 }
6627 }
6628 if (coof_recalc_active_ && !coof_first_open_ && !immediately
6629 && !finite_positive(coof_close_fill)
6630 && coof_script_bar_valid_
6631 && std::holds_alternative<native_order::Market>(request.trigger)) {
6632 const auto point = require_host().current_execution_point();
6633 const auto native = require_host().native_state();
6634 const double current_quote = point ? point->price : kNaN;
6635 const double next_waypoint = next_source_path_waypoint(
6636 coof_script_bar_, coof_context_.coordinate.path_phase, current_quote,
6637 native.spec ? native.spec->path_order : NativePathOrder::Auto,
6638 staged_.syminfo.mintick, config_.slippage);
6639 const bool buy = require_host().physical_position().signed_units < 0.0;
6640 const double next_fill = nearest_tick(
6641 next_waypoint + (buy ? 1.0 : -1.0) * config_.slippage
6642 * staged_.syminfo.mintick,
6643 staged_.syminfo.mintick);
6644 if (finite_positive(next_fill) && finite_positive(current_quote)
6645 && next_fill != current_quote) {
6646 const bool falling = next_fill < current_quote;
6647 if (buy) {
6648 request.trigger = falling
6651 } else {
6652 request.trigger = falling
6655 }
6656 snapshot.forced_execution_price = next_fill;
6657 }
6658 }
6659 // Only the generated callsite-token form represents source replacement.
6660 // Independent close statements in one evaluation must coexist (P1/P2).
6661 const SourceId replacement_key = default_fifo_close
6662 ? (callsite_token == 0
6663 ? SourceId{}
6664 : "__pine_close_site__" + std::to_string(callsite_token))
6665 : (callsite_token == 0 ? SourceId{}
6666 : id + "#close#" + std::to_string(callsite_token));
6667 if (coof_close_next_open) {
6668 snapshot.forced_execution_price = kNaN;
6669 request.trigger = native_order::Market{};
6670 pending_coof_requests_.push_back({
6671 std::move(request), std::move(snapshot), replacement_key,
6672 false, 0, true});
6673 return;
6674 }
6675 const PlacementSnapshot shadow_snapshot = snapshot;
6676 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false, replacement_key);
6677 if (!accepted && all_in_dependent_close) {
6678 source_shadow_pending_.push_back({shadow_snapshot, "__close__" + id});
6679 }
6680 const bool pooc_immediate_fifo = config_.process_orders_on_close
6681 && !coof_recalc_active_
6682 && !config_.close_entries_rule_any && closes_full_position
6683 && !reversal_pair;
6684 if ((immediately || (config_.process_orders_on_close
6685 && (cap.active() || pooc_immediate_fifo))) && accepted) {
6686 const auto outcome = require_host().execute_current(
6687 {*accepted, NativeCurrentPriceRule::NearestTick});
6688 if (const auto* applied = std::get_if<native_order::ExecutionAppliedEvent>(&outcome)) {
6689 consume_cohort_units(id, *applied);
6690 current_debited_applied_ordinals_.insert(applied->ordinal);
6691 if (applied->terminal) retire(*accepted);
6692 }
6693 }
6694}
6695
6697 if (intraday_loss_orders_blocked()) return;
6698 if (require_host().physical_position().signed_units == 0.0) return;
6699 if (const auto point = require_host().current_execution_point();
6700 point && cap_placement_denied(point->decision)) {
6701 return;
6702 }
6703 if (const auto point = require_host().current_execution_point()) {
6704 close_all_pending_script_bar_ = point->decision.script_bar_open_ms;
6705 }
6706 if (config_.calc_on_order_fills) {
6707 const auto point = require_host().current_execution_point();
6708 const std::int64_t script_open = point ? point->decision.script_bar_open_ms
6709 : require_host().native_decision_floor();
6710 std::vector<native_order::RequestHandle> newborns;
6711 for (const auto& handle : live_handles_) {
6712 const auto placement = placement_.find(handle.incarnation);
6713 if (placement != placement_.end() && placement->second.opening
6714 && placement->second.family == PineOrderFamily::Entry
6715 && placement->second.placement_script_open_ms == script_open) {
6716 newborns.push_back(handle);
6717 }
6718 }
6719 for (const auto& handle : newborns) {
6720 const auto result = require_host().cancel(handle);
6721 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
6722 }
6723 }
6724 native_order::Request request;
6725 request.intent = native_order::Flatten{}; request.label = "__close__";
6726 PlacementSnapshot snapshot;
6728 snapshot.source_id = request.label;
6729 snapshot.sizing = sizing_snapshot();
6730 const auto accepted = submit_or_replace(
6731 std::move(request), std::move(snapshot), false, "__pine_close_all");
6732 if (!accepted || config_.process_orders_on_close) return;
6733 const auto close = placement_.find(accepted->incarnation);
6734 if (close == placement_.end()) return;
6735 const auto side = static_cast<PositionSide>(close->second.projection_position_side);
6736 for (const auto& handle : live_handles_) {
6737 if (handle == *accepted) continue;
6738 const auto found = placement_.find(handle.incarnation);
6739 if (found == placement_.end()) continue;
6740 auto& pending = found->second;
6741 const bool pure_prior_stop = pending.opening
6742 && pending.family == PineOrderFamily::Entry
6743 && finite_positive(pending.exit_levels.stop)
6744 && !finite_positive(pending.exit_levels.limit)
6745 && !finite_positive(pending.exit_levels.trail_points)
6746 && !finite_positive(pending.exit_levels.trail_price)
6747 && !finite_positive(pending.exit_levels.trail_offset)
6748 && !pending.stop_limit_activated
6749 && pending.projection_created_bar < close->second.projection_created_bar
6750 && pending.projection_position_side == static_cast<std::int32_t>(side)
6751 && pending.is_long == (side == PositionSide::LONG)
6752 && !pending.projection_over_pyramiding;
6753 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
6754 const bool has_physical_id = pine_host
6755 && pine_host->adapter_has_open_entry_id(pending.source_id);
6756 if (!pure_prior_stop || !has_physical_id) continue;
6757 pending.preserved_by_close_all = *accepted;
6758 pending.preserved_close_all_bar = close->second.projection_created_bar;
6759 }
6760 const double cur_pos = require_host().physical_position().signed_units;
6761 const auto cur_pt = require_host().current_execution_point();
6762 if (cur_pos != 0.0 && cur_pt) {
6763 std::vector<native_order::RequestHandle> opposite_entries;
6764 for (const auto& handle : live_handles_) {
6765 if (handle == *accepted) continue;
6766 const auto found = placement_.find(handle.incarnation);
6767 if (found == placement_.end()) continue;
6768 const auto& pend = found->second;
6769 const bool unpriced_market = !finite_positive(pend.exit_levels.limit)
6770 && !finite_positive(pend.exit_levels.stop)
6771 && !finite_positive(pend.exit_levels.trail_points)
6772 && !finite_positive(pend.exit_levels.trail_price)
6773 && !finite_positive(pend.exit_levels.trail_offset);
6774 if (pend.opening && pend.family == PineOrderFamily::Entry
6775 && unpriced_market
6776 && !pend.projection_over_pyramiding
6777 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6778 opposite_entries.push_back(handle);
6779 }
6780 }
6781 for (const auto& handle : opposite_entries) {
6782 const auto found = placement_.find(handle.incarnation);
6783 if (found == placement_.end()) continue;
6784 PlacementSnapshot entry_snapshot = found->second;
6785 // ab9714be pine_fills.cpp:3848-3856: full market close processes before opposite entry so entry executes after close
6786 entry_snapshot.paired_reversal_parent = *accepted;
6787 entry_snapshot.market_admission = {};
6788 const auto result = require_host().cancel(handle);
6789 if (result.status == native_order::CancelStatus::Cancelled) {
6790 retire(handle);
6792 const double target = entry_snapshot.is_long
6793 ? entry_snapshot.requested_qty : -entry_snapshot.requested_qty;
6794 req.intent = std::isnan(target)
6795 ? native_order::OrderIntent{native_order::HostSized{native_order::HostSizedKind::Open,
6796 entry_snapshot.is_long ? native_order::Side::Long : native_order::Side::Short}}
6798 req.label = entry_snapshot.source_id;
6799 req.comment = entry_snapshot.comment;
6800 pending_entries_.push_back({std::move(req), std::move(entry_snapshot), entry_snapshot.source_id});
6801 }
6802 }
6803 for (auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
6804 if (it->opening && it->snapshot.family == PineOrderFamily::Entry
6805 && !it->snapshot.projection_over_pyramiding
6806 && it->snapshot.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6807 it->snapshot.paired_reversal_parent = *accepted;
6808 it->snapshot.market_admission = {};
6809 pending_entries_.push_back({std::move(it->request), std::move(it->snapshot), it->replacement_key});
6810 it = pending_same_bar_commands_.erase(it);
6811 } else {
6812 ++it;
6813 }
6814 }
6815 }
6816}
6817
6818void PineExecutionAdapter::exit(const SourceId& exit_id, const SourceId& from_entry,
6819 double limit_price, double stop_price, double trail_points,
6820 double trail_offset, double trail_price, double qty_percent,
6821 const std::string& comment, double qty,
6822 const std::string& oca_name, double profit_ticks,
6823 double loss_ticks) {
6824 const double requested_qty_percent = qty_percent;
6825 if (intraday_loss_orders_blocked()) return;
6826 if (const auto point = require_host().current_execution_point();
6827 point && cap_placement_denied(point->decision)) {
6828 return;
6829 }
6830 if (!from_entry.empty()) {
6831 const auto token = named_entry_cancel_tokens_.find(from_entry);
6832 if (token != named_entry_cancel_tokens_.end()) {
6833 bool recreated_parent = std::any_of(
6834 pending_entries_.begin(), pending_entries_.end(),
6835 [&](const PendingEntry& pending) {
6836 return pending.snapshot.opening
6837 && pending.snapshot.source_id == from_entry;
6838 });
6839 for (const auto& handle : live_handles_) {
6840 const auto parent = placement_.find(handle.incarnation);
6841 if (parent != placement_.end() && parent->second.opening
6842 && parent->second.family == PineOrderFamily::Entry
6843 && parent->second.source_id == from_entry) {
6844 recreated_parent = true;
6845 break;
6846 }
6847 }
6848 // The cancellation token belongs only to a retained child that
6849 // survives until the fresh parent is declared. Reissuing the
6850 // child first creates a fresh topology and consumes that token.
6851 if (!recreated_parent) named_entry_cancel_tokens_.erase(token);
6852 }
6853 }
6854 const bool actionable = !std::isnan(limit_price) || !std::isnan(stop_price)
6855 || !std::isnan(profit_ticks) || !std::isnan(loss_ticks)
6856 || !std::isnan(trail_points) || !std::isnan(trail_price);
6857 if (!actionable) {
6858 // ab9714be:pine_strategy_commands.cpp:1637-1660: an all-NaN
6859 // strategy.exit is inert, but a matching reissue still removes the
6860 // prior bracket before returning. trail_offset alone is not an
6861 // actionable leg.
6862 exit_cancel_bracket(exit_id, from_entry, comment);
6863 return;
6864 }
6865 // A pending variable short-context entry is only tentatively held for the
6866 // three-object ShortSeed command book. A bracket call proves it belongs
6867 // to an ordinary entry family, so materialize that entry before binding
6868 // the bracket just as the legacy source callback did.
6869 if (!pending_same_bar_commands_.empty()
6870 && config_.default_qty_type != static_cast<int>(QtyType::FIXED)
6871 && require_host().physical_position().signed_units < 0.0) {
6872 flush_pending_same_bar_commands();
6873 }
6874 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
6875 throw std::overflow_error("Pine source command sequence exhausted");
6876 }
6877 const std::uint64_t command_sequence = ++source_command_sequence_;
6878 // Trail point and offset operands are source tick counts, whereas the
6879 // generic native Trail carries prices. Preserve the source operands in
6880 // the placement projection and lower only the executable request here.
6881 // Points ceil away from entry (with the established source tolerance);
6882 // offsets truncate exactly, including the explicit-zero trail shape.
6883 const double source_trail_points = trail_points;
6884 const double source_trail_offset = trail_offset;
6885 const double source_trail_price = trail_price;
6886 const bool has_trail_request = !std::isnan(source_trail_points)
6887 || !std::isnan(source_trail_price);
6888
6889 // Relative levels resolve against a live source cohort. The original tick
6890 // facts remain in the snapshot for deferred/observer projections.
6891 const auto physical = require_host().physical_position();
6892 const SourceId partial_exit_key = exit_id + "\x1f" + from_entry;
6893 // ab9714be pine_strategy_commands.cpp:1661-1692: only a re-issue that is
6894 // itself explicitly partial against the live position (net of a same-bar
6895 // pending close) is suppressed; an explicit qty covering the whole
6896 // remaining position is a full exit and re-arms.
6897 const double live_exit_units = std::max(
6898 0.0, std::abs(physical.signed_units) - pending_same_bar_close_qty_);
6899 double reissue_percent = std::isnan(qty_percent)
6900 ? 100.0 : std::clamp(qty_percent, 0.0, 100.0);
6901 if (!std::isnan(qty) && live_exit_units > internal::kQtyEpsilon)
6902 reissue_percent = std::min(qty, live_exit_units) / live_exit_units * 100.0;
6903 const bool partial_reissue = reissue_percent < 100.0 - internal::kFullPercentEps;
6904 if (const auto consumed = consumed_partial_exit_cycles_.find(partial_exit_key);
6905 consumed != consumed_partial_exit_cycles_.end() && partial_reissue
6906 && live_exit_units > internal::kQtyEpsilon
6907 && consumed->second == current_position_cycle_) {
6908 return;
6909 }
6910 double entry_price = require_host().position_avg_price();
6911 const double tick = staged_.syminfo.mintick;
6912 bool parent_long = physical.signed_units > 0.0;
6913 bool known_parent_level = physical.signed_units != 0.0;
6914 // ab9714be src/source/pine_fills.cpp:4560-4564 (pending_order_level_resolved)
6915 // and 7208-7229: an exit's relative operands resolve against the LIVE
6916 // position once its from_entry filled this cycle (W23b); an empty from_entry
6917 // means "every entry", so the entry-bar priced-exit gate reads the opening
6918 // lot (W32a, ab9714be src/source/pine_strategy_commands.cpp:2595-2606,
6919 // pine_fills.cpp:7695-7728).
6920 const bool parent_filled_this_cycle = physical.signed_units != 0.0
6921 && !from_entry.empty()
6922 && [&] {
6923 const auto filled = cohorts_by_id_.find(from_entry);
6924 return filled != cohorts_by_id_.end() && !filled->second.opened.empty();
6925 }();
6926 const auto observe_staged_parent = [&](const PlacementSnapshot& parent) {
6927 if (parent_filled_this_cycle) return;
6928 if (!parent.opening || parent.family != PineOrderFamily::Entry) {
6929 return;
6930 }
6931 if (!from_entry.empty() && parent.source_id != from_entry) {
6932 return;
6933 }
6934 // A pending same-bar parent is the level basis only while its cohort
6935 // has no live exposure. With a lot of that id still open the exit
6936 // resolves against the LIVE position: ab9714be
6937 // materialize_relative_exit_prices_for_live_position (pine_fills.cpp:
6938 // 7208-7230) binds relative operands to position_entry_price_ of the
6939 // current cycle, never to a not-yet-filled re-entry's resting price.
6940 if (cohort_exposure_for(from_entry) > 0.0
6941 && ((physical.signed_units > 0.0) == parent.is_long)) {
6942 return;
6943 }
6944 parent_long = parent.is_long;
6945 known_parent_level = finite_positive(parent.exit_levels.limit);
6946 if (known_parent_level) entry_price = parent.exit_levels.limit;
6947 };
6948 for (const auto& parent : pending_same_bar_commands_)
6949 observe_staged_parent(parent.snapshot);
6950 for (const auto& parent : pending_entries_)
6951 observe_staged_parent(parent.snapshot);
6952 for (const auto& parent : pending_coof_requests_)
6953 observe_staged_parent(parent.snapshot);
6954 const auto cohort = cohorts_by_id_.find(from_entry);
6955 for (const auto& handle : live_handles_) {
6956 const auto parent = placement_.find(handle.incarnation);
6957 const bool already_opened = cohort != cohorts_by_id_.end()
6958 && std::find(cohort->second.opened.begin(), cohort->second.opened.end(), handle)
6959 != cohort->second.opened.end();
6960 if (parent != placement_.end() && !already_opened)
6961 observe_staged_parent(parent->second);
6962 }
6963 // ab9714be src/source/pine_fills.cpp:4560-4564: a pending same-id origin
6964 // never defers the levels once the id has filled in this cycle.
6965 if (cohort != cohorts_by_id_.end() && !parent_filled_this_cycle) {
6966 for (const auto position : live_origin_positions(cohort->second)) {
6967 const auto& origin = cohort->second.origins[position];
6968 const auto parent = placement_.find(origin.incarnation);
6969 if (parent == placement_.end() || !parent->second.opening
6970 || !origin_is_pending(origin)
6971 || std::find(cohort->second.opened.begin(), cohort->second.opened.end(), origin)
6972 != cohort->second.opened.end()) {
6973 continue;
6974 }
6975 // A prearmed bracket follows its pending MARKET/reversal parent,
6976 // not the opposite physical position still held at placement.
6977 // ab9714be pine_fills.cpp:7788-7800 then evaluates the child with
6978 // the parent's eventual close side and exact-touch direction.
6979 // The prearm serves the FLAT-to-position transition only: while a
6980 // lot of that id is live and the physical side already matches it,
6981 // the bracket resolves against the live position
6982 // (pine_fills.cpp:7208-7230), not against the same-id re-entry that
6983 // is still pending on this bar.
6984 const bool same_side_live = physical.signed_units != 0.0
6985 && (physical.signed_units > 0.0) == parent->second.is_long
6986 && cohort_exposure_for(from_entry) > 0.0;
6987 if (same_side_live) continue;
6988 parent_long = parent->second.is_long;
6989 // Relative profit/loss/trail operands bind to the parent's actual
6990 // fill, not its resting limit. An opening gap may improve that
6991 // fill, so keep these operands unresolved until the Applied event.
6992 known_parent_level = false;
6993 break;
6994 }
6995 }
6996 if (known_parent_level && finite_positive(entry_price) && finite_positive(tick)) {
6997 const bool long_side = physical.signed_units != 0.0 ? physical.signed_units > 0.0 : parent_long;
6998 if (!finite_positive(limit_price) && finite_positive(profit_ticks))
6999 limit_price = entry_price + (long_side ? 1.0 : -1.0) * profit_ticks * tick;
7000 if (!finite_positive(stop_price) && finite_positive(loss_ticks))
7001 stop_price = entry_price - (long_side ? 1.0 : -1.0) * loss_ticks * tick;
7002 if (std::isfinite(source_trail_points)) {
7003 const double trail_ticks = std::ceil(source_trail_points - 5e-5);
7004 trail_price = directional_tick(entry_price
7005 + (long_side ? 1.0 : -1.0) * trail_ticks * tick,
7006 tick, long_side);
7007 }
7008 }
7009 limit_price = source_level_on_price_grid(limit_price, tick);
7010 stop_price = source_level_on_price_grid(stop_price, tick);
7011 // A source trail offset is a tick count, which is the kernel's own
7012 // native_order::TrailTicks spelling: acceptance resolves it against the
7013 // run's price tick — the same syminfo mintick this adapter projects into
7014 // NativeRunSpec::price_tick (project()) — and stores a plain price
7015 // distance, so the adapter never multiplies a tick count by a tick.
7016 // Only the explicit-zero shape stays adapter policy: the legacy broker
7017 // rides the TICK-QUANTIZED running best, whereas the kernel's zero offset
7018 // rides the raw best and exits on any adverse ULP. Half a tick is exactly
7019 // that quantization boundary, and it is now spelled in ticks as well.
7020 std::optional<double> native_trail_offset_ticks;
7021 if (has_trail_request && std::isfinite(source_trail_offset)
7022 && source_trail_offset >= 0.0 && finite_positive(tick)) {
7023 const double offset_ticks = std::floor(source_trail_offset);
7024 native_trail_offset_ticks = offset_ticks == 0.0 ? 0.5 : offset_ticks;
7025 }
7026 const bool unresolved_trail = has_trail_request
7027 && !finite_positive(trail_price) && std::isfinite(source_trail_points);
7028 const bool unresolved_ticks =
7029 (!finite_positive(limit_price) && finite_positive(profit_ticks))
7030 || (!finite_positive(stop_price) && finite_positive(loss_ticks));
7031 const bool unresolved_relative = unresolved_trail || unresolved_ticks;
7032 if (unresolved_relative) {
7033 PendingRelativeExit pending;
7034 pending.exit_id = exit_id; pending.from_entry = from_entry;
7035 pending.trail_points = source_trail_points; pending.trail_offset = source_trail_offset;
7036 pending.trail_price = source_trail_price; pending.qty_percent = qty_percent;
7037 pending.comment = comment; pending.qty = qty; pending.oca_name = oca_name;
7038 pending.profit_ticks = profit_ticks; pending.loss_ticks = loss_ticks;
7039 auto existing = std::find_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
7040 [&](const PendingRelativeExit& value) {
7041 return value.exit_id == exit_id && value.from_entry == from_entry;
7042 });
7043 if (existing == pending_relative_exits_.end()) {
7044 pending_relative_exits_.push_back(std::move(pending));
7045 } else {
7046 const auto same = [](double left, double right) {
7047 return same_double_bits(left, right) || (std::isnan(left) && std::isnan(right));
7048 };
7049 const bool unchanged = same(existing->trail_points, pending.trail_points)
7050 && same(existing->trail_offset, pending.trail_offset)
7051 && same(existing->trail_price, pending.trail_price)
7052 && same(existing->qty_percent, pending.qty_percent)
7053 && same(existing->qty, pending.qty)
7054 && same(existing->profit_ticks, pending.profit_ticks)
7055 && same(existing->loss_ticks, pending.loss_ticks)
7056 && existing->comment == pending.comment
7057 && existing->oca_name == pending.oca_name;
7058 // The kernel child carries the definition it was anchored from.
7059 if (!unchanged) withdraw_anchored_relative_legs(&exit_id, &from_entry);
7060 *existing = std::move(pending);
7061 }
7062 source_shadow_pending_.erase(
7063 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
7064 [&](const SourceShadowPending& row) {
7065 return row.snapshot.source_id == exit_id
7066 && row.snapshot.from_entry == from_entry;
7067 }),
7068 source_shadow_pending_.end());
7069 // A known absolute sibling remains executable while the relative
7070 // trail/profit/loss component waits for its MARKET parent's fill.
7071 // ab9714be pine_strategy_commands.cpp:533-537: 0.0 is a present level.
7072 // Kernel validate_levels still refuses non-finite and negative
7073 // levels (A43); those stay command projections.
7074 if (finite_non_negative(limit_price) || finite_non_negative(stop_price)) {
7075 // Continue below and submit the known absolute sibling.
7076 } else {
7077 PlacementSnapshot shadow;
7078 shadow.family = has_trail_request ? PineOrderFamily::ExitTrail
7079 : (finite_positive(loss_ticks) ? PineOrderFamily::ExitStop
7081 shadow.source_id = exit_id;
7082 shadow.from_entry = from_entry;
7083 shadow.comment = comment;
7084 shadow.oca_name = oca_name;
7085 shadow.requested_qty = qty;
7086 shadow.qty_percent = qty_percent;
7087 shadow.command_sequence = command_sequence;
7088 shadow.source_sequence = ++source_sequence_;
7089 shadow.projection_position_side = static_cast<std::int32_t>(PositionSide::FLAT);
7090 shadow.exit_levels = {limit_price, stop_price, source_trail_points,
7091 source_trail_offset, source_trail_price,
7092 profit_ticks, loss_ticks};
7093 shadow.sizing = sizing_snapshot();
7094 source_shadow_pending_.push_back({std::move(shadow), exit_id});
7095 return;
7096 }
7097 }
7098 if (std::isnan(qty) && qty_percent == 100.0) {
7099 const auto cohort = cohorts_by_id_.find(from_entry);
7100 if (cohort != cohorts_by_id_.end() && !cohort->second.origins.empty()) {
7101 const auto origin = cohort->second.origins.back();
7102 const auto parent = placement_.find(origin.incarnation);
7103 if (parent != placement_.end() && parent->second.family == PineOrderFamily::Entry)
7104 parent->second.has_full_entry_bracket = true;
7105 }
7106 }
7107 const bool dynamic = std::isnan(qty);
7108 const auto family_key = key_for(exit_id, from_entry);
7109 const bool defer_for_same_bar_priority = !config_.calc_on_order_fills
7110 && std::any_of(pending_entries_.begin(), pending_entries_.end(),
7111 [](const PendingEntry& pending) {
7112 return pending.snapshot.retained_parent_topology;
7113 });
7114 const bool defer_for_same_bar_add_exit = dynamic
7115 && !config_.calc_on_order_fills && !coof_recalc_active_
7116 && physical.signed_units != 0.0 && !from_entry.empty()
7117 && (!std::isfinite(qty_percent) || qty_percent >= 100.0)
7118 && std::any_of(pending_entries_.begin(), pending_entries_.end(),
7119 [&](const PendingEntry& pending) {
7120 const auto& add = pending.snapshot;
7121 return add.opening && add.family == PineOrderFamily::Entry
7122 && add.source_id == from_entry
7123 && add.is_long == (physical.signed_units > 0.0)
7124 && !finite_positive(add.exit_levels.limit)
7125 && !finite_positive(add.exit_levels.stop)
7126 && !finite_positive(add.exit_levels.trail_offset)
7127 && add.oca_name.empty();
7128 });
7129 // The legacy pending book leaves an identical resting bracket untouched.
7130 // In the ordinary source-bar path, a dynamic exit resolves against its
7131 // live cohort only when it fills, so reissuing unchanged levels cannot
7132 // alter its executable terms. Avoid rebuilding two requests, snapshots,
7133 // and replacement events on the common every-bar bracket pattern.
7134 // A normalized full-percent reservation is `units / basis * 100`, which
7135 // can land a few binary64 ULPs below the literal 100% request. The owner
7136 // still treats the re-issued default bracket as the same full-position
7137 // reservation, so those representations are equivalent here.
7138 const auto same_dynamic_percent = [](double prior_percent, double request_percent) {
7139 if (std::isfinite(prior_percent) && std::isfinite(request_percent)
7140 && prior_percent >= 100.0 - 1e-12
7141 && request_percent >= 100.0 - 1e-12) {
7142 return true;
7143 }
7144 return same_double_bits(prior_percent, request_percent);
7145 };
7146 const auto unchanged_dynamic_leg = [&](PineOrderFamily family, double level) {
7147 const SourceId replacement_key = exit_id + "\x1f" + from_entry
7148 + std::to_string(static_cast<int>(family));
7149 const auto live = live_by_source_key_.find(key_for(replacement_key));
7150 if (live == live_by_source_key_.end()) return false;
7151 const auto placement = placement_.find(live->second.incarnation);
7152 if (placement == placement_.end()) return false;
7153 const auto& prior = placement->second;
7154 if (prior.family != family || !prior.deferred_cohort || !std::isnan(prior.requested_qty)
7155 || prior.source_id != exit_id || prior.from_entry != from_entry
7156 || prior.comment != comment || prior.oca_name != oca_name
7157 || prior.oca_type != 0
7158 || !same_dynamic_percent(prior.qty_percent, qty_percent)
7159 || prior.bracket_origin.incarnation != 0
7160 || !same_double_bits(prior.exit_levels.limit, limit_price)
7161 || !same_double_bits(prior.exit_levels.stop, stop_price)
7162 || !std::isnan(prior.exit_levels.trail_points)
7163 || !std::isnan(prior.exit_levels.trail_offset)
7164 || !std::isnan(prior.exit_levels.trail_price)
7165 || !std::isnan(prior.exit_levels.profit_ticks)
7166 || !std::isnan(prior.exit_levels.loss_ticks)) {
7167 return false;
7168 }
7169 return (family == PineOrderFamily::ExitLimit
7170 && same_double_bits(prior.exit_levels.limit, level))
7171 || (family == PineOrderFamily::ExitStop
7172 && same_double_bits(prior.exit_levels.stop, level));
7173 };
7174 const bool plain_dynamic_bracket = dynamic && !coof_recalc_active_
7175 && !materializing_relative_ && !config_.calc_on_order_fills
7176 && physical.signed_units != 0.0 && std::isnan(trail_points)
7177 && std::isnan(trail_offset) && std::isnan(trail_price)
7178 && std::isnan(profit_ticks) && std::isnan(loss_ticks)
7179 && (finite_positive(limit_price) || finite_positive(stop_price));
7180 if (plain_dynamic_bracket
7181 && (!finite_positive(limit_price)
7182 || unchanged_dynamic_leg(PineOrderFamily::ExitLimit, limit_price))
7183 && (!finite_positive(stop_price)
7184 || unchanged_dynamic_leg(PineOrderFamily::ExitStop, stop_price))) {
7185 return;
7186 }
7187 // Every leg emitted by one source strategy.exit shares its placement-time
7188 // sizing facts. Submitting the first resting sibling cannot alter the
7189 // physical account, so capture them once rather than re-marking equity
7190 // for each leg.
7191 const PineSizingSnapshot exit_sizing = sizing_snapshot();
7192 const native_order::Owner dynamic_owner = dynamic
7193 ? owner_for_close(from_entry, !materializing_relative_)
7195 const SourceId dynamic_group_name = dynamic
7196 ? (oca_name.empty() ? exit_id + "\x1f" + from_entry : oca_name) : SourceId{};
7197 const SourceId dynamic_key_prefix = dynamic ? exit_id + "\x1f" + from_entry : SourceId{};
7198 const auto pending_default_reversal_parent = [&](const PlacementSnapshot& parent) {
7199 if (!parent.opening || parent.family != PineOrderFamily::Entry
7200 || parent.source_id != from_entry) {
7201 return false;
7202 }
7203 // A flat, ordinarily pending parent remains cohort-bound: the
7204 // deferred-ANY contract requires its child to survive a same-id
7205 // parent replacement and grow with the replacement. WaitForApplied
7206 // is only the source reversal-parent relation below.
7207 if (physical.signed_units == 0.0) return false;
7208 return !std::isfinite(parent.requested_qty)
7209 && parent.is_long != (physical.signed_units > 0.0);
7210 };
7211 bool binds_pending_reversal_entry = false;
7212 for (const auto& pending : pending_same_bar_commands_)
7213 binds_pending_reversal_entry = binds_pending_reversal_entry
7214 || pending_default_reversal_parent(pending.snapshot);
7215 for (const auto& pending : pending_entries_)
7216 binds_pending_reversal_entry = binds_pending_reversal_entry
7217 || pending_default_reversal_parent(pending.snapshot);
7218 for (const auto& handle : live_handles_) {
7219 const auto found = placement_.find(handle.incarnation);
7220 if (found != placement_.end()
7221 && pending_default_reversal_parent(found->second)) {
7222 binds_pending_reversal_entry = true;
7223 }
7224 }
7225 double reserved_exit_qty = kNaN;
7226 double pending_parent_units = 0.0;
7227 const auto reservation_point = require_host().current_execution_point();
7228 const auto observe_pending_parent = [&](const PlacementSnapshot& parent) {
7229 if (!reservation_point || !parent.opening
7230 || parent.family != PineOrderFamily::Entry
7231 || parent.source_id != from_entry
7232 || parent.placement_script_open_ms
7233 != reservation_point->decision.script_bar_open_ms
7234 || parent.projection_over_pyramiding) {
7235 return;
7236 }
7237 const double units = finite_positive(parent.requested_qty)
7238 ? std::abs(parent.requested_qty) : parent.frozen_market_own_units;
7239 if (finite_positive(units)) pending_parent_units += units;
7240 };
7241 for (const auto& handle : live_handles_) {
7242 const auto parent = placement_.find(handle.incarnation);
7243 if (parent != placement_.end()) observe_pending_parent(parent->second);
7244 }
7245 for (const auto& parent : pending_entries_) observe_pending_parent(parent.snapshot);
7246 const double live_reservation_basis = binds_pending_reversal_entry ? 0.0
7247 : std::max(0.0, std::abs(physical.signed_units)
7248 - pending_same_bar_close_qty_ + pending_parent_units);
7249 const bool reservation_ok = compute_exit_reservation(
7250 exit_id, from_entry, qty, qty_percent, live_reservation_basis,
7251 reserved_exit_qty);
7252 if (!reservation_ok) {
7253 // clear_existing_exit_order ran before sizing on the legacy route:
7254 // a zero-capacity reissue removes its predecessor as well as refusing
7255 // the replacement (ab9714be:pine_strategy_commands.cpp:1688-1699,
7256 // :2739-2811).
7257 exit_cancel_bracket(exit_id, from_entry, comment);
7258 return;
7259 }
7260 const auto source_point = require_host().current_execution_point();
7261 const OrderBirth exit_birth = capture_order_birth();
7262 const auto exit_birth_reach = compat::pine::select_historical_birth_reach(
7263 exit_birth, has_trail_request);
7264 const bool historical_cascade =
7266 const bool pooc_short_tick_scope = source_point
7267 && config_.process_orders_on_close && !config_.calc_on_order_fills
7268 && !stream_mode_ && source_point->decision.sub_count <= 1
7269 && physical.signed_units < 0.0 && physical.lot_count == 1
7270 && position_open_script_bar_ < source_point->decision.script_bar_open_ms
7271 && config_.pyramiding == 0 && config_.slippage == 0
7272 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
7273 && std::abs(staged_.syminfo.pointvalue - 1.0) < 1e-12
7274 && active_staged_fx(source_point->decision.sub_bar_open_ms) == 1.0
7275 && staged_.account_fx_effective_from_ms.empty()
7276 && std::isnan(qty)
7277 && (!std::isfinite(qty_percent) || qty_percent >= 100.0 - 1e-9)
7278 && oca_name.empty() && !has_trail_request
7279 && finite_positive(staged_.syminfo.mintick);
7280 PineOrderFamily pooc_current_close_family = PineOrderFamily::Entry;
7281 if (pooc_short_tick_scope && source_point) {
7282 const double close = nearest_tick(source_point->price, staged_.syminfo.mintick);
7283 if (finite_positive(stop_price) && close >= stop_price)
7284 pooc_current_close_family = PineOrderFamily::ExitStop;
7285 else if (finite_positive(limit_price) && close <= limit_price)
7286 pooc_current_close_family = PineOrderFamily::ExitLimit;
7287 }
7288 auto submit_leg = [&](PineOrderFamily family, native_order::Trigger trigger) {
7289 bool defer_marketable_coof_stop = false;
7290 bool coof_limit_waypoint_qualified = false;
7291 double coof_limit_waypoint_price = kNaN;
7292 double coof_stop_waypoint_price = kNaN;
7293 if (coof_recalc_active_ && !coof_first_open_ && historical_cascade
7294 && coof_script_bar_valid_) {
7295 const auto native = require_host().native_state();
7296 const bool ordinary_path = !native.spec || native.spec->intrabar.is_none();
7297 const auto point = require_host().current_execution_point();
7298 if (ordinary_path && point && finite_positive(point->price)) {
7299 const auto phase = coof_context_.coordinate.path_phase;
7300 const double endpoint = next_source_path_waypoint(
7301 coof_script_bar_, phase, point->price,
7302 native.spec ? native.spec->path_order : NativePathOrder::Auto,
7303 staged_.syminfo.mintick, config_.slippage);
7304
7305 const bool closing_long = physical.signed_units > 0.0;
7306 if (family == PineOrderFamily::ExitLimit
7307 && finite_positive(limit_price) && finite_positive(endpoint)) {
7308 // ab9714be pine_fills.cpp:563-583 (KI-67 Model S): a priced
7309 // exit born in a mid-bar fill recalculation is held on the
7310 // remainder of its in-flight leg. It gap-fills at the
7311 // leg-end waypoint when its level lies in that remainder
7312 // (engine_path_resolve.cpp:1016-1046) or when it is the
7313 // marketable LIMIT born from the second fill at O
7314 // (pine_strategy_commands.cpp:2009-2015), which is held
7315 // through O->W1 and gets one gap attempt at W1.
7316 const bool marketable = closing_long
7317 ? point->price >= limit_price : point->price <= limit_price;
7318 const bool endpoint_satisfies = closing_long
7319 ? endpoint >= limit_price : endpoint <= limit_price;
7320 const bool endpoint_ahead = closing_long
7321 ? endpoint > point->price : endpoint < point->price;
7322 const bool in_flight_remainder = !marketable
7323 && endpoint_satisfies && endpoint_ahead;
7324 const bool later_same_open = phase == NativePathPhase::Open
7325 && marketable;
7326 if (in_flight_remainder
7327 || (later_same_open && endpoint_satisfies && endpoint_ahead)) {
7328 // The waypoint fill books bar_fill_price(endpoint)
7329 // (pine_fills.cpp:7962-7966); past an OFF-grid
7330 // endpoint (L 11.195 booked 11.20) the limit is a
7331 // touch trigger or the kernel refuses the booking.
7332 const bool off_grid_endpoint = finite_positive(staged_.syminfo.mintick)
7333 && source_bar_fill_tick(endpoint, staged_.syminfo.mintick) != endpoint;
7334 trigger = native_order::Limit{endpoint, off_grid_endpoint};
7335 coof_limit_waypoint_qualified = true;
7336 coof_limit_waypoint_price = endpoint;
7337 } else if (later_same_open) {
7338 // Armed at W1: it fills there when W1 satisfies the
7339 // level, otherwise on a later leg's cross.
7340 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
7341 trigger = native_order::StopLimit{
7342 endpoint, threshold ? threshold->price : limit_price};
7343 coof_limit_waypoint_qualified = endpoint_satisfies;
7344 } else if (marketable && !endpoint_ahead && !endpoint_satisfies
7345 && coof_remaining_recrosses(limit_price, closing_long)) {
7346 // Any other marketable level is held on the in-flight
7347 // leg and fills only on a later leg's exact cross
7348 // (pine_fills.cpp:576); the recross scope below decides
7349 // whether it stays on this bar.
7350 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
7351 trigger = native_order::StopLimit{
7352 endpoint, threshold ? threshold->price : limit_price};
7353 }
7354 } else if (family == PineOrderFamily::ExitStop
7355 && finite_positive(stop_price)) {
7356 const bool marketable = closing_long
7357 ? point->price <= stop_price : point->price >= stop_price;
7358 if (marketable && phase != NativePathPhase::Open
7359 && std::isfinite(qty) && finite_positive(endpoint)
7360 && !source_same_point(point->price, endpoint, staged_.syminfo.mintick)) {
7361 trigger = native_order::Stop{endpoint};
7362 coof_stop_waypoint_price = endpoint;
7363 } else {
7364 defer_marketable_coof_stop = marketable;
7365 }
7366 }
7367 }
7368 }
7369 if (pooc_short_tick_scope) {
7370 const double tick = staged_.syminfo.mintick;
7371 // The short exit is a buy. rounded(low) <= limit and
7372 // rounded(high) >= stop are compared on the grid INDEX of the
7373 // quantized extreme (ab9714be include/pineforge/engine.hpp:1268-
7374 // 1312, tick_grid_price: k / (1/mintick), so an on-grid level
7375 // equals its grid point). The k * mintick product sits one ULP
7376 // above a decimal on-grid level, which made an unbounded ULP walk
7377 // toward the level cross a whole tick; source_trigger_threshold
7378 // resolves the same half-tick boundary with a bounded walk.
7379 if (family == PineOrderFamily::ExitLimit && finite_positive(limit_price)) {
7380 trigger = native_order::Limit{source_trigger_threshold(
7381 limit_price, tick, /*is_buy=*/true, /*is_limit=*/true)};
7382 } else if (family == PineOrderFamily::ExitStop
7383 && finite_positive(stop_price)) {
7384 trigger = native_order::Stop{source_trigger_threshold(
7385 stop_price, tick, /*is_buy=*/true, /*is_limit=*/false)};
7386 }
7387 }
7388 auto submit_one = [&](native_order::Owner owner, bool host_sized,
7389 const SourceId& replacement_key, native_order::Group group,
7390 bool defer_new_instance,
7391 native_order::RequestHandle bracket_origin = {}) {
7392 native_order::Request request;
7393 request.intent = host_sized
7395 native_order::HostSizedKind::Close, std::nullopt}}
7398 request.label = exit_id; request.comment = comment; request.trigger = trigger;
7399 request.owner = std::move(owner);
7400 request.group = std::move(group);
7401 PlacementSnapshot snapshot;
7402 snapshot.family = family;
7403 snapshot.source_id = exit_id;
7404 snapshot.from_entry = from_entry;
7405 snapshot.comment = comment;
7406 snapshot.oca_name = oca_name;
7407 snapshot.requested_qty = qty;
7408 snapshot.qty_percent = qty_percent; snapshot.deferred_cohort = host_sized;
7410 binds_pending_reversal_entry;
7411 snapshot.is_long = false;
7412 snapshot.command_sequence = command_sequence;
7413 snapshot.bracket_origin = std::move(bracket_origin);
7414 snapshot.exit_levels = {limit_price, stop_price, source_trail_points,
7415 source_trail_offset, source_trail_price,
7416 profit_ticks, loss_ticks};
7417 snapshot.birth = exit_birth;
7418 snapshot.birth_reach = exit_birth_reach;
7419 snapshot.trail_activation_level = trail_price;
7420 snapshot.sizing = exit_sizing;
7421 // ab9714be pine_fills.cpp:2680-2696: update_trail_best_for_bar_open initializes running extreme from placement bar close
7422 if (family == PineOrderFamily::ExitTrail && std::isfinite(exit_sizing.price))
7423 snapshot.retained_trail_best = exit_sizing.price;
7424 snapshot.placement_cycle = current_position_cycle_;
7425 if (source_point) {
7426 snapshot.projection_created_bar =
7427 source_point->decision.coordinate.interval_index;
7428 snapshot.projection_position_side = physical.signed_units > 0.0
7429 ? static_cast<std::int32_t>(PositionSide::LONG)
7430 : (physical.signed_units < 0.0
7431 ? static_cast<std::int32_t>(PositionSide::SHORT)
7432 : static_cast<std::int32_t>(PositionSide::FLAT));
7433 snapshot.placement_script_open_ms =
7434 source_point->decision.script_bar_open_ms;
7435 snapshot.placement_sub_open_ms = source_point->decision.sub_bar_open_ms;
7436 }
7437 initialize_l4c_policy(snapshot, {});
7438 if (finite_positive(coof_limit_waypoint_price))
7439 snapshot.forced_execution_price = coof_limit_waypoint_price;
7440 else if (finite_positive(coof_stop_waypoint_price))
7441 snapshot.forced_execution_price = coof_stop_waypoint_price;
7442 if (coof_recalc_active_ && !coof_first_open_ && historical_cascade
7443 && family == PineOrderFamily::ExitStop
7444 && finite_positive(stop_price)) {
7445 const auto point = require_host().current_execution_point();
7446 const double birth = point ? point->price : kNaN;
7447 const double waypoint = coof_next_waypoint();
7448 const bool long_position = physical.signed_units > 0.0;
7449 const bool reached_on_next_leg = long_position
7450 ? (birth > stop_price && waypoint <= stop_price)
7451 : (birth < stop_price && waypoint >= stop_price);
7452 if (reached_on_next_leg) {
7453 snapshot.forced_execution_price = source_bar_fill_tick(
7454 waypoint, staged_.syminfo.mintick)
7455 + (long_position ? -1.0 : 1.0) * config_.slippage
7456 * staged_.syminfo.mintick;
7457 }
7458 }
7459 const double source_position = std::abs(require_host().physical_position().signed_units);
7460 if (std::isfinite(reserved_exit_qty)) {
7461 snapshot.projection_remaining_qty = reserved_exit_qty;
7462 const bool foreign_pending_opening = std::any_of(
7463 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
7464 const auto candidate = placement_.find(handle.incarnation);
7465 return candidate != placement_.end()
7466 && candidate->second.opening
7467 && candidate->second.source_id != from_entry;
7468 }) || std::any_of(
7469 pending_entries_.begin(), pending_entries_.end(),
7470 [&](const PendingEntry& candidate) {
7471 return candidate.snapshot.opening
7472 && candidate.snapshot.source_id != from_entry;
7473 });
7474 const double percent = std::isfinite(requested_qty_percent)
7475 ? requested_qty_percent : 100.0;
7476 snapshot.fixed_exit_reservation = std::isfinite(snapshot.requested_qty)
7477 || (!binds_pending_reversal_entry
7478 && physical.signed_units != 0.0
7479 && (percent < 100.0 - 1e-9
7480 || reserved_exit_qty < live_reservation_basis - 1e-9
7481 || (source_position > 0.0
7482 && reserved_exit_qty < source_position - 1e-9)
7483 || foreign_pending_opening));
7484 } else if (!binds_pending_reversal_entry && host_sized
7485 && !std::isfinite(snapshot.requested_qty)
7486 && source_position > 0.0) {
7487 const double percent = std::isfinite(snapshot.qty_percent)
7488 ? snapshot.qty_percent : 100.0;
7489 snapshot.projection_remaining_qty = quantize_close_units(source_position, percent);
7490 if (!from_entry.empty()) {
7491 std::map<SourceId, double> reserved_by_exit;
7492 for (const auto& handle : live_handles_) {
7493 const auto existing = placement_.find(handle.incarnation);
7494 if (existing == placement_.end()) continue;
7495 const auto& prior = existing->second;
7496 const bool exit = prior.family == PineOrderFamily::ExitLimit
7497 || prior.family == PineOrderFamily::ExitStop
7498 || prior.family == PineOrderFamily::ExitTrail;
7499 if (!exit || prior.from_entry != from_entry
7500 || prior.source_id == exit_id
7501 || !std::isfinite(prior.projection_remaining_qty)) {
7502 continue;
7503 }
7504 auto& held = reserved_by_exit[prior.source_id];
7505 held = std::max(held, prior.projection_remaining_qty);
7506 }
7507 double reserved = 0.0;
7508 for (const auto& row : reserved_by_exit) reserved += row.second;
7509 const double available = std::max(0.0, source_position - reserved);
7510 snapshot.projection_remaining_qty = std::min(
7511 snapshot.projection_remaining_qty, available);
7512 snapshot.fixed_exit_reservation = percent < 100.0
7513 || !reserved_by_exit.empty();
7514 }
7515 }
7516 if (config_.process_orders_on_close && from_entry.empty() && source_position > 0.0) {
7517 double requested = std::isfinite(snapshot.requested_qty)
7518 ? std::abs(snapshot.requested_qty)
7519 : (std::isfinite(snapshot.projection_remaining_qty)
7520 ? snapshot.projection_remaining_qty : source_position);
7521 double reserved = 0.0;
7522 for (const auto& handle : live_handles_) {
7523 const auto existing = placement_.find(handle.incarnation);
7524 if (existing == placement_.end()) continue;
7525 const auto& prior = existing->second;
7526 const bool global_exit = prior.from_entry.empty()
7527 && (prior.family == PineOrderFamily::ExitLimit
7528 || prior.family == PineOrderFamily::ExitStop
7529 || prior.family == PineOrderFamily::ExitTrail);
7530 if (!global_exit || prior.source_id == exit_id
7531 || !std::isfinite(prior.projection_remaining_qty)) {
7532 continue;
7533 }
7534 reserved += std::max(0.0, prior.projection_remaining_qty);
7535 }
7536 const double available = std::max(0.0, source_position - reserved);
7537 requested = std::min(requested, available);
7538 if (!(requested > 0.0)) return;
7539 snapshot.projection_remaining_qty = requested;
7540 }
7541 if (defer_marketable_coof_stop) {
7542 if (broker_open_epoch_ == std::numeric_limits<std::uint64_t>::max())
7543 throw std::overflow_error("source delayed market epoch exhausted");
7544 if (const auto point = require_host().current_execution_point()) {
7545 snapshot.projection_created_bar =
7546 point->decision.coordinate.interval_index;
7547 snapshot.placement_script_open_ms =
7548 point->decision.script_bar_open_ms;
7549 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
7550 }
7551 snapshot.projection_created_during_coof = true;
7552 snapshot.projection_coof_at_terminal = coof_context_.is_terminal_sub_bar;
7553 snapshot.projection_coof_mid_bar = !coof_context_.is_terminal_sub_bar;
7554 delayed_market_orders_.push_back({
7555 std::move(request), std::move(snapshot), replacement_key,
7556 broker_open_epoch_ + 1U, true});
7557 return;
7558 }
7559 if (coof_recalc_active_ && physical.signed_units != 0.0) {
7560 const auto point = require_host().current_execution_point();
7561 const double birth = point ? point->price : kNaN;
7562 const bool long_position = physical.signed_units > 0.0;
7563 const bool wrong_stop = family == PineOrderFamily::ExitStop
7564 && finite_positive(stop_price)
7565 && (long_position ? stop_price > birth : stop_price < birth);
7566 const bool wrong_limit = family == PineOrderFamily::ExitLimit
7567 && finite_positive(limit_price)
7568 && (long_position ? limit_price < birth : limit_price > birth);
7569 // Evaluated only for a wrong-side limit that can still hold:
7570 // the book scans below are otherwise dead on every reissue.
7571 const auto qualified_recross = [&] {
7572 if (coof_limit_waypoint_qualified) return true;
7573 if (!coof_remaining_recrosses(limit_price, long_position)) return false;
7574 const PlacementSnapshot* parent = nullptr;
7575 native_order::RequestHandle parent_handle{};
7576 if (const auto cohort = cohorts_by_id_.find(from_entry);
7577 cohort != cohorts_by_id_.end() && !cohort->second.opened.empty()) {
7578 parent_handle = cohort->second.opened.back();
7579 const auto found = placement_.find(parent_handle.incarnation);
7580 if (found != placement_.end()) parent = &found->second;
7581 }
7582 const bool plain_market_parent = parent
7583 && parent->family == PineOrderFamily::Entry
7584 && !finite_positive(parent->exit_levels.limit)
7585 && !finite_positive(parent->exit_levels.stop)
7586 && !finite_positive(parent->exit_levels.trail_offset)
7587 && !finite_positive(parent->exit_levels.trail_price);
7588 if (!plain_market_parent) return false;
7589 const double next_waypoint = coof_next_waypoint();
7590 const bool reachable_stop = finite_positive(stop_price)
7591 && (long_position
7592 ? (next_waypoint <= stop_price
7593 || coof_script_bar_.close <= stop_price)
7594 : (next_waypoint >= stop_price
7595 || coof_script_bar_.close >= stop_price));
7596 if (reachable_stop) return false;
7597 const bool competing_opening = std::any_of(
7598 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
7599 if (handle == parent_handle) return false;
7600 const auto candidate = placement_.find(handle.incarnation);
7601 return candidate != placement_.end()
7602 && candidate->second.opening;
7603 });
7604 if (competing_opening) return false;
7605 const bool direct_partial = point && std::any_of(
7606 placement_.begin(), placement_.end(), [&](const auto& row) {
7607 return row.second.family == PineOrderFamily::Close
7608 && row.second.immediately
7609 && row.second.projection_created_bar
7610 == point->decision.coordinate.interval_index;
7611 });
7612 return !direct_partial;
7613 };
7614 if ((wrong_stop || wrong_limit)
7615 && (coof_first_open_ || wrong_stop || !qualified_recross())) {
7617 delayed_market_orders_.push_back({
7618 std::move(request), std::move(snapshot), replacement_key,
7619 broker_open_epoch_ + 1U});
7620 return;
7621 }
7622 }
7623 const auto native = require_host().native_state();
7624 const bool stage_chart_tick_scope = config_.calc_on_order_fills
7625 && !config_.process_orders_on_close
7626 && coof_recalc_active_ && !defer_coof_tail()
7627 && (!native.spec || native.spec->intrabar.is_none())
7628 && (family == PineOrderFamily::ExitLimit
7629 || family == PineOrderFamily::ExitStop);
7630 if (stage_chart_tick_scope) {
7631 snapshot.projection_created_during_coof = true;
7632 snapshot.projection_coof_at_terminal = coof_context_.is_terminal_sub_bar;
7633 snapshot.projection_coof_mid_bar = !coof_context_.is_terminal_sub_bar;
7634 auto queued = std::find_if(
7635 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
7636 [&](const PendingBracketLeg& row) {
7637 return row.replacement_key == replacement_key;
7638 });
7639 PendingBracketLeg staged{std::move(request), std::move(snapshot),
7640 replacement_key, family_key};
7641 if (queued == pending_bracket_legs_.end())
7642 pending_bracket_legs_.push_back(std::move(staged));
7643 else
7644 *queued = std::move(staged);
7645 return;
7646 }
7647 if (auto queued = std::find_if(
7648 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
7649 [&](const PendingBracketLeg& row) {
7650 return row.replacement_key == replacement_key
7651 && row.snapshot.defer_until_post_parent_calculation;
7652 }); queued != pending_bracket_legs_.end()) {
7653 const std::uint64_t predecessor =
7654 queued->snapshot.legs.target().incarnation != 0
7655 ? queued->snapshot.legs.target().incarnation
7656 : queued->snapshot.projection_predecessor;
7657 snapshot.projection_predecessor = predecessor;
7658 snapshot.source_sequence = queued->snapshot.source_sequence;
7660 if (const auto point = require_host().current_execution_point()) {
7661 snapshot.projection_created_bar =
7662 point->decision.coordinate.interval_index;
7663 snapshot.projection_position_side =
7664 static_cast<std::int32_t>(PositionSide::FLAT);
7665 }
7666 *queued = PendingBracketLeg{std::move(request), std::move(snapshot),
7667 replacement_key, family_key};
7668 return;
7669 }
7670 if (defer_coof_tail() && !finite_positive(coof_stop_waypoint_price)) {
7671 pending_coof_requests_.push_back({std::move(request), std::move(snapshot), replacement_key,
7672 false, family_key, true});
7673 return;
7674 }
7676 && physical.signed_units == 0.0
7677 && !(cohort_exposure_for(snapshot.from_entry) > 0.0)) {
7678 auto queued = std::find_if(pending_bracket_legs_.begin(),
7679 pending_bracket_legs_.end(), [&](const PendingBracketLeg& row) {
7680 return row.replacement_key == replacement_key;
7681 });
7682 PendingBracketLeg staged{std::move(request), std::move(snapshot),
7683 replacement_key, family_key};
7684 if (queued == pending_bracket_legs_.end())
7685 pending_bracket_legs_.push_back(std::move(staged));
7686 else
7687 *queued = std::move(staged);
7688 return;
7689 }
7690 bool defer_for_live_parent = false;
7691 // ab9714be src/source/pine_fills.cpp:7461-7468: while flat, an exit
7692 // created while flat is skipped rather than removed, and waits in
7693 // the book for the position to open. Defer the leg until its parent
7694 // entry applies so the native core does not erase it while flat.
7695 // src/source/pine_strategy_commands.cpp:2595-2606 makes an empty
7696 // from_entry mean "every entry", so any pending opening entry is
7697 // its parent; a named parent keeps the (id, from_entry) key that
7698 // carries the queue position across a reissue
7699 // (src/source/pine_strategy_commands.cpp:2627-2648).
7700 const auto existing_leg = live_by_source_key_.find(key_for(replacement_key));
7701 if (!defer_for_same_bar_priority
7702 && require_host().physical_position().signed_units == 0.0) {
7703 const auto matches_parent = [&](const PlacementSnapshot& row) {
7704 return row.opening && row.family == PineOrderFamily::Entry
7705 && (snapshot.from_entry.empty()
7706 || row.source_id == snapshot.from_entry);
7707 };
7708 bool has_pending_parent = false;
7709 for (const auto& entry : pending_entries_) {
7710 if (matches_parent(entry.snapshot)) {
7711 has_pending_parent = true;
7712 break;
7713 }
7714 }
7715 for (const auto& entry : pending_same_bar_commands_) {
7716 if (matches_parent(entry.snapshot)) {
7717 has_pending_parent = true;
7718 break;
7719 }
7720 }
7721 for (const auto& live : live_handles_) {
7722 const auto parent = placement_.find(live.incarnation);
7723 if (parent == placement_.end()) continue;
7724 if (!matches_parent(parent->second)) continue;
7725 has_pending_parent = true;
7726 break;
7727 }
7728 if (has_pending_parent
7729 && (existing_leg != live_by_source_key_.end()
7730 || snapshot.from_entry.empty())) {
7731 defer_for_live_parent = true;
7732 }
7733 }
7734 if (defer_for_same_bar_priority || defer_for_live_parent) {
7735 if (const auto existing = live_by_source_key_.find(key_for(replacement_key));
7736 existing != live_by_source_key_.end()) {
7737 const auto predecessor = existing->second;
7738 const auto previous = placement_.find(predecessor.incarnation);
7739 if (previous != placement_.end()) {
7740 snapshot.projection_predecessor = predecessor.incarnation;
7741 snapshot.source_sequence = previous->second.source_sequence;
7742 }
7743 (void)require_host().cancel(predecessor);
7744 retire(predecessor);
7745 }
7746 if (const auto point = require_host().current_execution_point()) {
7747 snapshot.projection_created_bar = point->decision.coordinate.interval_index;
7748 snapshot.projection_created_bar_pinned = true;
7749 snapshot.projection_position_side = static_cast<std::int32_t>(PositionSide::FLAT);
7750 }
7751 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
7752 [&](const PendingBracketLeg& row) {
7753 return row.replacement_key == replacement_key;
7754 });
7755 PendingBracketLeg staged{std::move(request), std::move(snapshot), replacement_key,
7756 family_key};
7757 if (queued == pending_bracket_legs_.end())
7758 pending_bracket_legs_.push_back(std::move(staged));
7759 else
7760 *queued = std::move(staged);
7761 return;
7762 }
7763 if (defer_new_instance && live_by_source_key_.find(key_for(replacement_key))
7764 == live_by_source_key_.end()) {
7765 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
7766 [&](const PendingBracketLeg& row) { return row.replacement_key == replacement_key; });
7767 PendingBracketLeg staged{std::move(request), std::move(snapshot), replacement_key, family_key};
7768 if (queued == pending_bracket_legs_.end()) pending_bracket_legs_.push_back(std::move(staged));
7769 else *queued = std::move(staged);
7770 return;
7771 }
7772 // ab9714be src/source/pine_strategy_commands.cpp:2627-2648 keys a
7773 // source bracket by (exit id, from_entry) and a reissue replaces
7774 // the stored leg rather than adding a second one. A leg that was
7775 // staged for a pending parent is that stored leg, so the freshly
7776 // submitted instance supersedes it; otherwise the stale staged
7777 // levels resurrect when a later parent entry applies.
7778 for (auto stale = pending_bracket_legs_.begin();
7779 stale != pending_bracket_legs_.end();) {
7780 if (stale->replacement_key == replacement_key)
7781 stale = pending_bracket_legs_.erase(stale);
7782 else
7783 ++stale;
7784 }
7785 const std::uint64_t placement_high_water = placement_.high_water();
7786 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
7787 replacement_key);
7788 if (accepted) {
7789 auto& family = bracket_families_[family_key];
7790 // Every family member was remembered before this submission,
7791 // so a successor above the prior placement high-water cannot
7792 // already be present; only a returned existing handle needs
7793 // the membership scan.
7794 if (accepted->incarnation > placement_high_water
7795 || std::find(family.begin(), family.end(), *accepted) == family.end()) {
7796 family.push_back(*accepted);
7797 }
7798 }
7799 };
7800
7801 if (dynamic) {
7802 native_order::RequestHandle pending_origin{};
7803 bool defer_until_parent = false;
7804 if (!from_entry.empty()) {
7805 const auto cohort = cohorts_by_id_.find(from_entry);
7806 if (cohort != cohorts_by_id_.end() && cohort->second.opened.empty()) {
7807 for (const auto position : live_origin_positions(cohort->second)) {
7808 const auto& origin = cohort->second.origins[position];
7809 if (origin_is_pending(origin)) {
7810 pending_origin = origin;
7811 defer_until_parent = true;
7812 break;
7813 }
7814 }
7815 }
7816 }
7817 native_order::Owner owner = dynamic_owner;
7818 if (defer_until_parent) {
7819 const auto cohort = cohorts_by_id_.find(from_entry);
7820 if (cohort != cohorts_by_id_.end())
7821 owner = native_order::BindCohort{cohort->second.handle};
7822 defer_until_parent = false;
7823 }
7824 submit_one(std::move(owner), true,
7825 dynamic_key_prefix + std::to_string(static_cast<int>(family)),
7826 group_for(dynamic_group_name, 1, static_cast<std::int64_t>(family)),
7827 defer_until_parent || defer_for_same_bar_add_exit,
7828 pending_origin);
7829 return;
7830 }
7831
7832 // An explicit global exit is still a source-sized close over the
7833 // generic book. There is no empty-id cohort to bind; terms supplies
7834 // the literal units at the native candidate.
7835 if (from_entry.empty()) {
7836 const auto group_name = oca_name.empty() ? exit_id + "\x1f" + from_entry : oca_name;
7837 submit_one(native_order::Independent{}, true,
7838 exit_id + "\x1f" + from_entry + std::to_string(static_cast<int>(family)),
7839 group_for(group_name, 1, static_cast<std::int64_t>(family)), false);
7840 return;
7841 }
7842
7843 // An explicit bracket quantity is one independently persistent leg
7844 // for every source entry provenance, including an origin that is
7845 // still pending. BindCohort keeps that pending-origin leg deferred
7846 // without inventing a source id in the generic core; the source key
7847 // gives re-issues replacement semantics per (exit, from_entry, leg,
7848 // origin) rather than accidentally replacing a carried instance.
7849 const auto cohort = cohort_for(from_entry);
7850 const auto found = cohorts_by_id_.find(from_entry);
7851 std::vector<native_order::RequestHandle> origins;
7852 if (found != cohorts_by_id_.end()) {
7853 // ab9714be keys (exit id, from_entry) per position cycle
7854 // (cycle_filled_entry_ids_ is cleared on flat). Stale origins
7855 // from a prior cycle are neither pending nor opened; keeping
7856 // them prevents the empty-origins origin-zero fallback a first
7857 // cycle uses while the new parent is still in
7858 // pending_same_bar_commands_ (probe9 A v0 / bprakaash).
7859 // The roster keeps every origin the id ever had, so collect the
7860 // qualifying positions from its index and keep roster order.
7861 const auto& roster = found->second.origins;
7862 std::vector<std::size_t> positions;
7863 if (const auto* zero = roster.positions_of(0))
7864 positions.insert(positions.end(), zero->begin(), zero->end());
7865 for (const auto& live : live_handles_)
7866 if (origin_is_pending(live)) roster.append_positions_of(live, positions);
7867 for (const auto& opened : found->second.opened)
7868 roster.append_positions_of(opened, positions);
7869 std::sort(positions.begin(), positions.end());
7870 positions.erase(std::unique(positions.begin(), positions.end()), positions.end());
7871 for (const auto position : positions) origins.push_back(roster[position]);
7872 }
7873 if (origins.empty()) origins.push_back({});
7874 for (const auto& origin : origins) {
7875 const std::string origin_key = std::to_string(origin.incarnation);
7876 const auto replacement_key = exit_id + "\x1f" + from_entry + "\x1f"
7877 + std::to_string(static_cast<int>(family)) + "\x1f" + origin_key;
7878 const auto group_name = oca_name.empty()
7879 ? exit_id + "\x1f" + from_entry + "\x1f" + origin_key
7880 : oca_name + (origin.incarnation != 0 ? "\x1f" + origin_key : "");
7881 const bool has_live_leg = live_by_source_key_.find(key_for(replacement_key))
7882 != live_by_source_key_.end();
7883 const bool origin_opened = found != cohorts_by_id_.end()
7884 && std::find(found->second.opened.begin(), found->second.opened.end(), origin)
7885 != found->second.opened.end();
7886 // The consumed-leg scan walks every leg this (exit, from_entry)
7887 // family ever placed, so it runs only when it decides the skip: a
7888 // bracket reissued on every bar keeps its live leg and never
7889 // reaches it.
7890 const auto consumed_origin_leg = [&] {
7891 const auto family_it = bracket_families_.find(family_key);
7892 return family_it != bracket_families_.end()
7893 && std::any_of(family_it->second.begin(), family_it->second.end(),
7894 [&](const auto& handle) {
7895 const auto found_p = placement_.find(handle.incarnation);
7896 if (found_p == placement_.end()) return false;
7897 const auto& prior = found_p->second;
7898 return prior.family == family && prior.bracket_origin == origin
7899 && !prior.legs.dormant()
7900 && std::none_of(live_handles_.begin(), live_handles_.end(),
7901 [&](const native_order::RequestHandle& live) {
7902 return live.incarnation == handle.incarnation;
7903 });
7904 });
7905 };
7906 if (origin.incarnation != 0 && !has_live_leg && !origin_is_pending(origin)
7907 && (!origin_opened || consumed_origin_leg())) {
7908 continue;
7909 }
7910 native_order::Owner owner = !config_.close_entries_rule_any && origin_opened
7913 // ab9714be pine_orders.cpp:483-488: cancel_oca_group scopes cancellation strictly to matching oca_name group
7914 submit_one(std::move(owner), true, replacement_key,
7915 group_for(group_name, 1, static_cast<std::int64_t>(family)),
7916 !has_live_leg, origin);
7917 }
7918 };
7919 const bool exit_is_buy = !parent_long;
7920 // ab9714be pine_fills.cpp:7672-7675: an EXIT whose ``from_entry`` never
7921 // filled in the CURRENT position cycle is Removed before it can rest, and
7922 // the id set is cleared the moment the book goes flat or flips. A limit
7923 // level that is only marketable against the side held at issue time is
7924 // therefore never a live leg while its parent id belongs to a cycle that
7925 // has not opened yet; without the removal the leg rests on its cohort
7926 // binding and flattens the reversal's fresh lot at that lot's own entry
7927 // price on the same bar.
7928 const auto cycle_cohort = from_entry.empty()
7929 ? cohorts_by_id_.end() : cohorts_by_id_.find(from_entry);
7930 const bool from_entry_filled_this_cycle = from_entry.empty()
7931 || (cycle_cohort != cohorts_by_id_.end()
7932 && !cycle_cohort->second.opened.empty());
7933 bool placed_absolute_leg = false;
7934 if (finite_non_negative(limit_price)) {
7936 exit_limit_trigger(limit_price, tick, exit_is_buy)});
7937 placed_absolute_leg = true;
7938 } else if (std::isfinite(limit_price) && limit_price < 0.0
7939 && physical.signed_units > 0.0 && from_entry_filled_this_cycle) {
7940 // Sell limit < 0 is always marketable; A43 still refuses negatives.
7941 submit_leg(PineOrderFamily::ExitLimit, native_order::Market{});
7942 placed_absolute_leg = true;
7943 }
7944 // ab9714be pine_strategy_commands.cpp:1933 stores a negative stop level
7945 // verbatim: a buy stop below zero is crossed by every print and fills at
7946 // the next reachable point like any gapped stop. A43 refuses negative
7947 // triggers, so it rests at 0.0 (source_trigger_threshold's floor), which
7948 // every print also crosses. A negative sell stop is never reachable, yet
7949 // the legacy book still rests it and it reserves its share of the
7950 // position against later partial exits (ab9714be pine_strategy_commands.cpp:
7951 // 1929-1953); at 0.0 it rests the same way and no positive print crosses it.
7952 if (finite_non_negative(stop_price)
7953 || (std::isfinite(stop_price) && stop_price < 0.0)) {
7954 const double native_stop = source_trigger_threshold(
7955 stop_price, tick, exit_is_buy, false);
7956 submit_leg(PineOrderFamily::ExitStop, native_order::Stop{native_stop});
7957 placed_absolute_leg = true;
7958 }
7959 bool trail_one_shot = false;
7960 if (has_trail_request && finite_positive(trail_price)) {
7961 // The legacy broker compares trail activation against tick-quantized
7962 // OHLC extremes while retaining the raw running best. The native
7963 // geometric matcher receives raw segments, so move only the arm
7964 // threshold by half a tick (toward the reachable side); placement
7965 // facts and the eventual source fill remain on the original level.
7966 double native_trail_price = trail_price;
7967 bool trail_already_reached = false;
7968 const bool zero_distance = native_trail_offset_ticks
7969 && std::isfinite(source_trail_offset)
7970 && std::floor(source_trail_offset) == 0.0;
7971 if (finite_positive(tick)) {
7972 // ab9714be pine_fills.cpp:4592-4593: exit direction uses physical position side or exit intent direction
7973 const bool buy_close = physical.signed_units != 0.0
7974 ? physical.signed_units < 0.0 : exit_is_buy;
7975 const auto point = require_host().current_execution_point();
7976 const bool already_reached = point && (buy_close
7977 ? point->price <= trail_price : point->price >= trail_price);
7978 trail_already_reached = already_reached;
7979 const bool no_trailing_distance = !native_trail_offset_ticks || zero_distance;
7980 // An omitted offset and an explicit offset that truncates to zero
7981 // are both one-shot activation legs until the activation is
7982 // reached. Once a zero-distance trail is already armed at the
7983 // placement point, retain the generic Trail so its raw best can
7984 // ride subsequent bars.
7985 trail_one_shot = no_trailing_distance && !already_reached;
7986 if (zero_distance && point) {
7987 // Once the activation is already reached at placement, the
7988 // explicit-zero trail's first live print is its carried
7989 // running best. Arm on that print's grid image so a later
7990 // adverse leg does not incorrectly ride a raw sub-tick high.
7991 if (already_reached) {
7992 native_trail_price = nearest_tick(point->price, tick);
7993 }
7994 }
7995 }
7996 if (trail_one_shot) {
7997 const double one_shot_level = one_shot_trail_trigger(
7998 native_trail_price, source_trail_offset, tick, exit_is_buy,
7999 !std::isfinite(source_trail_offset) || zero_distance);
8000 // ab9714be engine_path_resolve.cpp:984-987 books the crossed
8001 // activation as a TRAIL event (fill.is_limit = false), and
8002 // pine_fills.cpp:4396-4411 / pine_policy_members.cpp:45-53 then
8003 // route it through apply_slippage, not apply_limit_fill: the fill
8004 // lands slippage ticks WORSE than the activation. An explicit
8005 // sub-tick offset keeps the unslipped activation as its touch
8006 // level (a slipped level would fire early), so the leg is a
8007 // fill-through limit whose settlement may pass that level.
8008 const bool slipped_touch = zero_distance && config_.slippage > 0
8009 && finite_positive(tick);
8010 submit_leg(PineOrderFamily::ExitTrail,
8011 native_order::Limit{one_shot_level, slipped_touch});
8012 } else if (native_trail_offset_ticks) {
8013 std::optional<double> native_arm_price = native_trail_price;
8014 // ab9714be engine_path_resolve.cpp:464-479: trailing exit already reached at placement arms immediately
8015 if (trail_already_reached)
8016 native_arm_price.reset();
8017 submit_leg(PineOrderFamily::ExitTrail, native_order::Trail{
8018 0.0, native_arm_price,
8019 native_order::TrailTicks{*native_trail_offset_ticks}});
8020 } else if (trail_already_reached) {
8021 // ab9714be engine_path_resolve.cpp:634-639: omitted-offset trail already active at placement is marketable at next open
8022 // An omitted offset that was already activated at placement is
8023 // marketable at the next open.
8024 submit_leg(PineOrderFamily::ExitTrail, native_order::Market{});
8025 } else {
8026 // An omitted source offset exits at activation. A generic limit
8027 // is the same one-shot direction for either close side. Its
8028 // executable level includes the legacy stop-style slippage so
8029 // the generic limit constraint and the source fill agree.
8030 const double slipped = trail_price + (exit_is_buy ? 1.0 : -1.0)
8031 * config_.slippage * tick;
8032 submit_leg(PineOrderFamily::ExitTrail, native_order::Limit{
8033 directional_tick(slipped, tick, exit_is_buy)});
8034 }
8035 }
8036 const bool exit_at_activation_trail = has_trail_request
8037 && (!std::isfinite(source_trail_offset)
8038 || std::floor(source_trail_offset) == 0.0);
8039 if (!trail_one_shot && exit_at_activation_trail && !finite_positive(stop_price)
8040 && finite_positive(trail_price)) {
8041 if (const auto point = require_host().current_execution_point()) {
8042 const bool long_side = require_host().physical_position().signed_units > 0.0;
8043 const bool already_armed = long_side ? point->price >= trail_price
8044 : point->price <= trail_price;
8045 if (already_armed) {
8046 // The explicit-zero trail is already active at the source
8047 // placement close. A sibling generic stop preserves the
8048 // next-open print decision; the Trail request still owns a
8049 // favourable-gap ride and all later path tracking.
8050 submit_leg(PineOrderFamily::ExitStop, native_order::Stop{point->price});
8051 }
8052 }
8053 }
8054 if (defer_for_same_bar_priority && !from_entry.empty())
8055 named_entry_cancel_tokens_.erase(from_entry);
8056 if (pooc_short_tick_scope && source_point) {
8057 bool competing_entry = false;
8058 for (const auto& handle : live_handles_) {
8059 const auto found = placement_.find(handle.incarnation);
8060 if (found != placement_.end() && found->second.opening) {
8061 competing_entry = true;
8062 break;
8063 }
8064 }
8065 if (!competing_entry) {
8066 const double close = nearest_tick(
8067 source_point->price, staged_.syminfo.mintick);
8068 const PineOrderFamily selected = pooc_current_close_family;
8069 if (selected != PineOrderFamily::Entry) {
8070 const SourceId replacement_key = exit_id + "\x1f" + from_entry
8071 + std::to_string(static_cast<int>(selected));
8072 const auto live = live_by_source_key_.find(key_for(replacement_key));
8073 if (live != live_by_source_key_.end()) {
8074 const auto found = placement_.find(live->second.incarnation);
8075 if (found != placement_.end()
8076 && found->second.projection_predecessor != 0
8077 && cohort_exposure_for(from_entry) > 0.0) {
8078 PlacementSnapshot immediate = found->second;
8079 cancel_bracket_siblings(live->second);
8080 native_order::Request request;
8081 request.intent = native_order::Flatten{};
8082 request.label = exit_id;
8083 request.comment = comment;
8084 request.trigger = native_order::Market{};
8085 immediate.forced_execution_price = close;
8086 immediate.projection_predecessor = live->second.incarnation;
8087 immediate.projection_predecessor_exit = true;
8088 const auto accepted = submit_or_replace(
8089 std::move(request), std::move(immediate), false,
8090 "__pooc_current_exit__" + exit_id + "\x1f" + from_entry);
8091 if (accepted) {
8092 (void)require_host().execute_current(
8093 {*accepted, NativeCurrentPriceRule::NearestTick});
8094 }
8095 }
8096 }
8097 }
8098 }
8099 }
8100
8101 // ab9714be pine_fills.cpp:7810-7842: under process_orders_on_close, a freshly
8102 // submitted priced exit leg that is already marketable against this same bar's close
8103 // fills immediately at the close.
8104 if (config_.process_orders_on_close && config_.calc_on_order_fills
8105 && !coof_recalc_active_ && source_point
8106 && source_point->decision.coordinate.provenance == NativePriceProvenance::Calculation
8107 && source_point->decision.coordinate.path_phase == NativePathPhase::None
8108 && physical.signed_units != 0.0) {
8109 const double quote = source_point->price;
8110 const bool closing_long = physical.signed_units > 0.0;
8111 struct Candidate {
8112 native_order::RequestHandle handle;
8113 PlacementSnapshot snapshot;
8114 };
8115 std::vector<Candidate> candidates;
8116 for (const auto& handle : live_handles_) {
8117 const auto found = placement_.find(handle.incarnation);
8118 if (found == placement_.end() || found->second.source_id != exit_id
8119 || found->second.from_entry != from_entry
8120 || (found->second.family != PineOrderFamily::ExitLimit
8121 && found->second.family != PineOrderFamily::ExitStop)) {
8122 continue;
8123 }
8124 const auto& row = found->second;
8125 const bool limit_hit = row.family == PineOrderFamily::ExitLimit
8126 && finite_positive(row.exit_levels.limit)
8127 && (closing_long ? quote >= row.exit_levels.limit
8128 : quote <= row.exit_levels.limit);
8129 const bool stop_hit = row.family == PineOrderFamily::ExitStop
8130 && finite_positive(row.exit_levels.stop)
8131 && (closing_long ? quote <= row.exit_levels.stop
8132 : quote >= row.exit_levels.stop);
8133 if (limit_hit || stop_hit) candidates.push_back({handle, row});
8134 }
8135 if (!candidates.empty()) {
8136 std::stable_sort(candidates.begin(), candidates.end(),
8137 [&](const auto& left, const auto& right) {
8138 return left.snapshot.command_sequence
8139 < right.snapshot.command_sequence;
8140 });
8141 const auto selected = candidates.front();
8142 cancel_bracket_siblings(selected.handle);
8143 native_order::Request request;
8144 request.intent = native_order::Flatten{};
8145 request.label = exit_id;
8146 request.comment = comment;
8147 request.trigger = native_order::Market{};
8148 PlacementSnapshot immediate = selected.snapshot;
8149 const bool stop_close = selected.snapshot.family == PineOrderFamily::ExitStop;
8150 immediate.forced_execution_price = nearest_tick(
8151 quote + (stop_close ? (closing_long ? -1.0 : 1.0) : 0.0)
8152 * config_.slippage * staged_.syminfo.mintick,
8153 staged_.syminfo.mintick);
8154 immediate.projection_predecessor = selected.handle.incarnation;
8155 immediate.projection_predecessor_exit = true;
8156 const auto accepted = submit_or_replace(
8157 std::move(request), std::move(immediate), false,
8158 exit_id + "\x1f" + from_entry
8159 + std::to_string(static_cast<int>(selected.snapshot.family)));
8160 if (accepted) {
8161 (void)require_host().execute_current(
8162 {*accepted, NativeCurrentPriceRule::NearestTick});
8163 }
8164 }
8165 }
8166 if (!placed_absolute_leg
8167 && !(has_trail_request && finite_positive(trail_price))) {
8168 // ab9714be pine_strategy_commands.cpp:533-537: only a NaN operand is
8169 // absent. Preserve a command projection for all-NaN absolute levels
8170 // with no trail arm; it never participates in matching or settlement.
8171 exit_cancel_bracket(exit_id, from_entry, comment);
8172 source_shadow_pending_.erase(
8173 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
8174 [&](const SourceShadowPending& row) {
8175 return row.snapshot.source_id == exit_id
8176 && row.snapshot.from_entry == from_entry;
8177 }),
8178 source_shadow_pending_.end());
8179 PlacementSnapshot snapshot;
8180 snapshot.family = has_trail_request ? PineOrderFamily::ExitTrail
8181 : (!std::isnan(stop_price) || !std::isnan(loss_ticks)
8183 snapshot.source_id = exit_id;
8184 snapshot.from_entry = from_entry;
8185 snapshot.comment = comment;
8186 snapshot.oca_name = oca_name;
8187 snapshot.requested_qty = qty;
8188 snapshot.projection_remaining_qty = reserved_exit_qty;
8189 snapshot.qty_percent = qty_percent;
8190 snapshot.command_sequence = command_sequence;
8191 snapshot.source_sequence = ++source_sequence_;
8192 snapshot.projection_created_bar = require_host().current_execution_point()
8193 ? require_host().current_execution_point()->decision.coordinate.interval_index : -1;
8194 snapshot.projection_position_side = physical.signed_units > 0.0
8195 ? static_cast<std::int32_t>(PositionSide::LONG)
8196 : (physical.signed_units < 0.0
8197 ? static_cast<std::int32_t>(PositionSide::SHORT)
8198 : static_cast<std::int32_t>(PositionSide::FLAT));
8199 snapshot.placement_cycle = current_position_cycle_;
8200 snapshot.exit_levels = {limit_price, stop_price, source_trail_points,
8201 source_trail_offset, source_trail_price,
8202 profit_ticks, loss_ticks};
8203 snapshot.sizing = sizing_snapshot();
8204 source_shadow_pending_.push_back({std::move(snapshot), exit_id});
8205 }
8206}
8207
8209 native_order::RequestHandle just_applied, bool post_calculation,
8210 bool pre_script_drain) {
8211 auto queued = std::move(pending_bracket_legs_);
8212 pending_bracket_legs_.clear();
8213 // ab9714be src/source/pine_scheduler.cpp:242 runs step 1 over the orders
8214 // that were already resting, and that pass walks a shared fill phase in
8215 // creation order (src/source/pine_fills.cpp:3782-3788). A resting bracket
8216 // settles on this bar ahead of the source body only when that walk reaches
8217 // its parent entry first: a leg the pass visits while the position is still
8218 // flat is skipped there (src/source/pine_fills.cpp:7461-7468) and only
8219 // revisited later, which on this route is the flush below the body.
8220 const auto point = require_host().current_execution_point();
8221 // A parent that filled in the open phase sat in phase 0 with the open-tick
8222 // marketables, so the pass walked it ahead of every priced bracket of the
8223 // bar. A parent that filled later on the path shares the bracket's phase,
8224 // and there only the book's creation order decides.
8225 const bool parent_walked_after_leg
8226 = point.has_value()
8227 && position_open_bar_index_
8228 == point->decision.coordinate.interval_index
8229 && position_open_phase_ != NativePathPhase::Open;
8230 const auto resolve_parent_handle
8231 = [&](const PlacementSnapshot& row)
8232 -> std::optional<native_order::RequestHandle> {
8233 auto origin = row.bracket_origin;
8234 if (origin.incarnation == 0 && !row.from_entry.empty()) {
8235 const auto cohort = cohorts_by_id_.find(row.from_entry);
8236 if (cohort != cohorts_by_id_.end()
8237 && !cohort->second.opened.empty()) {
8238 origin = cohort->second.opened.back();
8239 }
8240 }
8241 if (placement_.find(origin.incarnation) == placement_.end()) return std::nullopt;
8242 return origin;
8243 };
8244 const auto resolve_parent = [&](const PlacementSnapshot& row)
8245 -> const PlacementSnapshot* {
8246 const auto handle = resolve_parent_handle(row);
8247 if (!handle) return nullptr;
8248 return &placement_.find(handle->incarnation)->second;
8249 };
8250 // The owner's pass orders a shared fill phase by the order's creation
8251 // sequence (ab9714be src/source/pine_fills.cpp:3779-3785), the stamp the
8252 // book keeps across a same-id replacement
8253 // (src/source/pine_strategy_commands.cpp:481-482,
8254 // src/source/pine_strategy_commands.cpp:2627-2648). This route carries the
8255 // same fact on the snapshot's source sequence, which a re-issued bracket
8256 // inherits (src/source/pine_adapter.cpp:6601) and the book walk already
8257 // ranks on (src/source/pine_adapter.cpp:11288).
8258 const auto parent_precedes_leg
8259 = [](const PlacementSnapshot& leg, const PlacementSnapshot& parent) {
8260 return parent.source_sequence < leg.source_sequence;
8261 };
8262 // A parent recreated after a named entry cancellation is the retained
8263 // topology: its surviving bracket keeps the pre-cancellation book slot, so
8264 // the creation-order walk reaches the leg first and skips it while the
8265 // position is still flat (src/source/pine_fills.cpp:7461-7468). The owner's
8266 // one exception ranks that fresh parent ahead of the retained child
8267 // (ab9714be src/compat/pine/order_priority.cpp:31-85, declared by the
8268 // flat_retained_child_fresh_parent_order metadata). The child of that pair
8269 // has no native handle while this route still stages it, so the pair is
8270 // read from the placement snapshots and the three evidence boundaries of
8271 // the owner's predicate are carried as their source facts: the pass-time
8272 // gates of ab9714be src/compat/pine/order_priority.cpp:11-15 become "this
8273 // bar's position opened on this bar" (the owner ranks the pair while the
8274 // broker is still flat, and here the walk ran ahead of this drain) plus
8275 // "the pair is the whole book this route carries"; the incarnation
8276 // adjacency of src/compat/pine/order_priority.cpp:85 becomes two
8277 // consecutive source commands; and the owner's single exit order is the
8278 // pair of legs this route splits it into, so either leg may carry the
8279 // surviving-slot stamp for its bracket.
8280 const bool retained_parent_first
8281 = priority.attached() && priority.retained_parent_first();
8282 const auto exact_retained_child = [&](const PlacementSnapshot& row,
8283 std::uint64_t surviving) {
8284 const double percent = std::isfinite(row.qty_percent)
8285 ? row.qty_percent : 100.0;
8286 return (row.family == PineOrderFamily::ExitStop
8288 && !row.from_entry.empty()
8289 && row.projection_predecessor != 0
8290 && row.projection_predecessor == surviving
8292 == static_cast<std::int32_t>(PositionSide::FLAT)
8293 && point && row.projection_created_bar
8294 == point->decision.coordinate.interval_index - 1
8295 && !row.birth.from_fill()
8298 && !std::isfinite(row.requested_qty)
8299 && percent >= 100.0 - internal::kFullPercentEps
8300 && std::isfinite(row.exit_levels.stop)
8301 && std::isfinite(row.exit_levels.limit)
8302 && !std::isfinite(row.exit_levels.profit_ticks)
8303 && !std::isfinite(row.exit_levels.loss_ticks)
8304 && !std::isfinite(row.exit_levels.trail_points)
8305 && !std::isfinite(row.exit_levels.trail_price)
8306 && !std::isfinite(row.exit_levels.trail_offset)
8307 && row.oca_name.empty() && row.oca_type == 0;
8308 };
8309 const auto exact_fresh_parent = [&](const PlacementSnapshot& parent,
8310 std::uint64_t parent_incarnation,
8311 std::uint64_t child_predecessor) {
8312 const auto cancelled
8314 const auto surviving
8316 return parent.opening
8317 && parent.family == PineOrderFamily::Entry
8318 && parent.projection_position_side
8319 == static_cast<std::int32_t>(PositionSide::FLAT)
8320 && parent.projection_predecessor == 0
8321 && cancelled != 0 && cancelled < parent_incarnation
8322 && cancelled != child_predecessor
8323 && surviving > cancelled && surviving < parent_incarnation
8324 && point && parent.projection_created_bar
8325 == point->decision.coordinate.interval_index - 1
8326 && !std::isfinite(parent.requested_qty)
8327 && !parent.birth.from_fill()
8328 && !parent.projection_after_close
8330 && !parent.stop_limit_activated
8331 && std::isfinite(parent.exit_levels.stop)
8332 && !std::isfinite(parent.exit_levels.limit)
8333 && !std::isfinite(parent.exit_levels.trail_points)
8334 && !std::isfinite(parent.exit_levels.trail_price)
8335 && !std::isfinite(parent.exit_levels.trail_offset)
8336 && parent.oca_name.empty() && parent.oca_type == 0;
8337 };
8338 // The staged brackets of this drain that are the ranked child of a fresh
8339 // parent entry.
8340 std::vector<std::pair<std::string, std::string>> ranked_retained_brackets;
8341 if (pre_script_drain && retained_parent_first && point
8342 && position_open_bar_index_
8343 == point->decision.coordinate.interval_index
8344 && config_.process_orders_on_close && !config_.calc_on_order_fills
8345 && !coof_recalc_active_ && point->decision.sub_count <= 1) {
8346 std::vector<std::pair<std::string, std::string>> book_keys;
8347 const auto carry_key = [&](const PlacementSnapshot& row) {
8348 const auto key = std::make_pair(row.source_id, row.from_entry);
8349 if (std::find(book_keys.begin(), book_keys.end(), key)
8350 == book_keys.end())
8351 book_keys.push_back(key);
8352 };
8353 for (const auto& live : live_handles_) {
8354 const auto row = placement_.find(live.incarnation);
8355 if (row != placement_.end()) carry_key(row->second);
8356 }
8357 for (const auto& staged : queued) carry_key(staged.snapshot);
8358 for (const auto& entry : pending_entries_) carry_key(entry.snapshot);
8359 for (const auto& command : pending_same_bar_commands_)
8360 carry_key(command.snapshot);
8361 for (const auto& pending : pending_coof_requests_)
8362 carry_key(pending.snapshot);
8363 for (const auto& delayed : delayed_market_orders_)
8364 carry_key(delayed.snapshot);
8365 for (const auto& leg : queued) {
8366 const auto bracket = std::make_pair(leg.snapshot.source_id,
8367 leg.snapshot.from_entry);
8368 if (book_keys.size() != 1 || book_keys.front() != bracket) continue;
8369 const auto handle = resolve_parent_handle(leg.snapshot);
8370 if (!handle) continue;
8371 const auto parent = placement_.find(handle->incarnation);
8372 if (parent == placement_.end()
8373 || !parent->second.retained_parent_topology) continue;
8374 if (leg.snapshot.from_entry != parent->second.source_id) continue;
8375 if (leg.snapshot.command_sequence
8376 != parent->second.command_sequence + 1) continue;
8377 if (!exact_fresh_parent(parent->second, handle->incarnation,
8378 leg.snapshot.projection_predecessor))
8379 continue;
8380 for (const auto& sibling : queued) {
8381 if (sibling.snapshot.source_id != bracket.first
8382 || sibling.snapshot.from_entry != bracket.second) continue;
8383 if (!exact_retained_child(sibling.snapshot,
8385 continue;
8386 if (std::find(ranked_retained_brackets.begin(),
8387 ranked_retained_brackets.end(), bracket)
8388 == ranked_retained_brackets.end())
8389 ranked_retained_brackets.push_back(bracket);
8390 break;
8391 }
8392 }
8393 }
8394 // True when the owner's step-1 walk reaches this leg's parent entry before
8395 // the leg, so the leg settles with the position open ahead of the body. A
8396 // leg whose parent entry was the fill that opened the position has already
8397 // had that parent's calculation, so the walk also satisfies the deferred
8398 // post-parent-calculation parking flag (pine_adapter.cpp:7393).
8399 const auto parent_walks_first
8400 = [&](const PlacementSnapshot& leg) {
8401 if (!pre_script_drain) return false;
8402 const auto* parent = resolve_parent(leg);
8403 if (parent == nullptr) return false;
8404 if (parent->retained_parent_topology) {
8405 return std::find(ranked_retained_brackets.begin(),
8406 ranked_retained_brackets.end(),
8407 std::make_pair(leg.source_id, leg.from_entry))
8408 != ranked_retained_brackets.end();
8409 }
8410 return !parent_walked_after_leg
8411 || parent_precedes_leg(leg, *parent);
8412 };
8413 if (pre_script_drain) {
8414 std::vector<PendingBracketLeg> resting;
8415 resting.reserve(queued.size());
8416 for (auto& leg : queued) {
8417 const bool has_parent = resolve_parent(leg.snapshot) != nullptr;
8418 if (has_parent && !parent_walks_first(leg.snapshot)) {
8419 pending_bracket_legs_.push_back(std::move(leg));
8420 } else {
8421 resting.push_back(std::move(leg));
8422 }
8423 }
8424 queued = std::move(resting);
8425 }
8426 std::unordered_set<std::uint64_t> source_pending_orders;
8427 for (const auto& handle : live_handles_) {
8428 const auto live = placement_.find(handle.incarnation);
8429 if (live != placement_.end())
8430 source_pending_orders.insert(key_for(
8431 live->second.source_id, live->second.from_entry));
8432 }
8433 for (const auto& leg : queued)
8434 source_pending_orders.insert(key_for(
8435 leg.snapshot.source_id, leg.snapshot.from_entry));
8436 for (const auto& entry : pending_entries_)
8437 source_pending_orders.insert(key_for(
8438 entry.snapshot.source_id, entry.snapshot.from_entry));
8439 for (const auto& command : pending_same_bar_commands_)
8440 source_pending_orders.insert(key_for(
8441 command.snapshot.source_id, command.snapshot.from_entry));
8442 for (const auto& pending : pending_coof_requests_)
8443 source_pending_orders.insert(key_for(
8444 pending.snapshot.source_id, pending.snapshot.from_entry));
8445 for (const auto& delayed : delayed_market_orders_)
8446 source_pending_orders.insert(key_for(
8447 delayed.snapshot.source_id, delayed.snapshot.from_entry));
8448 for (const auto& shadow : source_shadow_pending_)
8449 source_pending_orders.insert(key_for(
8450 shadow.snapshot.source_id, shadow.snapshot.from_entry));
8451 const std::size_t source_pending_population = source_pending_orders.size();
8452 // Re-issued explicit brackets are one leg family per entry instance.
8453 // The legacy book walked instances first (T1/T2 for opening A, then
8454 // T1/T2 for opening B), not every T1 across all openings before T2.
8455 // Preserve original order for unbound/deferred rows (origin zero).
8456 std::stable_sort(queued.begin(), queued.end(), [](const PendingBracketLeg& left,
8457 const PendingBracketLeg& right) {
8458 const auto left_origin = left.snapshot.bracket_origin.incarnation;
8459 const auto right_origin = right.snapshot.bracket_origin.incarnation;
8460 if (left_origin == right_origin) return false;
8461 return left_origin < right_origin;
8462 });
8463 for (auto& leg : queued) {
8464 const bool competing_chart_tick = source_pending_population != 1U
8465 && leg.snapshot.projection_created_during_coof
8466 && (leg.snapshot.family == PineOrderFamily::ExitStop
8467 || leg.snapshot.family == PineOrderFamily::ExitLimit);
8468 const double competing_level = leg.snapshot.family == PineOrderFamily::ExitStop
8469 ? leg.snapshot.exit_levels.stop : leg.snapshot.exit_levels.limit;
8470 // ab9714be pine_fills.cpp:592-641: the chart-tick touch this shift
8471 // suppresses exists only for a level strictly inside (raw, tick(raw)],
8472 // i.e. OFF the tick grid. An on-grid level books AT the level
8473 // (bar_fill_price), so a half-tick native threshold beyond it turns
8474 // every such fill into an InvalidTerms rejection (sell limit booked
8475 // below its native level) and the leg never fills.
8476 const bool competing_level_on_grid = finite_positive(staged_.syminfo.mintick)
8477 && std::isfinite(competing_level)
8478 && nearest_tick(competing_level, staged_.syminfo.mintick) == competing_level;
8479 if (competing_chart_tick && !competing_level_on_grid) {
8480 const bool exit_is_buy = require_host().physical_position().signed_units < 0.0;
8481 const bool upward = leg.snapshot.family == PineOrderFamily::ExitLimit
8482 ? !exit_is_buy : exit_is_buy;
8483 const double source_level = competing_level;
8484 const double threshold = source_level + (upward ? 0.5 : -0.5)
8485 * staged_.syminfo.mintick;
8486 if (leg.snapshot.family == PineOrderFamily::ExitStop)
8487 leg.request.trigger = native_order::Stop{threshold};
8488 else
8489 leg.request.trigger = native_order::Limit{threshold};
8490 }
8492 && !(cohort_exposure_for(leg.snapshot.from_entry) > 0.0)) {
8493 pending_bracket_legs_.push_back(std::move(leg));
8494 continue;
8495 }
8496 // ab9714be pine_fills.cpp:7679-7700 evaluates a filled parent's priced
8497 // exits on its entry bar, whichever source batch placed the parent.
8498 // An explicit leg issued after its flat pyramiding=2 MARKET parent,
8499 // while that parent was still held in the same-bar batch, took origin
8500 // zero; the batch has now submitted the parent, so bind the leg to it
8501 // exactly as exit() binds a leg issued after a live pending parent:
8502 // it waits for the parent's opening instead of pre-arming on the
8503 // signal bar.
8504 if (leg.snapshot.bracket_origin.incarnation == 0 && config_.pyramiding == 2
8505 && std::isfinite(leg.snapshot.requested_qty)
8506 && !leg.snapshot.from_entry.empty()) {
8507 const auto cohort = cohorts_by_id_.find(leg.snapshot.from_entry);
8508 if (cohort != cohorts_by_id_.end() && cohort->second.opened.empty()) {
8509 for (const auto& origin : cohort->second.origins) {
8510 const auto parent = placement_.find(origin.incarnation);
8511 if (parent == placement_.end() || !origin_is_pending(origin)
8512 || !parent->second.opening
8513 || parent->second.family != PineOrderFamily::Entry
8514 || parent->second.placement_script_open_ms
8515 != leg.snapshot.placement_script_open_ms
8516 || parent->second.command_sequence
8517 >= leg.snapshot.command_sequence) {
8518 continue;
8519 }
8520 const std::string origin_key = std::to_string(origin.incarnation);
8521 const std::string family_tag = std::to_string(
8522 static_cast<int>(leg.snapshot.family));
8523 leg.snapshot.bracket_origin = origin;
8524 leg.replacement_key = leg.snapshot.source_id + "\x1f"
8525 + leg.snapshot.from_entry + "\x1f" + family_tag + "\x1f" + origin_key;
8526 const auto group_name = leg.snapshot.oca_name.empty()
8527 ? leg.snapshot.source_id + "\x1f" + leg.snapshot.from_entry
8528 + "\x1f" + origin_key
8529 : leg.snapshot.oca_name + "\x1f" + origin_key;
8530 leg.request.group = group_for(group_name, 1,
8531 static_cast<std::int64_t>(leg.snapshot.family));
8532 break;
8533 }
8534 }
8535 }
8536 bool retained_parent_pending = false;
8537 if ((leg.snapshot.projection_predecessor != 0
8539 && !leg.snapshot.from_entry.empty()) {
8540 const auto cohort = cohorts_by_id_.find(leg.snapshot.from_entry);
8541 if (cohort != cohorts_by_id_.end()) {
8542 // A physically flat book has no open lot for this id even
8543 // when the Applied of the fill that flattened it (a bracket
8544 // executed at its level in the pre-script drain) is not yet
8545 // observed, so the cohort's opened roster is still stale.
8546 // ab9714be pine_fills.cpp:7464-7467 only Skips an exit
8547 // created while flat, and the flat purge keeps it for its
8548 // pending parent (pine_fills.cpp:431-437,
8549 // pine_orders.cpp:507-527): it waits here for that parent.
8550 const bool no_open_lot = cohort->second.opened.empty()
8551 || require_host().physical_position().signed_units == 0.0;
8552 const auto live = no_open_lot
8553 ? live_origin_positions(cohort->second) : std::vector<std::size_t>{};
8554 retained_parent_pending = std::any_of(live.begin(), live.end(),
8555 [&](std::size_t position) {
8556 return origin_is_pending(cohort->second.origins[position]);
8557 });
8558 }
8559 }
8560 if ((leg.snapshot.bracket_origin.incarnation != 0
8561 && leg.snapshot.bracket_origin != just_applied
8562 && origin_is_pending(leg.snapshot.bracket_origin))
8563 || retained_parent_pending) {
8564 pending_bracket_legs_.push_back(std::move(leg));
8565 continue;
8566 }
8568 && !post_calculation && !parent_walks_first(leg.snapshot)) {
8569 pending_bracket_legs_.push_back(std::move(leg));
8570 continue;
8571 }
8572 bool execute_after_calculation = false;
8573 // ab9714be src/source/pine_fills.cpp:7695-7728 evaluates a priced exit on
8574 // the bar its parent entry fills, and a leg this route parked until
8575 // post-calculation still gets that bar's touch at its level price. The
8576 // stop sits on the far leg of the assumed intrabar path, so it only
8577 // fires when the parent's fill leg came first; the limit sits beyond the
8578 // fill on the parent's own leg, so the fill always precedes it. A level
8579 // on the wrong side of the fill price is skipped, as in the entry-bar
8580 // gate of src/source/pine_fills.cpp:7715-7727.
8581 const bool priced_exit_leg
8582 = leg.snapshot.family == PineOrderFamily::ExitStop
8583 || leg.snapshot.family == PineOrderFamily::ExitLimit;
8584 if ((leg.snapshot.projection_predecessor != 0
8586 && priced_exit_leg && policy_script_bar_valid_) {
8587 const auto cohort = cohorts_by_id_.find(leg.snapshot.from_entry);
8588 if (cohort != cohorts_by_id_.end() && !cohort->second.opened.empty()) {
8589 const auto parent = placement_.find(cohort->second.opened.back().incarnation);
8590 if (parent != placement_.end() && parent->second.opening) {
8591 const bool is_stop_leg
8592 = leg.snapshot.family == PineOrderFamily::ExitStop;
8593 const double level = is_stop_leg
8594 ? leg.snapshot.exit_levels.stop
8595 : leg.snapshot.exit_levels.limit;
8596 const bool high_first = source_path_uses_high_first(policy_script_bar_);
8597 const bool parent_before_child = is_stop_leg
8598 ? (parent->second.is_long ? high_first : !high_first) : true;
8599 // ab9714be pine_fills.cpp:7711-7713 + pine_policy_members.cpp:11-17
8600 // + engine.hpp:1374-1381: the entry-bar exit is tested
8601 // against the tick-quantized bar, so an on-grid level is
8602 // touched by a raw extreme half a tick short of it
8603 // (NYSE:F high 13.455 -> 13.46 reaches a 13.46 sell limit).
8604 // Only the calc_on_order_fills scheduler compares raw.
8605 const double tick = staged_.syminfo.mintick;
8606 const bool raw_bar = config_.calc_on_order_fills
8607 || !finite_positive(tick);
8608 const double bar_high = raw_bar ? policy_script_bar_.high
8609 : source_decimal_tick(policy_script_bar_.high, tick);
8610 const double bar_low = raw_bar ? policy_script_bar_.low
8611 : source_decimal_tick(policy_script_bar_.low, tick);
8612 const bool touched = is_stop_leg
8613 ? (parent->second.is_long ? bar_low <= level
8614 : bar_high >= level)
8615 : (parent->second.is_long ? bar_high >= level
8616 : bar_low <= level);
8617 const double fill_price
8618 = require_host().physical_position().average_price;
8619 const bool right_side = is_stop_leg
8620 || (parent->second.is_long ? level >= fill_price
8621 : level <= fill_price);
8622 execute_after_calculation = parent_before_child && touched && right_side;
8623 if (execute_after_calculation) {
8624 const double units = std::abs(
8625 require_host().physical_position().signed_units);
8626 leg.snapshot.post_parent_calc_level_fill = true;
8627 leg.snapshot.forced_execution_price = level;
8628 leg.snapshot.immediately = true;
8629 leg.snapshot.requested_qty = units;
8630 leg.snapshot.deferred_cohort = false;
8631 leg.request.intent = native_order::Reduce{
8633 leg.request.trigger = native_order::Market{};
8634 leg.request.owner = native_order::Independent{};
8635 leg.request.group = native_order::NoGroup{};
8636 }
8637 }
8638 }
8639 }
8640 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot), false,
8641 leg.replacement_key);
8642 if (accepted) {
8643 bracket_families_[leg.family_key].push_back(*accepted);
8644 if (execute_after_calculation) {
8645 (void)require_host().execute_current(
8646 {*accepted, NativeCurrentPriceRule::NearestTick});
8647 }
8648 }
8649 }
8650}
8651
8652void PineExecutionAdapter::materialize_pending_bracket_legs(
8654 const auto parent_it = placement_.find(event.handle().incarnation);
8655 const PlacementSnapshot* parent = parent_it == placement_.end()
8656 ? nullptr : &parent_it->second;
8657 const bool retained_parent = parent && parent->opening
8658 && parent->retained_parent_topology;
8659 if (parent && parent->opening && parent->oca_type == 1
8660 && !parent->oca_name.empty()) {
8661 pending_bracket_legs_.erase(std::remove_if(
8662 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
8663 [&](const PendingBracketLeg& leg) {
8664 return leg.snapshot.oca_name == parent->oca_name;
8665 }), pending_bracket_legs_.end());
8666 }
8667 std::uint64_t retained_family = 0;
8668 bool materialize_retained = false;
8669 if (retained_parent && parent->recreated_after_named_cancelled_entry_incarnation != 0
8672 const PendingBracketLeg* representative = nullptr;
8673 bool one_family = true;
8674 for (const auto& leg : pending_bracket_legs_) {
8675 if (leg.snapshot.from_entry != parent->source_id) continue;
8676 if (!representative) {
8677 representative = &leg;
8678 retained_family = leg.family_key;
8679 } else if (leg.family_key != retained_family) {
8680 one_family = false;
8681 }
8682 }
8683 bool foreign = false;
8684 if (representative) {
8685 for (const auto& row : placement_) {
8686 const auto& snapshot = row.second;
8687 if (snapshot.source_id != parent->source_id
8688 && snapshot.source_id != representative->snapshot.source_id
8689 && snapshot.family != PineOrderFamily::Margin) {
8690 foreign = true;
8691 break;
8692 }
8693 }
8694 }
8695 const bool exact_child = representative
8696 && representative->snapshot.projection_predecessor
8698 && representative->snapshot.source_sequence < parent->source_sequence
8699 && !std::isfinite(representative->snapshot.requested_qty)
8700 && (!std::isfinite(representative->snapshot.qty_percent)
8701 || representative->snapshot.qty_percent >= 100.0)
8702 && finite_positive(representative->snapshot.exit_levels.stop)
8703 && finite_positive(representative->snapshot.exit_levels.limit)
8704 && representative->snapshot.oca_name.empty()
8705 && representative->snapshot.oca_type == 0;
8706 materialize_retained = one_family && !foreign && exact_child
8707 && live_handles_.size() == 1 && live_handles_.front() == event.handle();
8708 }
8709 if (retained_parent && !materialize_retained) {
8710 for (auto& leg : pending_bracket_legs_) {
8711 if (leg.snapshot.from_entry == parent->source_id)
8712 leg.snapshot.defer_until_post_parent_calculation = true;
8713 }
8714 }
8715 std::vector<PendingBracketLeg> ready;
8716 for (auto it = pending_bracket_legs_.begin(); it != pending_bracket_legs_.end();) {
8717 const bool matches_parent = it->snapshot.from_entry.empty()
8718 || (parent && it->snapshot.from_entry == parent->source_id);
8719 const bool selected = retained_parent
8720 ? (materialize_retained && it->family_key == retained_family
8721 && it->snapshot.from_entry == parent->source_id)
8722 : (!it->snapshot.defer_until_post_parent_calculation
8723 && (it->snapshot.bracket_origin == event.handle()
8724 || (it->snapshot.bracket_origin.incarnation == 0 && matches_parent)));
8725 if (selected) {
8726 it->snapshot.bracket_origin = event.handle();
8727 ready.push_back(std::move(*it));
8728 it = pending_bracket_legs_.erase(it);
8729 } else {
8730 ++it;
8731 }
8732 }
8733 for (auto& leg : ready) {
8734 // ab9714be pine_fills.cpp:7650-7674 (finding-347), as in
8735 // flush_pending_bracket_legs: the opened parent leaves FIFO to settle
8736 // an explicit leg against the whole position.
8737 if (!retained_parent && leg.snapshot.bracket_origin == event.handle()
8738 && std::isfinite(leg.snapshot.requested_qty)
8739 && std::holds_alternative<native_order::BindCohort>(leg.request.owner)
8740 && !config_.close_entries_rule_any) {
8741 leg.request.owner = owner_for_close(leg.snapshot.from_entry, false);
8742 }
8743 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
8744 false, leg.replacement_key);
8745 if (accepted) bracket_families_[leg.family_key].push_back(*accepted);
8746 }
8747}
8748
8750 bool explicit_brackets_only, double current_open) {
8751 auto delayed = std::move(delayed_market_orders_);
8752 delayed_market_orders_.clear();
8753 for (auto& order : delayed) {
8754 const auto family = order.snapshot.family;
8755 const bool explicit_bracket = (order.snapshot.defer_until_post_parent_calculation
8756 || (std::isfinite(order.snapshot.requested_qty)
8757 && order.snapshot.bracket_origin.incarnation != 0))
8758 && (family == PineOrderFamily::ExitLimit
8759 || family == PineOrderFamily::ExitStop);
8760 const bool coof_delayed_price = order.snapshot.projection_created_during_coof
8761 && (family == PineOrderFamily::ExitStop
8762 || family == PineOrderFamily::ExitLimit);
8763 if (order.release_open_epoch <= broker_open_epoch_
8764 && (!explicit_brackets_only || explicit_bracket || coof_delayed_price)) {
8765 // A newer same-(id, from_entry) placement already superseded this
8766 // delayed leg: the owner drops same-id pending orders at replacement
8767 // time (ab9714be pine_strategy_commands.cpp:446), so the stale
8768 // command never reaches the book and must not resurrect a finished
8769 // cycle's levels against the new lot.
8770 const auto live_existing = live_by_source_key_.find(key_for(order.replacement_key));
8771 if (live_existing != live_by_source_key_.end()) {
8772 const auto found_p = placement_.find(live_existing->second.incarnation);
8773 if (found_p != placement_.end()
8774 && found_p->second.command_sequence > order.snapshot.command_sequence) {
8775 continue;
8776 }
8777 }
8778 const bool execute_coof_open = order.execute_at_open
8779 && finite_positive(current_open);
8780 if (execute_coof_open) {
8781 order.request.trigger = native_order::Market{};
8782 order.snapshot.forced_execution_price = current_open;
8783 }
8784 const auto family_key = key_for(
8785 order.snapshot.source_id, order.snapshot.from_entry);
8786 const auto accepted = submit_or_replace(
8787 std::move(order.request), std::move(order.snapshot),
8788 family == PineOrderFamily::Entry || family == PineOrderFamily::Order,
8789 order.replacement_key);
8790 if (accepted && (family == PineOrderFamily::ExitLimit
8791 || family == PineOrderFamily::ExitStop
8792 || family == PineOrderFamily::ExitTrail)) {
8793 bracket_families_[family_key].push_back(*accepted);
8794 }
8795 if (accepted && execute_coof_open) {
8796 (void)require_host().execute_current(
8797 {*accepted, NativeCurrentPriceRule::NearestTick});
8798 }
8799 } else {
8800 delayed_market_orders_.push_back(std::move(order));
8801 }
8802 }
8803}
8804
8807 flush_pending_same_bar_commands();
8808 auto queued = std::move(pending_entries_);
8809 pending_entries_.clear();
8810 auto deferred = std::remove_if(queued.begin(), queued.end(), [&](PendingEntry& entry) {
8811 const auto parent = entry.snapshot.paired_reversal_parent;
8812 if (parent.incarnation == 0) return false;
8813 const auto found = placement_.find(parent.incarnation);
8814 if (found == placement_.end()
8815 || (found->second.family != PineOrderFamily::Close
8816 && found->second.family != PineOrderFamily::CloseAll)) {
8817 return false;
8818 }
8819 pending_entries_.push_back(std::move(entry));
8820 return true;
8821 });
8822 queued.erase(deferred, queued.end());
8823 const bool recreated_parent = std::any_of(queued.begin(), queued.end(),
8824 [](const PendingEntry& entry) {
8825 return entry.snapshot.retained_parent_topology;
8826 });
8827 if (recreated_parent) {
8828 for (auto& entry : queued) {
8829 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot), true,
8830 entry.replacement_key);
8831 }
8832 return;
8833 }
8834 if (!queued.empty() && !pending_bracket_legs_.empty()) {
8835 auto brackets = std::move(pending_bracket_legs_);
8836 pending_bracket_legs_.clear();
8837 struct Candidate {
8838 int rank = 4;
8839 std::size_t index = 0;
8840 bool entry = false;
8841 };
8842 std::vector<Candidate> ordered;
8843 ordered.reserve(queued.size() + brackets.size());
8844 for (std::size_t index = 0; index < queued.size(); ++index) {
8845 ordered.push_back({queued[index].snapshot.is_long ? 1 : 2, index, true});
8846 }
8847 const double queued_position = require_host().physical_position().signed_units;
8848 for (std::size_t index = 0; index < brackets.size(); ++index) {
8849 const auto& snapshot = brackets[index].snapshot;
8850 int rank = 4;
8851 if (snapshot.family == PineOrderFamily::ExitStop) {
8852 rank = queued_position < 0.0 ? 1 : 2;
8853 } else if (snapshot.family == PineOrderFamily::ExitLimit) {
8854 rank = 3;
8855 }
8856 ordered.push_back({rank, index, false});
8857 }
8858 std::stable_sort(ordered.begin(), ordered.end(), [](const Candidate& left,
8859 const Candidate& right) {
8860 return left.rank < right.rank;
8861 });
8862 for (const auto& candidate : ordered) {
8863 if (candidate.entry) {
8864 auto& entry = queued[candidate.index];
8865 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot), true,
8866 entry.replacement_key);
8867 continue;
8868 }
8869 auto& leg = brackets[candidate.index];
8870 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
8871 false, leg.replacement_key);
8872 if (accepted) bracket_families_[leg.family_key].push_back(*accepted);
8873 }
8874 return;
8875 }
8876 std::stable_sort(queued.begin(), queued.end(), [](const PendingEntry& left,
8877 const PendingEntry& right) {
8878 const auto* left_stop = std::get_if<native_order::Stop>(&left.request.trigger);
8879 const auto* right_stop = std::get_if<native_order::Stop>(&right.request.trigger);
8880 const int left_rank = !left_stop ? 2 : (left.snapshot.is_long ? 0 : 1);
8881 const int right_rank = !right_stop ? 2 : (right.snapshot.is_long ? 0 : 1);
8882 return left_rank < right_rank;
8883 });
8884 for (auto& entry : queued) {
8885 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot), true,
8886 entry.replacement_key);
8887 }
8888}
8889
8890void PineExecutionAdapter::flush_pending_same_bar_commands() {
8891 auto queued = std::move(pending_same_bar_commands_);
8892 pending_same_bar_commands_.clear();
8893 pending_same_bar_close_qty_ = 0.0;
8894 if (queued.empty()) return;
8895
8896 const double batch_start = require_host().physical_position().signed_units;
8897 const auto apply_known_reversal_gap = [&](const auto& accepted) {
8898 if (!accepted || batch_start == 0.0
8899 || config_.default_qty_type
8900 != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
8901 || config_.default_qty_value > 100.0) {
8902 return;
8903 }
8904 const auto placed = placement_.find(accepted->incarnation);
8905 if (placed == placement_.end()
8906 || ((batch_start > 0.0) == placed->second.is_long)) {
8907 return;
8908 }
8909 const auto point = require_host().current_execution_point();
8910 auto* pine_host = dynamic_cast<PineStrategyHost*>(&require_host());
8911 if (!point || !pine_host) return;
8912 const auto next = pine_host->scheduler_.next_source_bar(
8913 point->decision.coordinate.interval_index);
8914 if (!next) return;
8915 NativeDecisionContext next_context = point->decision;
8916 ++next_context.coordinate.interval_index;
8917 next_context.coordinate.path_phase = NativePathPhase::Open;
8918 next_context.script_bar_open_ms = next->timestamp;
8919 next_context.sub_bar_open_ms = next->timestamp;
8920 apply_reversal_gap_bracket_policy(
8921 *next, next_context, /*defer_trails=*/true);
8922 };
8923 const bool variable_short_context = batch_start < 0.0
8924 && config_.default_qty_type != static_cast<int>(QtyType::FIXED);
8925 const bool full_short_seed = queued.size() == 3U
8926 && queued[0].opening && queued[0].snapshot.family == PineOrderFamily::Entry
8927 && queued[0].snapshot.is_long
8928 && queued[1].opening && queued[1].snapshot.family == PineOrderFamily::Entry
8929 && !queued[1].snapshot.is_long
8930 && queued[2].snapshot.frozen_market_targeted_close
8931 && queued[0].snapshot.source_id != queued[1].snapshot.source_id
8932 && queued[0].snapshot.source_id != queued[2].snapshot.source_id
8933 && queued[1].snapshot.source_id == queued[2].snapshot.source_id;
8934 const bool partial_short_seed = queued.size() == 2U
8935 && queued[0].opening && queued[0].snapshot.family == PineOrderFamily::Entry
8936 && queued[0].snapshot.is_long
8937 && queued[1].opening && queued[1].snapshot.family == PineOrderFamily::Entry
8938 && !queued[1].snapshot.is_long
8939 && std::any_of(live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
8940 const auto found = placement_.find(handle.incarnation);
8941 if (found == placement_.end()) return false;
8942 const auto& placeholder = found->second;
8943 return placeholder.family == PineOrderFamily::Close
8944 && placeholder.deferred_cohort
8945 && placeholder.source_id == queued[1].snapshot.source_id
8946 && placeholder.from_entry == queued[1].snapshot.source_id
8947 && placeholder.command_ordinal > queued[1].snapshot.command_ordinal;
8948 });
8949 const bool potential_short_seed = full_short_seed || partial_short_seed;
8950 // ab9714be src/compat/pine/market_admission.cpp:33-37 (explicit_pair_scope)
8951 // carries no commission term.
8952 const bool p2_candidate_scope = config_.pyramiding == 2
8953 && !config_.process_orders_on_close && !config_.calc_on_order_fills
8954 && !coof_recalc_active_ && config_.slippage == 0
8955 && config_.default_qty_type == static_cast<int>(QtyType::FIXED)
8956 && std::abs(config_.margin_long - 100.0) < 1e-12
8957 && std::abs(config_.margin_short - 100.0) < 1e-12
8958 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
8959 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
8960 && risk_.max_position_size <= 0.0 && !risk_.halted && !cap.active();
8961 const bool prior_entry_like = std::any_of(
8962 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
8963 const auto found = placement_.find(handle.incarnation);
8964 return found != placement_.end()
8965 && (found->second.family == PineOrderFamily::Entry
8966 || found->second.family == PineOrderFamily::Order);
8967 });
8968 const bool p2_explicit_pair = p2_candidate_scope && !source_batch_mutated_
8969 && !prior_entry_like && queued.size() == 2U
8970 && queued[0].opening && queued[1].opening
8971 && queued[0].snapshot.family == PineOrderFamily::Entry
8972 && queued[1].snapshot.family == PineOrderFamily::Entry
8973 && queued[0].snapshot.source_id != queued[1].snapshot.source_id
8974 && queued[0].snapshot.is_long != queued[1].snapshot.is_long
8975 && queued[0].snapshot.command_ordinal < queued[1].snapshot.command_ordinal
8976 && finite_positive(queued[0].snapshot.requested_qty)
8977 && finite_positive(queued[1].snapshot.requested_qty)
8978 && queued[0].snapshot.oca_name.empty()
8979 && queued[1].snapshot.oca_name.empty()
8980 && queued[0].snapshot.frozen_market_instruction
8981 && queued[1].snapshot.frozen_market_instruction;
8982 if (config_.pyramiding == 2 && !p2_explicit_pair && !potential_short_seed) {
8983 // Exact pair finalization is a whole-source-batch decision. Any
8984 // replacement/cancel, third entry-like instruction, prior resting
8985 // entry, or live risk/config deviation sends every survivor through
8986 // ordinary source order with only its own frozen quantity.
8987 std::stable_sort(queued.begin(), queued.end(), [&](const auto& left,
8988 const auto& right) {
8989 const bool left_replaces = live_by_source_key_.find(
8990 key_for(left.snapshot.source_id)) != live_by_source_key_.end();
8991 const bool right_replaces = live_by_source_key_.find(
8992 key_for(right.snapshot.source_id)) != live_by_source_key_.end();
8993 return left_replaces && !right_replaces;
8994 });
8995 for (auto& command : queued) {
8996 auto request = std::move(command.request);
8997 auto snapshot = std::move(command.snapshot);
8998 snapshot.paired_flat_market_candidate = false;
8999 snapshot.paired_flat_market_own_qty = kNaN;
9000 snapshot.paired_flat_market_peer_seq = 0;
9001 snapshot.paired_flat_market_transaction_qty = kNaN;
9002 snapshot.frozen_market_instruction = false;
9003 const double own = finite_positive(snapshot.frozen_market_own_units)
9004 ? snapshot.frozen_market_own_units : snapshot.requested_qty;
9005 if (command.opening && finite_positive(own)) {
9006 request.intent = native_order::HostSized{
9007 native_order::HostSizedKind::Open,
9008 snapshot.is_long ? native_order::Side::Long
9009 : native_order::Side::Short};
9010 snapshot.reverse_to = true;
9011 }
9012 (void)submit_or_replace(std::move(request), std::move(snapshot),
9013 command.opening, command.replacement_key);
9014 }
9015 return;
9016 }
9017 if (variable_short_context && !potential_short_seed) {
9018 // A variable-size source callback is tentatively staged because the
9019 // exact ShortSeed book is only recognizable after all commands return.
9020 // A nonmatching batch must go back through ordinary native requests in
9021 // source order; it must never inherit the frozen transaction behavior.
9022 for (auto& command : queued) {
9023 auto request = std::move(command.request);
9024 auto snapshot = std::move(command.snapshot);
9025 if (snapshot.frozen_market_targeted_close) {
9026 request.intent = native_order::HostSized{
9027 native_order::HostSizedKind::Close, std::nullopt};
9028 request.owner = owner_for_close(snapshot.from_entry, true);
9029 snapshot.deferred_cohort = true;
9030 snapshot.frozen_market_targeted_close = false;
9031 snapshot.frozen_market_instruction = false;
9032 } else {
9033 snapshot.frozen_market_instruction = false;
9034 }
9035 const auto accepted = submit_or_replace(
9036 std::move(request), std::move(snapshot), command.opening,
9037 command.replacement_key);
9038 apply_known_reversal_gap(accepted);
9039 }
9040 return;
9041 }
9042
9043 // Legacy `finalize_same_bar_market_tx_book` retains command order within
9044 // each broker-side pass but moves every BUY member before every SELL
9045 // member. The generic request core keeps submission order on an equal
9046 // point, so materialising the source batch in that order is sufficient
9047 // and does not add a source branch to generic matching.
9048 const bool cap_close_continuation =
9049 cap.configuration().count_pooc_full_close && queued.size() == 2U
9050 && std::count_if(queued.begin(), queued.end(), [](const auto& command) {
9051 return command.snapshot.frozen_market_targeted_close;
9052 }) == 1;
9053 std::stable_sort(queued.begin(), queued.end(), [&](const PendingSameBarCommand& left,
9054 const PendingSameBarCommand& right) {
9055 const auto buy_rank = [](const PendingSameBarCommand& command) {
9056 if (command.snapshot.frozen_market_targeted_close)
9057 return command.snapshot.frozen_market_target_was_long ? 1 : 0;
9058 return command.snapshot.is_long ? 0 : 1;
9059 };
9060 const int left_rank = buy_rank(left);
9061 const int right_rank = buy_rank(right);
9062 if (left_rank != right_rank) return left_rank < right_rank;
9063 if (cap_close_continuation
9064 && left.snapshot.frozen_market_targeted_close
9065 != right.snapshot.frozen_market_targeted_close) {
9066 return left.snapshot.frozen_market_targeted_close;
9067 }
9068 return false;
9069 });
9070
9071 const bool single_entry = queued.size() == 1
9072 && !queued.front().snapshot.frozen_market_targeted_close;
9073 const bool one_entry_one_close = queued.size() == 2U
9074 && std::count_if(queued.begin(), queued.end(), [](const auto& command) {
9075 return command.opening && !command.snapshot.frozen_market_targeted_close;
9076 }) == 1
9077 && std::count_if(queued.begin(), queued.end(), [](const auto& command) {
9078 return command.snapshot.frozen_market_targeted_close;
9079 }) == 1;
9080 double simulated = batch_start;
9081 std::optional<native_order::RequestHandle> short_seed_long;
9082 std::optional<native_order::RequestHandle> short_seed_materialize;
9083 std::optional<native_order::RequestHandle> short_seed_final;
9084 std::optional<native_order::RequestHandle> cap_close_parent;
9085 for (std::size_t i = 0; i < queued.size(); ++i) {
9086 auto& command = queued[i];
9087 auto request = std::move(command.request);
9088 const bool targeted_close = command.snapshot.frozen_market_targeted_close;
9089 auto snapshot = std::move(command.snapshot);
9090 bool opening = command.opening;
9091 const bool long_candidate = batch_start < 0.0 && opening
9092 && snapshot.family == PineOrderFamily::Entry && snapshot.is_long;
9093 const bool final_short_candidate = batch_start < 0.0 && opening
9094 && snapshot.family == PineOrderFamily::Entry && !snapshot.is_long;
9095 const bool materialize_candidate = batch_start < 0.0
9096 && snapshot.frozen_market_targeted_close
9097 && !snapshot.frozen_market_target_was_long;
9098
9099 if (!snapshot.frozen_market_targeted_close) {
9100 double units = snapshot.frozen_market_transaction_units;
9101 if (cap_close_continuation && simulated == 0.0
9102 && finite_positive(snapshot.frozen_market_own_units)) {
9103 units = snapshot.frozen_market_own_units;
9104 }
9105 if (!finite_positive(units)) continue;
9106 if (p2_explicit_pair
9107 && units > snapshot.frozen_market_own_units + 1e-10) {
9108 const double margin = snapshot.is_long
9109 ? config_.margin_long : config_.margin_short;
9110 const double required = units * snapshot.sizing.price
9111 * staged_.syminfo.pointvalue * snapshot.sizing.fx
9112 * margin / 100.0;
9113 if (!std::isfinite(required) || !std::isfinite(snapshot.sizing.equity)
9114 || required > snapshot.sizing.equity) {
9115 continue;
9116 }
9117 }
9118 if (single_entry) {
9119 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), opening,
9120 command.replacement_key);
9121 apply_known_reversal_gap(accepted);
9122 if (accepted && long_candidate) short_seed_long = *accepted;
9123 if (accepted && final_short_candidate) short_seed_final = *accepted;
9124 continue;
9125 }
9126 const bool affordability_reversal = snapshot.affordability_policy_active
9127 && batch_start != 0.0
9128 && ((batch_start > 0.0) != snapshot.is_long)
9129 && (snapshot.affordability_close_only || one_entry_one_close);
9130 if (affordability_reversal) {
9131 request.intent = native_order::HostSized{
9132 native_order::HostSizedKind::Open,
9133 snapshot.is_long ? native_order::Side::Long
9134 : native_order::Side::Short};
9135 } else {
9136 request.intent = native_order::Transact{
9137 snapshot.is_long ? units : -units};
9138 }
9139 simulated += snapshot.is_long ? units : -units;
9140 } else {
9141 const double target = snapshot.requested_qty;
9142 if (!finite_positive(target) || simulated == 0.0) continue;
9143 const bool target_long = snapshot.frozen_market_target_was_long;
9144 const bool still_target_side = (simulated > 0.0) == target_long;
9145 const double units = std::min(target, std::abs(simulated));
9146 if (!finite_positive(units)) continue;
9147 if (still_target_side) {
9148 request.intent = cap_close_continuation
9149 && units >= std::abs(simulated) - 1e-10
9151 : native_order::OrderIntent{native_order::Reduce{
9152 native_order::ExplicitUnits{units}}};
9153 simulated += simulated > 0.0 ? -units : units;
9154 } else {
9155 // A default-FIFO close whose original side was consumed may
9156 // become the legacy artifact only when its same-id frozen
9157 // MARKET entry is still later in the sorted broker pass.
9158 bool artifact = false;
9159 for (std::size_t later = i + 1; later < queued.size(); ++later) {
9160 const auto& sibling = queued[later];
9161 if (!sibling.snapshot.frozen_market_targeted_close
9162 && sibling.snapshot.frozen_market_instruction
9163 && sibling.snapshot.source_id == snapshot.source_id
9164 && sibling.snapshot.is_long == target_long) {
9165 artifact = true;
9166 break;
9167 }
9168 }
9169 if (!artifact) continue;
9170 const double signed_units = simulated > 0.0 ? units : -units;
9171 request.intent = native_order::Transact{signed_units};
9172 simulated += signed_units;
9173 // This is a broker-created artifact lot carrying the close
9174 // label, not a new source-id cohort member.
9175 opening = false;
9176 }
9177 }
9178 if (cap_close_continuation && opening && cap_close_parent)
9179 request.owner = native_order::WaitForApplied{*cap_close_parent};
9180 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), opening,
9181 command.replacement_key);
9182 if (accepted && targeted_close)
9183 cap_close_parent = *accepted;
9184 apply_known_reversal_gap(accepted);
9185 if (accepted && long_candidate) short_seed_long = *accepted;
9186 if (accepted && materialize_candidate) short_seed_materialize = *accepted;
9187 if (accepted && final_short_candidate) short_seed_final = *accepted;
9188 }
9189 if (short_seed_long && short_seed_materialize && short_seed_final) {
9190 const auto materialize_snapshot = placement_.find(short_seed_materialize->incarnation);
9191 const auto long_snapshot = placement_.find(short_seed_long->incarnation);
9192 const auto final_snapshot = placement_.find(short_seed_final->incarnation);
9193 if (materialize_snapshot != placement_.end() && long_snapshot != placement_.end()
9194 && final_snapshot != placement_.end()) {
9196 plan.long_entry = *short_seed_long;
9197 plan.materialize_long = *short_seed_materialize;
9198 plan.final_short = *short_seed_final;
9199 plan.seed_id = materialize_snapshot->second.source_id;
9200 plan.long_entry_id = long_snapshot->second.source_id;
9201 plan.final_short_id = final_snapshot->second.source_id;
9202 plan.materialize_label = "__close__" + plan.seed_id;
9203 plan.seed_qty = std::abs(batch_start);
9204 plan.seed_cycle = current_position_cycle_;
9205 pending_short_seed_ = {std::move(plan), broker_open_epoch_ + 1U, true};
9206 // This immutable receipt is available to the fixture's historical
9207 // handle probe even when a finite run ends before the next broker
9208 // open. PendingIntentView still exposes roles only after the
9209 // next-open live qualification below.
9210 short_seed_ = pending_short_seed_.plan;
9211 maybe_activate_short_seed_plan();
9212 }
9213 }
9214}
9215
9216void PineExecutionAdapter::materialize_relative_exits(
9217 PlacementSnapshot opening, const native_order::ExecutionAppliedEvent& event) {
9218 if (pending_relative_exits_.empty() || !finite_positive(staged_.syminfo.mintick)) {
9219 // No queued definition is served by this fill, so no child the
9220 // kernel armed on it has a source request to become.
9221 withdraw_anchored_relative_legs(nullptr, &opening.source_id);
9222 return;
9223 }
9224 std::vector<PendingRelativeExit> pending;
9225 for (auto it = pending_relative_exits_.begin(); it != pending_relative_exits_.end();) {
9226 if (it->from_entry == opening.source_id) {
9227 pending.push_back(std::move(*it));
9228 it = pending_relative_exits_.erase(it);
9229 } else {
9230 ++it;
9231 }
9232 }
9233 // Decided once for the whole parent: either every armed child is the
9234 // request this fill point submits, or all of them leave first and the
9235 // re-run below is the unchanged source pipeline.
9236 if (!armed_relative_legs_adoptable(opening, event, pending))
9237 withdraw_anchored_relative_legs(nullptr, &opening.source_id);
9238 const double tick = staged_.syminfo.mintick;
9239 for (const auto& value : pending) {
9240 double limit = kNaN;
9241 double stop = kNaN;
9242 double offset = value.trail_offset;
9243 // `ticks` from the parent's fill, snapped to the grid on the side that
9244 // keeps the level at least that far away (the kernel's Directional
9245 // anchor rounding spells the same ladder point for an armed child).
9246 const double side = opening.is_long ? 1.0 : -1.0;
9247 if (finite_positive(value.profit_ticks)) {
9248 limit = directional_tick(event.resolved_price + side * value.profit_ticks * tick,
9249 tick, opening.is_long);
9250 }
9251 if (finite_positive(value.loss_ticks)) {
9252 stop = directional_tick(event.resolved_price - side * value.loss_ticks * tick,
9253 tick, !opening.is_long);
9254 }
9255 // An omitted trail_offset is a one-shot activation leg in Pine. Do
9256 // not synthesize a trailing distance from trail_points here; an
9257 // explicit zero/sub-tick offset remains distinguishable and is
9258 // lowered by exit()'s native sentinel policy.
9259 materializing_relative_ = true;
9260 materializing_parent_ = event.handle();
9261 try {
9262 exit(value.exit_id, value.from_entry, limit, stop, value.trail_points, offset,
9263 value.trail_price, value.qty_percent, value.comment, value.qty, value.oca_name,
9264 kNaN, kNaN);
9265 } catch (...) {
9266 materializing_relative_ = false;
9267 materializing_parent_ = {};
9268 throw;
9269 }
9270 materializing_relative_ = false;
9271 materializing_parent_ = {};
9272 }
9273 // Structural, not counted: no child armed on this parent outlives the
9274 // parent's own fill notification. One the re-run did not adopt is not the
9275 // request this fill point asks for (another trigger, a staged or refused
9276 // leg), the source pipeline above already did what it does, and a kernel
9277 // request without a placement row must never reach a match.
9278 withdraw_anchored_relative_legs(nullptr, &opening.source_id);
9279}
9280
9281void PineExecutionAdapter::withdraw_anchored_relative_legs(
9282 const SourceId* exit_id, const SourceId* from_entry) {
9283 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
9284 const auto& leg = anchored_relative_legs_[index];
9285 if ((exit_id && leg.exit_id != *exit_id)
9286 || (from_entry && leg.from_entry != *from_entry)) {
9287 ++index;
9288 continue;
9289 }
9290 // Cancel first: a host that refuses the command throws, and the
9291 // record of a child that may still be live must survive that.
9292 const native_order::RequestHandle handle = leg.handle;
9293 (void)require_host().cancel(handle);
9294 anchored_relative_legs_.erase(anchored_relative_legs_.begin()
9295 + static_cast<std::ptrdiff_t>(index));
9296 ++anchored_relative_stats_.withdrawn;
9297 }
9298}
9299
9300bool PineExecutionAdapter::anchorable_relative_exit(
9301 const PendingRelativeExit& value, native_order::RequestHandle& parent,
9302 bool& parent_long) const {
9303 // The kernel arms the child as a host-sized close of the book its
9304 // parent's fill leaves (NativeArmScope::Book), which is this adapter's
9305 // fill-point leg under the FIFO close rule. The ANY rule binds that leg
9306 // to the named cohort instead, a scope the arm does not spell; and an
9307 // explicit quantity is not born at the fill point at all -- exit() stages
9308 // it per origin (pending_bracket_legs_) and submits it at a later flush.
9309 if (value.from_entry.empty() || config_.close_entries_rule_any || !std::isnan(value.qty))
9310 return false;
9311 if (stream_mode_ || !finite_positive(staged_.syminfo.mintick)) return false;
9312 const auto staged_same_id = [&](const PlacementSnapshot& row) {
9313 return row.opening && row.source_id == value.from_entry;
9314 };
9315 for (const auto& row : pending_same_bar_commands_)
9316 if (staged_same_id(row.snapshot)) return false;
9317 for (const auto& row : pending_entries_)
9318 if (staged_same_id(row.snapshot)) return false;
9319 for (const auto& row : pending_coof_requests_)
9320 if (staged_same_id(row.snapshot)) return false;
9321 for (const auto& row : delayed_market_orders_)
9322 if (staged_same_id(row.snapshot)) return false;
9323 std::size_t parents = 0;
9324 native_order::RequestHandle only{};
9325 bool only_long = true;
9326 for (const auto& handle : live_handles_) {
9327 const auto found = placement_.find(handle.incarnation);
9328 if (found == placement_.end() || !found->second.opening
9329 || found->second.source_id != value.from_entry) {
9330 continue;
9331 }
9332 if (found->second.family != PineOrderFamily::Entry) return false;
9333 only = handle;
9334 only_long = found->second.is_long;
9335 ++parents;
9336 }
9337 if (parents != 1) return false;
9338 parent = only;
9339 parent_long = only_long;
9340 return true;
9341}
9342
9343std::vector<PineExecutionAdapter::RelativeLegShape>
9344PineExecutionAdapter::relative_leg_shapes(const PendingRelativeExit& value,
9345 bool parent_long) const {
9346 // The legs exit() emits for the relative operands once they resolve, in
9347 // its own order (limit, stop, trail), each spelled as an anchored trigger:
9348 // the level placeholder the kernel fills at the arm and the signed tick
9349 // distance from the parent's fill.
9350 std::vector<RelativeLegShape> shapes;
9351 const double side = parent_long ? 1.0 : -1.0;
9352 if (finite_positive(value.profit_ticks)) {
9353 shapes.push_back({PineOrderFamily::ExitLimit, native_order::Limit{0.0},
9354 side * value.profit_ticks, value.profit_ticks});
9355 }
9356 if (finite_positive(value.loss_ticks)) {
9357 shapes.push_back({PineOrderFamily::ExitStop, native_order::Stop{0.0},
9358 -side * value.loss_ticks, value.loss_ticks});
9359 }
9360 if (std::isfinite(value.trail_points) && !finite_positive(value.trail_price)) {
9361 const double trail_ticks = std::ceil(value.trail_points - 5e-5);
9362 const bool has_offset = std::isfinite(value.trail_offset) && value.trail_offset >= 0.0;
9363 const double offset_ticks = has_offset ? std::floor(value.trail_offset) : kNaN;
9364 if (trail_ticks >= 1.0 && has_offset && offset_ticks >= 1.0) {
9365 shapes.push_back({PineOrderFamily::ExitTrail,
9366 native_order::Trail{0.0, 0.0,
9367 native_order::TrailTicks{offset_ticks}},
9368 side * trail_ticks, trail_ticks});
9369 } else if (trail_ticks >= 1.0 && (has_offset || !std::isfinite(value.trail_offset))) {
9370 // Omitted or explicit-zero offset: the one-shot activation touch,
9371 // a fill-through limit when the zero shape slips.
9372 shapes.push_back({PineOrderFamily::ExitTrail,
9373 native_order::Limit{0.0, has_offset && config_.slippage > 0},
9374 side * trail_ticks, trail_ticks});
9375 }
9376 }
9377 return shapes;
9378}
9379
9381 if (anchored_relative_legs_.empty() && pending_relative_exits_.empty()) return;
9382 // An armed child is adopted or withdrawn inside its parent's own fill
9383 // notification. One that is still here was armed by a fill this adapter
9384 // never materialized, and a kernel request without a placement row must
9385 // never reach a match.
9386 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
9387 if (!anchored_relative_legs_[index].armed) { ++index; continue; }
9388 const SourceId exit_id = anchored_relative_legs_[index].exit_id;
9389 const SourceId from_entry = anchored_relative_legs_[index].from_entry;
9390 withdraw_anchored_relative_legs(&exit_id, &from_entry);
9391 index = 0;
9392 }
9393 // A child whose definition is gone waits for nothing.
9394 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
9395 const auto& leg = anchored_relative_legs_[index];
9396 const bool queued = std::any_of(
9397 pending_relative_exits_.begin(), pending_relative_exits_.end(),
9398 [&](const PendingRelativeExit& value) {
9399 return value.exit_id == leg.exit_id && value.from_entry == leg.from_entry;
9400 });
9401 if (queued) { ++index; continue; }
9402 const SourceId exit_id = leg.exit_id;
9403 const SourceId from_entry = leg.from_entry;
9404 withdraw_anchored_relative_legs(&exit_id, &from_entry);
9405 index = 0;
9406 }
9407 if (pending_relative_exits_.empty()) return;
9408 // One decision per parent id: the armed children of a fill are adopted
9409 // together or not at all, so every queued exit of that id must be
9410 // expressible, or none is anchored and the fill point submits them all.
9411 std::vector<SourceId> parents;
9412 for (const auto& value : pending_relative_exits_) {
9413 if (std::find(parents.begin(), parents.end(), value.from_entry) == parents.end())
9414 parents.push_back(value.from_entry);
9415 }
9416 for (const auto& from_entry : parents) {
9418 bool parent_long = true;
9419 bool anchorable = true;
9420 for (const auto& value : pending_relative_exits_) {
9421 if (value.from_entry != from_entry) continue;
9422 anchorable = anchorable && anchorable_relative_exit(value, parent, parent_long);
9423 }
9424 const bool anchored = std::any_of(
9425 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
9426 [&](const AnchoredRelativeLeg& leg) { return leg.from_entry == from_entry; });
9427 if (anchored) {
9428 // A changed definition already withdrew its legs in exit(); what
9429 // is left here waits on this parent or on a parent that is gone.
9430 const bool complete = anchorable && std::all_of(
9431 pending_relative_exits_.begin(), pending_relative_exits_.end(),
9432 [&](const PendingRelativeExit& value) {
9433 if (value.from_entry != from_entry) return true;
9434 const auto shapes = relative_leg_shapes(value, parent_long);
9435 const auto legs = static_cast<std::size_t>(std::count_if(
9436 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
9437 [&](const AnchoredRelativeLeg& leg) {
9438 return leg.exit_id == value.exit_id
9439 && leg.from_entry == from_entry && leg.parent == parent;
9440 }));
9441 return legs == shapes.size();
9442 });
9443 if (complete) continue;
9444 withdraw_anchored_relative_legs(nullptr, &from_entry);
9445 }
9446 if (!anchorable) continue;
9447 for (const auto& value : pending_relative_exits_) {
9448 if (value.from_entry != from_entry) continue;
9449 const SourceId group_name = value.oca_name.empty()
9450 ? value.exit_id + "\x1f" + value.from_entry : value.oca_name;
9451 for (auto& shape : relative_leg_shapes(value, parent_long)) {
9452 AnchoredRelativeLeg leg;
9453 leg.exit_id = value.exit_id;
9454 leg.from_entry = value.from_entry;
9455 leg.family = shape.family;
9456 leg.parent = parent;
9457 leg.parent_long = parent_long;
9458 leg.operand_ticks = shape.operand_ticks;
9459 leg.trail_offset = value.trail_offset;
9460 leg.request.intent = native_order::HostSized{
9461 native_order::HostSizedKind::Close, std::nullopt};
9462 leg.request.label = value.exit_id;
9463 leg.request.comment = value.comment;
9464 leg.request.trigger = std::move(shape.trigger);
9465 // The leg exit() submits in the parent's fill callback is
9466 // born after the fill print and closes the book; so does this
9467 // child (AfterArmPrint, Book).
9468 leg.request.owner = native_order::WaitForApplied{
9469 parent, native_order::NativeArmVisibility::PendingUntilArmed,
9470 native_order::NativeArmFirstMatch::AfterArmPrint,
9471 native_order::NativeArmScope::Book};
9472 leg.request.group = group_for(group_name, 1,
9473 static_cast<std::int64_t>(shape.family));
9474 if (auto* member = std::get_if<native_order::Member>(&leg.request.group)) {
9475 // The kernel tells OCA siblings apart by cohort, so every
9476 // member needs its own. A submitted sibling carries its
9477 // source sequence (counting up from one); an anchored one
9478 // counts down from minus one.
9479 if (anchored_cohort_sequence_
9480 == std::numeric_limits<std::int64_t>::max()) {
9481 throw std::overflow_error("Pine anchored OCA member sequence exhausted");
9482 }
9483 member->cohort = -(++anchored_cohort_sequence_);
9484 }
9485 leg.request.anchor = native_order::FromOwnerFill{
9486 shape.signed_ticks, true, native_order::NativeAnchorRounding::Directional};
9487 const auto result = require_host().submit(leg.request);
9488 if (result.status != native_order::SubmitStatus::Accepted || !result.handle)
9489 continue;
9490 leg.handle = *result.handle;
9491 anchored_relative_legs_.push_back(std::move(leg));
9492 ++anchored_relative_stats_.anchored;
9493 }
9494 }
9495 }
9496}
9497
9498bool PineExecutionAdapter::armed_relative_legs_adoptable(
9499 const PlacementSnapshot& opening, const native_order::ExecutionAppliedEvent& event,
9500 const std::vector<PendingRelativeExit>& pending) const {
9501 // The fill-point re-run submits exactly the queued legs, at once, only
9502 // when nothing it would stage them behind is waiting.
9503 if (!pending_entries_.empty() || !pending_same_bar_commands_.empty()
9504 || !delayed_market_orders_.empty() || !pending_coof_requests_.empty()
9505 || coof_recalc_active_) {
9506 return false;
9507 }
9508 // Leg for leg: every shape of every queued definition has its own armed
9509 // child on this parent, and this parent carries no other child.
9510 std::size_t expected = 0;
9511 for (const auto& value : pending) {
9512 for (const auto& shape : relative_leg_shapes(value, opening.is_long)) {
9513 const auto children = std::count_if(
9514 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
9515 [&](const AnchoredRelativeLeg& leg) {
9516 return leg.exit_id == value.exit_id && leg.from_entry == value.from_entry
9517 && leg.family == shape.family
9518 && leg.request.trigger.index() == shape.trigger.index();
9519 });
9520 if (children != 1) return false;
9521 ++expected;
9522 }
9523 }
9524 std::size_t armed = 0;
9525 for (const auto& leg : anchored_relative_legs_) {
9526 if (leg.from_entry != opening.source_id) continue;
9527 if (leg.parent != event.handle() || !leg.armed || leg.parent_long != opening.is_long
9528 || !std::isfinite(leg.installed_level)) {
9529 return false;
9530 }
9531 ++armed;
9532 }
9533 return armed == expected && armed != 0;
9534}
9535
9536double PineExecutionAdapter::exit_limit_trigger(double limit_price, double tick,
9537 bool exit_is_buy) const noexcept {
9538 // ab9714be pine_policy_members.cpp:11-17 + engine.hpp:1374-1381: an
9539 // exit limit is tested against the tick-quantized bar, so an ON-grid
9540 // level is reached by a raw extreme half a tick short of it (NYSE:F
9541 // high 10.175 -> 10.18 fills a 10.18 sell limit). Only the
9542 // calc_on_order_fills scheduler compares the raw bar; there an
9543 // on-grid level stays raw.
9544 if (!finite_positive(tick)
9545 || (config_.calc_on_order_fills && nearest_tick(limit_price, tick) == limit_price)) {
9546 return limit_price;
9547 }
9548 return source_trigger_threshold(limit_price, tick, exit_is_buy, true);
9549}
9550
9551double PineExecutionAdapter::one_shot_trail_trigger(double activation, double source_trail_offset,
9552 double tick, bool exit_is_buy,
9553 bool quantized_activation) const noexcept {
9554 double one_shot_level = activation;
9555 if (!std::isfinite(source_trail_offset)) {
9556 const double slipped = one_shot_level
9557 + (exit_is_buy ? 1.0 : -1.0) * config_.slippage * tick;
9558 one_shot_level = directional_tick(slipped, tick, exit_is_buy);
9559 }
9560 // A sell one-shot keeps the k / (1 / mintick) grid spelling so a
9561 // chart print sitting exactly on the activation satisfies the
9562 // generic `print >= level` bit-for-bit. A buy one-shot keeps the
9563 // directional k * mintick spelling instead: ab9714be books the
9564 // crossed activation as a TRAIL event (engine_path_resolve.cpp:
9565 // 984-987, fill.is_limit = false), so apply_slippage snaps it
9566 // with round_to_mintick_directional, which materializes that same
9567 // product, and the immutable generic buy limit accepts it because
9568 // fill <= level holds by construction. Re-spelling the buy level
9569 // onto the division grid handed back a value one binary64 ULP
9570 // away from the owner's fill and that ULP rode the equity path
9571 // into printed PnL
9572 // (zz-pop-stevenygabbyperez-fast-scalper-with-stops short exit
9573 // 2025-04-02 22:00Z, activation 1870.0 - 3741 * 0.01: owner
9574 // 1832.5900000000001, re-spelled 1832.59).
9575 // ab9714be pine_fills.cpp:4607-4610: snap_trail_level_to_tick_grid
9576 // aligns the trail level to the tick grid (sell one-shot).
9577 if (!exit_is_buy) one_shot_level = source_level_on_price_grid(one_shot_level, tick);
9578 // ab9714be engine_path_resolve.cpp:297-308 and 746-766: an
9579 // omitted-offset trail's dormant activation is reached on the
9580 // tick-quantized path (design-trail-activation-tick-bar), so a
9581 // raw extreme half a tick short of the level already fires it.
9582 // Only the arm threshold moves; settlement books the source
9583 // level (source_trail_one_shot_fill).
9584 // ab9714be engine_path_resolve.cpp:292-308 + 746-766 test the
9585 // explicit-zero trail's dormant activation on the same
9586 // tick-quantized path, so its arm threshold is the same half-up
9587 // projection boundary: a buy one-shot is reached only strictly
9588 // below activation + half a tick (NYSE:F 2026-04-10 16:30Z low
9589 // 12.075 prints 12.08 and does not reach the 12.07 activation).
9590 if (quantized_activation && finite_positive(tick)) {
9591 one_shot_level = source_trigger_threshold(one_shot_level, tick, exit_is_buy, true);
9592 }
9593 return one_shot_level;
9594}
9595
9597 const NativeAnchoredLevelView& view) const {
9598 const auto found = std::find_if(
9599 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
9600 [&](const AnchoredRelativeLeg& leg) { return leg.handle == view.leg; });
9601 if (found == anchored_relative_legs_.end()) return std::nullopt;
9602 auto& leg = *found;
9603 const double tick = staged_.syminfo.mintick;
9604 leg.armed = true;
9605 leg.installed_level = kNaN;
9606 // Without a usable tick there is no projection to restate; the kernel
9607 // keeps its own level and the fill point withdraws the child.
9608 if (!finite_positive(tick)) return std::nullopt;
9609 // The kernel owns the fill-relative arithmetic: view.kernel_level is
9610 // fill + ticks on the tick ladder (FromOwnerFill, Directional), the very
9611 // ladder point materialize_relative_exits hands the fill-point exit().
9612 // What stays here is TradingView's spelling of that point and its
9613 // trigger projection, shared with exit().
9614 const bool exit_is_buy = !leg.parent_long;
9615 double installed = kNaN;
9616 if (leg.family == PineOrderFamily::ExitLimit) {
9617 installed = exit_limit_trigger(
9618 source_level_on_price_grid(view.kernel_level, tick), tick, exit_is_buy);
9619 } else if (leg.family == PineOrderFamily::ExitStop) {
9620 installed = source_trigger_threshold(
9621 source_level_on_price_grid(view.kernel_level, tick), tick, exit_is_buy, false);
9622 } else {
9623 const double trail_price = directional_tick(view.kernel_level, tick, leg.parent_long);
9624 installed = std::holds_alternative<native_order::Trail>(leg.request.trigger)
9625 ? trail_price
9626 : one_shot_trail_trigger(trail_price, leg.trail_offset, tick, exit_is_buy, true);
9627 }
9628 // The kernel refuses a level its trigger cannot hold and fails the run.
9629 // A projection that lands there (a short's relative level below zero) is
9630 // never the request the fill-point re-run submits, so the child is armed
9631 // on a representable placeholder. That is safe for one reason only: its
9632 // NaN installed level makes it unadoptable, and materialize_relative_exits
9633 // withdraws every unadopted child inside this same fill notification,
9634 // before the path resumes.
9635 const bool trail_arm = std::holds_alternative<native_order::Trail>(leg.request.trigger);
9636 if (!(trail_arm ? finite_positive(installed) : finite_non_negative(installed))) {
9637 leg.installed_level = kNaN;
9638 return tick;
9639 }
9640 leg.installed_level = installed;
9641 return installed;
9642}
9643
9645 const SourceId& from_entry,
9646 const std::string&) {
9647 const auto key = key_for(exit_id, from_entry);
9648 pending_relative_exits_.erase(std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
9649 [&](const PendingRelativeExit& value) {
9650 return value.exit_id == exit_id && value.from_entry == from_entry;
9651 }), pending_relative_exits_.end());
9652 if (!materializing_relative_) withdraw_anchored_relative_legs(&exit_id, &from_entry);
9653 pending_bracket_legs_.erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
9654 [&](const PendingBracketLeg& leg) { return leg.family_key == key; }), pending_bracket_legs_.end());
9655 // Cancellation can synchronously change source state. Copy the family
9656 // roster and release the map iterator before issuing any host operation.
9657 std::vector<native_order::RequestHandle> handles;
9658 bool found_family = false;
9659 if (const auto found = bracket_families_.find(key); found != bracket_families_.end()) {
9660 handles = found->second.members();
9661 bracket_families_.erase(found);
9662 found_family = true;
9663 }
9664 if (!found_family) return;
9665 for (const auto& handle : handles) {
9666 const auto result = require_host().cancel(handle);
9667 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
9668 }
9669}
9670
9672 NamedEntryCancelToken token;
9673 for (const auto& handle : live_handles_) {
9674 const auto snapshot = placement_.find(handle.incarnation);
9675 if (snapshot == placement_.end()) continue;
9676 const auto family = snapshot->second.family;
9677 if (family == PineOrderFamily::Entry && snapshot->second.source_id == id) {
9678 token.entry_incarnation = handle.incarnation;
9679 } else if ((family == PineOrderFamily::ExitLimit || family == PineOrderFamily::ExitStop
9680 || family == PineOrderFamily::ExitTrail)
9681 && snapshot->second.from_entry == id
9682 && token.surviving_exit_incarnation == 0) {
9683 token.surviving_exit_incarnation = handle.incarnation;
9684 }
9685 }
9686 if (token.entry_incarnation != 0) named_entry_cancel_tokens_[id] = token;
9687 else named_entry_cancel_tokens_.erase(id);
9688 const auto same_bar_before = pending_same_bar_commands_.size();
9689 pending_same_bar_commands_.erase(std::remove_if(pending_same_bar_commands_.begin(),
9690 pending_same_bar_commands_.end(), [&](const PendingSameBarCommand& command) {
9691 return command.snapshot.source_id == id;
9692 }), pending_same_bar_commands_.end());
9693 if (pending_same_bar_commands_.size() != same_bar_before)
9694 source_batch_mutated_ = true;
9695 pending_same_bar_close_qty_ = 0.0;
9696 for (const auto& command : pending_same_bar_commands_) {
9697 if (command.snapshot.frozen_market_targeted_close)
9698 pending_same_bar_close_qty_ += command.snapshot.requested_qty;
9699 }
9700 pending_relative_exits_.erase(std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
9701 [&](const PendingRelativeExit& value) { return value.exit_id == id || value.from_entry == id; }),
9702 pending_relative_exits_.end());
9703 withdraw_anchored_relative_legs(&id, nullptr);
9704 withdraw_anchored_relative_legs(nullptr, &id);
9705 pending_entries_.erase(std::remove_if(pending_entries_.begin(), pending_entries_.end(),
9706 [&](const PendingEntry& entry) { return entry.snapshot.source_id == id; }), pending_entries_.end());
9707 pending_bracket_legs_.erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
9708 [&](const PendingBracketLeg& leg) { return leg.snapshot.source_id == id; }),
9709 pending_bracket_legs_.end());
9710 std::vector<native_order::RequestHandle> matches;
9711 for (const auto& handle : live_handles_) {
9712 const auto snapshot = placement_.find(handle.incarnation);
9713 if (snapshot != placement_.end() && snapshot->second.source_id == id) matches.push_back(handle);
9714 }
9715 for (const auto& handle : matches) {
9716 const auto result = require_host().cancel(handle);
9717 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
9718 }
9719}
9720
9722 const auto handles = live_handles_;
9723 for (const auto& handle : handles) {
9724 const auto result = require_host().cancel(handle);
9725 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
9726 }
9727 bracket_families_.clear();
9728 pending_bracket_legs_.clear();
9729 pending_entries_.clear();
9730 delayed_market_orders_.clear();
9731 deferred_open_marketable_sells_.clear();
9732 throttled_reopen_rearm_.clear();
9733 pending_same_bar_commands_.clear();
9734 pending_same_bar_close_qty_ = 0.0;
9735 pending_relative_exits_.clear();
9736 withdraw_anchored_relative_legs(nullptr, nullptr);
9737}
9738
9739void PineExecutionAdapter::order(const SourceId& id, bool is_long, double qty,
9740 double limit_price, double stop_price,
9741 const std::string& oca_name, int oca_type) {
9742 if (intraday_loss_orders_blocked()) return;
9743 if (const auto point = require_host().current_execution_point();
9744 point && cap_placement_denied(point->decision)) {
9745 return;
9746 }
9747 // ab9714be pine_strategy_commands.cpp:2120-2203: strategy.order appends to pending book after earlier script commands
9748 if (!pending_same_bar_commands_.empty()) {
9749 source_batch_mutated_ = true;
9750 flush_pending_same_bar_commands();
9751 }
9752 // ab9714be pine_strategy_commands.cpp:2120-2203: strategy.order commits after preceding pending entries in source order
9753 if (!pending_entries_.empty()) {
9755 }
9756 if (id == "__close__") {
9757 const auto point = require_host().current_execution_point();
9758 std::vector<native_order::RequestHandle> replaced_close_all;
9759 for (const auto& handle : live_handles_) {
9760 const auto found = placement_.find(handle.incarnation);
9761 if (found != placement_.end()
9762 && found->second.family == PineOrderFamily::CloseAll
9763 && (!point || found->second.placement_script_open_ms
9764 == point->decision.script_bar_open_ms)) {
9765 replaced_close_all.push_back(handle);
9766 }
9767 }
9768 for (const auto& handle : replaced_close_all) {
9769 const auto result = require_host().cancel(handle);
9770 if (result.status != native_order::CancelStatus::Cancelled) continue;
9771 for (auto row : placement_) {
9772 if (row.second.preserved_by_close_all == handle) {
9773 row.second.preserved_by_close_all = {};
9774 row.second.preserved_close_all_bar = -1;
9775 }
9776 }
9777 retire(handle);
9778 }
9779 }
9780 for (const auto& handle : live_handles_) {
9781 const auto existing = placement_.find(handle.incarnation);
9782 if (existing != placement_.end()
9783 && existing->second.pooc_global_full_exit_dynamic_qty) {
9784 existing->second.pooc_global_full_exit_dynamic_qty = false;
9785 existing->second.pooc_global_full_exit_tracks_bound_adds = false;
9786 }
9787 }
9788 native_order::Request request;
9789 double risk_coof_forced_price = kNaN;
9790 double coof_market_fill = kNaN;
9791 bool delay_after_default_pair = false;
9792 limit_price = source_level_on_price_grid(limit_price, staged_.syminfo.mintick);
9793 stop_price = source_level_on_price_grid(stop_price, staged_.syminfo.mintick);
9794 const bool default_sized = std::isnan(qty);
9795 const double normalized_qty = default_sized ? qty
9796 : floor_quantity_grid(std::abs(qty), staged_.quantity_grid);
9797 // The CANCEL group is source-gated by its original requested quantity:
9798 // an opposite-side partial fill must not erase its sibling. Resolve that
9799 // shape through host terms so the adapter can retain the source operand.
9800 // Existing explicit non-cancel RAW orders stay on the generic Transact
9801 // path, including native OCA-reduce's working-reservation semantics.
9802 request.intent = (default_sized || oca_type == 1)
9803 ? native_order::OrderIntent{native_order::HostSized{native_order::HostSizedKind::Open,
9804 is_long ? native_order::Side::Long : native_order::Side::Short}}
9805 : native_order::OrderIntent{native_order::Transact{is_long ? normalized_qty : -normalized_qty}};
9806 request.label = id;
9807 double native_limit = limit_price;
9808 double native_stop = stop_price;
9809 if (finite_positive(limit_price) && !finite_positive(stop_price)) {
9810 native_limit = source_trigger_threshold(
9811 limit_price, staged_.syminfo.mintick, is_long, true);
9812 } else if (finite_positive(stop_price) && !finite_positive(limit_price)
9813 && !config_.calc_on_order_fills) {
9814 native_stop = source_trigger_threshold(
9815 stop_price, staged_.syminfo.mintick, is_long, false);
9816 }
9817 request.trigger = trigger_for(native_limit, native_stop);
9818 if (coof_recalc_active_ && coof_first_open_) {
9819 const auto point = require_host().current_execution_point();
9820 if (point && finite_positive(limit_price)
9821 && (is_long ? limit_price >= point->price
9822 : limit_price <= point->price)) {
9823 coof_market_fill = source_bar_fill_tick(
9824 point->price, staged_.syminfo.mintick);
9825 }
9826 }
9827 if (std::holds_alternative<native_order::Market>(request.trigger)) {
9828 const auto point = require_host().current_execution_point();
9829 if (coof_recalc_active_ && !coof_first_open_) {
9830 const double next_waypoint = coof_next_waypoint();
9831 const double current_quote = point ? point->price : kNaN;
9832 coof_market_fill = source_bar_fill_tick(
9833 next_waypoint, staged_.syminfo.mintick)
9834 + (is_long ? 1.0 : -1.0) * config_.slippage
9835 * staged_.syminfo.mintick;
9836 if (finite_positive(next_waypoint) && finite_positive(current_quote)
9837 && !source_same_point(current_quote, next_waypoint, staged_.syminfo.mintick)) {
9838 const bool falling = next_waypoint < current_quote;
9839 if (is_long) {
9840 request.trigger = falling
9842 : native_order::Trigger{native_order::Stop{next_waypoint}};
9843 } else {
9844 request.trigger = falling
9847 }
9848 }
9849 }
9850 if (coof_recalc_active_ && !coof_first_open_
9851 && risk_.max_intraday_loss > 0.0) {
9852 double target = kNaN;
9853 if (point) {
9854 switch (point->decision.coordinate.path_phase) {
9855 case NativePathPhase::Open: {
9856 const bool high_first = source_path_uses_high_first(coof_script_bar_);
9857 target = high_first ? coof_script_bar_.high : coof_script_bar_.low;
9858 break;
9859 }
9860 case NativePathPhase::High: target = coof_script_bar_.high; break;
9861 case NativePathPhase::Low: target = coof_script_bar_.low; break;
9862 default: break;
9863 }
9864 if (finite_positive(target)
9865 && !source_same_point(point->price, target, staged_.syminfo.mintick)
9866 && risk_coof_direct_script_bar_
9867 != point->decision.script_bar_open_ms) {
9868 risk_coof_forced_price = target;
9869 risk_coof_direct_script_bar_ = point->decision.script_bar_open_ms;
9870 }
9871 }
9872 }
9873 std::vector<std::pair<std::uint64_t, native_order::RequestHandle>> same_bar_defaults;
9874 for (const auto& handle : live_handles_) {
9875 const auto pending = placement_.find(handle.incarnation);
9876 if (pending == placement_.end()) continue;
9877 const auto& row = pending->second;
9878 if (row.opening && row.family == PineOrderFamily::Entry
9879 && !std::isfinite(row.requested_qty)
9880 && (!point || row.placement_script_open_ms
9881 == point->decision.script_bar_open_ms)
9882 && !finite_positive(row.exit_levels.limit)
9883 && !finite_positive(row.exit_levels.stop)) {
9884 same_bar_defaults.push_back({row.source_sequence, handle});
9885 }
9886 }
9887 if (same_bar_defaults.size() >= 2) {
9888 delay_after_default_pair = true;
9889 }
9890 }
9891 request.group = group_for(oca_name, oca_type);
9892 if (oca_type == 1) {
9893 // A Pine RAW cancel group fires only when the source request itself
9894 // completely fills. The generic request has no source requested-size
9895 // operand once an opposite close is bounded to live exposure, so the
9896 // adapter applies that source receipt from on_applied instead.
9897 request.group = native_order::NoGroup{};
9898 }
9899 if (default_sized && oca_type == 2) {
9900 if (auto* member = std::get_if<native_order::Member>(&request.group)) {
9901 // Pine's default-sized RAW sibling is cancelled after an OCA
9902 // reduce member fills; only an explicit quantity consumes the
9903 // group reduction as a residual working amount.
9904 member->effect = native_order::GroupEffect::Cancel;
9905 }
9906 }
9907 PlacementSnapshot snapshot;
9908 snapshot.family = PineOrderFamily::Order; snapshot.source_id = id; snapshot.oca_name = oca_name;
9909 snapshot.oca_type = oca_type;
9910 // strategy.order's source quantity is verbatim (unlike strategy.entry's
9911 // lot-grid floor); retain that literal in the adapter projection even
9912 // when the generic accepted request needs its separately normalized
9913 // executable operand.
9914 snapshot.requested_qty = qty;
9915 snapshot.is_long = is_long;
9916 snapshot.exit_levels.limit = limit_price;
9917 snapshot.exit_levels.stop = stop_price;
9918 snapshot.forced_execution_price = finite_positive(risk_coof_forced_price)
9919 ? risk_coof_forced_price : coof_market_fill;
9920 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
9921 throw std::overflow_error("Pine source command sequence exhausted");
9922 }
9923 snapshot.command_sequence = ++source_command_sequence_;
9924 snapshot.sizing = sizing_snapshot();
9925 if (default_sized && finite_positive(snapshot.sizing.price)
9926 && (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
9927 || config_.default_qty_type == static_cast<int>(QtyType::CASH))) {
9928 snapshot.sizing.frozen_units = default_sizing_units(snapshot.sizing);
9929 snapshot.sizing.at_fill = config_.calc_on_order_fills && coof_recalc_active_;
9930 }
9931 if (delay_after_default_pair) {
9932 if (broker_open_epoch_ == std::numeric_limits<std::uint64_t>::max())
9933 throw std::overflow_error("source delayed market epoch exhausted");
9934 if (const auto point = require_host().current_execution_point()) {
9935 snapshot.projection_created_bar = point->decision.coordinate.interval_index;
9936 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
9937 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
9938 }
9939 snapshot.projection_position_side = static_cast<std::int32_t>(PositionSide::FLAT);
9940 delayed_market_orders_.push_back({std::move(request), std::move(snapshot), id,
9941 broker_open_epoch_ + 1U});
9942 return;
9943 }
9944 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), true, id);
9945 if (!accepted) return;
9946 const auto entry = placement_.find(accepted->incarnation);
9947 if (entry == placement_.end()) return;
9948 const std::uint64_t entry_sequence = entry->second.command_sequence;
9949 for (const auto& handle : live_handles_) {
9950 if (handle == *accepted) continue;
9951 const auto existing = placement_.find(handle.incarnation);
9952 if (existing == placement_.end()) continue;
9953 auto& prior = existing->second;
9954 if (!prior.reservation_expansion.capture()
9955 || prior.command_sequence >= entry_sequence) {
9956 continue;
9957 }
9958 prior.pooc_global_full_exit_dynamic_qty = false;
9959 prior.pooc_global_full_exit_tracks_bound_adds = false;
9960 prior.reservation_expansion.close_population(accepted->incarnation);
9961 }
9962}
9963
9965 const NativeExecutionTermsFacts& facts) const {
9966 native_order::ExecutionTerms result{facts.default_resolved_price, std::nullopt,
9967 native_order::OpeningShape::Transact};
9968 const auto snapshot = placement_.find(facts.target.incarnation);
9969 if (snapshot == placement_.end()) {
9970 // R5: a kernel-originated liquidation carries no source placement row.
9971 // Its fill price is still TradingView's: the fire price it was rested
9972 // at, on the chart tick ladder, plus the EXIT side's own market
9973 // slippage (ab9714be pine_fills.cpp:1712-1726). A short's checkpoint
9974 // marked on the ladder, so its fire price is rounded there first.
9975 if (facts.definition
9976 && facts.definition->origin == native_order::RequestOrigin::KernelLiquidation) {
9977 double fire = facts.trigger_level ? *facts.trigger_level : facts.raw_price;
9978 if (facts.is_buy) fire = nearest_tick(fire, staged_.syminfo.mintick);
9979 if (finite_positive(fire))
9980 result.resolved_price = source_margin_fill_price(fire, facts.is_buy);
9981 }
9982 return result;
9983 }
9984 const auto& source = snapshot->second;
9985 // The already-armed explicit-zero trail's sibling generic stop (exit():
9986 // "A sibling generic stop preserves the next-open print decision") is the
9987 // same owner TRAIL leg (ab9714be engine_path_resolve.cpp:620-705,
9988 // try_exit_open_gap_fill), so it settles on the zero-offset trail policy.
9989 const bool zero_trail_sibling_stop = source.family == PineOrderFamily::ExitStop
9990 && !price_present(source.exit_levels.stop)
9991 && finite_positive(source.trail_activation_level);
9992 const bool explicit_zero_trail =
9993 (source.family == PineOrderFamily::ExitTrail || zero_trail_sibling_stop)
9994 && std::isfinite(source.exit_levels.trail_offset)
9995 && std::floor(source.exit_levels.trail_offset) == 0.0;
9996 const auto host_state = require_host().native_state();
9997 const bool sampled_one_price_gap = host_state.spec
9998 && !host_state.spec->intrabar.is_none()
9999 && facts.cursor.point.provenance == NativePriceProvenance::ModeledOHLCOpen
10000 && facts.price_kind == native_order::NativeCandidatePriceKind::PointPrice;
10001 const bool trail_limit_one_shot = source.family == PineOrderFamily::ExitTrail
10002 && std::holds_alternative<native_order::Limit>(facts.definition->request.trigger);
10003 const auto* trail_active = std::get_if<native_order::TrailActive>(&facts.trigger_state);
10004 const auto retained_trail_source_price = [&]() -> std::optional<double> {
10005 // ab9714be engine_path_resolve.cpp:494-510: explicit zero trail rides running best and does not offset trigger level
10006 if (!std::isfinite(source.retained_trail_best)
10007 || !std::isfinite(source.exit_levels.trail_offset)
10008 || source.exit_levels.trail_offset < 0.0
10009 || explicit_zero_trail
10010 || !finite_positive(staged_.syminfo.mintick)
10011 || facts.cursor.point.path_phase == NativePathPhase::Open) {
10012 return std::nullopt;
10013 }
10014 double best = source.retained_trail_best;
10015 if (trail_active && std::isfinite(trail_active->best_at_trigger)) {
10016 best = facts.is_buy ? std::min(best, trail_active->best_at_trigger)
10017 : std::max(best, trail_active->best_at_trigger);
10018 }
10019 const double offset = std::floor(source.exit_levels.trail_offset)
10020 * staged_.syminfo.mintick;
10021 const double level = best + (facts.is_buy ? offset : -offset);
10022 const double slipped = level + (facts.is_buy ? 1.0 : -1.0)
10023 * config_.slippage * staged_.syminfo.mintick;
10024 return directional_tick(slipped, staged_.syminfo.mintick, facts.is_buy);
10025 }();
10026 const auto observed_tick_trail_price = [&]() -> std::optional<double> {
10028 || facts.cursor.point.provenance != NativePriceProvenance::ObservedPrint) {
10029 return std::nullopt;
10030 }
10031 return facts.default_resolved_price;
10032 }();
10033 const bool placement_reached_trail_activation =
10034 std::isfinite(source.sizing.price) && std::isfinite(source.trail_activation_level)
10035 && (facts.is_buy ? source.sizing.price <= source.trail_activation_level
10036 : source.sizing.price >= source.trail_activation_level);
10037 const bool zero_trail_first_activation = explicit_zero_trail && trail_active
10038 && !placement_reached_trail_activation
10039 && std::isfinite(source.trail_activation_level)
10040 && ((facts.is_buy
10041 && trail_active->best_at_trigger <= source.trail_activation_level
10042 + staged_.syminfo.mintick * 1e-6)
10043 || (!facts.is_buy
10044 && trail_active->best_at_trigger >= source.trail_activation_level
10045 - staged_.syminfo.mintick * 1e-6));
10046 const auto zero_trail_source_price = [&]() -> std::optional<double> {
10047 if (!explicit_zero_trail || sampled_one_price_gap || !facts.trigger_level
10048 || !policy_script_bar_valid_ || !std::isfinite(source.trail_activation_level)) {
10049 return std::nullopt;
10050 }
10051 const double open = policy_script_bar_.open;
10052 const double placement = source.sizing.price;
10053 const double activation = source.trail_activation_level;
10054 const bool reached_at_placement = facts.is_buy
10055 ? placement <= activation : placement >= activation;
10056 const bool open_beyond = facts.is_buy
10057 ? open <= activation : open >= activation;
10058 const bool high_first = source_path_uses_high_first(policy_script_bar_);
10059 const bool adverse_first = facts.is_buy ? high_first : !high_first;
10060 const bool same_open = std::isfinite(placement)
10061 && std::abs(open - placement) <= staged_.syminfo.mintick * 0.5;
10062 if (reached_at_placement && same_open)
10063 return nearest_tick(open, staged_.syminfo.mintick);
10064 if (!reached_at_placement && !open_beyond)
10065 return nearest_tick(activation, staged_.syminfo.mintick);
10066 if (!reached_at_placement && open_beyond && adverse_first)
10067 return directional_tick(open, staged_.syminfo.mintick, facts.is_buy);
10068 return std::nullopt;
10069 };
10070 const auto zero_trail_policy_price = [&]() -> std::optional<double> {
10071 if (!explicit_zero_trail || !policy_script_bar_valid_
10072 || !std::isfinite(source.trail_activation_level)
10073 || !finite_positive(staged_.syminfo.mintick)) {
10074 return std::nullopt;
10075 }
10076 const double tick = staged_.syminfo.mintick;
10077 const bool long_side = !facts.is_buy;
10078 const double open = policy_script_bar_.open;
10079 const double activation = source.trail_activation_level;
10080 const double placement = source.sizing.price;
10081 const auto print = [&](double value) {
10082 return std::floor(value / tick + 0.5) * tick;
10083 };
10084 const auto level = [&](double value) {
10085 return directional_tick(value, tick, facts.is_buy);
10086 };
10087 const bool placement_armed = std::isfinite(placement)
10088 && (long_side ? nearest_tick(placement, tick) >= activation
10089 : nearest_tick(placement, tick) <= activation);
10090 const bool open_reaches = long_side
10091 ? open >= activation : open <= activation;
10092 const bool open_favorable = std::isfinite(placement)
10093 && (long_side ? open > placement : open < placement);
10094 const auto preopen = trail_state_at_open_.find(facts.target.incarnation);
10095 if (preopen != trail_state_at_open_.end() && preopen->second.activated
10096 && std::isfinite(preopen->second.current_level)) {
10097 const bool adverse_gap = long_side
10098 ? open <= preopen->second.current_level
10099 : open >= preopen->second.current_level;
10100 if (adverse_gap)
10101 return directional_tick(open, tick, facts.is_buy);
10102 }
10103 const bool high_first = source_path_high_first(
10104 policy_script_bar_, host_state.spec
10105 ? host_state.spec->path_order : NativePathOrder::Auto);
10106 const bool favorable_first = long_side ? high_first : !high_first;
10107 const double open_print = source_bar_fill_tick(open, tick);
10108 const double carried_best = preopen != trail_state_at_open_.end()
10109 && preopen->second.activated
10110 && std::isfinite(preopen->second.best_price)
10111 ? preopen->second.best_price : placement;
10112 const bool carried_armed = (preopen != trail_state_at_open_.end()
10113 && preopen->second.activated) || placement_armed;
10114 if (carried_armed && std::isfinite(carried_best)) {
10115 const bool adverse_gap = long_side ? open <= carried_best
10116 : open >= carried_best;
10117 if (adverse_gap) return open_print;
10118 }
10119 const bool zero_offset_open_arms = carried_armed
10120 ? (long_side ? open > carried_best : open < carried_best)
10121 : open_reaches;
10122 const bool print_at_open_level = zero_offset_open_arms
10123 && (long_side ? open_print <= open : open_print >= open);
10124 if (print_at_open_level)
10125 return directional_tick(open, tick, facts.is_buy);
10126 if (zero_offset_open_arms) {
10127 if (!favorable_first) return level(open);
10128 return level(long_side ? policy_script_bar_.high
10129 : policy_script_bar_.low);
10130 }
10131 bool armed = placement_armed;
10132 bool armed_from_open = false;
10133 double best = placement;
10134 if (!std::isfinite(best)) best = open;
10135 if (!armed && open_reaches && open_favorable) {
10136 armed = true;
10137 armed_from_open = true;
10138 best = open;
10139 }
10140 if (armed) {
10141 if (!armed_from_open && long_side && open <= best) return print(open);
10142 if (!armed_from_open && !long_side && open >= best) return print(open);
10143 if (armed_from_open || open_favorable) {
10144 best = open;
10145 if (print(open) == level(open)) return print(open);
10146 }
10147 }
10148 double path[4];
10149 path[0] = open;
10150 if (high_first) {
10151 path[1] = policy_script_bar_.high;
10152 path[2] = policy_script_bar_.low;
10153 } else {
10154 path[1] = policy_script_bar_.low;
10155 path[2] = policy_script_bar_.high;
10156 }
10157 path[3] = policy_script_bar_.close;
10158 for (int i = 1; i < 4; ++i) {
10159 const double from = path[i - 1];
10160 const double to = path[i];
10161 if (!armed) {
10162 const bool reached = long_side
10163 ? (to >= activation && to > from)
10164 : (to <= activation && to < from);
10165 if (reached) return level(activation);
10166 continue;
10167 }
10168 const bool favorable = long_side ? to > best : to < best;
10169 if (favorable) {
10170 best = to;
10171 continue;
10172 }
10173 const double stop = level(best);
10174 const bool crossed = long_side
10175 ? (to <= stop && from > stop)
10176 : (to >= stop && from < stop);
10177 if (crossed) return stop;
10178 }
10179 if (trail_active && std::isfinite(trail_active->best_at_trigger))
10180 return level(trail_active->best_at_trigger);
10181 return std::nullopt;
10182 };
10183 const auto& trigger = facts.definition->request.trigger;
10184 const bool limit_fill = std::holds_alternative<native_order::Limit>(trigger)
10185 || std::holds_alternative<native_order::StopLimit>(trigger);
10186 const auto owner_tick_fill = [&](double price) {
10187 // ab9714be engine.hpp:1207-1210 + 1264-1266: a fill at a raw bar or
10188 // waypoint print books floor(p / mintick + 0.5) * mintick. Keep that
10189 // binary64 form unless it lies past the immutable limit the generic
10190 // kernel checks (native_execution_consumer.cpp:2862-2875); there the
10191 // decimal grid form, since source_bar_fill_tick returns an on-grid
10192 // n * mintick unchanged.
10193 const double owner_form = nearest_tick(price, staged_.syminfo.mintick);
10194 std::optional<double> level;
10195 if (const auto* limit = std::get_if<native_order::Limit>(&trigger)) {
10196 level = limit->price;
10197 } else if (const auto* stop_limit = std::get_if<native_order::StopLimit>(&trigger)) {
10198 level = stop_limit->limit;
10199 }
10200 const bool within = !level
10201 || (facts.is_buy ? owner_form <= *level : owner_form >= *level);
10202 return within ? owner_form : source_decimal_tick(price, staged_.syminfo.mintick);
10203 };
10204 const auto source_forced_fill = [&](double forced) {
10205 return nearest_tick(forced, staged_.syminfo.mintick) == forced
10206 ? owner_tick_fill(forced)
10207 : source_bar_fill_tick(forced, staged_.syminfo.mintick);
10208 };
10209 const bool non_open = facts.cursor.point.path_phase != NativePathPhase::Open;
10210 const bool source_gap_point = !non_open
10211 || facts.cursor.point.provenance == NativePriceProvenance::ObservedPrint;
10212 const auto source_stop_fill = [&]() {
10213 const double source_level = source.family == PineOrderFamily::ExitTrail
10214 ? source.exit_levels.trail_price : source.exit_levels.stop;
10215 const double level = finite_positive(source_level) ? source_level
10216 : (facts.trigger_level ? *facts.trigger_level : facts.default_resolved_price);
10217 const double slipped = level + (facts.is_buy ? 1.0 : -1.0)
10218 * config_.slippage * staged_.syminfo.mintick;
10219 return directional_tick(slipped, staged_.syminfo.mintick, facts.is_buy);
10220 };
10221 const auto source_bar_fill = [&]() {
10222 // ab9714be engine.hpp:1160-1180 and pine_policy_members.cpp:45-53:
10223 // a raw bar print is rounded half-up first; slippage then rides on
10224 // that grid price and the directional projection is an identity.
10225 const double rounded = source_bar_fill_tick(
10226 facts.raw_price, staged_.syminfo.mintick);
10227 const double slipped = rounded + (facts.is_buy ? 1.0 : -1.0)
10228 * config_.slippage * staged_.syminfo.mintick;
10229 return directional_tick(slipped, staged_.syminfo.mintick, facts.is_buy);
10230 };
10231 const auto source_stop_resolved = [&]() {
10232 // ab9714be pine_fills.cpp:8002-8018: a realtime print gaps to the
10233 // observed price; an opening print already through the stop books
10234 // the open; otherwise the fill is the stop level (not the open, and
10235 // not a same-pass close's fill price).
10236 if (facts.cursor.point.provenance == NativePriceProvenance::ObservedPrint)
10237 return source_bar_fill();
10238 const double source_level = source.family == PineOrderFamily::ExitTrail
10239 ? source.exit_levels.trail_price : source.exit_levels.stop;
10240 const double level = finite_positive(source_level) ? source_level
10241 : (facts.trigger_level ? *facts.trigger_level : facts.default_resolved_price);
10242 // ab9714be pine_scheduler.cpp:911-916 keeps current_bar_.open as the
10243 // script-bar open while magnifier samples only update H/L/C, so
10244 // try_exit_open_gap_fill (engine_path_resolve.cpp:905-927) tests that
10245 // script open. Synthesized/distribution samples arrive as one-price
10246 // opens; a later sample through the stop is a path cross at the stop
10247 // level, not a fresh gap at the sample quote.
10248 double open_px = facts.raw_price;
10249 if (policy_script_bar_valid_
10250 && facts.cursor.point.path_phase == NativePathPhase::Open
10251 && facts.cursor.point.provenance == NativePriceProvenance::ModeledOHLCOpen
10252 && host_state.spec) {
10253 const auto* synthesized = host_state.spec->intrabar.synthesized_path();
10254 const auto* lower = host_state.spec->intrabar.lower();
10255 const bool one_price = synthesized != nullptr
10256 || (lower && lower->sample_eligibility
10258 if (one_price) open_px = policy_script_bar_.open;
10259 }
10260 // ab9714be pine_fills.cpp:4136-4240 + 7795-7800: a stop armed with its
10261 // pending MARKET parent and already breached at the parent's fill
10262 // open scratches at bar_fill_price(open). The leg becomes live only
10263 // after that opening fill, so the driver first presents it on the
10264 // next path segment, crossed at the segment's start: the script-bar
10265 // open itself. That is the same opening gap, not a path cross.
10266 const bool armed_after_open_fill = non_open
10268 && policy_script_bar_valid_
10269 && !config_.process_orders_on_close && !config_.calc_on_order_fills
10270 && host_state.spec && host_state.spec->intrabar.is_none()
10271 && facts.cursor.point.provenance == NativePriceProvenance::Confirmed
10272 && facts.raw_price == policy_script_bar_.open;
10273 if (armed_after_open_fill) open_px = policy_script_bar_.open;
10274 const bool open_gapped = (!non_open || armed_after_open_fill)
10275 && std::isfinite(level)
10276 && (facts.is_buy ? open_px >= level : open_px <= level);
10277 if (!open_gapped) return source_stop_fill();
10278 // ab9714be try_exit_open_gap_fill books bar.open (the script-bar
10279 // open), even when the matching sample is a later one-price tick.
10280 const double rounded = source_bar_fill_tick(
10281 open_px, staged_.syminfo.mintick);
10282 const double slipped = rounded + (facts.is_buy ? 1.0 : -1.0)
10283 * config_.slippage * staged_.syminfo.mintick;
10284 return directional_tick(slipped, staged_.syminfo.mintick, facts.is_buy);
10285 };
10286 const auto source_limit_fill = [&]() {
10287 // ab9714be pine_fills.cpp:7733-8072 + pine_policy_members.cpp:55-58:
10288 // an open gap receives the raw open with nearest-tick rounding;
10289 // otherwise a LIMIT receives its level, snapped limit-or-better and
10290 // never slipped.
10291 if (source.family == PineOrderFamily::ExitTrail && facts.trigger_level
10292 && non_open) {
10293 const double ticked = directional_tick(*facts.trigger_level, staged_.syminfo.mintick,
10294 !facts.is_buy);
10295 // ab9714be pine_fills.cpp:7970-7988: limit fill is unslipped limit-or-better clamped to trigger to preserve limit invariant
10296 return facts.is_buy ? std::min(ticked, *facts.trigger_level)
10297 : std::max(ticked, *facts.trigger_level);
10298 }
10299 const bool deferred_open_gap = source.defer_until_post_parent_calculation
10300 && facts.cursor.point.provenance == NativePriceProvenance::Confirmed;
10301 // ab9714be pine_fills.cpp:4136-4240 + 7795-7800: the limit leg of a
10302 // bracket armed with its pending MARKET parent and already marketable
10303 // at the parent's fill open scratches at bar_fill_price(open), exactly
10304 // like the stop leg (source_stop_resolved). The driver first presents
10305 // it on the next path segment, crossed at that segment's start: the
10306 // script-bar open itself.
10307 const bool limit_armed_after_open_fill = non_open
10309 && policy_script_bar_valid_
10310 && !config_.process_orders_on_close && !config_.calc_on_order_fills
10311 && host_state.spec && host_state.spec->intrabar.is_none()
10312 && facts.cursor.point.provenance == NativePriceProvenance::Confirmed
10313 && facts.raw_price == policy_script_bar_.open;
10314 if (!non_open || deferred_open_gap || limit_armed_after_open_fill) {
10315 const bool raw_oca_reduce = source.family == PineOrderFamily::Order
10316 && source.oca_type == 2;
10317 if (raw_oca_reduce) {
10318 return nearest_tick(facts.raw_price, staged_.syminfo.mintick);
10319 }
10320 // ab9714be pine_fills.cpp:7943-7945: an exit gapped at the open
10321 // books bar_fill_price(open).
10322 if (source.family == PineOrderFamily::ExitLimit) {
10323 return owner_tick_fill(facts.raw_price);
10324 }
10325 return source_bar_fill_tick(facts.raw_price, staged_.syminfo.mintick);
10326 }
10327 // ab9714be engine_path_resolve.cpp:365-367: stop-limit fills at unslipped stop activation price when marketable against limit
10328 if (std::holds_alternative<native_order::StopLimit>(trigger)) {
10329 return directional_tick(facts.raw_price, staged_.syminfo.mintick,
10330 !facts.is_buy);
10331 }
10332 const double level = finite_positive(source.exit_levels.limit)
10333 ? source.exit_levels.limit
10334 : (facts.trigger_level ? *facts.trigger_level : facts.raw_price);
10335 // Preserve a source level that is on the chart grid (including a
10336 // computed level only ULPs away from it). `directional_tick` can
10337 // return a value one binary64 ULP on the wrong side of that level
10338 // (2409.49 -> 2409.4900000000002 for a buy limit), which the generic
10339 // kernel then correctly rejects against its immutable limit.
10340 const double projected = directional_tick(
10341 level, staged_.syminfo.mintick, !facts.is_buy);
10342 const double level_on_grid = source_level_on_price_grid(
10343 level, staged_.syminfo.mintick);
10344 const bool grid_level = source_bar_fill_tick(
10345 level_on_grid, staged_.syminfo.mintick) == level_on_grid;
10346 const bool wrong_side = facts.is_buy
10347 ? projected > level
10348 : projected < level;
10349 // ab9714be pine_policy_members.cpp:53-58 books that projection as is
10350 // (a buy limit at 1.18054 fills 1.1805400000000001). Substitute the
10351 // grid level only where the native limit itself refuses it; a
10352 // half-tick-shifted exit threshold admits the owner's spelling.
10353 const auto* native_limit = std::get_if<native_order::Limit>(&trigger);
10354 const bool native_admits = native_limit
10355 && (facts.is_buy ? projected <= native_limit->price
10356 : projected >= native_limit->price);
10357 if (grid_level && wrong_side && !native_admits) {
10358 return level_on_grid;
10359 }
10360 return projected;
10361 };
10362 const auto source_trail_one_shot_fill = [&]() {
10363 // ab9714be pine_fills.cpp:7936-7958: an omitted-offset trail is a
10364 // stop-style activation print. When the next bar opens through the
10365 // raw activation, Pine projects that print directionally; it is not a
10366 // nearest-tick LIMIT gap. Explicit offsets and non-open crossings
10367 // keep the L6b limit-or-better projection.
10368 const double tick = staged_.syminfo.mintick;
10369 if (!std::isfinite(source.exit_levels.trail_offset)
10370 && finite_positive(tick) && finite_positive(source.trail_activation_level)) {
10371 // The native arm threshold sits half a tick inside the source
10372 // one-shot level (exit()). ab9714be engine_path_resolve.cpp:
10373 // 667-705 gap-fills only a RAW open at or past the activation;
10374 // an open short of it is reached on the tick path and books the
10375 // activation itself (engine_path_resolve.cpp:980-987).
10376 const double slipped = source.trail_activation_level
10377 + (facts.is_buy ? 1.0 : -1.0) * config_.slippage * tick;
10378 double level = directional_tick(slipped, tick, facts.is_buy);
10379 if (!facts.is_buy) level = source_level_on_price_grid(level, tick);
10380 const bool open_through = facts.is_buy
10381 ? facts.raw_price <= level : facts.raw_price >= level;
10382 if (facts.cursor.point.path_phase == NativePathPhase::Open && open_through) {
10383 return directional_tick(
10384 facts.default_resolved_price, tick, facts.is_buy);
10385 }
10386 const double ticked = directional_tick(level, tick, !facts.is_buy);
10387 return facts.is_buy ? std::min(ticked, level) : std::max(ticked, level);
10388 }
10389 if (!std::isfinite(source.exit_levels.trail_offset)
10390 && facts.cursor.point.path_phase == NativePathPhase::Open) {
10391 return directional_tick(
10392 facts.default_resolved_price, staged_.syminfo.mintick,
10393 facts.is_buy);
10394 }
10395 return source_limit_fill();
10396 };
10397 // Explicit native intents already carry their canonical trigger/fill
10398 // price. Limits retain their immutable generic value. The generic consumer
10399 // has already applied the one market slippage step; source projection only
10400 // rounds that resulting quote to the ordinary chart tick.
10401 // A core-sized opening is a host-resolved shape too: the core owns the
10402 // QUANTITY, the source still owns the fill price and every admission
10403 // policy below. Only an intent that also carries its own price -- a
10404 // literal transaction, a reversal target -- takes the explicit path.
10405 const bool core_sized = std::holds_alternative<native_order::Sized>(
10406 facts.definition->request.intent);
10407 if (!core_sized
10408 && !std::holds_alternative<native_order::HostSized>(facts.definition->request.intent)) {
10409 if (std::holds_alternative<native_order::Market>(trigger)) {
10410 result.resolved_price = source_bar_fill();
10411 } else if (limit_fill) {
10412 result.resolved_price = source_limit_fill();
10413 // ab9714be pine_fills.cpp:8004-8020: Order-family stop triggers resolve fill price via source stop resolution
10414 } else if ((source.family == PineOrderFamily::Entry
10415 || source.family == PineOrderFamily::Order)
10416 && std::holds_alternative<native_order::Stop>(trigger)
10417 && facts.trigger_level) {
10418 // pine_stream.cpp:278-303 at ab9714be presents each realtime trade
10419 // as a one-price broker point. A stop crossed by that print gaps
10420 // to the observed price; it is not interpolated back to its level.
10421 result.resolved_price = source_stop_resolved();
10422 }
10423 if (finite_positive(source.forced_execution_price)) {
10424 // ab9714be pine_fills.cpp:1712-1726: margin slices use the exact fire
10425 // price (W34a); every other forced fill goes through the chained-fill
10426 // tick-grid helper (W35a).
10427 result.resolved_price = source.family == PineOrderFamily::Margin
10428 ? source.forced_execution_price
10429 : source_forced_fill(source.forced_execution_price);
10430 }
10431 if (source.family == PineOrderFamily::ExitLimit && facts.trigger_level
10432 && facts.cursor.point.path_phase != NativePathPhase::Open) {
10433 result.resolved_price = directional_tick(
10434 result.resolved_price, staged_.syminfo.mintick, !facts.is_buy);
10435 const double constraint = finite_positive(source.exit_levels.limit)
10436 ? source.exit_levels.limit : *facts.trigger_level;
10437 result.resolved_price = facts.is_buy
10438 ? std::min(result.resolved_price, constraint)
10439 : std::max(result.resolved_price, constraint);
10440 } else if ((source.family == PineOrderFamily::ExitStop
10442 && facts.trigger_level
10443 && facts.cursor.point.path_phase != NativePathPhase::Open) {
10444 result.resolved_price = directional_tick(
10445 result.resolved_price, staged_.syminfo.mintick, facts.is_buy);
10446 }
10447 return result;
10448 }
10449 const bool live_direction_gate = source.family == PineOrderFamily::Entry
10450 && ((risk_.direction > 0 && !source.is_long)
10451 || (risk_.direction < 0 && source.is_long));
10452 if (live_direction_gate) {
10453 const bool opposite = facts.position.signed_units != 0.0
10454 && ((facts.position.signed_units > 0.0) != source.is_long);
10455 if (opposite) {
10456 result.units = facts.opposite_book_units;
10457 result.shape = native_order::OpeningShape::CloseOpposite;
10458 } else {
10459 // `allow_entry_in` rejects a flat/same-side forbidden instruction;
10460 // unlike an opposite fill it never manufactures a close at the
10461 // command boundary.
10462 result.units = 0.0;
10463 }
10464 return result;
10465 }
10466 const bool market_like = std::holds_alternative<native_order::Market>(trigger);
10467 // NativeRunSpec carries the generic slippage ticks, so its candidate
10468 // default is already the one-slippage source fill. The adapter only owns
10469 // the frozen source sizing basis; applying it again here would double-slip
10470 // a market order after the on-tick calculation.
10471 if (market_like) {
10472 result.resolved_price = source_bar_fill();
10473 } else if (limit_fill) {
10474 result.resolved_price = source_limit_fill();
10475 }
10476 if (finite_positive(source.forced_execution_price)) {
10477 result.resolved_price = source_forced_fill(source.forced_execution_price);
10478 }
10479 // Source stop/trail exits crossed inside a modeled path settle at their
10480 // armed level, whereas an open gap retains the presented open quote. The
10481 // generic driver deliberately exposes both facts; selecting this source
10482 // policy here preserves the non-gap relative-parent lifecycle.
10483 if ((source.family == PineOrderFamily::ExitLimit
10486 && facts.trigger_level
10487 && facts.price_kind == native_order::NativeCandidatePriceKind::TriggerLevel) {
10489 && facts.cursor.point.path_phase == NativePathPhase::Open) {
10490 result.resolved_price = source_bar_fill();
10491 } else if (observed_tick_trail_price) {
10492 result.resolved_price = *observed_tick_trail_price;
10493 } else if (retained_trail_source_price) {
10494 result.resolved_price = *retained_trail_source_price;
10495 } else if (const auto source_price = zero_trail_policy_price()) {
10496 result.resolved_price = *source_price;
10497 } else if (const auto source_price = zero_trail_source_price()) {
10498 result.resolved_price = *source_price;
10499 } else if (trail_limit_one_shot) {
10500 result.resolved_price = source_trail_one_shot_fill();
10501 } else if (explicit_zero_trail) {
10502 if (zero_trail_first_activation) {
10503 result.resolved_price = directional_tick(
10504 source.trail_activation_level, staged_.syminfo.mintick, facts.is_buy);
10505 } else {
10506 const double best = trail_active
10507 ? trail_active->best_at_trigger : facts.default_resolved_price;
10508 result.resolved_price = directional_tick(
10509 best, staged_.syminfo.mintick, facts.is_buy);
10510 }
10511 } else if ((source.family == PineOrderFamily::ExitStop || source.family == PineOrderFamily::Order)
10512 && facts.cursor.point.path_phase == NativePathPhase::Open) {
10513 result.resolved_price = source_bar_fill();
10514 } else if (source.family == PineOrderFamily::ExitLimit
10515 || (source.family == PineOrderFamily::Order && finite_positive(source.exit_levels.limit))) {
10516 result.resolved_price = source_limit_fill();
10517 } else if (source.family == PineOrderFamily::ExitTrail) {
10518 result.resolved_price = directional_tick(
10519 facts.default_resolved_price, staged_.syminfo.mintick, facts.is_buy);
10520 } else if ((source.family == PineOrderFamily::ExitStop || source.family == PineOrderFamily::Order)
10521 && std::isfinite(source.exit_levels.stop)) {
10522 result.resolved_price = directional_tick(
10523 source.exit_levels.stop, staged_.syminfo.mintick, facts.is_buy);
10524 } else {
10525 result.resolved_price = directional_tick(
10526 *facts.trigger_level, staged_.syminfo.mintick, facts.is_buy);
10527 }
10528 } else if (observed_tick_trail_price) {
10529 result.resolved_price = *observed_tick_trail_price;
10530 } else if (retained_trail_source_price) {
10531 result.resolved_price = *retained_trail_source_price;
10532 } else if (const auto source_price = zero_trail_policy_price()) {
10533 result.resolved_price = *source_price;
10534 } else if (const auto source_price = zero_trail_source_price()) {
10535 result.resolved_price = *source_price;
10536 } else if (trail_limit_one_shot && facts.trigger_level && !sampled_one_price_gap) {
10537 result.resolved_price = source_trail_one_shot_fill();
10538 } else if (explicit_zero_trail && facts.trigger_level && !sampled_one_price_gap) {
10539 // Native's positive sentinel offset keeps the generic trail alive;
10540 // source settlement prints the carried raw best on the directional
10541 // chart grid. A first activation is the source activation level.
10542 result.resolved_price = zero_trail_first_activation
10543 ? directional_tick(source.trail_activation_level, staged_.syminfo.mintick, facts.is_buy)
10544 : directional_tick(trail_active ? trail_active->best_at_trigger
10545 : facts.default_resolved_price,
10546 staged_.syminfo.mintick, facts.is_buy);
10547 } else if (source.family == PineOrderFamily::ExitStop && facts.trigger_level
10548 && facts.price_kind == native_order::NativeCandidatePriceKind::PointPrice
10549 && (facts.cursor.point.path_phase == NativePathPhase::Open
10550 || (source.defer_until_post_parent_calculation
10551 && facts.cursor.point.provenance
10552 == NativePriceProvenance::Confirmed))) {
10553 // A resting stop crossed by an adverse opening gap books the raw
10554 // opening print, then applies the ordinary nearest chart-tick print
10555 // projection (distinct from a non-gap trigger-level fill).
10556 result.resolved_price = nearest_tick(
10557 facts.default_resolved_price, staged_.syminfo.mintick);
10558 }
10559 if ((source.family == PineOrderFamily::ExitLimit
10560 || (source.family == PineOrderFamily::Order && finite_positive(source.exit_levels.limit)))
10561 && facts.trigger_level && facts.cursor.point.path_phase != NativePathPhase::Open) {
10562 // ab9714be:pine_policy_members.cpp:53-58. A computed LIMIT close is
10563 // limit-or-better: buys floor and sells ceil to the price grid. The
10564 // generic trigger level remains raw for reachability; only the booked
10565 // source fill receives this directional limit snap.
10566 if (finite_positive(source.exit_levels.limit)
10567 && !finite_positive(source.forced_execution_price)) {
10568 result.resolved_price = source_limit_fill();
10569 } else {
10570 result.resolved_price = directional_tick(
10571 result.resolved_price, staged_.syminfo.mintick, !facts.is_buy);
10572 const double constraint = finite_positive(source.exit_levels.limit)
10573 ? source.exit_levels.limit : *facts.trigger_level;
10574 result.resolved_price = facts.is_buy
10575 ? std::min(result.resolved_price, constraint)
10576 : std::max(result.resolved_price, constraint);
10577 }
10578 }
10580 && std::isfinite(source.exit_levels.trail_offset)
10581 && std::floor(source.exit_levels.trail_offset) == 0.0
10582 && policy_script_bar_valid_
10583 && facts.cursor.point.path_phase != NativePathPhase::Open) {
10584 const bool closing_long = facts.position.signed_units > 0.0;
10585 double activation = source.exit_levels.trail_price;
10586 if (!finite_positive(activation)
10587 && std::isfinite(source.exit_levels.trail_points)
10588 && finite_positive(staged_.syminfo.mintick)) {
10589 const double ticks = std::ceil(source.exit_levels.trail_points - 5e-5);
10590 activation = require_host().position_avg_price()
10591 + (closing_long ? 1.0 : -1.0) * ticks * staged_.syminfo.mintick;
10592 activation = directional_tick(
10593 activation, staged_.syminfo.mintick, closing_long);
10594 }
10595 const bool armed_inside_this_bar = closing_long
10596 ? policy_script_bar_.open < activation
10597 : policy_script_bar_.open > activation;
10598 if (finite_positive(activation) && armed_inside_this_bar) {
10599 // ab9714be:test_fills_edge.cpp:827-858 and the zero-offset trail
10600 // owner: an activation first reached inside a path is a one-shot
10601 // fill at that activation. A favourable opening gap keeps the
10602 // generic trail ride and is deliberately excluded here.
10603 result.resolved_price = directional_tick(
10604 activation, staged_.syminfo.mintick, facts.is_buy);
10605 }
10606 }
10607 if ((std::holds_alternative<native_order::Stop>(trigger)
10608 || std::holds_alternative<native_order::Trail>(trigger)
10609 || source.family == PineOrderFamily::Margin)
10610 && facts.trigger_level && !explicit_zero_trail && !trail_limit_one_shot
10611 && !(source.defer_until_post_parent_calculation
10612 && facts.cursor.point.provenance == NativePriceProvenance::Confirmed)
10613 && !retained_trail_source_price && !observed_tick_trail_price) {
10614 result.resolved_price = std::holds_alternative<native_order::Stop>(trigger)
10615 ? source_stop_resolved()
10616 : (source_gap_point ? source_bar_fill() : source_stop_fill());
10617 }
10618 if (finite_positive(source.forced_execution_price)) {
10619 result.resolved_price = source_forced_fill(source.forced_execution_price);
10620 } else if (const auto* touch = std::get_if<native_order::Limit>(&trigger);
10621 touch && touch->fill_through && trail_limit_one_shot
10622 && std::isfinite(result.resolved_price)) {
10623 // ab9714be pine_policy_members.cpp:45-53 (apply_slippage): the
10624 // zero-offset one-shot trail's source print moves slippage ticks
10625 // against the exit and is re-snapped directionally
10626 // (round_to_mintick_directional), exactly as the owner's TRAIL fill.
10627 const double tick = staged_.syminfo.mintick;
10628 result.resolved_price = directional_tick(
10629 result.resolved_price + (facts.is_buy ? 1.0 : -1.0) * config_.slippage * tick,
10630 tick, facts.is_buy);
10631 }
10634 || source.family == PineOrderFamily::Margin) {
10635 const bool explicit_source_exit = finite_positive(source.requested_qty)
10638 || source.family == PineOrderFamily::ExitTrail);
10639 if (explicit_source_exit
10640 || (source.family == PineOrderFamily::Close
10641 && source.close_batch_calls != 0)) {
10642 result.grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
10643 }
10644 if ((source.family == PineOrderFamily::ExitLimit
10647 && !(facts.scope_exposure_units > 0.0)) {
10648 result.units = 0.0;
10649 return result;
10650 }
10651 // ab9714be pine_fills.cpp:1618: whole-position coverage is spelled
10652 // `qty - kQtyEpsilon`, and pine_fills.cpp:1717-1723 then routes that
10653 // covered residual through the WHOLE-position exit rather than a
10654 // sized reduction. A reservation subtracted from the live basis in
10655 // binary64 can overshoot the exposure that actually remains at fill
10656 // time by a few ULPs; without this snap the leg is over-sized
10657 // against its scope, never fills, and strands a sub-lot remainder
10658 // that the 1x-margin path later fragments.
10659 const auto cover_full_scope = [&](double units) {
10660 if (facts.scope_exposure_units > 0.0
10661 && units >= facts.scope_exposure_units - internal::kQtyEpsilon) {
10662 // The covered residual IS the literal selected exposure, which
10663 // an earlier percentage close can have left a few binary64
10664 // steps off the run's quantity grid. The owner books such an
10665 // exit through the whole-position path without re-quantizing
10666 // it, so authenticate the literal units for the pure reduction
10667 // instead of letting the grid reject the leg and strand the
10668 // dust (ab9714be pine_fills.cpp:1717-1723).
10669 if (const auto* host_close = std::get_if<native_order::HostSized>(
10670 &facts.definition->request.intent);
10671 host_close
10672 && host_close->kind == native_order::HostSizedKind::Close) {
10673 result.grid_policy =
10674 native_order::ExecutionGridPolicy::ExplicitUnits;
10675 }
10676 return facts.scope_exposure_units;
10677 }
10678 return units;
10679 };
10680 const bool has_projected_remaining =
10681 (source.from_entry.empty() || source.fixed_exit_reservation)
10682 && std::isfinite(source.projection_remaining_qty);
10683 if (has_projected_remaining) {
10684 // ab9714be pine_fills.cpp:1708-1723: an exit reduction executes its
10685 // exact reservation without re-quantizing against the quantity grid,
10686 // so binary64 rounding of available = live_basis - reserved cannot
10687 // cause the native matcher to reject the leg as off-grid.
10688 if (const auto* host_close = std::get_if<native_order::HostSized>(
10689 &facts.definition->request.intent);
10690 host_close && host_close->kind == native_order::HostSizedKind::Close) {
10691 result.grid_policy =
10692 native_order::ExecutionGridPolicy::ExplicitUnits;
10693 }
10694 result.units = cover_full_scope(
10695 std::max(0.0, source.projection_remaining_qty));
10696 if ((source.family == PineOrderFamily::ExitLimit
10699 && source.qty_percent >= 100.0 - 1e-9
10700 && facts.scope_exposure_units > 0.0
10701 && (!source.from_entry.empty()
10702 || source.pooc_global_full_exit_dynamic_qty)) {
10703 // An adopted anchored leg is the same book close, armed.
10704 const auto& owner = facts.definition->request.owner;
10705 const auto* armed = std::get_if<native_order::WaitForApplied>(&owner);
10706 const bool book_owner = std::holds_alternative<native_order::Independent>(owner)
10707 || (armed && armed->scope == native_order::NativeArmScope::Book);
10708 const bool whole_scope_owner = !book_owner || source.from_entry.empty();
10709 if (whole_scope_owner) result.units = std::isfinite(source.requested_qty)
10710 ? std::min(*result.units, facts.scope_exposure_units)
10711 : cover_full_scope(facts.scope_exposure_units);
10712 }
10713 return result;
10714 }
10715 // A source full close is an all-live-cohort operation, not a stale
10716 // placement-sized reduction. The generic selected scope is the
10717 // authoritative physical sum at this candidate. Reusing the source
10718 // snapshot can differ by one binary64 rounding step after a previous
10719 // percentage close; that leaves a positive dust lot which the next
10720 // percent entry cannot represent alongside its new units. The fixed
10721 // quantity source transaction retains its captured own/transaction
10722 // facts, so it must not use this percent-sizing projection.
10723 // pine_strategy_commands.cpp:2775-2790 leaves a full-percentage exit
10724 // exact so it always flattens its live source scope. Do not send a
10725 // 100% HostSized close through the multiply/divide and quantity-grid
10726 // path: after an earlier margin slice that can floor one extra step
10727 // and leave a dust lot behind.
10728 if (source.deferred_cohort && !source.fixed_exit_reservation
10729 && !source.frozen_market_instruction
10730 && !std::isfinite(source.requested_qty)
10731 && (std::isnan(source.qty_percent) || source.qty_percent >= 100.0)) {
10732 result.units = cover_full_scope(facts.scope_exposure_units);
10733 return result;
10734 }
10735 if (finite_positive(source.requested_qty)) {
10736 result.units = source.requested_qty;
10737 return result;
10738 }
10739 double percent = source.qty_percent;
10740 if (std::isnan(percent)) percent = 100.0;
10741 const bool selected_exit =
10745 && std::holds_alternative<native_order::SelectedExposure>(facts.scope);
10746 result.units = selected_exit && percent == 100.0
10747 ? cover_full_scope(facts.scope_exposure_units)
10748 : cover_full_scope(
10749 quantize_close_units(facts.scope_exposure_units, percent));
10750 return result;
10751 }
10752 if (source.family == PineOrderFamily::Order && std::isfinite(source.requested_qty)) {
10753 result.units = std::max(0.0, source.requested_qty);
10754 const bool opposite = facts.position.signed_units != 0.0
10755 && ((facts.position.signed_units > 0.0) != source.is_long);
10756 if (opposite) {
10757 result.units = std::min(*result.units, facts.opposite_book_units);
10758 result.shape = native_order::OpeningShape::CloseOpposite;
10759 }
10760 return result;
10761 }
10762 if (source.family == PineOrderFamily::Entry && source.terms_priced_reverse) {
10763 const bool opposite_now = facts.position.signed_units != 0.0
10764 && ((facts.position.signed_units > 0.0) != source.is_long);
10765 if (source.affordability_close_only) {
10766 result.units = opposite_now ? facts.opposite_book_units : 0.0;
10767 result.shape = opposite_now ? native_order::OpeningShape::CloseOpposite
10768 : native_order::OpeningShape::Transact;
10769 return result;
10770 }
10771 double own_units = source.requested_qty;
10772 if (source.qty_type == static_cast<int>(QtyType::CASH)) {
10773 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10774 * facts.active_fx;
10775 own_units = finite_positive(denominator)
10776 ? floor_quantity_grid(source.requested_qty / denominator,
10777 staged_.quantity_grid) : 0.0;
10778 } else if (source.qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)) {
10779 // ab9714be pine_orders.cpp:96-191: a typed percentage reversal
10780 // sizes from the hypothetical Flatten's realized balance. The
10781 // old opening fee and this close's fee are thereby realized once,
10782 // before reserving the new percentage opening commission.
10783 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
10784 if (!pine_host) return result;
10785 const auto projection = pine_host->adapter_project_flatten(
10786 result.resolved_price, source.source_id, source.comment,
10787 facts.target.incarnation);
10788 double cash = projection.realized_balance * source.requested_qty / 100.0;
10789 if (config_.commission_type == static_cast<int>(CommissionType::PERCENT)
10790 && config_.commission_value > 0.0) {
10791 cash /= 1.0 + config_.commission_value / 100.0;
10792 }
10793 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10794 * facts.active_fx;
10795 own_units = std::isfinite(cash) && finite_positive(denominator)
10796 ? floor_quantity_grid(cash / denominator, staged_.quantity_grid) : 0.0;
10797 }
10798 result.units = own_units;
10799 const auto created_side = static_cast<PositionSide>(
10800 source.projection_position_side);
10801 if (facts.position.signed_units == 0.0
10802 && finite_positive(source.projection_tv_carry_qty)
10803 && created_side != PositionSide::FLAT
10804 && ((created_side == PositionSide::LONG) != source.is_long)) {
10805 result.units = std::abs(own_units) + source.projection_tv_carry_qty;
10806 result.shape = native_order::OpeningShape::Transact;
10807 return result;
10808 }
10809 if (finite_positive(source.frozen_reversal_transaction)
10810 && source.placement_cycle == current_position_cycle_
10811 && std::abs(facts.position.signed_units - (source.is_long
10812 ? -source.frozen_reversal_transaction : source.frozen_reversal_transaction))
10813 < 1e-12) {
10814 result.units = source.frozen_reversal_transaction;
10815 result.shape = native_order::OpeningShape::CloseOpposite;
10816 } else {
10817 // A co-queued source close can have flattened the placement-time
10818 // opposite side before this priced entry reaches its trigger.
10819 // ab9714be pine_fills.cpp:4618-4664 then executes an ordinary
10820 // opening, not a reversal against an already-consumed book.
10821 result.shape = opposite_now ? native_order::OpeningShape::ReverseTo
10822 : native_order::OpeningShape::Transact;
10823 }
10824 return result;
10825 }
10826 if (source.family == PineOrderFamily::Entry && finite_positive(source.requested_qty)) {
10827 if (source.qty_type == static_cast<int>(QtyType::CASH)) {
10828 result.units = finite_positive(result.resolved_price)
10829 ? source.requested_qty / (result.resolved_price * staged_.syminfo.pointvalue
10830 * facts.active_fx) : 0.0;
10831 } else if (source.qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)) {
10832 const double equity = percent_commission_live_equity(result.resolved_price);
10833 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10834 * facts.active_fx;
10835 double cash = equity * source.requested_qty / 100.0;
10836 if (config_.commission_type == static_cast<int>(CommissionType::PERCENT)
10837 && config_.commission_value > 0.0) {
10838 cash /= 1.0 + config_.commission_value / 100.0;
10839 }
10840 result.units = finite_positive(equity) && finite_positive(denominator)
10841 ? floor_quantity_grid(cash / denominator, staged_.quantity_grid) : 0.0;
10842 } else {
10843 result.units = source.requested_qty;
10844 }
10845 } else if (core_sized) {
10846 // The core resolved cash / (signal price * point value * fx) with the
10847 // fee reserve at acceptance and published the quotient as this
10848 // request's remaining units; only the source lot floor is left.
10849 // default_sizing_intent emits Sized only where that acceptance is
10850 // resolvable, so a missing quotient is a broken invariant (R5 N11).
10851 const auto* published = std::get_if<native_order::RemainingUnits>(&facts.remaining);
10852 if (!published) throw std::logic_error("core-sized quantity without the core's quotient");
10853 result.units = default_sizing_lot_floor(published->q);
10854 } else if (finite_positive(source.sizing.frozen_units) && !source.sizing.at_fill
10855 && (!(source.family == PineOrderFamily::Entry
10856 && finite_positive(source.exit_levels.stop)
10857 && !finite_positive(source.exit_levels.limit))
10858 || (config_.default_qty_type
10859 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
10860 && config_.default_qty_value <= 100.0))) {
10861 result.units = source.sizing.frozen_units;
10862 } else if (config_.default_qty_type == static_cast<int>(QtyType::FIXED)) {
10863 result.units = config_.default_qty_value;
10864 } else {
10865 const double equity = source.sizing.at_fill
10866 ? percent_commission_live_equity(result.resolved_price) : source.sizing.equity;
10867 const double price = source.sizing.at_fill ? result.resolved_price : source.sizing.price;
10868 const double fx = source.sizing.at_fill ? facts.active_fx : source.sizing.fx;
10869 PineSizingSnapshot sizing;
10870 sizing.price = price;
10871 sizing.fx = fx;
10872 sizing.mark = price;
10873 sizing.equity = equity;
10874 // ab9714be pine_policy_members.cpp:282: default sizing units divides equity by price, pointvalue, and currency fx
10875 result.units = default_sizing_units(sizing);
10876 }
10877 const auto created_side = static_cast<PositionSide>(source.projection_position_side);
10878 if (source.family == PineOrderFamily::Entry) {
10879 const bool opposite = facts.position.signed_units != 0.0
10880 && ((facts.position.signed_units > 0.0) != source.is_long);
10881 const bool keep_mc_close_surplus = [&]() {
10882 if (!source.affordability_keep_mc_close_surplus) return false;
10883 bool receipt_origin_live = false;
10884 for (const auto& cohort_id : cohort_order_) {
10885 const auto cohort = cohorts_by_id_.find(cohort_id);
10886 if (cohort == cohorts_by_id_.end()) continue;
10887 const auto units = cohort->second.live_units_by_origin.find(
10888 source.signal_close_mc_entry_incarnation);
10889 if (units != cohort->second.live_units_by_origin.end()
10890 && units->second > 0.0) {
10891 receipt_origin_live = true;
10892 break;
10893 }
10894 }
10895 const auto state = require_host().native_state();
10896 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
10897 const bool magnifier = pine_host
10898 && pine_host->scheduler_.bar_magnifier_enabled();
10899 const double close_surplus = source.projection_tv_carry_qty
10900 - std::abs(facts.position.signed_units);
10901 return source.signal_close_mc_bar == source.projection_created_bar
10902 && source.projection_created_bar
10903 == facts.cursor.point.interval_index - 1
10904 && source.signal_close_mc_entry_incarnation != 0
10905 && last_margin_call_event_ordinal_ == last_applied_ordinal_
10906 && pine_host
10907 && source.signal_close_mc_fill_seq
10908 == pine_host->adapter_broker_fill_event_sequence()
10909 && !config_.process_orders_on_close && !config_.calc_on_order_fills
10910 && !coof_recalc_active_ && !magnifier
10911 && state.phase == NativeRunPhase::Batch
10912 && std::holds_alternative<native_order::Market>(trigger)
10913 && !source.is_long && !std::isfinite(source.requested_qty)
10914 && !source.projection_after_close && !source.birth.from_fill()
10915 && source.projection_position_side
10916 == static_cast<std::int32_t>(PositionSide::LONG)
10917 && source.placement_cycle == current_position_cycle_
10918 && facts.position.signed_units > 0.0
10919 && facts.position.lot_count == 1U && receipt_origin_live
10920 && facts.position.signed_units
10921 == source.signal_close_mc_remaining_qty
10922 && std::isfinite(close_surplus)
10923 && std::abs(close_surplus - 1.0) < 1e-6;
10924 }();
10925 // ab9714be pine_fills.cpp:6577-6598: a default MARKET request carries
10926 // frozen_default_qty into execute_market_entry as a prequantized
10927 // quantity. Only per-call typed percentage requests use the
10928 // hypothetical-Flatten sizing path above.
10929 const bool default_money_candidate = std::holds_alternative<native_order::Market>(trigger)
10930 && !std::isfinite(source.requested_qty)
10931 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
10932 && std::abs(config_.default_qty_value - 100.0) < 1e-12
10933 && std::abs((source.is_long ? config_.margin_long : config_.margin_short) - 100.0)
10934 < 1e-12
10935 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
10936 && result.units && finite_positive(*result.units)
10937 && finite_positive(source.sizing.price)
10938 && finite_positive(source.sizing.equity)
10939 && finite_positive(source.sizing.fx)
10940 && finite_positive(staged_.syminfo.pointvalue);
10941 if (default_money_candidate) {
10942 bool ordinary_book = true;
10943 for (const auto& handle : live_handles_) {
10944 if (handle == facts.target) continue;
10945 const auto peer = placement_.find(handle.incarnation);
10946 if (peer == placement_.end()) continue;
10947 const auto& row = peer->second;
10948 const bool unpriced_close = (row.family == PineOrderFamily::Close
10949 || row.family == PineOrderFamily::CloseAll)
10950 && !finite_positive(row.exit_levels.limit)
10951 && !finite_positive(row.exit_levels.stop)
10952 && !finite_positive(row.exit_levels.trail_points)
10953 && !finite_positive(row.exit_levels.trail_price);
10954 if (!unpriced_close) {
10955 ordinary_book = false;
10956 break;
10957 }
10958 }
10959 const bool low_value_lot = *staged_.quantity_grid * source.sizing.price
10960 * staged_.syminfo.pointvalue * source.sizing.fx < 1.0;
10961 const bool ordinary_fractional = *staged_.quantity_grid < 1.0
10962 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
10963 && staged_.account_fx_effective_from_ms.empty()
10964 && config_.commission_value == 0.0 && config_.slippage == 0
10965 && !config_.process_orders_on_close && !config_.calc_on_order_fills
10966 && !stream_mode_ && config_.pyramiding >= 0 && config_.pyramiding <= 1
10967 && !cap.active() && risk_.max_intraday_loss <= 0.0
10968 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
10969 && ordinary_book;
10970 const bool whole_lot_tie_scope = *staged_.quantity_grid == 1.0
10971 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
10972 && staged_.account_fx_effective_from_ms.empty()
10973 && config_.commission_value == 0.0 && config_.slippage == 0
10974 && !config_.process_orders_on_close && !config_.calc_on_order_fills
10975 && !stream_mode_ && config_.pyramiding >= 0 && config_.pyramiding <= 1
10976 && !cap.active() && risk_.max_intraday_loss <= 0.0
10977 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
10978 && ordinary_book
10979 // ab9714be pine_fills.cpp:5190-5219: the whole-lot tie is
10980 // a no-gap rule. A favorable next-open price retains the
10981 // ordinary admitted fill even when rounded signal cost ties.
10982 && nearest_tick(result.resolved_price, staged_.syminfo.mintick)
10983 == source.sizing.price;
10984 const auto native_state = require_host().native_state();
10985 const bool pooc_flat_money = config_.process_orders_on_close
10986 && source.projection_position_side
10987 == static_cast<std::int32_t>(PositionSide::FLAT)
10988 && !source.projection_after_close && source.projection_predecessor == 0
10989 && facts.position.signed_units == 0.0
10990 && source.projection_created_bar == facts.cursor.point.interval_index
10991 && !source.birth.from_fill() && source.oca_name.empty()
10992 && config_.pyramiding >= 0 && config_.pyramiding <= 1
10993 && config_.commission_value == 0.0 && config_.slippage >= 0
10994 && finite_positive(staged_.syminfo.mintick)
10995 && *staged_.quantity_grid < 1.0
10996 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
10997 && staged_.account_fx == 1.0
10998 && staged_.account_fx_effective_from_ms.empty()
10999 && (!native_state.spec || native_state.spec->intrabar.is_none())
11000 && !stream_mode_ && !cap.active()
11001 && risk_.max_intraday_loss <= 0.0
11002 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
11003 && nearest_tick(facts.raw_price, staged_.syminfo.mintick)
11004 == nearest_tick(source.sizing.mark, staged_.syminfo.mintick);
11005 if (whole_lot_tie_scope) {
11006 const double cost = *result.units * source.sizing.price;
11007 if (std::isfinite(cost)
11008 && cost == source_money_round(source.sizing.equity)
11009 && cost > source.sizing.equity) {
11010 result.units = 0.0;
11011 result.shape = native_order::OpeningShape::Transact;
11012 return result;
11013 }
11014 }
11015 if (low_value_lot || ordinary_fractional || pooc_flat_money) {
11016 const double notional_per_price = *result.units
11017 * staged_.syminfo.pointvalue * source.sizing.fx;
11018 const double rounded_cost = source_money_round(
11019 notional_per_price
11020 * (pooc_flat_money ? source.sizing.mark : source.sizing.price));
11021 if (source.sizing.equity + 1e-9 < rounded_cost) {
11022 result.units = keep_mc_close_surplus ? 1.0
11023 : (opposite ? facts.opposite_book_units : 0.0);
11024 result.shape = keep_mc_close_surplus
11025 ? native_order::OpeningShape::ReverseTo
11026 : (opposite ? native_order::OpeningShape::CloseOpposite
11027 : native_order::OpeningShape::Transact);
11028 return result;
11029 }
11030 if (!source.projection_after_close) {
11031 const double affordable_price = source_money_round(
11032 source_money_round(source.sizing.equity) / notional_per_price);
11033 if (std::isfinite(affordable_price)
11034 && affordable_price < source.sizing.price) {
11035 result.units = 0.0;
11036 result.shape = native_order::OpeningShape::Transact;
11037 return result;
11038 }
11039 }
11040 }
11041 }
11042 bool affordability_close_only = source.affordability_close_only;
11043 if (!affordability_close_only && source.affordability_policy_active && opposite) {
11044 const double margin = source.is_long ? config_.margin_long : config_.margin_short;
11045 const double own = result.units ? *result.units : 0.0;
11046 const double fill = nearest_tick(result.resolved_price, staged_.syminfo.mintick);
11047 const double required = own * fill * staged_.syminfo.pointvalue * facts.active_fx
11048 * margin / 100.0;
11049 // The source affordability tuple freezes its MTM equity at the
11050 // signal. The entry's later gap changes the cost, not the
11051 // carried-position mark; this is the NQ/rampatel close-only rule.
11052 const double equity = finite_positive(source.sizing.equity)
11053 ? source.sizing.equity : require_host().native_marked_equity(fill);
11054 const double epsilon = std::max(
11055 1e-9, std::abs(equity) * 1e-12);
11056 affordability_close_only = margin > 0.0 && std::isfinite(required)
11057 && (!std::isfinite(equity) || required > equity + epsilon);
11058 }
11059 if (affordability_close_only) {
11060 if (!opposite) {
11061 result.units = 0.0;
11062 return result;
11063 }
11064 result.units = keep_mc_close_surplus ? 1.0
11065 : facts.opposite_book_units;
11066 result.shape = keep_mc_close_surplus
11067 ? native_order::OpeningShape::ReverseTo
11068 : native_order::OpeningShape::CloseOpposite;
11069 return result;
11070 }
11071 }
11072 if (source.family == PineOrderFamily::Order) {
11073 const bool opposite = facts.position.signed_units != 0.0
11074 && ((facts.position.signed_units > 0.0) != source.is_long);
11075 if (opposite) {
11076 result.units = std::min(*result.units, facts.opposite_book_units);
11077 result.shape = native_order::OpeningShape::CloseOpposite;
11078 }
11079 return result;
11080 }
11081 if (source.family == PineOrderFamily::Entry && source.sequential_group != 0
11082 && source.sequential_rank != 0 && source.has_full_entry_bracket) {
11083 bool paired = false;
11084 std::vector<std::uint64_t> placement_handles;
11085 placement_handles.reserve(placement_.size());
11086 for (const auto& row : placement_) placement_handles.push_back(row.first);
11087 std::sort(placement_handles.begin(), placement_handles.end());
11088 for (const auto handle : placement_handles) {
11089 const auto found = placement_.find(handle);
11090 if (found == placement_.end()) continue;
11091 const auto& peer = found->second;
11092 if (peer.family == PineOrderFamily::Entry
11093 && peer.sequential_group == source.sequential_group
11094 && peer.sequential_rank != 0 && peer.sequential_rank != source.sequential_rank
11095 && peer.has_full_entry_bracket
11096 && !(source.is_long && peer.replaced_opening
11097 && peer.replacement_predecessor_market)) {
11098 paired = true;
11099 break;
11100 }
11101 }
11102 if (paired) {
11103 const bool opposite = facts.position.signed_units != 0.0
11104 && ((facts.position.signed_units > 0.0) != source.is_long);
11105 if (source.sequential_rank == 1 && opposite)
11106 result.units = std::max(std::abs(facts.position.signed_units), *result.units);
11107 result.shape = native_order::OpeningShape::Transact;
11108 return result;
11109 }
11110 }
11111 // A same-id default-percent replacement over an opposite open book is a
11112 // source transaction (reduce the carried side by its frozen own size),
11113 // not the ordinary auto-reversal shape. The replacement fact is captured
11114 // before submit_or_replace retires its predecessor.
11115 const bool opposite_at_fill = facts.position.signed_units != 0.0
11116 && ((facts.position.signed_units > 0.0) != source.is_long);
11117 const auto live_side = facts.position.signed_units > 0.0
11120 const bool prior_cycle_close_only = opposite_at_fill
11121 && created_side != live_side
11122 && !source.projection_opposite_market_predecessor
11123 && (finite_positive(source.exit_levels.stop)
11124 || finite_positive(source.exit_levels.limit));
11125 const bool deferred_flip_from_flat = facts.position.signed_units == 0.0
11126 && source.projection_position_side
11127 != static_cast<std::int32_t>(PositionSide::FLAT)
11128 && ((source.projection_position_side
11129 == static_cast<std::int32_t>(PositionSide::LONG)) != source.is_long)
11130 && (finite_positive(source.exit_levels.stop)
11131 || finite_positive(source.exit_levels.limit))
11132 && finite_positive(source.projection_tv_carry_qty)
11133 && result.units && finite_positive(*result.units);
11134 if (deferred_flip_from_flat) {
11135 // ab9714be pine_orders.cpp:676-696 (KI-64): an opposite priced entry
11136 // which outlives the position it was placed against opens its own
11137 // units plus the captured carried side.
11138 result.units = *result.units + source.projection_tv_carry_qty;
11139 result.shape = native_order::OpeningShape::Transact;
11140 }
11141 if (opposite_at_fill) {
11142 const bool source_close_precedes = source.projection_after_close;
11143 const bool replacement_transaction = source.reverse_to
11144 && source.replaced_opening
11145 && source.replacement_predecessor_market && !source.is_long
11146 && !std::isfinite(source.requested_qty)
11147 && config_.default_qty_type
11148 == static_cast<int>(QtyType::PERCENT_OF_EQUITY);
11149 const bool flat_dual_stop = source.projection_position_side
11150 == static_cast<std::int32_t>(PositionSide::FLAT)
11151 && finite_positive(source.exit_levels.stop)
11152 && !finite_positive(source.exit_levels.limit)
11153 && std::any_of(placement_.begin(), placement_.end(),
11154 [&](const auto& row) {
11155 const auto& peer = row.second;
11156 return row.first != facts.target.incarnation && peer.opening
11157 && peer.family == PineOrderFamily::Entry
11158 && peer.projection_position_side
11159 == static_cast<std::int32_t>(PositionSide::FLAT)
11160 && peer.projection_created_bar == source.projection_created_bar
11161 && peer.is_long != source.is_long
11162 && finite_positive(peer.exit_levels.stop)
11163 && !finite_positive(peer.exit_levels.limit);
11164 });
11165 result.shape = source_close_precedes || replacement_transaction
11166 ? native_order::OpeningShape::Transact
11167 : prior_cycle_close_only
11168 ? native_order::OpeningShape::CloseOpposite
11169 : flat_dual_stop
11170 ? native_order::OpeningShape::Transact
11171 : native_order::OpeningShape::ReverseTo;
11172 }
11173 return result;
11174}
11175
11177 std::uint64_t incarnation) const noexcept {
11178 // True when the request being committed is the bracket leg this route
11179 // force-executed at its own level after its parent entry's calculation
11180 // (ab9714be src/source/pine_fills.cpp:7695-7728).
11181 const auto snapshot = placement_.find(incarnation);
11182 return snapshot != placement_.end()
11183 && snapshot->second.post_parent_calc_level_fill;
11184}
11185
11186// ab9714be pine_fills.cpp:5736-5744: priced exit fills flag fold_exit_path_extremes_ to fold pre-fill path excursion
11187bool PineExecutionAdapter::source_priced_exit(std::uint64_t incarnation) const noexcept {
11188 const auto snapshot = placement_.find(incarnation);
11189 if (snapshot == placement_.end()) return false;
11190 const auto& source = snapshot->second;
11191 return (source.family == PineOrderFamily::ExitLimit
11194 && (std::isfinite(source.exit_levels.limit)
11195 || std::isfinite(source.exit_levels.stop)
11196 || std::isfinite(source.exit_levels.trail_points)
11197 || std::isfinite(source.exit_levels.trail_price)
11198 || std::isfinite(source.exit_levels.trail_offset));
11199}
11200
11201std::optional<double> PineExecutionAdapter::source_trail_offset_ticks(std::uint64_t incarnation) const noexcept {
11202 const auto snapshot = placement_.find(incarnation);
11203 if (snapshot == placement_.end()) return std::nullopt;
11204 if (snapshot->second.family != PineOrderFamily::ExitTrail) return std::nullopt;
11205 if (std::isnan(snapshot->second.exit_levels.trail_offset)) return 0.0;
11206 return compat::pine::trail_offset_to_ticks(snapshot->second.exit_levels.trail_offset);
11207}
11208
11209bool PineExecutionAdapter::source_margin_exit(std::uint64_t incarnation) const noexcept {
11210 const auto snapshot = placement_.find(incarnation);
11211 if (snapshot == placement_.end()) return false;
11212 return snapshot->second.family == PineOrderFamily::Margin;
11213}
11214
11215// A request the kernel's own margin model originated. Before on_applied
11216// adopts it into the source placement table there is no row to read, so its
11217// origin is the only thing that identifies it.
11219 const native_order::DefinitionRef& definition) noexcept {
11220 return static_cast<bool>(definition)
11221 && definition->origin == native_order::RequestOrigin::KernelLiquidation;
11222}
11223
11224bool PineExecutionAdapter::has_pending_market_exit(int current_bar) const noexcept {
11225 const auto physical = require_host().physical_position();
11226 if (physical.signed_units == 0.0) return false;
11227 const bool is_long = physical.signed_units > 0.0;
11228 for (const auto& handle : live_handles_) {
11229 const auto it = placement_.find(handle.incarnation);
11230 if (it == placement_.end()) continue;
11231 const auto& sn = it->second;
11232 if (current_bar >= 0 && sn.projection_created_bar != current_bar - 1) {
11233 continue;
11234 }
11235 if (sn.opening && sn.family == PineOrderFamily::Entry
11236 && sn.is_long != is_long
11237 && !std::isfinite(sn.exit_levels.stop)
11238 && !std::isfinite(sn.exit_levels.limit)
11239 && !std::isfinite(sn.exit_levels.trail_points)
11240 && !std::isfinite(sn.exit_levels.trail_price)
11241 && !std::isfinite(sn.exit_levels.trail_offset)) {
11242 return true;
11243 }
11244 if (sn.family == PineOrderFamily::Close || sn.family == PineOrderFamily::CloseAll) {
11245 const auto target_side = is_long ? static_cast<std::int32_t>(PositionSide::LONG)
11246 : static_cast<std::int32_t>(PositionSide::SHORT);
11247 if (sn.projection_position_side == target_side
11248 || sn.projection_position_side == static_cast<std::int32_t>(PositionSide::FLAT)) {
11249 return true;
11250 }
11251 }
11252 }
11253 return false;
11254}
11255
11257 const NativePrecommitView& view, double held_units) const {
11258 // ab9714be pine_fills.cpp:164-218: the scheduler, account and single-lot
11259 // book facts, then the one pending order of that book.
11260 const auto state = require_host().native_state();
11261 const double tick = staged_.syminfo.mintick;
11262 if (config_.process_orders_on_close || config_.calc_on_order_fills
11263 || stream_mode_ || (state.spec && !state.spec->intrabar.is_none())
11264 || !(held_units > 1.0) || require_host().physical_position().lot_count != 1
11265 || config_.commission_value != 0.0 || config_.slippage != 0
11266 || std::abs(config_.margin_long - 100.0) > 1e-12
11267 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
11268 || !staged_.account_fx_effective_from_ms.empty()
11269 || active_staged_fx(view.cursor.point.effective_time_ms) != 1.0
11270 || cap.active() || risk_.max_intraday_loss != 0.0
11271 || risk_.max_drawdown != 0.0 || risk_.max_cons_loss_days > 0
11272 || !pending_entries_.empty() || !pending_same_bar_commands_.empty()
11273 || !pending_bracket_legs_.empty() || !finite_positive(tick)
11274 || !finite_positive(view.raw_price)) {
11275 return false;
11276 }
11277 const PlacementSnapshot* exit = nullptr;
11278 for (const auto& handle : live_handles_) {
11279 if (handle == view.target) continue;
11280 const auto found = placement_.find(handle.incarnation);
11281 if (found == placement_.end()) return false;
11282 const auto& leg = found->second;
11283 // Broker margin slices are not source pending orders.
11284 if (leg.family == PineOrderFamily::Margin) continue;
11285 if (leg.family != PineOrderFamily::ExitLimit
11286 && leg.family != PineOrderFamily::ExitStop) {
11287 return false;
11288 }
11289 if (exit && (leg.source_id != exit->source_id
11290 || leg.from_entry != exit->from_entry)) {
11291 return false;
11292 }
11293 exit = &leg;
11294 }
11295 if (!exit || exit->from_entry.empty() || exit->legs.dormant()
11296 || exit->projection_created_bar >= view.cursor.point.interval_index
11297 || !exit->oca_name.empty() || exit->oca_type != 0
11298 || !std::isnan(exit->exit_levels.trail_points)
11299 || !std::isnan(exit->exit_levels.trail_offset)
11300 || !std::isnan(exit->exit_levels.trail_price)
11301 || std::isinf(exit->exit_levels.limit) || std::isinf(exit->exit_levels.stop)) {
11302 return false;
11303 }
11304 const auto cohort = cohorts_by_id_.find(exit->from_entry);
11305 if (cohort == cohorts_by_id_.end() || cohort->second.opened.empty()) return false;
11306 // One ordinary own full-position reservation.
11307 if (std::isnan(exit->requested_qty)) {
11308 if (!std::isfinite(exit->qty_percent) || exit->qty_percent < 100.0) return false;
11309 } else if (!std::isfinite(exit->requested_qty)
11310 || exit->requested_qty < held_units - internal::kQtyEpsilon) {
11311 return false;
11312 }
11313 const double limit = exit->exit_levels.limit;
11314 const double stop = exit->exit_levels.stop;
11315 if (!finite_positive(limit) && !finite_positive(stop)) return false;
11316 // Every finite leg participates in opening marketability, on the
11317 // tick-quantized open (broker_trigger_bar).
11318 const double open = nearest_tick(view.raw_price, tick);
11319 return !((std::isfinite(limit) && open >= limit)
11320 || (std::isfinite(stop) && open <= stop));
11321}
11322
11324 const auto snapshot = placement_.find(view.target.incarnation);
11325 // R5: a kernel-originated liquidation IS a margin slice, and it reaches
11326 // this hook before it is booked -- before on_applied adopts it into the
11327 // source placement table. The excursion chronology below is decided here,
11328 // so it has to recognise the slice from its origin.
11329 const bool kernel_liquidation = source_kernel_liquidation(view.definition);
11330 if (snapshot != placement_.end() || kernel_liquidation) {
11331 static const PlacementSnapshot kKernelLiquidation = [] {
11334 return row;
11335 }();
11336 const auto& source = snapshot != placement_.end() ? snapshot->second
11337 : kKernelLiquidation;
11338 const auto physical = require_host().physical_position();
11339 // ab9714be pine_scheduler.cpp:257-278: process_margin_call runs after
11340 // update_per_trade_extremes sampled the script bar into every lot that
11341 // is still open, so the residual it splits off inherits that complete
11342 // bar (POOC samples only the traversed waypoint prefix,
11343 // pine_fills.cpp:2014-2023). RULING A48: the host owns the sample
11344 // itself; this resolves which of the owner's two chronologies born the
11345 // slice (complete-bar trim or traversed prefix).
11346 if (source.family == PineOrderFamily::Margin
11347 && view.inspected_closed_units > 0.0) {
11348 if (auto* pine = dynamic_cast<PineStrategyHost*>(&require_host())) {
11349 const Bar& sample_bar = pine->current_bar_;
11350 const bool is_long_pos = pine->position_side_ == PositionSide::LONG;
11351 const bool crosses = opening_slice_precedes_priced_exit_fill(
11352 live_handles_, placement_, sample_bar,
11354 is_long_pos, staged_.syminfo.mintick,
11355 require_host().position_avg_price(), current_position_cycle_,
11356 [this](const SourceId& id) { return from_entry_filled_this_cycle(id); });
11357 const bool one_x_long_opening = physical.signed_units > 0.0
11358 && !config_.process_orders_on_close
11359 && std::isfinite(config_.margin_long)
11360 && std::abs(config_.margin_long - 100.0) < 1e-12;
11361 // ab9714be pine_fills.cpp:2224: the general pre-exit arm takes
11362 // the slice only when the adverse extreme strictly precedes the
11363 // priced exit's own fill; a crossing leg moves it ahead of the
11364 // bar's update_per_trade_extremes. The 1x-long opening arm is
11365 // the only current-coordinate route through that pre-exit hook.
11366 const bool pre_exit_chronology = view.current
11367 ? (one_x_long_opening && crosses)
11368 : crosses;
11369 pine->excursion_margin_prefix_ = config_.process_orders_on_close
11370 || pre_exit_chronology;
11371 // ab9714be pine_scheduler.cpp:167-191 and pine_fills.cpp:
11372 // 2525-2678: a carried position's slice at the bar open runs
11373 // while current_bar_ is restricted to the opening point, and
11374 // pine_fills.cpp:2150-2305 books the general pre-exit slice
11375 // before update_per_trade_extremes (pine_scheduler.cpp:
11376 // 257-278) with fold_exit_path_extremes_ cleared
11377 // (pine_fills.cpp:5858): either row owns only its carried
11378 // extremes and the fill.
11379 bool carried_open_slice =
11380 view.cursor.point.path_phase == NativePathPhase::Open
11381 && position_open_bar_index_ >= 0
11382 && position_open_bar_index_ < view.cursor.point.interval_index;
11383 // ab9714be pine_fills.cpp:2544-2548 exempts a 1x long from the
11384 // open slice: its opening-point rounded-money call is booked
11385 // before sampling only through
11386 // process_carried_long_money_before_priced_orders
11387 // (pine_fills.cpp:164-218), whose book is exactly one resting
11388 // full-position priced exit of the open lot that the open does
11389 // not reach. Otherwise the end-of-bar process_margin_call
11390 // (pine_fills.cpp:1350-1352, after pine_scheduler.cpp:257-270
11391 // sampled the bar) fires at the same open point and the slice
11392 // inherits the complete bar.
11393 if (carried_open_slice && one_x_long_opening) {
11394 carried_open_slice = carried_long_money_precedes_priced_exit(
11395 view, physical.signed_units);
11396 }
11397 pine->excursion_margin_fill_only_ = !config_.process_orders_on_close
11398 && (carried_open_slice || (!view.current && crosses));
11399 }
11400 }
11401 // ab9714be pine_fills.cpp:7449-7459: a deferred strategy.close belongs
11402 // to the position cycle it was issued in. When the live position
11403 // opened on a bar later than the close's creation bar — the next open's
11404 // opposite entry applied first and flipped the book — the legacy book
11405 // Removes the stale close instead of flattening the freshly opened lot
11406 // at its own entry price. The two legacy exclusions are the short-seed
11407 // MATERIALIZE_LONG close and the same-bar market transaction's targeted
11408 // close artifact.
11409 const bool stale_close_for_new_position =
11411 || source.family == PineOrderFamily::CloseAll)
11412 && static_cast<PositionSide>(source.projection_position_side)
11414 && physical.signed_units != 0.0
11415 && position_open_bar_index_ > source.projection_created_bar
11416 && !source.frozen_market_targeted_close
11417 && !(short_seed_.active && view.target == short_seed_.materialize_long);
11418 if (stale_close_for_new_position) return NativePrecommitVerdict::Refuse;
11419 // ab9714be pine_fills.cpp:7483-7537: priced (stop/limit) entries are
11420 // throttled to one opening from flat per bar after an earlier entry
11421 // fill. A same-direction pyramid while still in position is the
11422 // exception and is admitted below by the not-flat check. Not gated on
11423 // process_orders_on_close (the owner applies it at the POOC fill
11424 // point too); COOF and stream stay excluded.
11425 const bool leftover_flat_stop = view.definition
11426 && std::holds_alternative<native_order::Stop>(view.definition->request.trigger)
11427 && !finite_positive(source.exit_levels.limit);
11428 // ab9714be pine_fills.cpp:3687-3788: an older same-direction flip-prep
11429 // stop that this bar also touches fills first and consumes the
11430 // from-flat carry. Skip this later stop until that sibling opens,
11431 // then re-arm it (probe 72/93 S then S2).
11432 if (source.family == PineOrderFamily::Entry
11433 && view.account.would_open
11434 && physical.signed_units == 0.0
11435 && leftover_flat_stop
11436 && policy_script_bar_valid_
11437 && !config_.calc_on_order_fills
11438 && !stream_mode_) {
11439 for (const auto& handle : live_handles_) {
11440 if (handle == view.target) continue;
11441 const auto found = placement_.find(handle.incarnation);
11442 if (found == placement_.end()) continue;
11443 const auto& prior = found->second;
11444 if (!prior.opening || prior.family != PineOrderFamily::Entry
11445 || prior.is_long != source.is_long
11446 || !finite_positive(prior.exit_levels.stop)
11447 || finite_positive(prior.exit_levels.limit)
11448 // ab9714be pine_fills.cpp:3895-3899: stable_sort orders touched entry stops by created_seq (source_sequence)
11449 || prior.source_sequence >= source.source_sequence) {
11450 continue;
11451 }
11452 const auto created = static_cast<PositionSide>(
11453 prior.projection_position_side);
11454 // ab9714be pine_fills.cpp:3687-3788 / 7470-7516: the pending
11455 // scan is book order, not path order. An older same-direction
11456 // stop that this bar also touches fills first, including a
11457 // leftover flat-armed leg (probe 80 morning LE then afternoon
11458 // LE2). Same-side-created pyramid adds are not leftovers.
11459 if (created != PositionSide::FLAT
11460 && (created == PositionSide::LONG) == prior.is_long) {
11461 continue;
11462 }
11463 // Same-bar siblings keep fill_phase (open-tick vs path). A
11464 // leftover from an earlier bar that this bar also touches
11465 // is the book-order override (LE then LE2).
11466 if (prior.projection_created_bar == source.projection_created_bar) {
11467 continue;
11468 }
11469 const bool source_open_mkt = source.is_long
11470 ? policy_script_bar_.open >= source.exit_levels.stop
11471 : policy_script_bar_.open <= source.exit_levels.stop;
11472 const bool prior_open_mkt = prior.is_long
11473 ? policy_script_bar_.open >= prior.exit_levels.stop
11474 : policy_script_bar_.open <= prior.exit_levels.stop;
11475 if (source_open_mkt != prior_open_mkt) continue;
11476 const bool prior_touched = prior.is_long
11477 ? policy_script_bar_.high >= prior.exit_levels.stop
11478 : policy_script_bar_.low <= prior.exit_levels.stop;
11479 if (!prior_touched) continue;
11480 if (!throttled_rearm_already_queued(throttled_reopen_rearm_, source))
11481 throttled_reopen_rearm_.push_back(source);
11482 return NativePrecommitVerdict::Refuse;
11483 }
11484 }
11485 if (source.family == PineOrderFamily::Entry
11486 && view.account.would_open
11487 && physical.signed_units == 0.0
11488 && entry_openings_this_interval_ > 0
11489 && view.cursor.point.interval_index == entry_openings_interval_index_
11490 && leftover_flat_stop
11491 && !config_.calc_on_order_fills
11492 && !stream_mode_) {
11493 // The legacy throttle is OrderEligibility::Skip for the bar; the
11494 // order keeps resting. A generic Refuse is terminal, so re-arm the
11495 // original stop at the bar close (L9g).
11496 if (!throttled_rearm_already_queued(throttled_reopen_rearm_, source))
11497 throttled_reopen_rearm_.push_back(source);
11498 return NativePrecommitVerdict::Refuse;
11499 }
11500 const bool opposite_entry = source.family == PineOrderFamily::Entry
11501 && physical.signed_units != 0.0
11502 && ((physical.signed_units > 0.0) != source.is_long);
11503 // ab9714be pine_risk.cpp:111-118, called only from
11504 // pine_orders.cpp:221 and pine_fills.cpp:4720-4727: these latches
11505 // gate a same-side/flat ENTRY at its fill. They never gate closes,
11506 // exits, RAW orders, or the closing half of an opposite entry.
11507 if (source.family == PineOrderFamily::Entry && !opposite_entry) {
11508 if (risk_.halted) return NativePrecommitVerdict::Refuse;
11509 if (risk_.max_cons_loss_days > 0
11510 && day_ledger_.consecutive_loss_days >= risk_.max_cons_loss_days) {
11511 return NativePrecommitVerdict::Refuse;
11512 }
11513 // pine_risk.cpp:115 compares the live source position, not the
11514 // candidate's resulting quantity. Equality blocks a further add.
11515 if (risk_.max_position_size > 0.0 && view.account.would_open
11516 && std::abs(physical.signed_units) >= risk_.max_position_size) {
11517 return NativePrecommitVerdict::Refuse;
11518 }
11519 }
11520 const bool exit = source.family == PineOrderFamily::ExitLimit
11523 if (exit && !source.immediately) {
11524 if (!config_.close_entries_rule_any && !source.from_entry.empty()) {
11525 const auto cohort = cohorts_by_id_.find(source.from_entry);
11526 if (cohort == cohorts_by_id_.end() || cohort->second.opened.empty()) {
11527 return NativePrecommitVerdict::Refuse;
11528 }
11529 }
11530 const bool restored_after_margin = source.restored_after_margin
11531 || (source.legs.last_action()
11532 && source.legs.last_action()->cause.phase
11533 == exit_legs::Phase::AfterMargin
11534 && !source.legs.dormant());
11535 const bool retained_trail = source.family == PineOrderFamily::ExitTrail
11536 && !source.legs.retired(exit_legs::Leg::Trail);
11537 if (!retained_trail
11538 && (source.legs.dormant()
11539 || (!restored_after_margin
11540 && follows_same_bar_declined_reversal(source, view)))) {
11541 return NativePrecommitVerdict::Refuse;
11542 }
11543 }
11544 }
11545 if (!view.account.would_open) return NativePrecommitVerdict::Admit;
11546 if (snapshot == placement_.end()) return NativePrecommitVerdict::Refuse;
11547 const auto& source = snapshot->second;
11548 if (cap.active() && cap.budget().latched()
11549 && source.source_id != "__intraday_cap_close__") {
11550 const auto& transfer = cap.budget().transfer();
11551 const bool inherited = transfer
11552 && transfer->inheritor == view.target.incarnation
11553 && transfer->close_fill == cap_latest_fill_;
11554 if (!inherited) return NativePrecommitVerdict::Refuse;
11555 }
11556 const auto physical_now = require_host().physical_position();
11557 if (source.family == PineOrderFamily::Entry && config_.pyramiding == 0
11558 && view.account.would_open && physical_now.signed_units != 0.0
11559 && ((physical_now.signed_units > 0.0) == source.is_long)) {
11560 return NativePrecommitVerdict::Refuse;
11561 }
11562 // ab9714be pine_fills.cpp:5551-5554,5564-5664: the high-level MARKET
11563 // affordability gate never owned RAW strategy.order. A36 places host
11564 // admission before the generic gate so this explicit exclusion can remain
11565 // source policy while the native core still owns the resulting fill and
11566 // any post-fill margin checkpoint.
11567 if (source.family == PineOrderFamily::Order) {
11568 return NativePrecommitVerdict::AdmitWithHostMargin;
11569 }
11570
11571 // ab9714be pine_policy_members.cpp:153-210 and pine_fills.cpp:5627-5644:
11572 // a same-bar process-on-close long uses ten-significant-digit signal money
11573 // and the slipped signal threshold. This is the source host's complete
11574 // margin decision for the candidate; a genuine post-fill shortfall is
11575 // admitted and becomes the observable opening-margin event.
11576 const auto native_state = require_host().native_state();
11577 const bool pooc_default_all_in = !std::isfinite(source.requested_qty)
11578 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11579 && config_.default_qty_value == 100.0
11580 && finite_positive(source.sizing.frozen_units)
11581 && finite_positive(source.sizing.equity) && source.sizing.fx == 1.0;
11582 const bool pooc_explicit_fixed = finite_positive(source.requested_qty)
11583 && (source.qty_type < 0 || source.qty_type == static_cast<int>(QtyType::FIXED))
11584 && finite_positive(source.projection_affordability_equity)
11585 && source.projection_affordability_held_qty == 0.0;
11586 const bool pooc_money_scope = config_.process_orders_on_close
11587 && source.family == PineOrderFamily::Entry
11588 && std::holds_alternative<native_order::Market>(view.definition->request.trigger)
11589 && source.is_long && source.opening
11590 && source.projection_created_bar == view.cursor.point.interval_index
11591 && source.projection_position_side == static_cast<std::int32_t>(PositionSide::FLAT)
11592 && !source.projection_after_close && !source.birth.from_fill()
11593 && source.projection_predecessor == 0 && !source.replaced_opening
11594 && source.oca_name.empty() && source.oca_type == 0
11595 && physical_now.signed_units == 0.0
11596 && config_.pyramiding >= 0 && config_.pyramiding <= 1
11597 && config_.margin_long == 100.0 && config_.commission_value == 0.0
11598 && config_.slippage >= 0 && finite_positive(staged_.syminfo.mintick)
11599 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
11600 && *staged_.quantity_grid < 1.0
11601 && staged_.syminfo.pointvalue == 1.0 && staged_.account_fx == 1.0
11602 && source.sizing.fx == 1.0 && staged_.account_fx_effective_from_ms.empty()
11603 && (!native_state.spec || native_state.spec->intrabar.is_none())
11604 && !stream_mode_ && !cap.active()
11605 && risk_.max_intraday_loss <= 0.0 && risk_.max_drawdown <= 0.0
11606 && risk_.max_cons_loss_days == 0
11607 && (pooc_default_all_in || pooc_explicit_fixed);
11608 if (pooc_money_scope) {
11609 const double units = pooc_default_all_in
11610 ? source.sizing.frozen_units : source.requested_qty;
11611 const double equity = pooc_default_all_in
11612 ? source.sizing.equity : source.projection_affordability_equity;
11613 const double signal = pooc_default_all_in
11614 ? source.sizing.mark : source.projection_affordability_signal_price;
11615 const double admission_price = pooc_default_all_in
11616 ? source.sizing.price
11617 : nearest_tick(signal + config_.slippage * staged_.syminfo.mintick,
11618 staged_.syminfo.mintick);
11619 const double rounded_cost = source_money_round(units * signal);
11620 const double affordable_price = source_money_round(
11621 source_money_round(equity) / units);
11622 if (!finite_positive(units) || !finite_positive(equity)
11623 || !finite_positive(signal) || !finite_positive(admission_price)
11624 || equity + 1e-9 < rounded_cost
11625 || (std::isfinite(affordable_price)
11626 && affordable_price < admission_price)) {
11627 return NativePrecommitVerdict::Refuse;
11628 }
11629 return NativePrecommitVerdict::AdmitWithHostMargin;
11630 }
11631 if (source.family == PineOrderFamily::Entry && source.affordability_policy_active) {
11632 const double margin_pct = source.is_long ? config_.margin_long : config_.margin_short;
11633 const double fx = active_staged_fx(view.cursor.point.effective_time_ms);
11634 const auto physical = require_host().physical_position();
11635 // ab9714be pine_fills.cpp:5560-5622: an ordinary explicit fixed
11636 // MARKET opening at fractional-lot resolution first passes the
11637 // rounded-money signal-cost and affordable-price checks. This is a
11638 // source broker precommit policy; the configured default sizing mode
11639 // does not participate in its decision.
11640 const double explicit_units = std::abs(view.inspected_opened_units);
11641 const bool explicit_money_scope =
11642 std::holds_alternative<native_order::Market>(view.definition->request.trigger)
11643 && std::isfinite(source.requested_qty) && source.requested_qty > 0.0
11644 && (source.qty_type < 0
11645 || source.qty_type == static_cast<int>(QtyType::FIXED))
11646 && physical.signed_units == 0.0
11647 && source.projection_position_side
11648 == static_cast<std::int32_t>(PositionSide::FLAT)
11649 && !source.projection_after_close && source.projection_predecessor == 0
11650 && source.oca_type == 0 && source.oca_name.empty()
11651 && view.cursor.point.interval_index == source.projection_created_bar + 1
11652 && live_handles_.size() == 1
11653 && config_.margin_long == 100.0 && config_.margin_short == 100.0
11654 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
11655 && *staged_.quantity_grid < 1.0
11656 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
11657 && staged_.account_fx == 1.0
11658 && staged_.account_fx_effective_from_ms.empty()
11659 && config_.slippage == 0
11660 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
11661 && !config_.process_orders_on_close && !config_.calc_on_order_fills
11662 && !stream_mode_ && config_.pyramiding >= 0 && config_.pyramiding <= 1
11663 && !cap.active() && risk_.max_intraday_loss <= 0.0
11664 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
11665 && finite_positive(source.projection_affordability_equity)
11666 && finite_positive(source.projection_affordability_signal_price)
11667 && finite_positive(explicit_units)
11668 && *staged_.quantity_grid * source.projection_affordability_signal_price < 1.0;
11669 if (explicit_money_scope) {
11670 const double equity = source.projection_affordability_equity;
11671 const double signal = source.projection_affordability_signal_price;
11672 const double rounded_cost = source_money_round(explicit_units * signal);
11673 const double affordable_price = source_money_round(
11674 source_money_round(equity) / explicit_units);
11675 if (equity + 1e-9 < rounded_cost
11676 || (std::isfinite(affordable_price) && affordable_price < signal)) {
11677 return NativePrecommitVerdict::Refuse;
11678 }
11679 }
11680 const bool same_side = physical.signed_units != 0.0
11681 && ((physical.signed_units > 0.0) == source.is_long);
11682 const bool reversal = physical.signed_units != 0.0
11683 && ((physical.signed_units > 0.0) != source.is_long);
11684 const double units = same_side
11685 ? std::abs(view.account.resulting_abs_notional)
11686 / (view.resolved_price * staged_.syminfo.pointvalue * fx)
11687 : std::abs(view.inspected_opened_units);
11688 const double required = units * view.resolved_price * staged_.syminfo.pointvalue * fx
11689 * margin_pct / 100.0;
11690 // The placement tuple deliberately excludes the prospective opening
11691 // commission. Use its source-time MTM equity for fixed/cash/explicit
11692 // affordability instead of the native post-open projection.
11693 double equity = reversal
11694 ? view.account.marked_equity
11695 : (finite_positive(source.sizing.equity)
11696 ? source.sizing.equity : view.account.marked_equity);
11697 const bool pooc_slipped_signal = config_.process_orders_on_close
11698 && source.projection_created_bar == view.cursor.point.interval_index
11699 && source.projection_position_side
11700 == static_cast<std::int32_t>(PositionSide::FLAT)
11701 && physical.signed_units == 0.0
11702 && std::holds_alternative<native_order::Market>(
11703 view.definition->request.trigger)
11704 && finite_positive(source.projection_affordability_signal_price);
11705 if (pooc_slipped_signal) {
11706 const double signal_fill = nearest_tick(
11707 source.projection_affordability_signal_price
11708 + (source.is_long ? 1.0 : -1.0) * config_.slippage
11709 * staged_.syminfo.mintick,
11710 staged_.syminfo.mintick);
11711 const double signal_threshold = units * signal_fill
11712 * staged_.syminfo.pointvalue * fx * margin_pct / 100.0;
11713 if (std::isfinite(signal_threshold))
11714 equity = std::max(equity, signal_threshold);
11715 }
11716 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
11717 if (!(margin_pct > 0.0) || !std::isfinite(margin_pct)) {
11718 return NativePrecommitVerdict::AdmitWithHostMargin;
11719 }
11720 // ab9714be pine_fills.cpp:5564-5663: a MARKET reversal is costed as its
11721 // own new side at max(tick(close(S)), tick(fill)) against the PLACEMENT
11722 // equity snapshot, fees excluded. The post-close marked book already
11723 // carries the reversal's closing fee and the carried side's gap, so it
11724 // declined fee-only shortfalls that the owner admits and then trims at
11725 // the fill (pine_fills.cpp:5999-6005, 1386-1482).
11726 const bool market_reversal = reversal
11727 && std::holds_alternative<native_order::Market>(view.definition->request.trigger)
11728 && finite_positive(source.projection_affordability_equity)
11729 && finite_positive(source.projection_affordability_signal_price);
11730 if (market_reversal) {
11731 const double raw_fill = finite_positive(view.raw_price)
11732 ? view.raw_price : view.resolved_price;
11733 const double admit_price = std::max(
11734 source.projection_affordability_signal_price,
11735 nearest_tick(raw_fill, staged_.syminfo.mintick));
11736 const double placement_equity = source.projection_affordability_equity;
11737 const double reversal_required = units * admit_price
11738 * staged_.syminfo.pointvalue * fx * margin_pct / 100.0;
11739 const double guard = std::max(1e-9, std::abs(placement_equity) * 1e-12);
11740 if (!std::isfinite(reversal_required)
11741 || reversal_required > placement_equity + guard) {
11742 return NativePrecommitVerdict::Refuse;
11743 }
11744 return NativePrecommitVerdict::AdmitWithHostMargin;
11745 }
11746 if (!std::isfinite(required) || !std::isfinite(equity) || required > equity + epsilon) {
11747 return NativePrecommitVerdict::Refuse;
11748 }
11749 return NativePrecommitVerdict::AdmitWithHostMargin;
11750 }
11751 const bool exit = source.family == PineOrderFamily::ExitLimit
11753 if (exit) {
11754 // A bound exit whose selected scope has no remaining physical units is
11755 // a stale source leg, not a zero-quantity trade. The legacy pending
11756 // book removed that sibling before settlement; refusing the native
11757 // candidate preserves the same observable trade roster.
11758 if (!view.account.would_open && !(view.inspected_closed_units > 0.0))
11759 return NativePrecommitVerdict::Refuse;
11760 const auto& bounds = source.leg_activation.bounds();
11761 const bool stop_leg = source.family == PineOrderFamily::ExitStop;
11762 const bool limit_leg = source.family == PineOrderFamily::ExitLimit;
11763 if (bounds && current_position_cycle_ > 0) {
11764 const bool ready = stop_leg
11765 ? source.leg_activation.stop_ready(current_position_cycle_, view.cursor.point.interval_index)
11766 : (limit_leg ? source.leg_activation.limit_ready(
11767 current_position_cycle_, view.cursor.point.interval_index) : true);
11768 if (!ready) return NativePrecommitVerdict::Refuse;
11769 }
11770 if (source.legs.retired(exit_legs::Leg::Stop)
11771 && source.legs.retired(exit_legs::Leg::Limit)
11772 && source.legs.retired(exit_legs::Leg::Trail)) {
11773 return NativePrecommitVerdict::Refuse;
11774 }
11775 }
11776 if (source.family == PineOrderFamily::Entry && source.reverse_to
11777 && !source.projection_after_close
11778 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11779 && config_.default_qty_value >= 100.0
11780 && finite_positive(source.sizing.frozen_units)) {
11781 // pine_fills.cpp:5497-5519 prices an all-in reversal against the
11782 // source-time frozen equity even under COOF. The native marked book
11783 // includes the carried side's gap PnL and would otherwise admit the
11784 // +1-gap reversal that the source broker declines.
11785 const double margin = source.is_long ? config_.margin_long : config_.margin_short;
11786 const double fx = active_staged_fx(view.cursor.point.effective_time_ms);
11787 const double required = source.sizing.frozen_units * view.resolved_price
11788 * staged_.syminfo.pointvalue * fx * margin / 100.0;
11789 const double equity = source.sizing.equity;
11790 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
11791 const bool nested_price_gap_affordable = margin == 100.0
11792 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
11793 && *staged_.quantity_grid < 1.0
11794 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
11795 && staged_.account_fx_effective_from_ms.empty()
11796 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
11797 && config_.commission_value == 0.0 && config_.slippage == 0
11798 && !config_.process_orders_on_close && !config_.calc_on_order_fills
11799 && !stream_mode_ && view.resolved_price > source.sizing.price
11800 && source_money_round(source_money_round(equity)
11801 / source.sizing.frozen_units) >= view.resolved_price;
11802 if (!std::isfinite(required) || !std::isfinite(equity)
11803 || (required > equity + guard && !nested_price_gap_affordable)) {
11804 return NativePrecommitVerdict::Refuse;
11805 }
11806 }
11807 const bool variable_default = config_.default_qty_type != static_cast<int>(QtyType::FIXED);
11808 if (variable_default && !source.frozen_market_instruction && config_.pyramiding > 0
11809 && view.inspected_closed_units == 0.0
11810 && require_host().physical_position().lot_count
11811 >= static_cast<std::size_t>(config_.pyramiding)) {
11812 return NativePrecommitVerdict::Refuse;
11813 }
11814 const double margin_pct = view.account.incoming_short
11815 ? config_.margin_short : config_.margin_long;
11816 if (!(margin_pct > 0.0) || !std::isfinite(margin_pct))
11817 return NativePrecommitVerdict::AdmitWithHostMargin;
11818 const double fraction = margin_pct / 100.0;
11819 if (finite_positive(source.sizing.frozen_units) && !source.sizing.at_fill) {
11820 const double frozen_required = std::abs(source.sizing.frozen_units)
11821 * source.sizing.price * staged_.syminfo.pointvalue * source.sizing.fx * fraction;
11822 // ab9714be pine_fills.cpp:4814-4897: a reversal rechecks its frozen
11823 // quantity at the actual fill price. A lower fill can therefore admit
11824 // a signal tuple that is fractionally over budget; flat/same-side
11825 // openings still require their placement tuple to be valid.
11826 if (!std::isfinite(frozen_required) || !std::isfinite(source.sizing.equity)) {
11827 return NativePrecommitVerdict::Refuse;
11828 }
11829 const auto state = require_host().native_state();
11830 const bool live_slippage_changed = state.spec
11831 && config_.slippage != static_cast<int>(state.spec->slippage_ticks);
11832 const bool placement_sized_stop = source.family == PineOrderFamily::Entry
11833 && finite_positive(source.exit_levels.stop)
11834 && !finite_positive(source.exit_levels.limit)
11835 && !std::isfinite(source.requested_qty);
11836 if (placement_sized_stop && live_slippage_changed) {
11837 // The adapter owns this admission (the placement tuple was frozen
11838 // at a different slippage); say so, or the run spec's own per-side
11839 // initial fraction would decline the opening TradingView takes.
11840 return NativePrecommitVerdict::AdmitWithHostMargin;
11841 }
11842 // The frozen tuple protects a rate rollover (the FX opening checkpoint
11843 // owns that later adjustment), but an ordinary price gap is still
11844 // rechecked at the fill just as the legacy KI-54 admission path does.
11845 const double active_fx = active_staged_fx(view.cursor.point.effective_time_ms);
11846 if (active_fx == source.sizing.fx) {
11847 const double fill_required = view.account.resulting_abs_notional * fraction;
11848 const auto physical = require_host().physical_position();
11849 const bool reversal = physical.signed_units != 0.0
11850 && ((physical.signed_units > 0.0) != source.is_long);
11851 const bool variable_batch = source.frozen_market_instruction
11852 && (config_.default_qty_type == static_cast<int>(QtyType::CASH)
11853 || (config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11854 && config_.default_qty_value < 100.0));
11855 // An all-in source reversal remains admitted against the source
11856 // call snapshot. Re-marking the carried side at a later gap
11857 // would manufacture buying power that the legacy precommit did
11858 // not grant (the ShortSeed all-in rejection controls).
11859 const bool all_in_reversal = reversal
11860 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11861 && config_.default_qty_value >= 100.0;
11862 const bool close_then_open_margin_checkpoint = all_in_reversal
11863 && source.projection_after_close && margin_pct == 100.0
11864 && std::holds_alternative<native_order::Market>(
11865 view.definition->request.trigger);
11866 // ab9714be pine_fills.cpp:5288-5390 (gap-reject), :5392-5526
11867 // (KI-54 frozen sizing) and engine_fills.cpp:4618
11868 // (stop_entry_margin_admission_declines): a TRUE-FLAT all-in
11869 // default percent_of_equity==100 opening is costed against the
11870 // PRE-commission placement equity snapshot. Commission is
11871 // EXCLUDED from fill-time affordability -- a fee-only (or one-tick
11872 // grid-rounding) overage admits here and the KI-61 entry-bar
11873 // margin-call trim downstream books the observable residual lot
11874 // that closes again on the entry bar. Judging the same opening
11875 // against the post-commission marked equity instead declines the
11876 // whole entry and loses both lots.
11877 const bool all_in_true_flat_opening = !reversal
11878 && source.family == PineOrderFamily::Entry
11879 && !std::isfinite(source.requested_qty)
11880 && physical.signed_units == 0.0
11881 && source.projection_position_side
11882 == static_cast<std::int32_t>(PositionSide::FLAT)
11883 && !source.projection_after_close
11884 && config_.default_qty_type
11885 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11886 && std::abs(config_.default_qty_value - 100.0) < 1e-12
11887 && std::abs(margin_pct - 100.0) < 1e-12
11888 && finite_positive(source.sizing.equity);
11889 const double fill_equity = (variable_batch || all_in_reversal
11890 || all_in_true_flat_opening)
11891 ? source.sizing.equity : view.account.marked_equity;
11892 const double float_guard = std::max(
11893 1e-9, std::abs(source.sizing.equity) * 1e-12);
11894 bool paired_market_opening = false;
11895 for (const auto& handle : live_handles_) {
11896 if (handle == view.target) continue;
11897 const auto peer = placement_.find(handle.incarnation);
11898 if (peer == placement_.end()) continue;
11899 const auto& row = peer->second;
11900 if (row.opening && row.family == PineOrderFamily::Entry
11901 && row.placement_script_open_ms == source.placement_script_open_ms
11902 && !finite_positive(row.exit_levels.limit)
11903 && !finite_positive(row.exit_levels.stop)) {
11904 paired_market_opening = true;
11905 break;
11906 }
11907 }
11908 const auto native_state = require_host().native_state();
11909 const bool magnified = native_state.spec
11910 && !native_state.spec->intrabar.is_none();
11911 const bool price_gap_scope = source.family == PineOrderFamily::Entry
11912 && !std::isfinite(source.requested_qty)
11913 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11914 && config_.default_qty_value == 100.0
11915 && std::abs((source.is_long ? config_.margin_long : config_.margin_short)
11916 - 100.0) < 1e-12
11917 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
11918 && *staged_.quantity_grid < 1.0
11919 && staged_.syminfo.pointvalue == 1.0 && source.sizing.fx == 1.0
11920 && staged_.account_fx_effective_from_ms.empty()
11921 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
11922 && config_.commission_value == 0.0 && config_.slippage == 0
11923 && !config_.process_orders_on_close && !config_.calc_on_order_fills
11924 && !stream_mode_ && !magnified && !source.birth.from_fill()
11925 && !source.projection_after_close
11926 && view.resolved_price > source.sizing.price
11927 && ((physical.signed_units == 0.0
11928 && source.projection_position_side
11929 == static_cast<std::int32_t>(PositionSide::FLAT)
11930 && !paired_market_opening)
11931 || reversal);
11932 const bool price_gap_affordable = price_gap_scope
11933 && source_money_round(source_money_round(source.sizing.equity)
11934 / source.sizing.frozen_units) >= view.resolved_price;
11935 const bool true_flat_gap_scope = source.family == PineOrderFamily::Entry
11936 && source.projection_position_side
11937 == static_cast<std::int32_t>(PositionSide::FLAT)
11938 && !source.projection_after_close
11939 && physical.signed_units == 0.0
11940 && config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
11941 && config_.default_qty_value == 100.0
11942 && !config_.process_orders_on_close;
11943 if (true_flat_gap_scope
11944 && fill_required > source.sizing.equity + float_guard
11945 && !price_gap_affordable) {
11946 return NativePrecommitVerdict::Refuse;
11947 }
11948 if (source.family == PineOrderFamily::Entry
11949 && source.projection_after_close && physical.signed_units == 0.0) {
11950 // ab9714be pine_fills.cpp:4814-4897 / AG-C1: after an earlier
11951 // same-tick close, the new flat opening is admitted on its
11952 // frozen sizing notional. A worse fill is handled by the
11953 // post-opening margin slice, not by declining the entry.
11954 // AdmitWithHostMargin, not a bare admission: the adapter is
11955 // taking responsibility for this opening's margin check, and
11956 // the run spec now carries a per-side initial fraction that
11957 // would otherwise decline it.
11958 return NativePrecommitVerdict::AdmitWithHostMargin;
11959 }
11960 double admission_guard = float_guard;
11961 if (!reversal && staged_.quantity_grid) {
11962 admission_guard = std::max(admission_guard,
11963 *staged_.quantity_grid * view.resolved_price
11964 * staged_.syminfo.pointvalue * active_fx * fraction);
11965 }
11966 if (!std::isfinite(fill_required) || !std::isfinite(fill_equity)
11967 || (fill_required > fill_equity + admission_guard
11968 && !price_gap_affordable
11969 && !close_then_open_margin_checkpoint)) {
11970 return NativePrecommitVerdict::Refuse;
11971 }
11972 }
11973 return NativePrecommitVerdict::AdmitWithHostMargin;
11974 }
11975 const double required = view.account.resulting_abs_notional * fraction;
11976 // A 1x LONG opening may be admitted against its pre-entry realized
11977 // budget even when the entry commission makes the post-entry marked
11978 // equity fractionally short. The adapter immediately runs the
11979 // opening-price margin checkpoint from on_native_applied, which produces
11980 // the source-required 4x/one-contract reduction before any same-bar
11981 // exit. Rejecting here would erase that observable margin event.
11982 const bool opening_margin_checkpoint =
11983 source.family == PineOrderFamily::Entry && margin_pct == 100.0
11984 && finite_positive(source.requested_qty)
11985 && std::isfinite(view.account.realized_balance)
11986 && required <= view.account.realized_balance;
11987 if (opening_margin_checkpoint) return NativePrecommitVerdict::AdmitWithHostMargin;
11988 if (!std::isfinite(required) || !std::isfinite(view.account.marked_equity)
11989 || required > view.account.marked_equity) {
11990 return NativePrecommitVerdict::Refuse;
11991 }
11992 return NativePrecommitVerdict::AdmitWithHostMargin;
11993}
11994
11995std::int64_t PineExecutionAdapter::chart_day_key(std::int64_t timestamp_ms) const noexcept {
11996 const std::time_t seconds = static_cast<std::time_t>(timestamp_ms / 1000);
11997 std::tm fields{};
11998 const auto utc = [&]() {
11999 return ::gmtime_r(&seconds, &fields) != nullptr;
12000 };
12001 const std::string& timezone = staged_.chart_timezone;
12002 if (timezone.empty() || timezone == "UTC" || timezone == "Etc/UTC") {
12003 if (!utc()) return std::numeric_limits<std::int64_t>::min();
12004 } else {
12005 try {
12006 tz_util::ScopedTimezone guard(timezone);
12007 if (::localtime_r(&seconds, &fields) == nullptr) {
12008 if (!utc()) return std::numeric_limits<std::int64_t>::min();
12009 }
12010 } catch (...) {
12011 // A malformed staged timezone must not turn a source-policy
12012 // read into an unhashable partial state. The run-spec validator
12013 // owns rejection; preserve the legacy UTC fallback meanwhile.
12014 if (!utc()) return std::numeric_limits<std::int64_t>::min();
12015 }
12016 }
12017 return static_cast<std::int64_t>(fields.tm_mday) * 100
12018 + static_cast<std::int64_t>(fields.tm_mon + 1);
12019}
12020
12021compat::pine::CapClock PineExecutionAdapter::cap_clock(
12022 const NativeDecisionContext& context) const {
12023 const std::int64_t key = chart_day_key(context.sub_bar_open_ms);
12024 return {context.sub_bar_open_ms,
12025 staged_.syminfo.session.empty() ? "24x7" : staged_.syminfo.session,
12026 staged_.syminfo.timezone.empty() ? "UTC" : staged_.syminfo.timezone,
12027 static_cast<int>(key / 100), static_cast<int>(key % 100)};
12028}
12029
12030compat::pine::Calculation PineExecutionAdapter::cap_calculation(
12031 const NativeDecisionContext& context) const {
12032 const auto state = require_host().native_state();
12033 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
12034 const bool magnifier = pine_host
12035 ? pine_host->scheduler_.bar_magnifier_enabled()
12036 : context.sub_count > 1;
12037 return {config_.process_orders_on_close, config_.calc_on_order_fills,
12038 coof_recalc_active_, magnifier,
12039 state.phase == NativeRunPhase::Warmup,
12040 state.phase != NativeRunPhase::Realtime,
12041 !config_.close_entries_rule_any,
12042 context.coordinate.interval_index};
12043}
12044
12045compat::pine::MatchedAttempt PineExecutionAdapter::cap_attempt(
12046 const PlacementSnapshot& snapshot, std::uint64_t incarnation,
12047 const native_order::ExecutionAppliedEvent* applied) const {
12049 if (snapshot.family == PineOrderFamily::Entry) {
12050 kind = (finite_positive(snapshot.exit_levels.limit)
12051 || finite_positive(snapshot.exit_levels.stop))
12053 }
12054 const auto position = require_host().physical_position();
12055 compat::pine::Side side = position.signed_units > 0.0
12057 : (position.signed_units < 0.0 ? compat::pine::Side::Short
12059 std::size_t prefill_entries = position.lot_count;
12060 if (applied) {
12061 if (applied->closed_units > 0.0) {
12062 // The matched ENTRY saw the side opposite its requested side.
12063 // Factor A only needs that exact side to avoid misclassifying a
12064 // reversal as a same-side no-op.
12065 side = snapshot.is_long ? compat::pine::Side::Short
12067 } else if (applied->opened_units != 0.0
12068 && applied->cycle_before != applied->cycle_after) {
12070 prefill_entries = 0;
12071 } else if (applied->opened_units != 0.0
12072 && applied->opened_lot_incarnation != 0
12073 && prefill_entries > 0) {
12074 // A same-side add created exactly one new physical opening.
12075 --prefill_entries;
12076 }
12077 } else {
12078 const auto projected = static_cast<PositionSide>(snapshot.projection_position_side);
12079 if (projected == PositionSide::FLAT) {
12081 prefill_entries = 0;
12082 }
12083 }
12084 const int live_entries = prefill_entries
12085 > static_cast<std::size_t>(std::numeric_limits<int>::max())
12086 ? std::numeric_limits<int>::max() : static_cast<int>(prefill_entries);
12087 return {kind, incarnation, snapshot.projection_created_bar,
12088 snapshot.is_long, side, live_entries,
12089 config_.pyramiding};
12090}
12091
12092bool PineExecutionAdapter::cap_placement_denied(const NativeDecisionContext& context) {
12093 return cap.active() && cap.placement(cap_clock(context)) == compat::pine::Placement::Deny;
12094}
12095
12096bool PineExecutionAdapter::intraday_loss_orders_blocked() const noexcept {
12097 return risk_.intraday_block_day != std::numeric_limits<std::int64_t>::min()
12098 && risk_.intraday_block_day == day_ledger_.current_day;
12099}
12100
12101void PineExecutionAdapter::update_risk_state(double mark_price) {
12102 if (std::isfinite(mark_price)) {
12103 const double equity = require_host().native_marked_equity(mark_price);
12104 if (std::isfinite(equity)) {
12105 if (!std::isfinite(risk_.observed_peak_equity)
12106 || equity > risk_.observed_peak_equity) {
12107 risk_.observed_peak_equity = equity;
12108 }
12109 const double drawdown = risk_.observed_peak_equity - equity;
12110 if (drawdown > risk_.observed_max_drawdown)
12111 risk_.observed_max_drawdown = drawdown;
12112 }
12113 }
12114 if (risk_.halted) return;
12115 if (risk_.max_drawdown > 0.0 && std::isfinite(risk_.observed_peak_equity)) {
12116 const double threshold = risk_.max_drawdown_percent
12117 ? risk_.observed_peak_equity * risk_.max_drawdown / 100.0
12118 : risk_.max_drawdown;
12119 if (risk_.observed_max_drawdown >= threshold) {
12120 risk_.halted = true;
12121 return;
12122 }
12123 }
12124 if (risk_.max_cons_loss_days > 0
12125 && day_ledger_.consecutive_loss_days >= risk_.max_cons_loss_days) {
12126 risk_.halted = true;
12127 }
12128}
12129
12130bool PineExecutionAdapter::intraday_loss_breached(double mark_price) const noexcept {
12131 if (!(risk_.max_intraday_loss > 0.0) || intraday_loss_orders_blocked()
12132 || !std::isfinite(day_ledger_.intraday_start_equity)
12133 || !std::isfinite(mark_price)) {
12134 return false;
12135 }
12136 const double equity = require_host().native_marked_equity(mark_price);
12137 const double loss = day_ledger_.intraday_start_equity - equity;
12138 const double threshold = risk_.max_intraday_loss_percent
12139 ? day_ledger_.intraday_start_equity * risk_.max_intraday_loss / 100.0
12140 : risk_.max_intraday_loss;
12141 if (!(threshold > 0.0) || !(loss > 0.0) || !std::isfinite(loss)) return false;
12142 const double epsilon = 1e-9 * std::max(1.0, std::abs(threshold));
12143 return loss + epsilon >= threshold;
12144}
12145
12146// =========================================================== R5 margin policy
12147// The kernel (src/native_execution_consumer.cpp, R5 lanes L4/L4b) owns the
12148// margin MECHANISM: when the requirement is tested, the liquidation request it
12149// originates, its Superseded re-pricing, its receipt and its MarginCallEvent.
12150// The answers below are the only TradingView-specific things left in that
12151// loop -- when TradingView would check, what money it compares, and how many
12152// units it takes -- and each is answered on the kernel's own facts.
12153
12154// TradingView's money at one mark, which is what `required > equity` is made
12155// of. This is the single implementation: `submit_margin_call_slice` (the
12156// checkpoints the kernel has no point for) and `resolve_margin_requirement`
12157// (the kernel's own path check) both read it, so the two routes cannot drift.
12159 double mark_price, std::int64_t sub_bar_open_ms) const {
12160 SourceMarginMoney money;
12161 const auto position = require_host().physical_position();
12162 if (position.signed_units < 0.0) {
12163 // ab9714be pine_fills.cpp: a short's checkpoint marks on the chart
12164 // tick, while the slice still rests at the raw waypoint.
12165 mark_price = nearest_tick(mark_price, staged_.syminfo.mintick);
12166 }
12167 money.mark = mark_price;
12168 money.held = std::abs(position.signed_units);
12169 const double margin_pct = position.signed_units > 0.0
12170 ? config_.margin_long : config_.margin_short;
12171 if (!source_margin_call_enabled_ || !(money.held > 0.0)
12172 || !finite_positive(mark_price) || !finite_positive(margin_pct)
12173 || !finite_positive(staged_.syminfo.pointvalue)) {
12174 return money;
12175 }
12176 const double fx = active_staged_fx(sub_bar_open_ms);
12177 const double fraction = margin_pct / 100.0;
12178 money.unit_margin = mark_price * staged_.syminfo.pointvalue * fx * fraction;
12179 money.exact_required = money.held * money.unit_margin;
12180 money.required = money.exact_required;
12181 if (staged_.quantity_grid && *staged_.quantity_grid > 0.0
12182 && staged_.account_fx_effective_from_ms.empty()) {
12183 const double lot_value = *staged_.quantity_grid * mark_price
12184 * staged_.syminfo.pointvalue * fx;
12185 if (std::isfinite(lot_value) && lot_value < 1.0) {
12186 // ab9714be pine_fills.cpp:11491-11499: a sub-unit lot puts the
12187 // requirement on TradingView's ten-significant-digit money ladder.
12188 money.required = source_money_round(money.exact_required);
12189 }
12190 }
12191 // ab9714be pine_fills.cpp:1411-1423: fee-adjusted live equity.
12192 money.equity = percent_commission_live_equity(mark_price);
12193 money.valid = finite_positive(money.unit_margin) && std::isfinite(money.equity);
12194 return money;
12195}
12196
12197// TradingView's slice quantity from that money: the lot-floored restore taken
12198// four times, floored to the lot again, and the family R whole-drop band for a
12199// restore that floors below one lot.
12201 const SourceMarginMoney& money, bool opening_checkpoint) const {
12202 if (!money.valid || !(money.required > money.equity)) return 0.0;
12203 const double raw_minimum = opening_checkpoint && money.required == money.exact_required
12204 ? money.held - money.equity / money.unit_margin
12205 : (money.required - money.equity) / money.unit_margin;
12206 if (!(raw_minimum > 0.0) || !std::isfinite(raw_minimum)) return 0.0;
12207 // ab9714be pine_fills.cpp:1572-1575: a dust-sized restore requirement is
12208 // not a broker action. It must be discarded before lot quantization, so
12209 // floating-point residue at a 1x full-margin opening cannot become a
12210 // 4x epsilon Reduce (and a phantom trade row).
12211 if (raw_minimum <= internal::kQtyEpsilon) return 0.0;
12212 double minimum = raw_minimum;
12213 if (staged_.quantity_grid) {
12214 minimum = std::floor(raw_minimum / *staged_.quantity_grid)
12215 * *staged_.quantity_grid;
12216 }
12217 double units = minimum > 0.0 ? 4.0 * minimum : 0.0;
12218 if (units > 0.0 && staged_.quantity_grid) {
12219 units = std::floor(units / *staged_.quantity_grid + 1e-6)
12220 * *staged_.quantity_grid;
12221 }
12222 if (!(units > 0.0) && staged_.quantity_grid
12223 && *staged_.quantity_grid <= 1.0
12224 && raw_minimum > internal::kQtyEpsilon && raw_minimum < 1.0) {
12225 const double candidate = std::min(1.0, money.held);
12226 const double rounded = floor_quantity_grid(candidate, staged_.quantity_grid);
12227 const double guard = std::max(1e-12, std::abs(candidate) * 1e-12);
12228 if (candidate >= money.held - guard || std::abs(rounded - candidate) <= guard)
12229 units = candidate;
12230 }
12231 units = std::min(money.held, units);
12232 // ab9714be pine_fills.cpp:1708: the final slice quantity carries the same
12233 // slack gate, so a floored-to-dust restore closes nothing at all.
12234 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units)) return 0.0;
12235 return units;
12236}
12237
12238// The forced execution price of a liquidation that fired at `fire`: the fire
12239// price on the chart tick ladder, then the EXIT side's own market slippage
12240// (ab9714be pine_fills.cpp:1712-1726 and :2649-2658). Reducing a long is a
12241// sell (slippage subtracts); reducing a short is a buy (it adds). At zero
12242// slippage this is the identity on the ladder.
12243double PineExecutionAdapter::source_margin_fill_price(double fire, bool close_is_buy) const {
12244 if (config_.slippage == 0) return fire;
12245 const double rounded = source_bar_fill_tick(fire, staged_.syminfo.mintick);
12246 const double slipped = rounded + (close_is_buy ? 1.0 : -1.0)
12247 * config_.slippage * staged_.syminfo.mintick;
12248 return directional_tick(slipped, staged_.syminfo.mintick, close_is_buy);
12249}
12250
12251// ab9714be pine_fills.cpp:5159-5221: a rounded whole-lot tie is rejected only
12252// for the SOLE opening. A coexisting resting entry excludes that rejection,
12253// and the lot that was admitted on the rounded tie must not then be
12254// immediately liquidated for the same sub-lot representation residue. This is
12255// a rule about when TradingView does not check at all, so it vetoes the
12256// adapter's own checkpoints and, through margin_check_allowed, the kernel's
12257// check point too.
12259 if (!staged_.quantity_grid || *staged_.quantity_grid != 1.0
12260 || config_.default_qty_type != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
12261 || config_.default_qty_value != 100.0 || config_.commission_value != 0.0) {
12262 return false;
12263 }
12264 bool rounded_tie_opening = false;
12265 for (const auto& cohort_id : cohort_order_) {
12266 const auto cohort = cohorts_by_id_.find(cohort_id);
12267 if (cohort == cohorts_by_id_.end()) continue;
12268 for (const auto& origin : cohort->second.opened) {
12269 const auto opening = placement_.find(origin.incarnation);
12270 if (opening == placement_.end()) continue;
12271 const auto& row = opening->second;
12272 const double cost = row.sizing.frozen_units * row.sizing.price
12273 * staged_.syminfo.pointvalue * row.sizing.fx;
12274 if (row.opening && std::isfinite(cost)
12275 && cost == source_money_round(row.sizing.equity)
12276 && cost > row.sizing.equity) {
12277 rounded_tie_opening = true;
12278 break;
12279 }
12280 }
12281 if (rounded_tie_opening) break;
12282 }
12283 if (!rounded_tie_opening) return false;
12284 for (const auto& handle : live_handles_) {
12285 const auto pending = placement_.find(handle.incarnation);
12286 if (pending == placement_.end()) continue;
12287 const auto& row = pending->second;
12288 if (row.opening && (finite_positive(row.exit_levels.limit)
12289 || finite_positive(row.exit_levels.stop))) {
12290 return true;
12291 }
12292 }
12293 return false;
12294}
12295
12296// TradingView's scheduling (MG-I). The kernel offers its own check points;
12297// this admits exactly the ones the adapter's own scheduling decided to check
12298// at, and refuses the rest. A refused point is inert: the kernel measures
12299// nothing, re-arms nothing and withdraws nothing there, so the state stays as
12300// the last admitted point left it. The decision itself is taken where
12301// TradingView takes it -- inside on_bar_open, with the bar's competing
12302// orders in front of it -- and only carried here.
12304 const NativeMarginCheckPoint& point) const {
12305 // The kernel's FX-roll point is not TradingView's: its account-currency
12306 // rollover revaluation is the broker-open slice of
12307 // apply_fx_open_margin_slice, taken in on_bar_open on the source's own
12308 // sub-bar rate. Refused by kind, ahead of the ordinal test, because a
12309 // roll can share its driver point with an armed BarOpen check.
12310 if (point.kind == NativeMarginCheckKind::FxRoll) return false;
12311 // The kernel's BarOpen point and its post-fill AfterApplied re-arm are
12312 // both points TradingView checks at -- schedule_margin_call_path is
12313 // called from on_bar_open and from on_applied -- so the admitted point is
12314 // named by the driver point the decision was taken at, not by its kind.
12315 const std::uint64_t ordinal = point.cursor.point.ordinal;
12316 if (kernel_margin_resize_point_ == ordinal) {
12317 // The post-exit re-size point (on_applied, a priced bracket leg of
12318 // this script bar). The legacy broker cancelled the live path slice
12319 // there and re-scheduled from the reduced book, and did NOTHING when
12320 // no slice was live -- so this point is TradingView's only while one
12321 // rests. With one resting the kernel withdraws it and re-rests
12322 // whatever schedule_margin_call_path armed on the reduced book; with
12323 // the book flat the same admission withdraws it and rests nothing.
12324 if (!point.liquidation_resting) return false;
12325 } else if (kernel_margin_path_point_ != ordinal) {
12326 return false;
12327 }
12328 // The rounded-tie veto is re-read here rather than at the arming site: the
12329 // book it inspects is the one standing at the check point.
12331}
12332
12333// TradingView's money (MG-A + MG-B), restated for the kernel's breach test at
12334// the mark it measured. nullopt would keep the kernel's own unrounded
12335// requirement against its own marked equity, which is a different broker.
12336std::optional<NativeMarginDecision> PineExecutionAdapter::resolve_margin_requirement(
12337 const NativeMarginRequirementView& view) const {
12338 const auto money = source_margin_money(view.mark, view.cursor.point.open_ms);
12339 if (!money.valid) return std::nullopt;
12340 NativeMarginDecision decision;
12341 decision.required = money.required;
12342 decision.equity = money.equity;
12343 return decision;
12344}
12345
12346// TradingView's sizing (MG-F + MG-G), on the kernel's own facts. This hook is
12347// the whole sizing authority for every call the kernel makes on the adapter's
12348// behalf, which is why project() declares no kernel sizing knob for the model.
12350 const NativeMarginCallView& view) const {
12351 // Always an answer, never nullopt. A non-positive answer is the kernel's
12352 // documented refusal, which withdraws the reduction instead of booking one.
12353 //
12354 // TradingView never takes the adverse-path slice on a 1x long: the
12355 // one-contract money call owns that book instead
12356 // (submit_tv_money_long_margin_call). Every dispatch site guards on that
12357 // before it schedules, but the book can turn into a 1x long between the
12358 // scheduling decision and the check point -- a reversal at the same
12359 // driver point does exactly that -- and the book that decides is the one
12360 // the call would be made against. This is a refusal of the CALL, not of
12361 // the check point: the point still runs, so a reduction resting from an
12362 // earlier book is withdrawn here rather than left behind.
12363 if (view.position.signed_units > 0.0 && std::isfinite(config_.margin_long)
12364 && std::abs(config_.margin_long - 100.0) < 1e-12) {
12365 return 0.0;
12366 }
12367 const auto money = source_margin_money(view.mark, view.cursor.point.open_ms);
12368 return source_margin_units(money, false);
12369}
12370
12371// A TradingView margin checkpoint the kernel has no check point for (MG-I):
12372// the bar-open mark, the script-close pass, the stream tick, the opening
12373// print. The money, the mark's chart-tick rounding and the sizing are the
12374// shared source_margin_* rules -- the same ones the kernel's own path check
12375// reads through the requirement and units hooks -- and the slice is a market
12376// execution at the current point, never a resting order: the kernel owns
12377// every resting liquidation now.
12378bool PineExecutionAdapter::submit_margin_call_slice(
12379 double mark_price, const NativeDecisionContext& context,
12380 bool opening_checkpoint) {
12381 const auto position = require_host().physical_position();
12382 const auto money = source_margin_money(mark_price, context.sub_bar_open_ms);
12383 mark_price = money.mark;
12384 if (!money.valid) return false;
12385 if (source_margin_rounded_tie_veto()) return false;
12386 const double units = source_margin_units(money, opening_checkpoint);
12387 if (!(units > 0.0)) return false;
12388
12389 // ab9714be pine_fills.cpp:1712-1726 books the checkpoint's residual
12390 // against bar_fill_price(fire) and only then applies the EXIT side's own
12391 // market slippage. The opening checkpoint hands this helper the
12392 // already-SLIPPED opening print, so the entry-side slippage step is undone
12393 // here first and submit_margin_call_units re-applies the exit side on top
12394 // of the raw chart fill. At zero slippage the reconstruction is the
12395 // identity.
12396 double close_base = mark_price;
12397 if (opening_checkpoint && std::isfinite(config_.slippage)
12398 && config_.slippage != 0.0) {
12399 close_base = source_bar_fill_tick(
12400 mark_price - (position.signed_units > 0.0 ? 1.0 : -1.0)
12401 * config_.slippage * staged_.syminfo.mintick,
12402 staged_.syminfo.mintick);
12403 }
12404 return submit_margin_call_units(close_base, context, units);
12405}
12406
12407bool PineExecutionAdapter::submit_margin_call_units(
12408 double mark_price, const NativeDecisionContext& context, double units,
12409 bool force_execution_price) {
12410 const auto position = require_host().physical_position();
12411 const double held = std::abs(position.signed_units);
12412 if (!(units > 0.0) || !std::isfinite(units) || !(held > 0.0)
12413 || !finite_positive(mark_price)) {
12414 return false;
12415 }
12416 units = std::min(units, held);
12417 native_order::Request request;
12418 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
12419 request.label = kMarginCallLabel;
12420 request.comment = "Margin call";
12421 PlacementSnapshot snapshot;
12422 snapshot.family = PineOrderFamily::Margin;
12423 snapshot.source_id = request.label;
12424 snapshot.requested_qty = units;
12425 if (force_execution_price) {
12426 // ab9714be pine_fills.cpp:1712-1726 and :2649-2658: the margin-call
12427 // close helper books bar_fill_price(fire) and then applies the EXIT
12428 // side's own market slippage exactly as the adverse-extreme cascade
12429 // does. The generic forced-execution fact only rounds to the chart
12430 // tick, so the closing slippage step is reproduced here on the fire
12431 // price before it is pinned. Reducing a long is a sell (slippage
12432 // subtracts); reducing a short is a buy (slippage adds). At zero
12433 // slippage this is the identity, leaving every slippage-free tape
12434 // byte-identical.
12435 snapshot.forced_execution_price =
12436 source_margin_fill_price(mark_price, position.signed_units < 0.0);
12437 }
12438 snapshot.sizing = sizing_snapshot();
12439 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
12441 if (!accepted) return false;
12442 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
12443 return true;
12444}
12445
12446bool PineExecutionAdapter::submit_tv_money_long_margin_call(
12447 const Bar& bar, const NativeDecisionContext& context) {
12448 // The one-contract 10-significant-digit money residual is an adapter
12449 // policy over the native position and its ordinary chart path. It is not
12450 // a second matching loop: the resulting reduction is still a generic
12451 // current execution with an immutable source terms fact.
12452 const auto position = require_host().physical_position();
12453 const auto grid = staged_.quantity_grid;
12454 if (!source_margin_call_enabled_ || stream_mode_
12455 || position.signed_units <= 0.0 || position.lot_count != 1
12456 || std::abs(config_.margin_long - 100.0) > 1e-12
12457 || config_.commission_value != 0.0 || config_.slippage != 0
12458 || (config_.process_orders_on_close
12459 && (config_.pyramiding < 0 || config_.pyramiding > 1))
12460 || !grid || !(*grid > 0.0) || *grid > 1.0
12461 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
12462 || active_staged_fx(context.sub_bar_open_ms) != 1.0
12463 || !staged_.account_fx_effective_from_ms.empty()
12464 || cap.active() || risk_.max_intraday_loss > 0.0
12465 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
12466 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
12467 return false;
12468 }
12469 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue
12470 * active_staged_fx(context.sub_bar_open_ms);
12471 if (!config_.process_orders_on_close
12472 && (config_.pyramiding < 0 || config_.pyramiding > 1)
12473 && std::isfinite(lot_value) && lot_value >= 1.0) {
12474 // The high-value fractional extension is pinned only for a single
12475 // opening slot; low-value rounded-money path checks remain valid with
12476 // larger source pyramiding limits (open-money-before-priced-exit).
12477 return false;
12478 }
12479 if (position_open_priced_
12480 && (!std::isfinite(lot_value) || lot_value >= 1.0)) {
12481 return false;
12482 }
12483 if (config_.process_orders_on_close) {
12484 for (const auto& handle : live_handles_) {
12485 const auto found = placement_.find(handle.incarnation);
12486 if (found != placement_.end()
12487 && found->second.family != PineOrderFamily::Margin) {
12488 return false;
12489 }
12490 }
12491 }
12492
12493 int begin = 0;
12494 if (position_open_script_bar_ == context.script_bar_open_ms) {
12495 // A new position can see only the suffix after its actual native
12496 // opening point. The high-value residual witnesses deliberately
12497 // cover a true market opening at O; a close-time/priced entry cannot
12498 // retrospectively inspect this bar.
12499 if (position_open_phase_ != NativePathPhase::Open) return false;
12500 begin = 0;
12501 }
12502 const bool high_first = source_path_uses_high_first(bar);
12503 const double path[] = {bar.open, high_first ? bar.high : bar.low,
12504 high_first ? bar.low : bar.high, bar.close};
12505 const double quantity = position.signed_units;
12506 const double point_value = staged_.syminfo.pointvalue;
12507 // ab9714be pine_fills.cpp:1929-1965: a single-entry, commission-free book
12508 // whose cached net profit carries tracked addition roundoff narrows the
12509 // 1e-7 guard to what the current evaluation and that history support, so
12510 // a real 1e-7 rounding deficit fires (Q 891538.56 at 1.15798 marked at
12511 // 1.15808: equity 1032472.9759999 against required money 1032472.976).
12512 const auto* pine = dynamic_cast<const PineStrategyHost*>(&require_host());
12513 const bool supported_guard_scope = pine
12514 && pine->position_entry_count_ == 1 && pine->pyramid_entries_.size() == 1
12515 && pine->net_profit_sum_ == pine->net_profit_roundoff_value_
12516 && std::isfinite(pine->net_profit_roundoff_bound_);
12517 for (int index = begin; index != 4; ++index) {
12518 const double price = path[index];
12519 if (!finite_positive(price)) continue;
12520 const double exact_value = quantity * price * point_value;
12521 const double equity = require_host().native_marked_equity(price);
12522 const double rounded_value = source_money_round(exact_value);
12523 // This trigger is exclusively for an exact-funded book whose
12524 // 10-significant-digit account valuation is fractionally larger.
12525 // Preserve the base 1e-7 guard for ordinary historical arithmetic.
12526 double arithmetic_guard = 1e-7;
12527 if (supported_guard_scope) {
12528 const double entry_value = quantity * pine->position_entry_price_ * point_value;
12529 const double money_scale = std::max({
12530 std::abs(pine->current_equity()), std::abs(pine->open_profit(price)),
12531 std::abs(entry_value), std::abs(exact_value), std::abs(equity),
12532 std::abs(rounded_value)});
12533 const double evaluation_guard = 8.0
12534 * std::numeric_limits<double>::epsilon() * money_scale;
12535 const double supported_guard = std::nextafter(
12536 evaluation_guard + pine->net_profit_roundoff_bound_,
12537 std::numeric_limits<double>::infinity());
12538 if (std::isfinite(supported_guard))
12539 arithmetic_guard = std::min(arithmetic_guard, supported_guard);
12540 }
12541 if (!std::isfinite(exact_value) || !std::isfinite(equity)
12542 || equity + arithmetic_guard < exact_value
12543 || !(equity + arithmetic_guard < rounded_value)) {
12544 continue;
12545 }
12546 const double units = std::min(1.0, quantity);
12547 const double rounded_units = std::round(units / *grid) * *grid;
12548 const double guard = std::max({1e-12, std::abs(units) * 1e-12,
12549 std::abs(*grid) * 1e-9});
12550 if (units < quantity - guard && std::abs(rounded_units - units) > guard)
12551 return false;
12552 // ab9714be pine_fills.cpp:1985-2020 books the one contract at
12553 // bar_fill_price(fire), the path point that fired, not wherever the
12554 // checkpoint callback happens to stand (the script-bar close).
12555 return submit_margin_call_units(price, context, units, true);
12556 }
12557 return false;
12558}
12559
12560bool PineExecutionAdapter::slipped_pooc_opening_money_scope(
12561 const Bar& bar, const NativeDecisionContext& context) const {
12562 // pine_fills.cpp:1753-1775 and :1823-1868 at ab9714be: a terminal
12563 // process_orders_on_close MARKET entry with positive slippage is the sole
12564 // owner of the deferred opening-money check. Recover the retired lot
12565 // provenance from durable cohort and placement receipts.
12566 const auto position = require_host().physical_position();
12567 const auto grid = staged_.quantity_grid;
12568 if (!source_margin_call_enabled_ || stream_mode_
12569 || !config_.process_orders_on_close || config_.slippage <= 0
12570 || position.signed_units <= 1.0 || position.lot_count != 1
12571 || std::abs(config_.margin_long - 100.0) > 1e-12
12572 || config_.pyramiding < 0 || config_.pyramiding > 1
12573 || config_.commission_value != 0.0
12574 || !grid || !(*grid > 0.0) || *grid >= 1.0
12575 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
12576 || active_staged_fx(context.sub_bar_open_ms) != 1.0
12577 || !staged_.account_fx_effective_from_ms.empty()
12578 || context.driver_statistics.intrabar_path_enabled
12579 || cap.active() || risk_.max_intraday_loss > 0.0
12580 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
12581 || position_open_phase_ != NativePathPhase::Close
12582 || position_open_priced_
12583 || position_open_script_bar_ == std::numeric_limits<std::int64_t>::min()
12584 || !live_handles_.empty() || !pending_bracket_legs_.empty()
12585 || !pending_entries_.empty() || !pending_same_bar_commands_.empty()
12586 || !pending_relative_exits_.empty() || !pending_coof_requests_.empty()
12587 || !source_shadow_pending_.empty()) {
12588 return false;
12589 }
12590
12591 const CohortFacts* sole_cohort = nullptr;
12592 for (const auto& row : cohorts_by_id_) {
12593 if (row.second.opened.empty()) continue;
12594 if (sole_cohort != nullptr) return false;
12595 sole_cohort = &row.second;
12596 }
12597 if (sole_cohort == nullptr || sole_cohort->opened.size() != 1
12598 || sole_cohort->live_units_by_origin.size() != 1) {
12599 return false;
12600 }
12601 const auto origin = sole_cohort->opened.front();
12602 const auto live_units = sole_cohort->live_units_by_origin.find(origin.incarnation);
12603 const auto placement = placement_.find(origin.incarnation);
12604 if (live_units == sole_cohort->live_units_by_origin.end()
12605 || !(live_units->second > 1.0)
12606 || placement == placement_.end()
12607 || placement->second.family != PineOrderFamily::Entry
12608 || !placement->second.opening || !placement->second.is_long
12609 || finite_positive(placement->second.exit_levels.limit)
12610 || finite_positive(placement->second.exit_levels.stop)
12611 || finite_positive(placement->second.exit_levels.trail_offset)
12612 || placement->second.placement_script_open_ms
12613 != position_open_script_bar_) {
12614 return false;
12615 }
12616
12617 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue;
12618 return std::isfinite(lot_value) && lot_value < 1.0;
12619}
12620
12621bool PineExecutionAdapter::submit_slipped_pooc_opening_money_call(
12622 const Bar& bar, const NativeDecisionContext& context) {
12623 if (!slipped_pooc_opening_money_scope(bar, context)
12624 || position_open_script_bar_ == context.script_bar_open_ms
12625 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
12626 return false;
12627 }
12628 const auto position = require_host().physical_position();
12629 const double exact_value = position.signed_units * bar.open
12630 * staged_.syminfo.pointvalue;
12631 const double equity = require_host().native_marked_equity(bar.open);
12632 const double rounded_value = source_money_round(exact_value);
12633 if (!std::isfinite(exact_value) || !std::isfinite(equity)
12634 || equity < exact_value || !(equity < rounded_value)) {
12635 return false;
12636 }
12637
12638 return submit_margin_call_units(
12639 bar.open, context, std::min(1.0, position.signed_units), false);
12640}
12641
12642bool PineExecutionAdapter::schedule_tv_money_long_margin_before_trail(
12643 const Bar& bar, const NativeDecisionContext& context) {
12644 const auto position = require_host().physical_position();
12645 const auto grid = staged_.quantity_grid;
12646 if (!source_margin_call_enabled_ || config_.calc_on_order_fills || stream_mode_
12647 || position.signed_units <= 1.0 || position.lot_count != 1
12648 || std::abs(config_.margin_long - 100.0) > 1e-12
12649 || config_.commission_value != 0.0 || config_.slippage != 0
12650 || config_.pyramiding < 0 || config_.pyramiding > 1
12651 || !grid || !(*grid > 0.0) || *grid >= 1.0
12652 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
12653 || active_staged_fx(context.sub_bar_open_ms) != 1.0
12654 || !staged_.account_fx_effective_from_ms.empty()
12655 || cap.active() || risk_.max_intraday_loss > 0.0
12656 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
12657 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
12658 return false;
12659 }
12660 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue
12661 * active_staged_fx(context.sub_bar_open_ms);
12662 if (position_open_priced_
12663 && (!std::isfinite(lot_value) || lot_value >= 1.0)) {
12664 return false;
12665 }
12666
12667 const PlacementSnapshot* owned_trail = nullptr;
12668 std::uint64_t owned_trail_incarnation = 0;
12669 for (const auto& handle : live_handles_) {
12670 const auto found = placement_.find(handle.incarnation);
12671 if (found == placement_.end()) continue;
12672 const auto& candidate = found->second;
12673 if (candidate.family == PineOrderFamily::Margin) continue;
12674 const bool exit = candidate.family == PineOrderFamily::ExitLimit
12675 || candidate.family == PineOrderFamily::ExitStop
12676 || candidate.family == PineOrderFamily::ExitTrail;
12677 if (exit && !candidate.from_entry.empty()
12678 && cohort_exposure_for(candidate.from_entry) == 0.0) {
12679 continue;
12680 }
12681 if (!exit || candidate.family != PineOrderFamily::ExitTrail
12682 || owned_trail != nullptr) {
12683 return false;
12684 }
12685 owned_trail = &candidate;
12686 owned_trail_incarnation = handle.incarnation;
12687 }
12688 if (owned_trail) {
12689 const bool full = !std::isfinite(owned_trail->requested_qty)
12690 && (!std::isfinite(owned_trail->qty_percent)
12691 || owned_trail->qty_percent >= 100.0);
12692 const bool relative = std::isfinite(owned_trail->exit_levels.trail_points)
12693 && !std::isfinite(owned_trail->exit_levels.trail_price)
12694 && std::isfinite(owned_trail->exit_levels.trail_offset)
12695 && owned_trail->exit_levels.trail_offset > 0.0;
12696 if (!full || !relative || !owned_trail->oca_name.empty()) return false;
12697 }
12698
12699 const bool high_first = source_path_uses_high_first(bar);
12700 const double path[] = {bar.open, high_first ? bar.high : bar.low,
12701 high_first ? bar.low : bar.high, bar.close};
12702 double fire_price = kNaN;
12703 int fire_point = -1;
12704 constexpr double kArithmeticGuard = 1e-7;
12705 for (int point = 0; point != 4; ++point) {
12706 const double price = path[point];
12707 if (!finite_positive(price)) continue;
12708 const double exact_value = position.signed_units * price
12709 * staged_.syminfo.pointvalue;
12710 const double equity = require_host().native_marked_equity(price);
12711 const double rounded_value = source_money_round(exact_value);
12712 if (std::isfinite(exact_value) && std::isfinite(equity)
12713 && equity + kArithmeticGuard >= exact_value
12714 && equity + kArithmeticGuard < rounded_value) {
12715 fire_price = price;
12716 fire_point = point;
12717 break;
12718 }
12719 }
12720 if (!finite_positive(fire_price)) return false;
12721 if (config_.process_orders_on_close && owned_trail) {
12722 // ab9714be pine_fills.cpp:378-384, 1843-1846 and 1935-1937: under
12723 // POOC a resting order suppresses the carried rounded-money check
12724 // unless it is the owned trailing exit filling on THIS bar, and then
12725 // only a path point strictly before that fill may fire. Walk the
12726 // trail over the same waypoints from its state at the open.
12727 const double tick = staged_.syminfo.mintick;
12728 double activation = owned_trail->trail_activation_level;
12729 if (!finite_positive(activation) && finite_positive(tick)) {
12730 activation = require_host().position_avg_price()
12732 owned_trail->exit_levels.trail_points) * tick;
12733 }
12734 const double offset = compat::pine::trail_offset_to_ticks(
12735 owned_trail->exit_levels.trail_offset) * tick;
12736 if (!finite_positive(activation) || !std::isfinite(offset)) return false;
12737 const auto state = trail_state_at_open_.find(owned_trail_incarnation);
12738 bool armed = state != trail_state_at_open_.end() && state->second.activated
12739 && finite_positive(state->second.best_price);
12740 double best = armed ? state->second.best_price : kNaN;
12741 int fill_point = -1;
12742 for (int point = 0; point != 4 && fill_point < 0; ++point) {
12743 const double price = path[point];
12744 if (!finite_positive(price)) continue;
12745 if (!armed) {
12746 if (price >= activation) {
12747 armed = true;
12748 best = price;
12749 }
12750 continue;
12751 }
12752 if (offset > 0.0 ? price <= best - offset : price < best) {
12753 fill_point = point;
12754 } else {
12755 best = std::max(best, price);
12756 }
12757 }
12758 if (fill_point < 0 || !(fire_point < fill_point)) return false;
12759 }
12760
12761 native_order::Request request;
12762 request.intent = native_order::Reduce{native_order::ExplicitUnits{
12763 std::min(1.0, position.signed_units)}};
12764 request.label = kMarginCallLabel;
12765 request.comment = "Margin call";
12766 request.trigger = fire_price <= bar.open
12767 ? native_order::Trigger{native_order::Stop{fire_price}}
12768 : native_order::Trigger{native_order::Limit{fire_price}};
12769 PlacementSnapshot snapshot;
12770 snapshot.family = PineOrderFamily::Margin;
12771 snapshot.source_id = request.label;
12772 snapshot.requested_qty = std::min(1.0, position.signed_units);
12773 snapshot.sizing = sizing_snapshot();
12774 return static_cast<bool>(submit_or_replace(
12775 std::move(request), std::move(snapshot), false,
12776 "__tv_money_margin_path__"));
12777}
12778
12779bool PineExecutionAdapter::market_orders_pending_at_close(
12780 const NativeDecisionContext& context, std::uint64_t except_incarnation) const {
12781 for (const auto& handle : live_handles_) {
12782 if (handle.incarnation == except_incarnation) continue;
12783 const auto found = placement_.find(handle.incarnation);
12784 if (found == placement_.end()) continue;
12785 const auto& row = found->second;
12786 const bool market_family = row.family == PineOrderFamily::Entry
12787 || row.family == PineOrderFamily::Order
12788 || row.family == PineOrderFamily::Close
12789 || row.family == PineOrderFamily::CloseAll;
12790 if (!market_family) continue;
12791 if (std::isfinite(row.exit_levels.limit) || std::isfinite(row.exit_levels.stop)) continue;
12792 if (row.projection_created_bar != context.coordinate.interval_index) continue;
12793 return true;
12794 }
12795 return false;
12796}
12797
12798bool PineExecutionAdapter::carried_pooc_short_margin_before_script_scope(
12799 const NativeDecisionContext& context) const {
12800 const auto position = require_host().physical_position();
12801 if (!config_.process_orders_on_close || config_.calc_on_order_fills
12802 || stream_mode_ || position.signed_units >= 0.0 || position.lot_count != 1
12803 || position_open_script_bar_ == std::numeric_limits<std::int64_t>::min()
12804 || position_open_script_bar_ == context.script_bar_open_ms
12805 || config_.pyramiding < 0 || config_.pyramiding > 1
12806 || config_.commission_value != 0.0 || config_.slippage != 0
12807 || std::abs(config_.margin_short - 100.0) > 1e-12
12808 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
12809 || active_staged_fx(context.sub_bar_open_ms) != 1.0
12810 || !staged_.account_fx_effective_from_ms.empty()
12811 || !staged_.quantity_grid || !(*staged_.quantity_grid > 0.0)
12812 || *staged_.quantity_grid >= 1.0 || cap.active()
12813 || risk_.max_intraday_loss > 0.0 || risk_.max_drawdown > 0.0
12814 || risk_.max_cons_loss_days > 0) {
12815 return false;
12816 }
12817 const PlacementSnapshot* trail = nullptr;
12818 for (const auto& handle : live_handles_) {
12819 const auto found = placement_.find(handle.incarnation);
12820 if (found == placement_.end()) continue;
12821 const auto& candidate = found->second;
12822 if (candidate.family == PineOrderFamily::Margin) continue;
12823 if (trail != nullptr || candidate.family != PineOrderFamily::ExitTrail) {
12824 return false;
12825 }
12826 trail = &candidate;
12827 }
12828 if (!trail || trail->from_entry.empty()
12829 || trail->projection_created_bar >= context.coordinate.interval_index
12830 || std::isfinite(trail->requested_qty)
12831 || (!std::isfinite(trail->qty_percent) || trail->qty_percent < 100.0)
12832 || std::isfinite(trail->exit_levels.stop)
12833 || std::isfinite(trail->exit_levels.limit)
12834 || std::isfinite(trail->exit_levels.profit_ticks)
12835 || std::isfinite(trail->exit_levels.loss_ticks)
12836 || !std::isfinite(trail->exit_levels.trail_offset)
12837 || !(trail->exit_levels.trail_offset > 0.0)
12838 || (!std::isfinite(trail->exit_levels.trail_points)
12839 && !std::isfinite(trail->exit_levels.trail_price))
12840 || !trail->oca_name.empty() || trail->oca_type != 0) {
12841 return false;
12842 }
12843 const double cohort = cohort_exposure_for(trail->from_entry);
12844 return std::isfinite(cohort)
12845 && cohort == std::abs(position.signed_units);
12846}
12847
12848bool PineExecutionAdapter::carried_pooc_short_priced_exit_after_adverse_scope(
12849 const Bar& bar) const {
12850 const auto position = require_host().physical_position();
12851 if (position.signed_units >= 0.0 || position.lot_count != 1
12852 || std::abs(bar.high - bar.open) >= std::abs(bar.open - bar.low)) {
12853 return false;
12854 }
12855 const PlacementSnapshot* priced = nullptr;
12856 for (const auto& handle : live_handles_) {
12857 const auto found = placement_.find(handle.incarnation);
12858 if (found == placement_.end()) continue;
12859 const auto& candidate = found->second;
12860 if (candidate.family == PineOrderFamily::Margin) continue;
12861 if (candidate.family != PineOrderFamily::ExitLimit
12862 || priced != nullptr || candidate.from_entry.empty()
12863 || cohort_exposure_for(candidate.from_entry) <= 0.0) {
12864 return false;
12865 }
12866 priced = &candidate;
12867 }
12868 if (!priced || !finite_positive(priced->exit_levels.limit)
12869 || bar.low > priced->exit_levels.limit
12870 || std::isfinite(priced->requested_qty)
12871 || (!std::isfinite(priced->qty_percent)
12872 || priced->qty_percent < 100.0)
12873 || !priced->oca_name.empty()) {
12874 return false;
12875 }
12876 return cohort_exposure_for(priced->from_entry)
12877 == std::abs(position.signed_units);
12878}
12879
12880bool PineExecutionAdapter::defer_rounded_pooc_short_margin_until_close(
12881 const Bar& bar) const {
12882 const auto position = require_host().physical_position();
12883 const auto grid = staged_.quantity_grid;
12884 if (!config_.process_orders_on_close || config_.calc_on_order_fills
12885 || stream_mode_ || position.signed_units >= 0.0 || position.lot_count != 1
12886 || position_open_script_bar_ >= bar.timestamp
12887 || config_.pyramiding < 0 || config_.pyramiding > 1
12888 || config_.commission_value != 0.0 || config_.slippage != 0
12889 || std::abs(config_.margin_short - 100.0) > 1e-12
12890 || !grid || !(*grid > 0.0) || !(*grid < 1.0)
12891 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
12892 || active_staged_fx(bar.timestamp) != 1.0
12893 || cap.active() || risk_.max_intraday_loss > 0.0
12894 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
12895 || !finite_positive(bar.high)) {
12896 return false;
12897 }
12898 const double adverse = nearest_tick(bar.high, staged_.syminfo.mintick);
12899 if (!finite_positive(adverse)
12900 || !(*grid * adverse * staged_.syminfo.pointvalue < 1.0)) {
12901 return false;
12902 }
12903
12904 // ab9714be:pine_fills.cpp:1173-1264. Rounded-money POOC shorts defer the
12905 // adverse checkpoint until after the source body unless the completed
12906 // old-order pass contains exactly one live, full owned trailing exit.
12907 // The no-trail case is the observable R26 timing discriminator.
12908 // ab9714be:pine_fills.cpp:7672-7675: that pass Removes an EXIT whose
12909 // from_entry never filled in the current position cycle (the opposite
12910 // side's per-bar strategy.exit), so it never counts as a second order.
12911 const PlacementSnapshot* only = nullptr;
12912 for (const auto& handle : live_handles_) {
12913 const auto found = placement_.find(handle.incarnation);
12914 if (found == placement_.end()) continue;
12915 const auto& row = found->second;
12916 const bool exit_family = row.family == PineOrderFamily::ExitLimit
12917 || row.family == PineOrderFamily::ExitStop
12918 || row.family == PineOrderFamily::ExitTrail;
12919 if (exit_family && !row.from_entry.empty()) {
12920 const auto cohort = cohorts_by_id_.find(row.from_entry);
12921 if (cohort == cohorts_by_id_.end() || cohort->second.opened.empty())
12922 continue;
12923 }
12924 if (only) return true;
12925 only = &row;
12926 }
12927 if (!only || only->family != PineOrderFamily::ExitTrail
12928 || only->from_entry.empty() || only->legs.dormant()
12929 || only->legs.pending_replacement()
12930 || std::isfinite(only->exit_levels.stop)
12931 || std::isfinite(only->exit_levels.limit)
12932 || !finite_positive(only->exit_levels.trail_offset)
12933 || (!std::isfinite(only->exit_levels.trail_points)
12934 && !std::isfinite(only->exit_levels.trail_price))) {
12935 return true;
12936 }
12937 const double held = std::abs(position.signed_units);
12938 const bool full = std::isfinite(only->projection_remaining_qty)
12939 ? only->projection_remaining_qty >= held - 1e-10
12940 : (std::isfinite(only->requested_qty)
12941 ? std::abs(only->requested_qty) >= held - 1e-10
12942 : std::isfinite(only->qty_percent) && only->qty_percent >= 100.0);
12943 return !full;
12944}
12945
12946bool PineExecutionAdapter::schedule_margin_call_path(
12947 const Bar& bar, const NativeDecisionContext& context) {
12948 const auto position = require_host().physical_position();
12949 if (position.signed_units == 0.0) return false;
12950 if (config_.process_orders_on_close) {
12951 const bool competing_entry = std::any_of(
12952 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
12953 const auto found = placement_.find(handle.incarnation);
12954 return found != placement_.end() && found->second.opening
12955 && (found->second.family == PineOrderFamily::Entry
12956 || found->second.family == PineOrderFamily::Order);
12957 });
12958 // ab9714be pine_fills.cpp:1172-1230 / :2462-2523 (executed on both
12959 // libraries, Fable delta-2 P0-A): while a competing pending entry-like
12960 // order exists, a carried POOC short takes no open/path margin slice
12961 // on that bar at all — the slice lands at the close checkpoint after
12962 // the script instead (base bar-1 view -12.60172 with a parked entry,
12963 // -12.44432 without). Neither a prior margin event (A42's latch) nor
12964 // an exit-comment scan (L4a) is part of the legacy predicate.
12965 // The legacy sites are the carried POOC *short* checkpoints; a long
12966 // position keeps the ordinary path slice (L8a margin_call_latch).
12967 if (competing_entry && position.signed_units < 0.0) return false;
12968 // ab9714be pine_scheduler.cpp:246-281: a carried POOC short outside
12969 // the fee-free pre-script checkpoint (pine_fills.cpp:1172-1196) with
12970 // nothing resting (no before-priced-exit hook, pine_fills.cpp:2150)
12971 // is sliced only by the end-of-bar process_margin_call, after the
12972 // close-time script sized its brackets against the untrimmed short.
12973 // The post-script carried POOC short checkpoint (on_bar_close) owns
12974 // that slice; an intrabar path slice would trim the position first.
12975 const bool resting_order = std::any_of(
12976 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
12977 const auto found = placement_.find(handle.incarnation);
12978 return found == placement_.end()
12979 || found->second.family != PineOrderFamily::Margin;
12980 });
12981 if (!config_.calc_on_order_fills && !stream_mode_
12982 && position.signed_units < 0.0
12983 && (config_.commission_value != 0.0 || config_.slippage != 0)
12984 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()
12985 && position_open_script_bar_ != context.script_bar_open_ms
12986 && !resting_order && pending_bracket_legs_.empty()
12987 && pending_coof_requests_.empty() && delayed_market_orders_.empty()
12988 && pending_entries_.empty()) {
12989 return false;
12990 }
12991 }
12992 // ab9714be pine_fills.cpp:1025-1063, :1314-1339: an entry-bar margin
12993 // pass sees only the OHLC suffix after the actual opening point. Later
12994 // bars enter here from Open and retain the ordinary remaining path.
12995 const bool high_first = source_path_uses_high_first(bar);
12996 struct Waypoint { NativePathPhase phase; double price; };
12997 const Waypoint path[] = {
12998 {NativePathPhase::Open, bar.open},
12999 {high_first ? NativePathPhase::High : NativePathPhase::Low,
13000 high_first ? bar.high : bar.low},
13001 {high_first ? NativePathPhase::Low : NativePathPhase::High,
13002 high_first ? bar.low : bar.high},
13003 {NativePathPhase::Close, bar.close},
13004 };
13005 int current = -1;
13006 for (int index = 0; index < 4; ++index) {
13007 if (path[index].phase == context.coordinate.path_phase) {
13008 current = index;
13009 break;
13010 }
13011 }
13012 double adverse = kNaN;
13013 for (int index = current + 1; index < 4; ++index) {
13014 if (!finite_positive(path[index].price)) continue;
13015 if (!std::isfinite(adverse)
13016 || (position.signed_units > 0.0
13017 ? path[index].price < adverse : path[index].price > adverse)) {
13018 adverse = path[index].price;
13019 }
13020 }
13021 if (!finite_positive(adverse)) return false;
13022 // R5: the placement is the kernel's. TradingView's scheduling decision has
13023 // just been taken above -- with this bar's competing orders in front of
13024 // it, exactly where the legacy broker takes it -- so all that is left is
13025 // to admit the kernel's own check point for this driver point. The kernel
13026 // then measures the same adverse mark (margin_sizing_price), asks this
13027 // adapter for TradingView's money and units, and rests the reduction at
13028 // that mark itself (NativeLiquidationCheck::PathAdverseExtremeMark).
13029 kernel_margin_path_point_ = context.coordinate.ordinal;
13030 // The answer the caller needs is whether a call will be made, which is the
13031 // same money the kernel is about to ask for.
13032 return source_margin_units(
13033 source_margin_money(adverse, context.sub_bar_open_ms), false) > 0.0;
13034}
13035
13036bool PineExecutionAdapter::declined_reversal_at_open(const Bar& bar) const {
13037 const auto position = require_host().physical_position();
13038 if (position.signed_units == 0.0) return false;
13039 for (const auto& handle : live_handles_) {
13040 const auto found = placement_.find(handle.incarnation);
13041 if (found == placement_.end()) continue;
13042 const auto& candidate = found->second;
13043 if (!candidate.opening || candidate.family != PineOrderFamily::Entry
13044 || candidate.is_long == (position.signed_units > 0.0)
13045 || !candidate.reverse_to || candidate.projection_after_close) {
13046 continue;
13047 }
13048 double units = candidate.sizing.frozen_units;
13049 if (!finite_positive(units)) units = config_.default_qty_value;
13050 const double fill = nearest_tick(bar.open, staged_.syminfo.mintick);
13051 const double margin = candidate.is_long ? config_.margin_long : config_.margin_short;
13052 const double required = units * fill * staged_.syminfo.pointvalue
13053 * active_staged_fx(bar.timestamp) * margin / 100.0;
13054 const double equity = candidate.sizing.equity;
13055 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
13056 if (finite_positive(units) && std::isfinite(required)
13057 && std::isfinite(equity) && required > equity + epsilon) {
13058 return true;
13059 }
13060 }
13061 return false;
13062}
13063
13064void PineExecutionAdapter::defer_declined_reversal_exits_at_adverse(
13065 const Bar& bar, const NativeDecisionContext&, bool margin_scheduled) {
13066 const auto position = require_host().physical_position();
13067 if (position.signed_units == 0.0) return;
13068 const double adverse = position.signed_units > 0.0 ? bar.low : bar.high;
13069 if (!finite_positive(adverse)) return;
13070
13071 const PlacementSnapshot* declined_reversal = nullptr;
13072 for (const auto& handle : live_handles_) {
13073 const auto found = placement_.find(handle.incarnation);
13074 if (found == placement_.end()) continue;
13075 const auto& candidate = found->second;
13076 if (!candidate.opening || candidate.family != PineOrderFamily::Entry
13077 || candidate.is_long == (position.signed_units > 0.0)
13078 || !candidate.reverse_to || candidate.projection_after_close) {
13079 continue;
13080 }
13081 double units = candidate.sizing.frozen_units;
13082 if (!finite_positive(units)) units = config_.default_qty_value;
13083 const double fill = nearest_tick(bar.open, staged_.syminfo.mintick);
13084 const double margin = candidate.is_long ? config_.margin_long : config_.margin_short;
13085 const double required = units * fill * staged_.syminfo.pointvalue
13086 * active_staged_fx(bar.timestamp) * margin / 100.0;
13087 const double equity = candidate.sizing.equity;
13088 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
13089 if (finite_positive(units) && std::isfinite(required)
13090 && std::isfinite(equity) && required > equity + epsilon) {
13091 declined_reversal = &candidate;
13092 break;
13093 }
13094 }
13095 if (!declined_reversal) return;
13096
13097 // ab9714be:pine_fills.cpp:5509-5519,7232-7277. A whole-position
13098 // strategy.close created after this same-bar reversal is the reversal's
13099 // dependent closing leg. If the opening half is unaffordable, remove
13100 // that close before the generic open candidate is visited; otherwise its
13101 // ordinary MARKET trigger would flatten the held position independently
13102 // and erase the margin/revival chronology the source command specified.
13103 std::vector<native_order::RequestHandle> dependent_closes;
13104 const double held = std::abs(position.signed_units);
13105 for (const auto& handle : live_handles_) {
13106 const auto found = placement_.find(handle.incarnation);
13107 if (found == placement_.end()) continue;
13108 const auto& snapshot = found->second;
13109 const bool full = std::isfinite(snapshot.projection_remaining_qty)
13110 ? snapshot.projection_remaining_qty >= held - 1e-10
13111 : (std::isfinite(snapshot.requested_qty)
13112 ? std::abs(snapshot.requested_qty) >= held - 1e-10
13113 : std::isfinite(snapshot.qty_percent)
13114 && snapshot.qty_percent >= 100.0 - 1e-9);
13115 if (snapshot.family == PineOrderFamily::Close && full
13116 && snapshot.command_ordinal > declined_reversal->command_ordinal
13117 && snapshot.projection_created_bar
13118 == declined_reversal->projection_created_bar) {
13119 dependent_closes.push_back(handle);
13120 }
13121 }
13122 for (const auto& handle : dependent_closes) {
13123 const auto result = require_host().cancel(handle);
13124 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
13125 }
13126
13127 struct DeferredStop {
13128 native_order::RequestHandle handle;
13129 PlacementSnapshot snapshot;
13130 };
13131 std::vector<DeferredStop> stops;
13132 for (const auto& handle : live_handles_) {
13133 const auto found = placement_.find(handle.incarnation);
13134 if (found == placement_.end()) continue;
13135 const auto& snapshot = found->second;
13136 if (snapshot.family != PineOrderFamily::ExitStop
13137 || snapshot.from_entry.empty()
13138 || !finite_positive(snapshot.exit_levels.stop)
13139 || std::isfinite(snapshot.requested_qty)
13140 || !(snapshot.qty_percent >= 100.0 - 1e-9)) {
13141 continue;
13142 }
13143 const bool adverse_reaches = position.signed_units > 0.0
13144 ? adverse <= snapshot.exit_levels.stop
13145 : adverse >= snapshot.exit_levels.stop;
13146 if (adverse_reaches) stops.push_back({handle, snapshot});
13147 }
13148 for (auto& deferred : stops) {
13149 if (!margin_scheduled) {
13150 const auto result = require_host().cancel(deferred.handle);
13151 if (result.status == native_order::CancelStatus::Cancelled)
13152 retire(deferred.handle);
13153 continue;
13154 }
13155 native_order::Request request;
13156 request.intent = native_order::HostSized{
13157 native_order::HostSizedKind::Close, std::nullopt};
13158 request.label = deferred.snapshot.source_id;
13159 request.comment = deferred.snapshot.comment;
13160 request.trigger = native_order::Stop{adverse};
13161 request.owner = owner_for_close(deferred.snapshot.from_entry, true);
13162 const std::string group_name = deferred.snapshot.oca_name.empty()
13163 ? deferred.snapshot.source_id + "\x1f" + deferred.snapshot.from_entry
13164 : deferred.snapshot.oca_name;
13165 request.group = group_for(group_name, 1);
13166 deferred.snapshot.forced_execution_price = adverse;
13167 const SourceId replacement_key = deferred.snapshot.source_id + "\x1f"
13168 + deferred.snapshot.from_entry
13169 + std::to_string(static_cast<int>(PineOrderFamily::ExitStop));
13170 const auto accepted = submit_or_replace(
13171 std::move(request), std::move(deferred.snapshot), false,
13172 replacement_key);
13173 if (accepted) {
13174 bracket_families_[key_for(
13175 placement_.at(accepted->incarnation).source_id,
13176 placement_.at(accepted->incarnation).from_entry)].push_back(*accepted);
13177 }
13178 }
13179}
13180
13181bool PineExecutionAdapter::submit_intraday_loss_close(
13182 double mark_price, const NativeDecisionContext& context, bool execute_current) {
13183 if (!intraday_loss_breached(mark_price)
13184 || require_host().physical_position().signed_units == 0.0) {
13185 return false;
13186 }
13187 native_order::Request request;
13188 request.intent = native_order::Flatten{};
13189 // The legacy forced-close report has an empty exit id and this exact
13190 // comment. An empty generic label is supported by the request algebra.
13191 request.comment = kIntradayLossComment;
13192 if (!execute_current) request.trigger = native_order::Stop{mark_price};
13193 PlacementSnapshot snapshot;
13194 snapshot.family = PineOrderFamily::Risk;
13195 snapshot.source_id = "__intraday_loss__";
13196 snapshot.comment = request.comment;
13197 snapshot.sizing = sizing_snapshot();
13198 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
13199 "__intraday_loss_close__");
13200 if (!accepted) return false;
13201 if (execute_current) {
13202 const auto result = require_host().execute_current(
13203 {*accepted, NativeCurrentPriceRule::NearestTick});
13204 if (const auto* applied = std::get_if<native_order::ExecutionAppliedEvent>(&result);
13205 applied && applied->closed_units > 0.0) {
13206 risk_.intraday_block_day = chart_day_key(context.sub_bar_open_ms);
13207 risk_.intraday_cancel_pending = true;
13208 }
13209 }
13210 return true;
13211}
13212
13213void PineExecutionAdapter::schedule_intraday_loss_path(
13214 const Bar& bar, const NativeDecisionContext& context) {
13215 const auto position = require_host().physical_position();
13216 if (position.signed_units == 0.0) return;
13217 const double adverse = position.signed_units > 0.0 ? bar.low : bar.high;
13218 if (!finite_positive(adverse) || adverse == bar.open) return;
13219 if (position.signed_units > 0.0 ? !(adverse < bar.open) : !(adverse > bar.open))
13220 return;
13221 (void)submit_intraday_loss_close(adverse, context, false);
13222}
13223
13224void PineExecutionAdapter::execute_due_cap_close(const NativeDecisionContext& context) {
13225 const auto due = cap.due_cause();
13226 if (!due || context.coordinate.interval_index <= due->trigger_bar
13227 || require_host().physical_position().signed_units == 0.0) {
13228 return;
13229 }
13230 native_order::Request request;
13231 request.intent = native_order::Flatten{};
13232 request.comment = "Close Position (Max number of filled orders in one day)";
13233 PlacementSnapshot snapshot;
13234 snapshot.family = PineOrderFamily::CloseAll;
13235 snapshot.source_id = "__intraday_cap_close__";
13236 snapshot.comment = request.comment;
13237 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
13238 "__intraday_cap_close__");
13239 if (accepted) {
13240 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
13241 cap.after_immediate_close_attempt();
13242 }
13243}
13244
13245void PineExecutionAdapter::execute_cap_close_now(const compat::pine::CloseNow& close) {
13246 if (require_host().physical_position().signed_units == 0.0) {
13247 cap.after_immediate_close_attempt();
13248 return;
13249 }
13250 native_order::Request request;
13251 request.intent = native_order::Flatten{};
13252 request.comment = close.request.comment;
13253 PlacementSnapshot snapshot;
13254 snapshot.family = PineOrderFamily::CloseAll;
13255 snapshot.source_id = "__intraday_cap_close__";
13256 snapshot.comment = close.request.comment;
13257 const bool closing_long = require_host().physical_position().signed_units > 0.0;
13258 snapshot.forced_execution_price = nearest_tick(
13259 close.price + (closing_long ? -1.0 : 1.0) * config_.slippage * staged_.syminfo.mintick,
13260 staged_.syminfo.mintick);
13261 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), false,
13262 "__intraday_cap_close__");
13263 if (accepted) {
13264 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
13265 }
13266 cap.after_immediate_close_attempt();
13267}
13268
13269void PineExecutionAdapter::observe_intraday_cap(
13270 const native_order::ExecutionAppliedEvent& event,
13271 const PlacementSnapshot& snapshot, const NativeDecisionContext& context) {
13272 if (!cap.active()) return;
13273 if (snapshot.family == PineOrderFamily::Margin || snapshot.family == PineOrderFamily::Risk
13274 || snapshot.source_id == "__intraday_cap_close__")
13275 return;
13276 const auto clock = cap_clock(context);
13277 const auto calculation = cap_calculation(context);
13278 if (snapshot.family == PineOrderFamily::Close
13279 || snapshot.family == PineOrderFamily::CloseAll) {
13280 const bool full = event.closed_units > 0.0
13281 && require_host().physical_position().signed_units == 0.0;
13282 const bool observe_close = cap.direct_close_routing(calculation, full)
13284 const bool ordinary_full_close = full && calculation.process_on_close
13285 && !calculation.calc_on_fills && !calculation.coof_scheduler
13286 && !calculation.magnifier && !calculation.stream_warmup
13287 && calculation.stream_idle && calculation.fifo;
13288 if (observe_close || ordinary_full_close) {
13290 if (event.closed_trade_count > 0
13291 && event.first_trade_index
13292 < static_cast<std::size_t>(require_host().trade_count())) {
13293 before = require_host().get_trade(
13294 static_cast<int>(event.first_trade_index)).is_long
13296 }
13297 std::vector<compat::pine::ContinuationCandidate> candidates;
13298 std::optional<native_order::RequestHandle> continuation;
13299 std::optional<PlacementSnapshot> continuation_snapshot;
13300 std::size_t continuation_index = 0;
13301 std::uint64_t continuation_sequence =
13302 std::numeric_limits<std::uint64_t>::max();
13303 for (const auto& handle : live_handles_) {
13304 if (handle == event.handle()) continue;
13305 const auto found = placement_.find(handle.incarnation);
13306 if (found == placement_.end()) continue;
13307 const auto& candidate = found->second;
13308 const bool market = candidate.family == PineOrderFamily::Entry
13309 && !finite_positive(candidate.exit_levels.limit)
13310 && !finite_positive(candidate.exit_levels.stop)
13311 && !finite_positive(candidate.exit_levels.trail_offset);
13312 candidates.push_back({
13314 : (candidate.family == PineOrderFamily::Entry
13317 candidate.projection_created_bar, candidate.is_long,
13318 static_cast<std::int64_t>(candidate.command_sequence),
13319 handle.incarnation});
13320 if (market && candidate.is_long != (before == compat::pine::Side::Long)
13321 && candidate.projection_created_bar == calculation.bar
13322 && candidate.command_sequence < continuation_sequence) {
13323 continuation = handle;
13324 continuation_snapshot = candidate;
13325 continuation_index = candidates.size() - 1U;
13326 continuation_sequence = candidate.command_sequence;
13327 }
13328 }
13329 double continuation_units = continuation_snapshot
13330 ? continuation_snapshot->frozen_market_own_units : kNaN;
13331 if (continuation_snapshot && !finite_positive(continuation_units)) {
13332 if (finite_positive(continuation_snapshot->requested_qty)) {
13333 continuation_units = std::abs(continuation_snapshot->requested_qty);
13334 } else if (config_.default_qty_type == static_cast<int>(QtyType::FIXED)) {
13335 continuation_units = config_.default_qty_value;
13336 } else {
13337 continuation_units = continuation_snapshot->sizing.frozen_units;
13338 }
13339 }
13340 if (candidates.size() != 1U) continuation.reset();
13341 if (candidates.size() == 1U && continuation && continuation_snapshot
13342 && finite_positive(continuation_units)) {
13343 const auto old = *continuation;
13344 const auto cancelled = require_host().cancel(old);
13345 if (cancelled.status == native_order::CancelStatus::Cancelled) retire(old);
13346 native_order::Request request;
13347 request.intent = native_order::Transact{
13348 continuation_snapshot->is_long
13349 ? continuation_units : -continuation_units};
13350 request.label = continuation_snapshot->source_id;
13351 request.comment = continuation_snapshot->comment;
13352 request.trigger = native_order::Market{};
13353 continuation_snapshot->projection_predecessor = old.incarnation;
13354 continuation_snapshot->projection_predecessor_market = true;
13355 // This is an adapter-created continuation request, not a
13356 // second binding of the original immutable admission draft.
13357 // Give the successor its own journal event while retaining
13358 // predecessor provenance on the placement snapshot.
13359 continuation_snapshot->market_admission = {};
13360 const auto accepted = submit_or_replace(
13361 std::move(request), *continuation_snapshot, true,
13362 continuation_snapshot->source_id);
13363 if (accepted) {
13364 continuation = *accepted;
13365 candidates[continuation_index].incarnation = accepted->incarnation;
13366 } else {
13367 continuation.reset();
13368 candidates.erase(candidates.begin()
13369 + static_cast<std::ptrdiff_t>(continuation_index));
13370 }
13371 }
13372 if (observe_close)
13373 cap.committed_close(clock, calculation, before, event.ordinal, candidates);
13374 cap_latest_fill_ = event.ordinal;
13375 if (continuation) {
13376 (void)require_host().execute_current(
13377 {*continuation, NativeCurrentPriceRule::NearestTick});
13378 }
13379 }
13380 // Direct closes are candidate C, not ordinary matched-attempt factor
13381 // A. Unsupported calculation modes and metadata-off runs deliberately
13382 // remain uncounted.
13383 return;
13384 }
13385 const auto attempt = cap_attempt(snapshot, event.handle().incarnation, &event);
13386 const auto origin = cap.origin(clock, calculation, event.handle().incarnation, cap_latest_fill_);
13387 const auto admission = cap.pre_dispatch(clock, calculation, attempt, cap_latest_fill_);
13388 if (admission.dispatch == compat::pine::Dispatch::Decline) {
13389 cap.decline(event.handle().incarnation);
13390 return;
13391 }
13392 const bool primary_fill_applied = event.closed_units > 0.0
13393 || event.opened_units != 0.0 || event.closed_trade_count > 0
13394 || event.opened_lot_incarnation != 0
13395 || event.cycle_before != event.cycle_after;
13396 cap.outcome(primary_fill_applied ? compat::pine::FillOutcome::Committed
13398 origin);
13399 const auto position = require_host().physical_position();
13400 Bar prices = policy_script_bar_valid_ ? policy_script_bar_ : coof_script_bar_;
13401 if (const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host())) {
13402 if (const auto broker = pine_host->scheduler_.broker_bar(context)) prices = *broker;
13403 }
13404 const auto decision = cap.post_dispatch(admission, calculation, attempt,
13405 position.signed_units > 0.0 ? compat::pine::Side::Long
13406 : (position.signed_units < 0.0 ? compat::pine::Side::Short
13408 current_position_cycle_,
13409 {((context.coordinate.path_phase == NativePathPhase::None || context.coordinate.path_phase == NativePathPhase::Close)
13410 && config_.process_orders_on_close)
13411 ? prices.close : event.resolved_price,
13412 prices.open, prices.high, prices.low});
13413 if (const auto* now = std::get_if<compat::pine::CloseNow>(&decision)) {
13414 execute_cap_close_now(*now);
13415 }
13416 cap_latest_fill_ = event.ordinal;
13417}
13418
13419void PineExecutionAdapter::observe_intraday_cap_noop(
13420 bool is_long, const NativeDecisionContext& context) {
13421 if (!cap.active()) return;
13422 PlacementSnapshot snapshot;
13423 snapshot.family = PineOrderFamily::Entry;
13424 snapshot.is_long = is_long;
13425 snapshot.projection_position_side = is_long
13426 ? static_cast<std::int32_t>(PositionSide::LONG)
13427 : static_cast<std::int32_t>(PositionSide::SHORT);
13428 snapshot.projection_created_bar = context.coordinate.interval_index;
13429 snapshot.source_sequence = source_sequence_;
13430 const auto clock = cap_clock(context);
13431 const auto calculation = cap_calculation(context);
13432 const auto attempt = cap_attempt(snapshot, 0);
13433 const auto origin = cap.origin(clock, calculation, 0, cap_latest_fill_);
13434 const auto admission = cap.pre_dispatch(clock, calculation, attempt, cap_latest_fill_);
13435 if (admission.dispatch == compat::pine::Dispatch::Decline) {
13436 cap.decline(0);
13437 return;
13438 }
13440 const auto position = require_host().physical_position();
13441 Bar prices = policy_script_bar_valid_ ? policy_script_bar_ : coof_script_bar_;
13442 if (const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host())) {
13443 if (const auto broker = pine_host->scheduler_.broker_bar(context)) prices = *broker;
13444 }
13445 const auto decision = cap.post_dispatch(admission, calculation, attempt,
13446 position.signed_units > 0.0 ? compat::pine::Side::Long
13447 : (position.signed_units < 0.0 ? compat::pine::Side::Short
13449 current_position_cycle_,
13450 {context.coordinate.path_phase == NativePathPhase::None ? prices.close
13451 : require_host().current_execution_point()->price,
13452 prices.open, prices.high, prices.low});
13453 if (const auto* now = std::get_if<compat::pine::CloseNow>(&decision)) {
13454 execute_cap_close_now(*now);
13455 }
13456}
13457
13459 cap.source_batch_end();
13460}
13461
13462void PineExecutionAdapter::record_market_review(
13463 admission::Checkpoint checkpoint, int bar,
13464 const std::vector<native_order::RequestHandle>& handles) {
13465 std::vector<native_order::RequestHandle> selected;
13466 selected.reserve(handles.size());
13467 for (const auto& handle : handles) {
13468 const auto found = placement_.find(handle.incarnation);
13469 if (found == placement_.end()) continue;
13470 const auto& draft = found->second.market_admission;
13471 if (!draft.observation() || draft.review()) continue;
13472 const bool belongs = checkpoint == admission::Checkpoint::TerminalGross
13473 || (checkpoint == admission::Checkpoint::DefaultGross
13475 || (checkpoint == admission::Checkpoint::ExplicitPair
13477 if (belongs) selected.push_back(handle);
13478 }
13479 if (selected.empty()) return;
13480
13481 auto allocation = admission_journal.reserve();
13483 review.receipt = {allocation.sequence(), checkpoint, bar, 0};
13484 review.open_price = policy_script_bar_valid_ ? policy_script_bar_.open : kNaN;
13485 const auto physical = require_host().physical_position();
13486 review.position_side = physical.signed_units > 0.0
13487 ? static_cast<int>(PositionSide::LONG)
13488 : (physical.signed_units < 0.0 ? static_cast<int>(PositionSide::SHORT)
13489 : static_cast<int>(PositionSide::FLAT));
13490 review.position_cycle = current_position_cycle_;
13491 for (const auto& handle : selected) {
13492 auto found = placement_.find(handle.incarnation);
13493 if (found == placement_.end()) continue;
13494 auto& snapshot = found->second;
13495 const auto& origin = snapshot.market_admission.observation();
13496 if (!origin) continue;
13497 admission::BookObservation book;
13498 book.incarnation = handle.incarnation;
13499 book.priority = static_cast<std::int64_t>(snapshot.source_sequence);
13500 book.bar = snapshot.projection_created_bar;
13501 book.type = snapshot.family == PineOrderFamily::Order ? 3 : 0;
13502 book.placement_side = snapshot.projection_position_side;
13503 book.buy = snapshot.is_long;
13504 book.id = snapshot.source_id;
13505 book.oca_name = snapshot.oca_name;
13506 book.oca_type = snapshot.oca_type;
13507 book.birth = snapshot.birth;
13508 book.prices = {snapshot.exit_levels.limit, snapshot.exit_levels.stop,
13509 snapshot.exit_levels.trail_points,
13510 snapshot.exit_levels.trail_price,
13511 snapshot.exit_levels.trail_offset};
13512 book.draft = snapshot.market_admission;
13513 review.book.push_back(book);
13514 review.reviewed.push_back(book);
13515 snapshot.market_admission.reviewed(
13516 {allocation.sequence(), checkpoint, bar, origin->command});
13517 if (review.reviewed.size() == 1U) review.configuration = origin->configuration;
13518 }
13519 admission_journal.append(std::move(review));
13520}
13521
13522void PineExecutionAdapter::refresh_pending_sizing_after_margin(
13523 const native_order::ExecutionAppliedEvent& event,
13524 const NativeDecisionContext& context) {
13525 const double mark = policy_script_bar_valid_
13526 ? policy_script_bar_.close : event.resolved_price;
13527 const double marked_equity = percent_commission_live_equity(
13528 nearest_tick(mark, staged_.syminfo.mintick));
13529 const double active_fx = active_staged_fx(context.sub_bar_open_ms);
13530 const std::uint64_t cause_fill =
13531 static_cast<PineStrategyHost&>(require_host()).broker_fill_event_seq_;
13532 for (const auto& handle : live_handles_) {
13533 auto found = placement_.find(handle.incarnation);
13534 if (found == placement_.end()) continue;
13535 auto& snapshot = found->second;
13536 if (snapshot.projection_created_bar != context.coordinate.interval_index
13537 || !snapshot.market_admission.observation()) {
13538 continue;
13539 }
13540 const admission::SizingObservation before{
13541 snapshot.sizing.frozen_units, snapshot.sizing.equity,
13542 snapshot.sizing.price, snapshot.sizing.mark, snapshot.sizing.fx};
13543 const double affordability_before = snapshot.projection_affordability_equity;
13544 bool revised = false;
13545 const bool market_entry = snapshot.opening
13546 && (snapshot.family == PineOrderFamily::Entry
13547 || snapshot.family == PineOrderFamily::Order)
13548 && !finite_positive(snapshot.exit_levels.limit)
13549 && !finite_positive(snapshot.exit_levels.stop);
13550 if (market_entry && std::isfinite(snapshot.sizing.frozen_units)) {
13551 snapshot.sizing.equity = marked_equity;
13552 snapshot.sizing.fx = active_fx;
13553 snapshot.sizing.frozen_units = default_sizing_units(snapshot.sizing);
13554 revised = true;
13555 }
13556 if (market_entry
13557 && std::isfinite(snapshot.projection_affordability_equity)) {
13558 snapshot.projection_affordability_equity =
13559 require_host().native_marked_equity(mark);
13560 revised = true;
13561 }
13562 if (!revised || cause_fill == 0) continue;
13563
13564 const auto& origin = snapshot.market_admission.observation();
13565 auto allocation = admission_journal.reserve();
13566 admission::SizingEvent sizing;
13567 sizing.receipt = {allocation.sequence(), cause_fill,
13568 context.coordinate.interval_index, origin->command};
13569 sizing.incarnation = handle.incarnation;
13570 sizing.before = before;
13571 sizing.after = {snapshot.sizing.frozen_units, snapshot.sizing.equity,
13572 snapshot.sizing.price, snapshot.sizing.mark,
13573 snapshot.sizing.fx};
13574 sizing.affordability_equity_before = affordability_before;
13575 sizing.affordability_equity_after =
13576 snapshot.projection_affordability_equity;
13577 snapshot.market_admission.sizing_revised(sizing.receipt);
13578 admission_journal.append(std::move(sizing));
13579 }
13580}
13581
13582void PineExecutionAdapter::apply_open_market_admission(
13583 const NativeDecisionContext& context) {
13584 const bool began_flat = require_host().physical_position().signed_units == 0.0;
13585 const int source_bar = context.coordinate.interval_index - 1;
13586 struct Candidate {
13587 native_order::RequestHandle handle;
13588 const PlacementSnapshot* snapshot = nullptr;
13589 };
13590 std::vector<Candidate> market;
13591 bool foreign_live_order = false;
13592 // The historical placement scan is consumed only by the two-candidate
13593 // pair rule below; evaluate it there rather than on every broker open.
13594 const auto commands_on_bar = [&]() {
13595 std::size_t count = 0;
13596 for (const auto& row : placement_) {
13597 const auto& snapshot = row.second;
13598 if (snapshot.projection_created_bar != source_bar
13599 || (snapshot.family != PineOrderFamily::Entry
13600 && snapshot.family != PineOrderFamily::Order)) {
13601 continue;
13602 }
13603 ++count;
13604 }
13605 for (const auto& delayed : delayed_market_orders_) {
13606 if (delayed.snapshot.projection_created_bar == source_bar)
13607 ++count;
13608 }
13609 return count;
13610 };
13611 for (const auto& handle : live_handles_) {
13612 const auto found = placement_.find(handle.incarnation);
13613 if (found == placement_.end()) continue;
13614 const auto& snapshot = found->second;
13615 const bool unpriced_entry = snapshot.projection_created_bar == source_bar
13616 && snapshot.family == PineOrderFamily::Entry
13617 && !finite_positive(snapshot.exit_levels.limit)
13618 && !finite_positive(snapshot.exit_levels.stop)
13619 && !finite_positive(snapshot.exit_levels.trail_points)
13620 && !finite_positive(snapshot.exit_levels.trail_price)
13621 && !finite_positive(snapshot.exit_levels.trail_offset)
13622 && !snapshot.birth.from_fill() && !snapshot.birth.at_terminal_fill()
13623 && !compat::pine::historical_cascade_reach(snapshot.birth_reach);
13624 if (unpriced_entry) {
13625 market.push_back({handle, &snapshot});
13626 continue;
13627 }
13628 const bool same_bar_unpriced_close = snapshot.projection_created_bar == source_bar
13629 && snapshot.family == PineOrderFamily::Close
13630 && snapshot.oca_name.empty()
13631 && !finite_positive(snapshot.exit_levels.limit)
13632 && !finite_positive(snapshot.exit_levels.stop)
13633 && !finite_positive(snapshot.exit_levels.trail_points)
13634 && !finite_positive(snapshot.exit_levels.trail_price)
13635 && !finite_positive(snapshot.exit_levels.trail_offset)
13636 && !snapshot.birth.from_fill() && !snapshot.birth.at_terminal_fill()
13637 && !compat::pine::historical_cascade_reach(snapshot.birth_reach);
13638 if (!same_bar_unpriced_close) foreign_live_order = true;
13639 }
13640 const bool family_s_command_order =
13641 config_.default_qty_type == static_cast<int>(QtyType::FIXED)
13642 && config_.pyramiding == 1 && config_.slippage == 0
13643 && config_.commission_value == 0.0;
13644 std::stable_sort(market.begin(), market.end(), [&](const Candidate& left,
13645 const Candidate& right) {
13646 const std::uint64_t left_key = family_s_command_order
13647 ? left.snapshot->command_sequence : left.snapshot->source_sequence;
13648 const std::uint64_t right_key = family_s_command_order
13649 ? right.snapshot->command_sequence : right.snapshot->source_sequence;
13650 if (left_key != right_key) return left_key < right_key;
13651 if (left.snapshot->source_sequence != right.snapshot->source_sequence)
13652 return left.snapshot->source_sequence < right.snapshot->source_sequence;
13653 return left.handle.incarnation < right.handle.incarnation;
13654 });
13655
13656 std::vector<native_order::RequestHandle> cancellations;
13657 const auto cancel_later = [&](const native_order::RequestHandle& handle) {
13658 if (std::find(cancellations.begin(), cancellations.end(), handle)
13659 == cancellations.end()) {
13660 cancellations.push_back(handle);
13661 }
13662 };
13663
13664 if (market.size() == 2 && !foreign_live_order && commands_on_bar() == 2) {
13665 const auto& first = *market[0].snapshot;
13666 const auto& second = *market[1].snapshot;
13667 if (first.projection_predecessor == 0 && second.projection_predecessor == 0) {
13668 const bool default_pair =
13669 compat::pine::awaits_default_review(first.market_admission)
13670 && compat::pine::awaits_default_review(second.market_admission)
13671 && first.source_id != second.source_id
13672 && first.is_long != second.is_long;
13673 if (default_pair) {
13674 // ab9714be pine_fills.cpp:3005-3009: an earlier command that
13675 // was already at the entry cap contributes no broker movement
13676 // to the later call's all-in gross cost.
13677 const double first_units = first.projection_over_pyramiding
13678 ? 0.0 : first.sizing.frozen_units;
13679 const double first_margin = first.is_long
13680 ? config_.margin_long : config_.margin_short;
13681 const double second_margin = second.is_long
13682 ? config_.margin_long : config_.margin_short;
13683 const double required = first_units * first.sizing.price
13684 * staged_.syminfo.pointvalue * first.sizing.fx
13685 * first_margin / 100.0
13686 + second.sizing.frozen_units * second.sizing.price
13687 * staged_.syminfo.pointvalue * second.sizing.fx
13688 * second_margin / 100.0;
13689 const double equity = std::min(first.sizing.equity, second.sizing.equity);
13690 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
13691 if (std::isfinite(required) && std::isfinite(equity)
13692 && required > equity + guard) {
13693 cancel_later(market[1].handle);
13694 }
13695 } else if (began_flat && first.source_id != second.source_id
13696 && first.is_long != second.is_long && config_.pyramiding == 2
13697 && finite_positive(first.requested_qty)
13698 && finite_positive(second.requested_qty)
13699 && first.qty_type < 0 && second.qty_type < 0
13700 && first.oca_name.empty() && second.oca_name.empty()) {
13701 const double required = (first.requested_qty + second.requested_qty)
13702 * first.sizing.price * staged_.syminfo.pointvalue * first.sizing.fx;
13703 if (std::isfinite(required) && std::isfinite(first.sizing.equity)
13704 && required > first.sizing.equity) {
13705 cancel_later(market[1].handle);
13706 }
13707 }
13708 }
13709 }
13710
13711 // A same-side-at-placement request is executable only if an earlier
13712 // opposite command in this broker batch can move the account before its
13713 // turn. This retires the live-LONG pair's first no-op while preserving
13714 // the ordinary priced/raw/carried-book controls where the later request
13715 // becomes a reversal after its earlier sibling fills.
13716 for (std::size_t index = 0; index < market.size(); ++index) {
13717 if (!market[index].snapshot->projection_over_pyramiding) continue;
13718 bool earlier_opposite = false;
13719 for (std::size_t prior = 0; prior < index; ++prior) {
13720 if (market[prior].snapshot->is_long != market[index].snapshot->is_long) {
13721 earlier_opposite = true;
13722 break;
13723 }
13724 }
13725 if (!earlier_opposite) cancel_later(market[index].handle);
13726 }
13727 std::vector<native_order::RequestHandle> review_handles;
13728 review_handles.reserve(market.size());
13729 for (const auto& candidate : market) review_handles.push_back(candidate.handle);
13730 record_market_review(admission::Checkpoint::DefaultGross,
13731 context.coordinate.interval_index, review_handles);
13732 record_market_review(admission::Checkpoint::ExplicitPair,
13733 context.coordinate.interval_index, review_handles);
13734 for (const auto& handle : cancellations) {
13735 const auto result = require_host().cancel(handle);
13736 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
13737 }
13738}
13739
13740void PineExecutionAdapter::defer_open_marketable_sells(const Bar& bar) {
13741 if (config_.calc_on_order_fills || stream_mode_)
13742 return;
13743 if (require_host().physical_position().signed_units != 0.0) return;
13744 const auto native = require_host().native_state();
13745 const NativePathOrder path_order = native.spec ? native.spec->path_order
13746 : NativePathOrder::Auto;
13747 const bool high_first = source_path_high_first(bar, path_order);
13748 // ab9714be pine_fills.cpp:3687-3860 is not gated on process_orders_on_close.
13749 // Under POOC the fill point of a marketable order is the bar close
13750 // (pine_fills.cpp:7964-7965), not the open.
13751 const double fill_point_price = config_.process_orders_on_close ? bar.close : bar.open;
13752 struct Candidate {
13753 native_order::RequestHandle handle;
13754 const PlacementSnapshot* snapshot = nullptr;
13755 double path_position = 0.0;
13756 bool open_marketable = false;
13757 bool touched = false;
13758 };
13759 std::vector<Candidate> buys;
13760 std::vector<Candidate> sells;
13761 for (const auto& handle : live_handles_) {
13762 const auto found = placement_.find(handle.incarnation);
13763 if (found == placement_.end()) continue;
13764 const auto& snapshot = found->second;
13765 if (snapshot.family != PineOrderFamily::Entry
13766 || !finite_positive(snapshot.exit_levels.stop)
13767 || finite_positive(snapshot.exit_levels.limit)
13768 || finite_positive(snapshot.exit_levels.trail_points)
13769 || finite_positive(snapshot.exit_levels.trail_price)
13770 || finite_positive(snapshot.exit_levels.trail_offset)) {
13771 continue;
13772 }
13773 Candidate row;
13774 row.handle = handle;
13775 row.snapshot = &snapshot;
13776 row.open_marketable = pure_stop_entry_marketable_at(snapshot, fill_point_price);
13778 bar, high_first, snapshot.exit_levels.stop, snapshot.is_long,
13779 &row.path_position);
13780 if (snapshot.is_long) buys.push_back(row);
13781 else sells.push_back(row);
13782 }
13783 if (buys.empty() || sells.empty()) return;
13784 std::uint64_t min_open_buy_incarnation = std::numeric_limits<std::uint64_t>::max();
13785 bool open_buy = false;
13786 for (const auto& buy : buys) {
13787 if (!buy.open_marketable) continue;
13788 open_buy = true;
13789 min_open_buy_incarnation = std::min(min_open_buy_incarnation, buy.handle.incarnation);
13790 }
13791 if (!open_buy) return;
13792 std::vector<Candidate> deferred;
13793 bool deferred_open_sell = false;
13794 for (const auto& sell : sells) {
13795 if (sell.open_marketable && sell.handle.incarnation < min_open_buy_incarnation) {
13796 deferred.push_back(sell);
13797 deferred_open_sell = true;
13798 }
13799 }
13800 if (!deferred_open_sell) return;
13801 for (const auto& sell : sells) {
13802 if (sell.open_marketable) continue;
13803 // ab9714be: a resting stop the bar path never reaches is not an
13804 // order of this bar; it stays in the book for a later bar (L9d).
13805 if (!sell.touched) continue;
13806 deferred.push_back(sell);
13807 }
13808 std::stable_sort(deferred.begin(), deferred.end(),
13809 [](const Candidate& left, const Candidate& right) {
13810 return left.handle.incarnation < right.handle.incarnation;
13811 });
13812 deferred.erase(std::unique(deferred.begin(), deferred.end(),
13813 [](const Candidate& left, const Candidate& right) {
13814 return left.handle.incarnation == right.handle.incarnation;
13815 }),
13816 deferred.end());
13817 for (const auto& sell : deferred) {
13818 DeferredOpenMarketableSell row;
13819 row.snapshot = *sell.snapshot;
13820 row.replacement_key = sell.snapshot->source_id;
13821 row.fill_price = sell.open_marketable ? fill_point_price : sell.snapshot->exit_levels.stop;
13822 row.path_position = sell.path_position;
13823 row.open_marketable = sell.open_marketable;
13824 const auto result = require_host().cancel(sell.handle);
13825 if (result.status != native_order::CancelStatus::Cancelled) continue;
13826 retire(sell.handle);
13827 deferred_open_marketable_sells_.push_back(std::move(row));
13828 }
13829}
13830
13831void PineExecutionAdapter::admit_deferred_open_marketable_sells() {
13832 auto queued = std::move(deferred_open_marketable_sells_);
13833 deferred_open_marketable_sells_.clear();
13834 std::stable_sort(queued.begin(), queued.end(),
13835 [](const DeferredOpenMarketableSell& left,
13836 const DeferredOpenMarketableSell& right) {
13837 if (left.open_marketable != right.open_marketable)
13838 return !left.open_marketable && right.open_marketable;
13839 if (left.path_position != right.path_position)
13840 return left.path_position < right.path_position;
13841 return left.snapshot.command_sequence < right.snapshot.command_sequence;
13842 });
13843 for (auto& row : queued) {
13844 native_order::Request request;
13845 if (row.snapshot.deferred_cohort || !std::isfinite(row.snapshot.requested_qty)) {
13846 request.intent = native_order::HostSized{
13847 native_order::HostSizedKind::Open, native_order::Side::Short};
13848 } else {
13849 request.intent = native_order::Transact{-std::abs(row.snapshot.requested_qty)};
13850 }
13851 request.label = row.snapshot.source_id;
13852 request.comment = row.snapshot.comment;
13853 request.trigger = native_order::Market{};
13854 request.group = group_for(row.snapshot.oca_name, row.snapshot.oca_type);
13855 row.snapshot.forced_execution_price = row.fill_price;
13856 // ab9714be pine_fills.cpp:8517-8538: the second of a flat dual-stop
13857 // pair is a Transact against the already-opened side, not ReverseTo.
13858 row.snapshot.projection_after_close = true;
13859 row.snapshot.cancellation = {};
13860 row.snapshot.market_admission = {};
13861 const SourceId key = row.replacement_key;
13862 // ab9714be pine_fills.cpp:7483-7537: once a priced entry filled on
13863 // this bar, an entry that would open from flat is skipped for the
13864 // bar and keeps resting. Re-arm the original stop for the next bar
13865 // instead of filling it at the deferred price (L9d).
13866 const bool throttled_reopen =
13867 require_host().physical_position().signed_units == 0.0
13868 && entry_openings_this_interval_ > 0;
13869 if (throttled_reopen) {
13870 request.trigger = native_order::Stop{row.snapshot.exit_levels.stop};
13871 row.snapshot.forced_execution_price = kNaN;
13872 row.snapshot.projection_after_close = false;
13873 (void)submit_or_replace(std::move(request), std::move(row.snapshot), true, key);
13874 continue;
13875 }
13876 const auto accepted = submit_or_replace(
13877 std::move(request), std::move(row.snapshot), true, key);
13878 if (accepted) {
13879 (void)require_host().execute_current(
13880 {*accepted, NativeCurrentPriceRule::NearestTick});
13881 }
13882 }
13883}
13884
13885void PineExecutionAdapter::apply_reversal_gap_bracket_policy(
13886 const Bar& bar, const NativeDecisionContext& context, bool defer_trails) {
13887 const auto physical = require_host().physical_position();
13888 if (physical.signed_units == 0.0) return;
13889 const int source_bar = context.coordinate.interval_index - 1;
13890 bool opposite_market = false;
13891 bool gap_decline = false;
13892 for (const auto& handle : live_handles_) {
13893 const auto found = placement_.find(handle.incarnation);
13894 if (found == placement_.end()) continue;
13895 const auto& entry = found->second;
13896 const bool opposite = (physical.signed_units > 0.0) != entry.is_long;
13897 if (!opposite || entry.family != PineOrderFamily::Entry || !entry.opening
13898 || entry.projection_created_bar != source_bar
13899 || std::isfinite(entry.requested_qty)
13900 || finite_positive(entry.exit_levels.limit)
13901 || finite_positive(entry.exit_levels.stop)
13902 || config_.default_qty_type
13903 != static_cast<int>(QtyType::PERCENT_OF_EQUITY)
13904 || config_.default_qty_value > 100.0
13905 || !finite_positive(entry.sizing.frozen_units)
13906 || !finite_positive(entry.sizing.equity)
13907 || !finite_positive(entry.sizing.price)
13908 || !finite_positive(entry.sizing.fx)) {
13909 continue;
13910 }
13911 const double margin = entry.is_long ? config_.margin_long : config_.margin_short;
13912 if (!finite_positive(margin) || !finite_positive(staged_.syminfo.pointvalue))
13913 continue;
13914 const double notional_per_price = entry.sizing.frozen_units
13915 * staged_.syminfo.pointvalue * entry.sizing.fx;
13916 if (!finite_positive(notional_per_price)) continue;
13917
13918 // ab9714be pine_fills.cpp:5010-5164: a rule-5 signal-price whole
13919 // rejection leaves the held position's bracket alone. Only a call
13920 // which passed that placement boundary and was then refused by the
13921 // adverse opening gap owns the declined-reversal bracket suspension.
13922 const bool money_scope = staged_.quantity_grid
13923 && *staged_.quantity_grid > 0.0 && *staged_.quantity_grid < 1.0
13924 && staged_.syminfo.pointvalue == 1.0 && entry.sizing.fx == 1.0
13925 && staged_.account_fx_effective_from_ms.empty()
13926 && config_.commission_value == 0.0 && config_.slippage == 0
13927 && !config_.process_orders_on_close && !config_.calc_on_order_fills
13928 && !stream_mode_;
13929 double affordable_price = kNaN;
13930 if (money_scope) {
13931 const double rounded_cost = source_money_round(
13932 notional_per_price * entry.sizing.price);
13933 affordable_price = source_money_round(
13934 source_money_round(entry.sizing.equity) / notional_per_price);
13935 if (entry.sizing.equity + 1e-9 < rounded_cost
13936 || (std::isfinite(affordable_price)
13937 && affordable_price < entry.sizing.price)) {
13938 continue;
13939 }
13940 }
13941 opposite_market = true;
13942 const double fill = source_bar_fill_tick(bar.open, staged_.syminfo.mintick)
13943 + (entry.is_long ? 1.0 : -1.0)
13944 * config_.slippage * staged_.syminfo.mintick;
13945 const double required = notional_per_price * fill * margin / 100.0;
13946 const double guard = std::max(1e-9, std::abs(entry.sizing.equity) * 1e-12);
13947 const bool price_band_admitted = money_scope
13948 && std::isfinite(affordable_price) && affordable_price >= fill;
13949 if (std::isfinite(required)
13950 && required > entry.sizing.equity + guard
13951 && !price_band_admitted) {
13952 gap_decline = true;
13953 break;
13954 }
13955 }
13956 if (!opposite_market) return;
13957
13958 const double held_margin = physical.signed_units > 0.0
13959 ? config_.margin_long : config_.margin_short;
13960 const double open_fill = source_bar_fill_tick(bar.open, staged_.syminfo.mintick);
13961 const double open_fx = active_staged_fx(bar.timestamp);
13962 const double held_required = std::abs(physical.signed_units) * open_fill
13963 * staged_.syminfo.pointvalue * open_fx * held_margin / 100.0;
13964 const double held_equity = require_host().native_marked_equity(open_fill);
13965 const bool opening_margin_slice = source_margin_call_enabled_
13966 && finite_positive(held_margin) && std::isfinite(held_required)
13967 && std::isfinite(held_equity) && held_required > held_equity;
13968
13969 struct RetiredLeg {
13970 native_order::RequestHandle handle;
13971 std::optional<PendingBracketLeg> delayed_leg;
13972 std::optional<PlacementSnapshot> margin_revival;
13973 bool release_at_open = false;
13974 };
13975 std::vector<RetiredLeg> retired_legs;
13976 for (const auto& handle : live_handles_) {
13977 const auto found = placement_.find(handle.incarnation);
13978 if (found == placement_.end()) continue;
13979 const auto& leg = found->second;
13980 const bool stop_or_limit = leg.family == PineOrderFamily::ExitStop
13981 || leg.family == PineOrderFamily::ExitLimit;
13982 double trail_activation = leg.exit_levels.trail_price;
13983 if (!finite_positive(trail_activation)
13984 && finite_positive(leg.exit_levels.trail_points)
13985 && finite_positive(staged_.syminfo.mintick)) {
13986 trail_activation = require_host().position_avg_price()
13987 + (physical.signed_units > 0.0 ? 1.0 : -1.0)
13988 * leg.exit_levels.trail_points * staged_.syminfo.mintick;
13989 trail_activation = directional_tick(
13990 trail_activation, staged_.syminfo.mintick,
13991 physical.signed_units > 0.0);
13992 }
13993 const bool omitted_offset_trail = leg.family == PineOrderFamily::ExitTrail
13994 && !std::isfinite(leg.exit_levels.trail_offset)
13995 && finite_positive(trail_activation);
13996 const bool exit_is_buy = physical.signed_units < 0.0;
13997 const double priced_level = leg.family == PineOrderFamily::ExitStop
13998 ? leg.exit_levels.stop : leg.exit_levels.limit;
13999 const bool gapped_priced_leg = stop_or_limit && finite_positive(priced_level)
14000 && (physical.signed_units > 0.0
14001 ? (leg.family == PineOrderFamily::ExitStop
14002 ? bar.open <= priced_level : bar.open >= priced_level)
14003 : (leg.family == PineOrderFamily::ExitStop
14004 ? bar.open >= priced_level : bar.open <= priced_level));
14005 const bool reorder_priced_leg = gapped_priced_leg
14006 && (!gap_decline || opening_margin_slice);
14007 // A declined reversal makes a standing non-gapped bracket dormant; it
14008 // does not delete it. Only a leg already marketable at this opening
14009 // needs the L5b reorder/retirement path. The broader condition erased
14010 // REVIVE-B's later margin restoration before the margin event existed.
14011 const bool retire_priced_leg = gapped_priced_leg
14012 && (gap_decline || reorder_priced_leg);
14013 const bool retire_trail = gap_decline && omitted_offset_trail;
14014 if ((!retire_priced_leg && !retire_trail)
14015 || (!leg.from_entry.empty()
14016 && !(cohort_exposure_for(leg.from_entry) > 0.0))) {
14017 continue;
14018 }
14019 std::optional<PendingBracketLeg> delayed;
14020 std::optional<PlacementSnapshot> margin_revival;
14021 bool release_at_open = false;
14022 if (reorder_priced_leg) {
14023 native_order::Request request;
14024 request.intent = native_order::HostSized{
14025 native_order::HostSizedKind::Close, std::nullopt};
14026 request.label = leg.source_id;
14027 request.comment = leg.comment;
14028 // Defer the gapped bracket until the first later waypoint so the
14029 // already-accepted opposite MARKET is adjudicated at O first.
14030 // The source report still settles at the saved gap-open price.
14031 const double deferred_level = physical.signed_units > 0.0
14032 ? bar.low : bar.high;
14033 request.trigger = native_order::Stop{source_trigger_threshold(
14034 deferred_level, staged_.syminfo.mintick, exit_is_buy, false)};
14035 request.owner = owner_for_close(leg.from_entry, true);
14036 const std::string group_name = leg.oca_name.empty()
14037 ? leg.source_id + "\x1f" + leg.from_entry : leg.oca_name;
14038 request.group = group_for(group_name, 1);
14039 const std::string replacement_key = leg.source_id + "\x1f"
14040 + leg.from_entry + std::to_string(static_cast<int>(leg.family));
14041 PlacementSnapshot reordered = leg;
14042 reordered.forced_execution_price = open_fill;
14043 delayed.emplace(PendingBracketLeg{
14044 std::move(request), std::move(reordered), replacement_key,
14045 key_for(leg.source_id, leg.from_entry)});
14046 release_at_open = true;
14047 } else if (retire_trail) {
14048 const bool long_position = physical.signed_units > 0.0;
14049 const bool activated_at_open = long_position
14050 ? bar.open >= trail_activation
14051 : bar.open <= trail_activation;
14052 if (!activated_at_open) {
14053 native_order::Request request;
14054 request.intent = native_order::HostSized{
14055 native_order::HostSizedKind::Close, std::nullopt};
14056 request.label = leg.source_id;
14057 request.comment = leg.comment;
14058 request.trigger = native_order::Limit{
14059 directional_tick(trail_activation,
14060 staged_.syminfo.mintick,
14061 exit_is_buy)};
14062 request.owner = owner_for_close(leg.from_entry, true);
14063 const std::string group_name = leg.oca_name.empty()
14064 ? leg.source_id + "\x1f" + leg.from_entry : leg.oca_name;
14065 request.group = group_for(group_name, 1);
14066 const std::string replacement_key = leg.source_id + "\x1f"
14067 + leg.from_entry
14068 + std::to_string(static_cast<int>(PineOrderFamily::ExitTrail));
14069 delayed.emplace(PendingBracketLeg{
14070 std::move(request), leg, replacement_key,
14071 key_for(leg.source_id, leg.from_entry)});
14072 }
14073 }
14074 if (gap_decline && leg.family == PineOrderFamily::ExitStop
14075 && !reorder_priced_leg) {
14076 margin_revival = leg;
14077 }
14078 retired_legs.push_back({handle, std::move(delayed),
14079 std::move(margin_revival), release_at_open});
14080 }
14081 for (auto& retired : retired_legs) {
14082 const auto result = require_host().cancel(retired.handle);
14083 if (result.status != native_order::CancelStatus::Cancelled) continue;
14084 if (const auto found = placement_.find(retired.handle.incarnation);
14085 found != placement_.end()) {
14086 found->second.cancellation = {
14088 retired.handle.incarnation,
14089 static_cast<std::int64_t>(found->second.source_sequence),
14090 retired.handle.incarnation, found->second.placement_cycle,
14091 found->second.legs.revision(), found->second.requested_qty, kNaN};
14092 }
14093 retire(retired.handle);
14094 if (retired.margin_revival) {
14095 pending_margin_revivals_.push_back({
14096 std::move(*retired.margin_revival),
14097 context.coordinate.interval_index});
14098 }
14099 if (retired.delayed_leg) {
14100 if (retired.release_at_open) {
14101 auto reordered = std::move(*retired.delayed_leg);
14102 const auto accepted = submit_or_replace(
14103 std::move(reordered.request), std::move(reordered.snapshot),
14104 false, reordered.replacement_key);
14105 if (accepted)
14106 bracket_families_[reordered.family_key].push_back(*accepted);
14107 } else if (defer_trails) {
14108 auto delayed = std::move(*retired.delayed_leg);
14109 delayed_market_orders_.push_back({
14110 std::move(delayed.request), std::move(delayed.snapshot),
14111 std::move(delayed.replacement_key), broker_open_epoch_ + 1U});
14112 } else {
14113 pending_bracket_legs_.push_back(std::move(*retired.delayed_leg));
14114 }
14115 }
14116 }
14117}
14118
14119void PineExecutionAdapter::reaccept_gapped_bracket_behind_same_id_add(
14120 const Bar& bar, const NativeDecisionContext& context) {
14121 // ab9714be pine_fills.cpp:3799-3845 (KI-62, samebar_add_exit_first): a
14122 // from_entry PRICED bracket leg gapped through at the open and its own
14123 // same-id pure MARKET pyramid add both fill at the opening tick, where
14124 // the owner's priority is buy-market(1) > sell-market(2) > gapped
14125 // limit(3); a long's gapped stop is a sell (2), a short's a buy (1).
14126 // Unless the leg strictly precedes, the add is judged first against the
14127 // still-open position (pyramiding cap, pine_orders.cpp:738, and margin)
14128 // and the leg then closes the id, covering the add
14129 // (pine_fills.cpp:7026-7033). The native matcher breaks a same-point tie
14130 // by incarnation, so the older leg would flatten the book and the add
14131 // would open from flat. Re-accept the unchanged leg at this opening: its
14132 // fresh incarnation orders it behind the add at the same open point.
14133 if (config_.calc_on_order_fills || config_.process_orders_on_close
14134 || stream_mode_ || coof_recalc_active_ || context.sub_index != 0) {
14135 return;
14136 }
14137 const auto physical = require_host().physical_position();
14138 if (physical.signed_units == 0.0) return;
14139 const bool long_position = physical.signed_units > 0.0;
14140 const auto pure_market_add = [&](const PlacementSnapshot& add,
14141 const SourceId& from_entry) {
14142 return add.opening
14143 && (add.family == PineOrderFamily::Entry
14144 || add.family == PineOrderFamily::Order)
14145 && add.source_id == from_entry && add.is_long == long_position
14146 && !finite_positive(add.exit_levels.limit)
14147 && !finite_positive(add.exit_levels.stop)
14148 && !finite_positive(add.exit_levels.trail_points)
14149 && !finite_positive(add.exit_levels.trail_price)
14150 && !finite_positive(add.exit_levels.trail_offset)
14151 && !add.birth.from_fill() && !add.birth.at_terminal_fill();
14152 };
14153 std::vector<native_order::RequestHandle> gapped;
14154 for (const auto& handle : live_handles_) {
14155 const auto found = placement_.find(handle.incarnation);
14156 if (found == placement_.end()) continue;
14157 const auto& leg = found->second;
14158 if ((leg.family != PineOrderFamily::ExitStop
14159 && leg.family != PineOrderFamily::ExitLimit)
14160 || leg.from_entry.empty() || !leg.deferred_cohort
14161 || std::isfinite(leg.requested_qty)
14162 || (std::isfinite(leg.qty_percent) && leg.qty_percent < 100.0 - 1e-9)
14163 || finite_positive(leg.exit_levels.trail_points)
14164 || finite_positive(leg.exit_levels.trail_price)
14165 || finite_positive(leg.exit_levels.trail_offset)
14166 || !(cohort_exposure_for(leg.from_entry) > 0.0)) {
14167 continue;
14168 }
14169 int exit_priority = 0;
14170 if (leg.family == PineOrderFamily::ExitStop && finite_positive(leg.exit_levels.stop)
14171 && (long_position ? bar.open <= leg.exit_levels.stop
14172 : bar.open >= leg.exit_levels.stop)) {
14173 exit_priority = long_position ? 2 : 1;
14174 } else if (leg.family == PineOrderFamily::ExitLimit
14175 && finite_positive(leg.exit_levels.limit)
14176 && (long_position ? bar.open >= leg.exit_levels.limit
14177 : bar.open <= leg.exit_levels.limit)) {
14178 exit_priority = 3;
14179 }
14180 const int add_priority = long_position ? 1 : 2;
14181 if (exit_priority == 0 || exit_priority < add_priority) continue;
14182 const bool add_waits = std::any_of(live_handles_.begin(), live_handles_.end(),
14183 [&](const native_order::RequestHandle& live) {
14184 if (live.incarnation < handle.incarnation) return false;
14185 const auto add = placement_.find(live.incarnation);
14186 return add != placement_.end()
14187 && pure_market_add(add->second, leg.from_entry);
14188 });
14189 if (add_waits) gapped.push_back(handle);
14190 }
14191 const bool exit_is_buy = !long_position;
14192 const double tick = staged_.syminfo.mintick;
14193 for (const auto& handle : gapped) {
14194 const auto found = placement_.find(handle.incarnation);
14195 if (found == placement_.end()) continue;
14196 PlacementSnapshot leg = found->second;
14197 const auto result = require_host().cancel(handle);
14198 if (result.status != native_order::CancelStatus::Cancelled) continue;
14199 found->second.cancellation = {
14200 PineCancellationCause::Dependency, 1, 0, handle.incarnation,
14201 static_cast<std::int64_t>(leg.source_sequence), handle.incarnation,
14202 leg.placement_cycle, leg.legs.revision(), leg.requested_qty, kNaN};
14203 retire(handle);
14204 native_order::Request request;
14205 request.intent = native_order::HostSized{
14206 native_order::HostSizedKind::Close, std::nullopt};
14207 request.label = leg.source_id;
14208 request.comment = leg.comment;
14209 if (leg.family == PineOrderFamily::ExitStop) {
14210 request.trigger = native_order::Stop{source_trigger_threshold(
14211 leg.exit_levels.stop, tick, exit_is_buy, false)};
14212 } else {
14213 const double snapped = nearest_tick(leg.exit_levels.limit, tick);
14214 request.trigger = native_order::Limit{
14215 !finite_positive(tick) || snapped == leg.exit_levels.limit
14216 ? leg.exit_levels.limit
14217 : source_trigger_threshold(leg.exit_levels.limit, tick,
14218 exit_is_buy, true)};
14219 // A limit accepted at this opening would book its own level; the
14220 // owner's gapped limit fills limit-or-better at the open print.
14221 leg.forced_execution_price = source_bar_fill_tick(bar.open, tick);
14222 }
14223 request.owner = owner_for_close(leg.from_entry, true);
14224 const std::string group_name = leg.oca_name.empty()
14225 ? leg.source_id + "\x1f" + leg.from_entry : leg.oca_name;
14226 request.group = group_for(group_name, 1, static_cast<std::int64_t>(leg.family));
14227 leg.cancellation = {};
14228 const std::uint64_t family_key = key_for(leg.source_id, leg.from_entry);
14229 const SourceId replacement_key = leg.source_id + "\x1f" + leg.from_entry
14230 + std::to_string(static_cast<int>(leg.family));
14231 const auto accepted = submit_or_replace(
14232 std::move(request), std::move(leg), false, replacement_key);
14233 if (accepted) bracket_families_[family_key].push_back(*accepted);
14234 }
14235}
14236
14237void PineExecutionAdapter::apply_terminal_explicit_market_policy(
14238 const NativeDecisionContext& context) {
14239 if (!config_.process_orders_on_close
14240 || config_.pyramiding != 0 || stream_mode_) {
14241 return;
14242 }
14243 struct Candidate {
14244 native_order::RequestHandle handle;
14245 PlacementSnapshot snapshot;
14246 std::uint64_t priority = 0;
14247 };
14248 std::vector<Candidate> candidates;
14249 for (const auto& handle : live_handles_) {
14250 const auto found = placement_.find(handle.incarnation);
14251 if (found == placement_.end()) continue;
14252 const auto& row = found->second;
14253 if (!row.opening || row.family != PineOrderFamily::Entry
14254 || row.projection_created_bar != context.coordinate.interval_index
14255 || !finite_positive(row.requested_qty)
14256 || finite_positive(row.exit_levels.limit)
14257 || finite_positive(row.exit_levels.stop)
14258 || !row.oca_name.empty() || row.oca_type != 0
14259 || row.birth.from_fill() || row.birth.at_terminal_fill()
14260 || compat::pine::historical_cascade_reach(row.birth_reach)) {
14261 continue;
14262 }
14263 const PlacementSnapshot* origin = &row;
14264 std::unordered_set<std::uint64_t> seen;
14265 while (origin->projection_predecessor != 0
14266 && seen.insert(origin->projection_predecessor).second) {
14267 const auto prior = placement_.find(origin->projection_predecessor);
14268 if (prior == placement_.end()) break;
14269 origin = &prior->second;
14270 }
14271 candidates.push_back({handle, row, origin->source_sequence});
14272 }
14273 if (candidates.size() < 2) return;
14274 std::stable_sort(candidates.begin(), candidates.end(),
14275 [](const Candidate& left, const Candidate& right) {
14276 if (left.priority != right.priority) return left.priority < right.priority;
14277 return left.snapshot.source_sequence < right.snapshot.source_sequence;
14278 });
14279 std::vector<native_order::RequestHandle> terminal_review_handles;
14280 terminal_review_handles.reserve(candidates.size());
14281 for (const auto& candidate : candidates)
14282 terminal_review_handles.push_back(candidate.handle);
14283
14284 const auto& first = candidates[0].snapshot;
14285 const auto& second = candidates[1].snapshot;
14286 const bool clean_pair = candidates.size() == 2
14287 && config_.calc_on_order_fills && config_.slippage == 0
14288 && config_.commission_value == 0.0
14289 && config_.margin_long == 100.0 && config_.margin_short == 100.0
14290 && entry_attempt_bar_ == context.coordinate.interval_index
14291 && entry_attempts_on_bar_ == 2
14292 && first.projection_predecessor == 0 && second.projection_predecessor == 0
14293 && first.recreated_after_named_cancelled_entry_incarnation == 0
14294 && second.recreated_after_named_cancelled_entry_incarnation == 0
14295 && first.source_id != second.source_id && first.is_long != second.is_long
14296 && compat::pine::explicit_qualification(first.market_admission)
14297 && compat::pine::explicit_qualification(second.market_admission);
14298 if (clean_pair) {
14299 const double required = (first.requested_qty + second.requested_qty)
14300 * second.sizing.price * staged_.syminfo.pointvalue * second.sizing.fx;
14301 const double equity = std::min(first.sizing.equity, second.sizing.equity);
14302 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
14303 if (std::isfinite(required) && std::isfinite(equity)
14304 && required > equity + guard) {
14305 const auto result = require_host().cancel(candidates[1].handle);
14306 if (result.status == native_order::CancelStatus::Cancelled)
14307 retire(candidates[1].handle);
14308 candidates.resize(1);
14309 }
14310 }
14311 record_market_review(admission::Checkpoint::TerminalGross,
14312 context.coordinate.interval_index,
14313 terminal_review_handles);
14314
14315 // ab9714be pine_fills.cpp:3023-3270 and pine_orders.cpp:193-276:
14316 // outside the exact gross-decline book, explicit opposite entry calls
14317 // retain source order and each later opposite call is a full ReverseTo,
14318 // not a pair of net Transact deltas from the shared flat placement state.
14319 for (const auto& candidate : candidates) {
14320 const auto result = require_host().cancel(candidate.handle);
14321 if (result.status == native_order::CancelStatus::Cancelled)
14322 retire(candidate.handle);
14323 }
14324 int simulated_sign = 0;
14325 for (auto& candidate : candidates) {
14326 native_order::Request request;
14327 const int requested_sign = candidate.snapshot.is_long ? 1 : -1;
14328 const double signed_units = requested_sign * candidate.snapshot.requested_qty;
14329 if (simulated_sign != 0 && simulated_sign != requested_sign) {
14330 request.intent = native_order::ReverseTo{signed_units};
14331 candidate.snapshot.reverse_to = true;
14332 } else {
14333 request.intent = native_order::Transact{signed_units};
14334 candidate.snapshot.reverse_to = false;
14335 }
14336 request.label = candidate.snapshot.source_id;
14337 request.comment = candidate.snapshot.comment;
14338 const SourceId replacement_key = candidate.snapshot.source_id;
14339 candidate.snapshot.market_admission = {};
14340 const auto accepted = submit_or_replace(
14341 std::move(request), std::move(candidate.snapshot), true,
14342 replacement_key);
14343 if (accepted) {
14344 simulated_sign = requested_sign;
14345 (void)require_host().execute_current(
14346 {*accepted, NativeCurrentPriceRule::NearestTick});
14347 }
14348 }
14349}
14350
14352 // Terminal entry refusals have no Applied notification. Consume their
14353 // generic receipt before the next matching point so their deferred
14354 // per-origin bracket legs cannot close a different cohort member.
14356 trail_state_at_open_.clear();
14357 for (const auto& handle : live_handles_) {
14358 const auto placement = placement_.find(handle.incarnation);
14359 if (placement == placement_.end()
14360 || placement->second.family != PineOrderFamily::ExitTrail) {
14361 continue;
14362 }
14363 if (const auto state = require_host().trail_state(handle))
14364 trail_state_at_open_.emplace(handle.incarnation, *state);
14365 }
14366 // The preceding source broker batch is complete at this next opening.
14367 // This is deliberately after any POOC after-calculation matching of the
14368 // prior script bar, so a same-batch cap transfer remains available to its
14369 // designated sibling.
14371 if (context.coordinate.interval_index != entry_openings_interval_index_) {
14372 entry_openings_interval_index_ = context.coordinate.interval_index;
14373 entry_openings_this_interval_ = 0;
14374 }
14375 if (context.script_bar_open_ms != last_broker_open_ms_) {
14376 last_broker_open_ms_ = context.script_bar_open_ms;
14377 ++broker_open_epoch_;
14378 }
14379 // ab9714be prearmed pending-entry legs become executable at the next
14380 // broker opening, before that bar's path is matched. Releasing them only
14381 // from the later source close callback misses the intended bar.
14382 release_delayed_orders(/*explicit_brackets_only=*/true, bar.open);
14383 activate_short_seed_plan_at_open(context);
14384 update_l4c_priority();
14385 apply_open_market_admission(context);
14386 defer_open_marketable_sells(bar);
14387 reaccept_gapped_bracket_behind_same_id_add(bar, context);
14388 source_shadow_pending_.clear();
14389 coof_script_bar_ = bar;
14390 coof_script_bar_valid_ = true;
14391 policy_script_bar_ = bar;
14392 policy_script_bar_valid_ = true;
14393 // The C observer snapshots the ordinary flat two-stop arbitration at the
14394 // bar boundary, before either native request can fill or be declined.
14395 // COOF has its own callback scheduling and deliberately leaves this
14396 // ordinary-path projection untouched, matching the legacy contract.
14397 last_bar_dual_entry_path_ = 0;
14398 last_bar_dual_entry_script_open_ms_ = context.script_bar_open_ms;
14399 if (!config_.calc_on_order_fills && require_host().physical_position().signed_units == 0.0) {
14400 std::vector<const PlacementSnapshot*> stops;
14401 for (const auto& handle : live_handles_) {
14402 const auto found = placement_.find(handle.incarnation);
14403 if (found == placement_.end()) continue;
14404 const auto& snapshot = found->second;
14405 if (snapshot.family != PineOrderFamily::Entry
14406 || !finite_positive(snapshot.exit_levels.stop)
14407 || std::isfinite(snapshot.exit_levels.limit)
14408 || finite_positive(snapshot.exit_levels.trail_offset)) {
14409 continue;
14410 }
14411 stops.push_back(&snapshot);
14412 }
14413 if (stops.size() == 2 && stops[0]->is_long != stops[1]->is_long) {
14414 const auto* long_stop = stops[0]->is_long ? stops[0] : stops[1];
14415 const auto* short_stop = stops[0]->is_long ? stops[1] : stops[0];
14416 const bool long_touched = bar.high >= long_stop->exit_levels.stop;
14417 const bool short_touched = bar.low <= short_stop->exit_levels.stop;
14418 if (long_touched && short_touched) {
14419 const bool high_first = path_order_ == NativePathOrder::HighFirst
14420 || (path_order_ == NativePathOrder::Auto
14421 && std::abs(bar.high - bar.open)
14422 <= std::abs(bar.open - bar.low));
14423 last_bar_dual_entry_path_ = high_first ? 1 : 2;
14424 }
14425 }
14426 }
14427 flush_coof_tail(/*openings_only=*/false, /*include_next_open=*/true);
14428 suspend_coof_declined_reversal_at_open(bar, context);
14429 if (close_all_pending_script_bar_ != context.script_bar_open_ms)
14430 close_all_pending_script_bar_ = std::numeric_limits<std::int64_t>::min();
14431 pooc_open_script_bar_ = context.script_bar_open_ms;
14432 pooc_open_basis_ = std::abs(require_host().physical_position().signed_units);
14433 day_ledger_.current_day = chart_day_key(context.sub_bar_open_ms);
14434 if (day_ledger_.intraday_loss_day != day_ledger_.current_day) {
14435 day_ledger_.intraday_loss_day = day_ledger_.current_day;
14436 day_ledger_.intraday_start_equity = require_host().native_marked_equity(bar.open);
14437 day_ledger_.intraday_realized = 0.0;
14438 }
14439 execute_due_cap_close(context);
14440 apply_fx_open_margin_slice(bar, context);
14441 (void)submit_slipped_pooc_opening_money_call(bar, context);
14442 const auto opening_position = require_host().physical_position();
14443 const bool long_full_margin = opening_position.signed_units > 0.0
14444 && std::abs(config_.margin_long - 100.0) < 1e-12;
14445 bool marketable_limit_at_open = false;
14446 if (long_full_margin) {
14447 for (const auto& handle : live_handles_) {
14448 const auto found = placement_.find(handle.incarnation);
14449 if (found == placement_.end()) continue;
14450 const auto& candidate = found->second;
14451 const bool exit = candidate.family == PineOrderFamily::ExitLimit
14452 || candidate.family == PineOrderFamily::ExitStop
14453 || candidate.family == PineOrderFamily::ExitTrail;
14454 if (exit && std::isfinite(candidate.exit_levels.limit)
14455 && bar.open >= candidate.exit_levels.limit) {
14456 marketable_limit_at_open = true;
14457 break;
14458 }
14459 }
14460 }
14461 if (long_full_margin && !marketable_limit_at_open
14462 && position_open_script_bar_ != context.script_bar_open_ms) {
14463 const Bar open_only{bar.open, bar.open, bar.open, bar.open, 0.0,
14464 bar.timestamp};
14465 (void)submit_tv_money_long_margin_call(open_only, context);
14466 if (last_margin_call_script_bar_ != context.script_bar_open_ms)
14467 (void)schedule_tv_money_long_margin_before_trail(bar, context);
14468 }
14469 if (!long_full_margin && staged_.account_fx_effective_from_ms.empty()) {
14470 const double held_at_open = std::abs(opening_position.signed_units);
14471 bool opposite_entry_waits = false;
14472 bool whole_market_close_waits = false;
14473 for (const auto& handle : live_handles_) {
14474 const auto found = placement_.find(handle.incarnation);
14475 if (found == placement_.end()) continue;
14476 const auto& pending = found->second;
14477 if (pending.opening
14478 && (pending.family == PineOrderFamily::Entry
14479 || pending.family == PineOrderFamily::Order)
14480 && pending.projection_created_bar < context.coordinate.interval_index
14481 && pending.is_long != (opening_position.signed_units > 0.0)) {
14482 opposite_entry_waits = true;
14483 }
14484 const bool market_close = pending.family == PineOrderFamily::Close
14485 || pending.family == PineOrderFamily::CloseAll;
14486 if (!market_close
14487 || pending.projection_created_bar >= context.coordinate.interval_index
14488 || finite_positive(pending.exit_levels.limit)
14489 || finite_positive(pending.exit_levels.stop)
14490 || finite_positive(pending.exit_levels.trail_offset)) {
14491 continue;
14492 }
14493 const double closing = std::isfinite(pending.projection_remaining_qty)
14494 ? pending.projection_remaining_qty
14495 : (std::isfinite(pending.requested_qty)
14496 ? std::abs(pending.requested_qty)
14497 : (std::isfinite(pending.qty_percent)
14498 && pending.qty_percent >= 100.0 - 1e-9
14499 ? held_at_open : 0.0));
14500 const double owned = pending.from_entry.empty()
14501 ? held_at_open : cohort_exposure_for(pending.from_entry);
14502 if (pending.family == PineOrderFamily::CloseAll
14503 || (owned >= held_at_open - 1e-10
14504 && closing >= held_at_open - 1e-10)) {
14505 whole_market_close_waits = true;
14506 }
14507 }
14508 // ab9714be pine_fills.cpp:2462-2523: an unconditional whole close
14509 // resting for this opening fills before the open margin checkpoint.
14510 // A close paired with an opposite entry is conditional on that entry's
14511 // admission and therefore does not suppress the slice.
14512 whole_market_close_waits = whole_market_close_waits
14513 && !opposite_entry_waits;
14514 const double opening_mark = nearest_tick(bar.open, staged_.syminfo.mintick);
14515 const bool opening_margin_applied =
14516 !whole_market_close_waits
14517 && submit_margin_call_slice(opening_mark, context);
14518 // pine_fills.cpp:2525-2678 gives an opening slice priority over the
14519 // remaining path. The surviving book is then evaluated over the
14520 // suffix: a restored bracket at an earlier level wins naturally, while
14521 // an unprotected position can take a second slice at the adverse
14522 // extreme on the same bar.
14523 const bool declined_reversal = declined_reversal_at_open(bar);
14524 bool margin_scheduled = false;
14525 if (!opening_margin_applied
14526 && !defer_rounded_pooc_short_margin_until_close(bar)
14527 && (!whole_market_close_waits || declined_reversal)) {
14528 margin_scheduled = schedule_margin_call_path(bar, context);
14529 }
14530 if (declined_reversal && !opening_margin_applied) {
14531 defer_declined_reversal_exits_at_adverse(
14532 bar, context, margin_scheduled);
14533 }
14534 }
14535 (void)submit_intraday_loss_close(bar.open, context, true);
14536 schedule_intraday_loss_path(bar, context);
14537 schedule_preopen_margin_slice(bar, context);
14538 if (context.sub_index == 0)
14539 cap.ordinary_open(context.coordinate.interval_index);
14540}
14541
14543 const Bar& tick, const NativeTickContext& context) {
14544 // A realtime print is a current generic decision point. The source
14545 // policy owns the financial threshold; the native request core still
14546 // owns request acceptance, settlement, receipts and any later matching.
14547 (void)submit_margin_call_slice(tick.close, context.decision);
14548}
14549
14550void PineExecutionAdapter::rearm_throttled_reopens() {
14551 auto queued = std::move(throttled_reopen_rearm_);
14552 throttled_reopen_rearm_.clear();
14553 const auto physical = require_host().physical_position();
14554 for (auto& snapshot : queued) {
14555 native_order::Request request;
14556 if (snapshot.deferred_cohort || !std::isfinite(snapshot.requested_qty)) {
14558 native_order::HostSizedKind::Open,
14559 snapshot.is_long ? native_order::Side::Long : native_order::Side::Short};
14560 } else {
14561 const double units = std::abs(snapshot.requested_qty);
14562 request.intent = native_order::Transact{snapshot.is_long ? units : -units};
14563 }
14564 request.label = snapshot.source_id;
14565 request.comment = snapshot.comment;
14566 request.trigger = native_order::Stop{snapshot.exit_levels.stop};
14567 request.group = group_for(snapshot.oca_name, snapshot.oca_type);
14568 const bool same_dir = physical.signed_units != 0.0
14569 && ((physical.signed_units > 0.0) == snapshot.is_long);
14570 const bool already_touched = policy_script_bar_valid_
14571 && finite_positive(snapshot.exit_levels.stop)
14572 && (snapshot.is_long
14573 ? policy_script_bar_.high >= snapshot.exit_levels.stop
14574 : policy_script_bar_.low <= snapshot.exit_levels.stop);
14575 // The kernel already walked past the nearer stop. Keep the owner's
14576 // fill price (the stop level) instead of the current path quote.
14577 // ab9714be pine_fills.cpp:8008-8018: round_to_mintick_directional rounds touched stop execution price to tick
14578 snapshot.forced_execution_price = (same_dir && already_touched)
14579 ? (finite_positive(staged_.syminfo.mintick)
14580 ? directional_tick(snapshot.exit_levels.stop, staged_.syminfo.mintick, snapshot.is_long)
14581 : snapshot.exit_levels.stop)
14582 : kNaN;
14583 snapshot.projection_after_close = false;
14584 snapshot.cancellation = {};
14585 snapshot.market_admission = {};
14586 const SourceId key = snapshot.source_id;
14587 const auto accepted = submit_or_replace(
14588 std::move(request), std::move(snapshot), true, key);
14589 if (accepted && same_dir && already_touched) {
14590 (void)require_host().execute_current(
14591 {*accepted, NativeCurrentPriceRule::NearestTick});
14592 }
14593 }
14594}
14595
14596void PineExecutionAdapter::flush_pooc_marketable_limit_entry_fills(
14597 const Bar& bar, const NativeDecisionContext& context) {
14598 // ab9714be pine_fills.cpp:7604-7646 (classify_order_eligibility) +
14599 // pine_fills.cpp:8022-8043 (evaluate_fill_price): under
14600 // process_orders_on_close, a pure LIMIT entry (no stop, no trail) placed by
14601 // this bar's source calc is evaluated in the post-calculation fill pass
14602 // (pine_scheduler.cpp:259 step 4) against THIS bar's close. When the close
14603 // is on the marketable side of the limit it fills there, limit-or-better,
14604 // at bar_fill_price(bar.close) with no slippage; otherwise it rests and
14605 // gets the ordinary touch evaluation from the next bar on. Scoped to the
14606 // ordinary (non-COOF, non-stream) route and a flat book.
14607 if (config_.calc_on_order_fills || !config_.process_orders_on_close
14608 || stream_mode_ || coof_recalc_active_) {
14609 return;
14610 }
14611 if (require_host().physical_position().signed_units != 0.0) return;
14612 const double raw_close = bar.close;
14613 if (!finite_positive(raw_close)) return;
14614 std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>> marketable;
14615 for (const auto& handle : live_handles_) {
14616 const auto found = placement_.find(handle.incarnation);
14617 if (found == placement_.end()) continue;
14618 const auto& row = found->second;
14619 if (row.family != PineOrderFamily::Entry || !row.opening) continue;
14620 if (row.birth.from_fill()) continue;
14621 if (row.projection_created_bar != context.coordinate.interval_index) continue;
14622 if (row.projection_position_side
14623 != static_cast<std::int32_t>(PositionSide::FLAT)) continue;
14624 if (!finite_positive(row.exit_levels.limit)
14625 || std::isfinite(row.exit_levels.stop)) continue;
14626 if (row.direction_gate || row.terms_priced_reverse
14627 || row.paired_flat_market_candidate) continue;
14628 const bool at_close = row.is_long ? raw_close <= row.exit_levels.limit
14629 : raw_close >= row.exit_levels.limit;
14630 if (at_close) marketable.emplace_back(handle, row);
14631 }
14632 for (auto& candidate : marketable) {
14633 // An earlier fill of this pass leaves the book non-flat; the owner's
14634 // later rows are then ordinary pending orders again.
14635 if (require_host().physical_position().signed_units != 0.0) break;
14636 const auto& row = candidate.second;
14637 const bool host_sized = row.deferred_cohort
14638 || row.qty_type == static_cast<int>(QtyType::CASH)
14639 || row.qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY);
14640 if (!host_sized && !finite_positive(row.requested_qty)) continue;
14641 native_order::Request request;
14642 request.intent = host_sized
14643 ? native_order::OrderIntent{native_order::HostSized{
14644 native_order::HostSizedKind::Open,
14645 row.is_long ? native_order::Side::Long : native_order::Side::Short}}
14647 row.is_long ? row.requested_qty : -row.requested_qty}};
14648 request.label = row.source_id;
14649 request.comment = row.comment;
14650 request.trigger = native_order::Market{};
14651 request.group = group_for(row.oca_name, row.oca_type);
14652 PlacementSnapshot immediate = row;
14653 immediate.cancellation = {};
14654 immediate.market_admission = {};
14655 // bar_fill_price(bar.close): the raw close, nearest-tick rounded
14656 // (pine_fills.cpp:8038-8041); a limit fill is never slipped.
14657 immediate.forced_execution_price = source_bar_fill_tick(
14658 raw_close, staged_.syminfo.mintick);
14659 const auto accepted = submit_or_replace(
14660 std::move(request), std::move(immediate), true, row.source_id);
14661 if (accepted) {
14662 (void)require_host().execute_current(
14663 {*accepted, NativeCurrentPriceRule::NearestTick});
14664 }
14665 }
14666}
14667
14668void PineExecutionAdapter::flush_pooc_marketable_exit_fills(
14669 const Bar& bar, const NativeDecisionContext& context) {
14670 // ab9714be pine_fills.cpp:7604-7648 + 7810-7843: under process_orders_on_close,
14671 // a priced exit leg placed by this bar's source calc that is already marketable
14672 // against this same bar's close fills in the post-calculation fill pass at the
14673 // close (stop leg first, at most one leg per exit order) instead of resting
14674 // for the next bar. Only the ordinary (non-COOF, non-stream) route is scoped.
14675 if (config_.calc_on_order_fills || !config_.process_orders_on_close
14676 || stream_mode_ || coof_recalc_active_) {
14677 return;
14678 }
14679 const auto physical = require_host().physical_position();
14680 if (physical.signed_units == 0.0) return;
14681 const bool closing_long = physical.signed_units > 0.0;
14682 const double raw_close = bar.close;
14683 const double tick = staged_.syminfo.mintick;
14684 // ab9714be pine_fills.cpp:7318-7365 (pooc_short_exit_trigger_close): the
14685 // admission gate and the fill evaluation of one POOC same-bar exit reissue
14686 // test a SINGLE trigger close. The pinned short reissue tests the broker's
14687 // TICK close (C11.575 -> 11.58 skips L11.576782, C11.695 -> 11.70 reaches
14688 // S11.698693, C12.495 -> 12.50 reaches S12.496973); every other
14689 // configuration tests the RAW close. The booked price is
14690 // bar_fill_price(bar.close) either way, so only the tests move. The scope
14691 // below is the close-time image of the placement-time pooc_short_tick_scope
14692 // of the strategy.exit lowering, which owns the same pinned reissue.
14693 bool competing_entry = false;
14694 for (const auto& handle : live_handles_) {
14695 const auto found = placement_.find(handle.incarnation);
14696 if (found != placement_.end() && found->second.opening) {
14697 competing_entry = true;
14698 break;
14699 }
14700 }
14701 const double held_units = std::abs(physical.signed_units);
14702 const bool pinned_short_scope = !closing_long && !competing_entry
14703 && physical.lot_count == 1
14704 && position_open_script_bar_
14705 != std::numeric_limits<std::int64_t>::min()
14706 && position_open_script_bar_ < context.script_bar_open_ms
14707 && config_.pyramiding == 0 && config_.slippage == 0
14708 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
14709 && std::abs(staged_.syminfo.pointvalue - 1.0) < 1e-12
14710 && active_staged_fx(context.sub_bar_open_ms) == 1.0
14711 && staged_.account_fx_effective_from_ms.empty()
14712 && finite_positive(tick);
14713 const auto pinned_tick_close = [&](const PlacementSnapshot& row) {
14714 if (!pinned_short_scope || row.projection_predecessor == 0) return false;
14715 if (row.projection_position_side
14716 == static_cast<std::int32_t>(PositionSide::FLAT)) return false;
14717 if (!row.oca_name.empty()) return false;
14718 if (!std::isnan(row.exit_levels.trail_points)
14719 || !std::isnan(row.exit_levels.trail_price)
14720 || !std::isnan(row.exit_levels.trail_offset)) return false;
14721 if (std::isfinite(row.qty_percent)
14722 && row.qty_percent < 100.0 - 1e-9) return false;
14723 const double leg_units = std::isfinite(row.projection_remaining_qty)
14724 ? std::max(0.0, row.projection_remaining_qty)
14725 : (std::isfinite(row.requested_qty)
14726 ? std::abs(row.requested_qty) : held_units);
14727 if (std::abs(leg_units - held_units) > 1e-9) return false;
14728 return !row.from_entry.empty()
14729 && cohort_exposure_for(row.from_entry) > 0.0;
14730 };
14731 struct Leg {
14732 native_order::RequestHandle handle;
14733 PlacementSnapshot snapshot;
14734 };
14735 struct Group {
14736 bool has_stop = false;
14737 Leg stop{};
14738 bool has_limit = false;
14739 Leg limit{};
14740 };
14741 std::vector<std::pair<SourceId, Group>> groups;
14742 std::map<SourceId, std::size_t> index;
14743 for (const auto& handle : live_handles_) {
14744 const auto found = placement_.find(handle.incarnation);
14745 if (found == placement_.end()) continue;
14746 const auto& row = found->second;
14747 if (row.family != PineOrderFamily::ExitLimit
14748 && row.family != PineOrderFamily::ExitStop) continue;
14749 if (row.birth.from_fill()) continue;
14750 if (row.projection_created_bar != context.coordinate.interval_index) continue;
14751 const bool stop_leg = row.family == PineOrderFamily::ExitStop;
14752 const double level = stop_leg ? row.exit_levels.stop : row.exit_levels.limit;
14753 if (!finite_positive(level)) continue;
14754 const SourceId key = row.source_id + "\x1f" + row.from_entry;
14755 auto found_group = index.find(key);
14756 if (found_group == index.end()) {
14757 found_group = index.emplace(key, groups.size()).first;
14758 groups.push_back({key, Group{}});
14759 }
14760 Group& group = groups[found_group->second].second;
14761 if (stop_leg) {
14762 if (!group.has_stop) {
14763 group.has_stop = true;
14764 group.stop = Leg{handle, row};
14765 }
14766 } else if (!group.has_limit) {
14767 group.has_limit = true;
14768 group.limit = Leg{handle, row};
14769 }
14770 }
14771 for (auto& entry : groups) {
14772 Group& group = entry.second;
14773 // Two-stage gate, both stages mirroring ab9714be. Stage one is the
14774 // classify_order_eligibility POOC gate evaluated over the whole
14775 // order: the EXIT order carries is_long=false always, so it tests
14776 // the short-side (buy-close) direction for each leg and admits the
14777 // order when either leg passes (pine_fills.cpp:7621-7648). Stage
14778 // two is evaluate_fill_price's exit_same_bar_reissue marketability
14779 // test, which uses the position side (pine_fills.cpp:7810-7843);
14780 // the same-bar close fill fires on a stage-two leg only when the
14781 // order also passed stage one. Both stages read the same trigger
14782 // close (pooc_short_exit_trigger_close, pine_fills.cpp:7318-7365).
14783 const double stop_level = group.has_stop ? group.stop.snapshot.exit_levels.stop : kNaN;
14784 const double limit_level = group.has_limit ? group.limit.snapshot.exit_levels.limit : kNaN;
14785 const bool pinned_reissue = (group.has_stop
14786 && pinned_tick_close(group.stop.snapshot))
14787 || (group.has_limit && pinned_tick_close(group.limit.snapshot));
14788 const double quote_close = pinned_reissue
14789 ? source_bar_fill_tick(raw_close, tick) : raw_close;
14790 const bool gate = (group.has_stop && quote_close >= stop_level)
14791 || (group.has_limit && quote_close <= limit_level);
14792 if (!gate) continue;
14793 const bool fill_stop = group.has_stop
14794 && (closing_long ? quote_close <= stop_level : quote_close >= stop_level);
14795 const bool fill_limit = group.has_limit
14796 && (closing_long ? quote_close >= limit_level : quote_close <= limit_level);
14797 if (!fill_stop && !fill_limit) continue;
14798 const Leg& selected = fill_stop ? group.stop : group.limit;
14799 if (!selected.handle.incarnation) continue;
14800 const auto& row = selected.snapshot;
14801 const double units = std::isfinite(row.projection_remaining_qty)
14802 ? std::max(0.0, row.projection_remaining_qty)
14803 : (std::isfinite(row.requested_qty) ? std::abs(row.requested_qty) : 0.0);
14804 if (!(units > 0.0)) continue;
14805 // ab9714be pine_fills.cpp:1618 and pine_fills.cpp:1708-1723 spell
14806 // full-position coverage as `qty - kQtyEpsilon`, cap the quantity at
14807 // the whole position, and then book it through the WHOLE-position exit
14808 // instead of a sized reduction. Earlier same-bar reissues in this pass
14809 // have already reduced the book, so the test reads the position fresh.
14810 // Submitting the reservation's stale binary64 residual as a sized
14811 // reduction instead is refused off-grid, and the sub-lot remainder it
14812 // strands is what the 1x-margin path later fragments.
14813 const double live_held_units =
14814 std::abs(require_host().physical_position().signed_units);
14815 const bool covers_live_book = live_held_units > 0.0
14816 && units >= live_held_units - internal::kQtyEpsilon;
14817 cancel_bracket_siblings(selected.handle);
14818 native_order::Request request;
14819 request.intent = covers_live_book
14822 native_order::Reduce{native_order::ExplicitUnits{units}}};
14823 request.label = row.source_id;
14824 request.comment = row.comment;
14825 request.trigger = native_order::Market{};
14826 PlacementSnapshot immediate = row;
14827 const bool stop_close = row.family == PineOrderFamily::ExitStop;
14828 // A stop leg books on the closing side's own path
14829 // (apply_fill_slippage(price, is_buy)): closing a SHORT is a BUY, so
14830 // the slip is ADDED to the close; closing a LONG is a SELL, so it is
14831 // subtracted. A limit leg is never slipped.
14832 immediate.forced_execution_price = nearest_tick(
14833 raw_close + (stop_close ? (closing_long ? -1.0 : 1.0) : 0.0)
14834 * config_.slippage * tick,
14835 tick);
14836 immediate.projection_predecessor = selected.handle.incarnation;
14837 immediate.projection_predecessor_exit = true;
14838 const auto accepted = submit_or_replace(
14839 std::move(request), std::move(immediate), false,
14840 row.source_id + "\x1f" + row.from_entry
14841 + std::to_string(static_cast<int>(row.family)));
14842 if (accepted) {
14843 (void)require_host().execute_current(
14844 {*accepted, NativeCurrentPriceRule::NearestTick});
14845 }
14846 }
14847}
14848
14850 const Bar& bar, const NativeDecisionContext& context) {
14851 flush_pooc_marketable_limit_entry_fills(bar, context);
14852 flush_pooc_marketable_exit_fills(bar, context);
14853 admit_deferred_open_marketable_sells();
14854 rearm_throttled_reopens();
14855 // A tolerant stream can synthesize a pair-less script callback without a
14856 // separate open hook. Batch bars always pass through on_bar_open and keep
14857 // their completed arbitration observable after the run.
14858 if (stream_mode_
14859 && last_bar_dual_entry_script_open_ms_ != context.script_bar_open_ms) {
14860 last_bar_dual_entry_path_ = 0;
14861 last_bar_dual_entry_script_open_ms_ = context.script_bar_open_ms;
14862 }
14863 apply_terminal_explicit_market_policy(context);
14864 update_risk_state(bar.close);
14865 if (stream_mode_) return;
14866 // The native callback frame remains current after the source script
14867 // returns. Reproduce the legacy once-per-script-bar margin checkpoint at
14868 // the adverse path extreme, unless the earlier open/path policy already
14869 // applied a margin slice on this script bar.
14870 if (last_margin_call_script_bar_ == context.script_bar_open_ms) return;
14871 if (submit_tv_money_long_margin_call(bar, context)) return;
14872 if (defer_rounded_pooc_short_margin_until_close(bar)) {
14873 const double adverse = nearest_tick(bar.high, staged_.syminfo.mintick);
14874 (void)submit_margin_call_slice(adverse, context);
14875 return;
14876 }
14877 const auto position = require_host().physical_position();
14878 // ab9714be pine_scheduler.cpp:262-283: in the non-POOC pass, process_margin_call
14879 // runs after invoke_chart_on_bar. A commissioned explicit-qty 1x short's
14880 // adverse-extreme checkpoint therefore lands here, post-script, after the
14881 // script's brackets have already sized against the untrimmed opening lot.
14882 const bool non_pooc_commissioned_short = !config_.process_orders_on_close
14883 && position.signed_units < 0.0
14884 && config_.margin_short == 100.0
14885 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
14886 && config_.commission_value > 0.0;
14887 if (non_pooc_commissioned_short && finite_positive(bar.high)) {
14888 // ab9714be pine_fills.cpp:5998-6005 + 6168-6179 queue an opening
14889 // affordability event for a fresh explicit-qty MARKET 1x short, and
14890 // process_margin_call (pine_fills.cpp:1266-1340, the non-POOC
14891 // post-script checkpoint) trims it at the fill first, then runs the
14892 // entry-bar adverse pass over the survivor (the mdfe3757
14893 // XAUUSD@15 2025-04-08 13:30Z pin there: 1.28 at the 3013.745 fill,
14894 // then 2.4 at the 3017.3 high). on_applied defers this commissioned
14895 // shape to here, so the fill-price trim precedes the adverse slice.
14896 if (position_open_script_bar_ == context.script_bar_open_ms
14897 && position_open_phase_ != NativePathPhase::Close) {
14898 std::size_t openings = 0;
14899 bool explicit_market_opening = false;
14900 for (const auto& cohort_id : cohort_order_) {
14901 const auto cohort = cohorts_by_id_.find(cohort_id);
14902 if (cohort == cohorts_by_id_.end()
14903 || cohort->second.cycle != current_position_cycle_) {
14904 continue;
14905 }
14906 for (const auto& origin : cohort->second.opened) {
14907 const auto opening = placement_.find(origin.incarnation);
14908 if (opening == placement_.end()) continue;
14909 ++openings;
14910 const auto& row = opening->second;
14911 explicit_market_opening = row.opening && !row.is_long
14912 && row.family == PineOrderFamily::Entry
14913 && finite_positive(row.requested_qty)
14914 && !price_present(row.exit_levels.limit)
14915 && !price_present(row.exit_levels.stop);
14916 }
14917 }
14918 const double fill_price = require_host().position_avg_price();
14919 if (openings == 1 && explicit_market_opening && finite_positive(fill_price))
14920 (void)submit_margin_call_slice(fill_price, context, true);
14921 }
14922 const double adverse = nearest_tick(bar.high, staged_.syminfo.mintick);
14923 (void)submit_margin_call_slice(adverse, context);
14924 }
14925 const bool carried_pooc_short = config_.process_orders_on_close
14926 && !config_.calc_on_order_fills && position.signed_units < 0.0
14927 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()
14928 && position_open_script_bar_ != context.script_bar_open_ms;
14929 if (carried_pooc_short && finite_positive(bar.high)) {
14930 // ab9714be pine_scheduler.cpp:260-278: the script's new market orders
14931 // fill at the close (step 4) before process_margin_call runs. While
14932 // such an order is live the checkpoint is deferred to the last of
14933 // those fills (on_applied) so it evaluates the post-fill book
14934 // (executed on both libraries: a reversal entry consumes the carried
14935 // short with no close slice, Fable delta-2 P0-A).
14936 if (market_orders_pending_at_close(context)) {
14937 pooc_close_checkpoint_deferred_ms_ = context.script_bar_open_ms;
14938 return;
14939 }
14940 (void)submit_margin_call_slice(bar.high, context);
14941 }
14942 // Ordinary price-path slices are born at the native open/applied points
14943 // and matched by the generic driver at their actual waypoint. This
14944 // post-calculation checkpoint owns the source-only rounded-money policy;
14945 // replaying the full bar's adverse quote here would incorrectly give a
14946 // close-time position access to prices it did not yet exist through.
14947}
14948
14950 const NativeDecisionContext& context) {
14951 // materialize_relative_exits can submit new legs. A submission inserts
14952 // into placement_ and may rehash it, so no reference or iterator into
14953 // placement_ may survive that call.
14954 std::optional<PlacementSnapshot> placement_snapshot;
14955 if (const auto placement = placement_.find(event.handle().incarnation);
14956 placement != placement_.end()) {
14957 placement_snapshot = placement->second;
14958 } else if (event.definition
14959 && event.definition->origin
14960 == native_order::RequestOrigin::KernelLiquidation) {
14961 // R5: the kernel originates its own liquidation, so there is no
14962 // submit through submit_or_replace to record one. Adopt it into the
14963 // source placement table on arrival, with the same family the
14964 // adapter's own slice carried, so every Margin-family branch below --
14965 // the pending-sizing refresh, the bracket revival, the excursion
14966 // sample, source_margin_exit -- sees it exactly as before.
14967 PlacementSnapshot adopted;
14969 adopted.source_id = "__margin_call__";
14970 adopted.requested_qty = event.closed_units;
14971 adopted.forced_execution_price = event.resolved_price;
14972 adopted.sizing = sizing_snapshot();
14973 placement_.try_emplace(event.handle().incarnation, adopted);
14974 placement_snapshot = adopted;
14975 }
14976 if (placement_snapshot && event.closed_trade_count > 0) {
14977 for (std::size_t i = 0; i < event.closed_trade_count; ++i) {
14978 const std::size_t index = event.first_trade_index + i;
14979 if (index >= trade_exit_phase_.size()) {
14980 trade_exit_phase_.resize(index + 1, static_cast<std::uint8_t>(NativePathPhase::None));
14981 }
14982 trade_exit_phase_[index] = static_cast<std::uint8_t>(context.coordinate.path_phase);
14983 }
14984 const bool from_bracket =
14985 placement_snapshot->family == PineOrderFamily::ExitLimit
14986 || placement_snapshot->family == PineOrderFamily::ExitStop
14987 || placement_snapshot->family == PineOrderFamily::ExitTrail;
14988 // RULING A48: the closed row's excursions come from the host's own
14989 // per-lot sampler, so the resting-stop fill-based drawdown
14990 // normalization this call site used to request is gone with it.
14991 if (auto* pine_host = dynamic_cast<PineStrategyHost*>(&require_host())) {
14992 pine_host->adapter_label_bracket_trades(event, from_bracket);
14993 }
14994 }
14995 if (placement_snapshot && placement_snapshot->opening) {
14996 std::vector<native_order::RequestHandle> paired_closes;
14997 for (const auto& handle : live_handles_) {
14998 if (handle == event.handle()) continue;
14999 const auto pending = placement_.find(handle.incarnation);
15000 if (pending != placement_.end()
15001 && pending->second.paired_reversal_parent == event.handle()) {
15002 paired_closes.push_back(handle);
15003 }
15004 }
15005 for (const auto& handle : paired_closes) {
15006 const auto result = require_host().cancel(handle);
15007 if (result.status != native_order::CancelStatus::Cancelled) continue;
15008 if (const auto pending = placement_.find(handle.incarnation);
15009 pending != placement_.end()) {
15010 pending->second.cancellation = {
15012 event.handle().incarnation,
15013 static_cast<std::int64_t>(placement_snapshot->source_sequence),
15014 handle.incarnation, pending->second.placement_cycle,
15015 pending->second.legs.revision(),
15016 pending->second.requested_qty, kNaN};
15017 }
15018 retire(handle);
15019 }
15020 }
15021 if (placement_snapshot && event.closed_units > 0.0
15022 && require_host().physical_position().signed_units == 0.0
15023 && (placement_snapshot->family == PineOrderFamily::Close
15024 || placement_snapshot->family == PineOrderFamily::CloseAll
15025 || placement_snapshot->family == PineOrderFamily::Order)) {
15026 const auto closed_side = static_cast<PositionSide>(
15027 placement_snapshot->projection_position_side);
15028 std::vector<native_order::RequestHandle> stale_entries;
15029 for (const auto& handle : live_handles_) {
15030 if (handle == event.handle()) continue;
15031 const auto found = placement_.find(handle.incarnation);
15032 if (found == placement_.end()) continue;
15033 const auto& pending = found->second;
15034 if (!pending.opening || pending.family != PineOrderFamily::Entry
15035 || pending.is_long != (closed_side == PositionSide::LONG)
15036 || pending.projection_position_side
15037 != static_cast<std::int32_t>(closed_side)) {
15038 continue;
15039 }
15040 const bool resting_limit =
15041 pending.projection_created_bar < context.coordinate.interval_index
15042 && finite_positive(pending.exit_levels.limit)
15043 && !finite_positive(pending.exit_levels.stop);
15044 const bool coqueued_within_cap =
15045 pending.projection_created_bar
15046 == placement_snapshot->projection_created_bar
15047 && (!pending.projection_over_pyramiding
15048 || (placement_snapshot->close_batch_calls != 0
15049 && (finite_positive(pending.exit_levels.limit)
15050 || finite_positive(pending.exit_levels.stop))));
15051 const bool preserved_stop =
15052 placement_snapshot->family == PineOrderFamily::CloseAll
15053 && pending.preserved_by_close_all == event.handle()
15054 && pending.preserved_close_all_bar
15055 == placement_snapshot->projection_created_bar;
15056 const bool frozen_over_cap_transaction = pending.frozen_market_instruction
15057 && pending.projection_over_pyramiding;
15058 if (!resting_limit && !coqueued_within_cap && !preserved_stop
15059 && !frozen_over_cap_transaction) {
15060 stale_entries.push_back(handle);
15061 }
15062 }
15063 for (const auto& handle : stale_entries) {
15064 const auto result = require_host().cancel(handle);
15065 if (result.status != native_order::CancelStatus::Cancelled) continue;
15066 if (const auto found = placement_.find(handle.incarnation);
15067 found != placement_.end()) {
15068 found->second.cancellation = {
15070 event.handle().incarnation,
15071 static_cast<std::int64_t>(placement_snapshot->source_sequence),
15072 handle.incarnation, found->second.placement_cycle,
15073 found->second.legs.revision(), found->second.requested_qty, kNaN};
15074 }
15075 retire(handle);
15076 }
15077 }
15078 if (placement_snapshot
15079 && (placement_snapshot->family == PineOrderFamily::Close
15080 || placement_snapshot->family == PineOrderFamily::CloseAll)
15081 && event.closed_units > 0.0) {
15082 // ab9714be pine_fills.cpp:7461-7468: the close that flattened the book
15083 // is bound to the position cycle it was issued in. Every exit created
15084 // while that position was open is Removed at the flat, before the
15085 // paired reversal entry of the NEXT cycle is released below, so the
15086 // stale bracket can neither fill against the fresh lot on this bar nor
15087 // be revived against a later reuse of the entry id
15088 // (pine_fills.cpp:7667-7675). Only a bracket the script placed behind
15089 // this same close, for the close's own paired reversal, is reached by
15090 // the legacy queue walk after that entry filled and stays armed.
15091 if (require_host().physical_position().signed_units == 0.0)
15092 retire_in_position_exits_at_flat(/*preserve_pending_parents=*/true,
15093 /*dormant_rows_only=*/false,
15094 &*placement_snapshot);
15095 std::vector<PendingEntry> remaining;
15096 std::vector<PendingEntry> after_close;
15097 remaining.reserve(pending_entries_.size());
15098 after_close.reserve(pending_entries_.size());
15099 for (auto& entry : pending_entries_) {
15100 if (entry.snapshot.paired_reversal_parent == event.handle()) {
15101 after_close.push_back(std::move(entry));
15102 } else {
15103 remaining.push_back(std::move(entry));
15104 }
15105 }
15106 pending_entries_ = std::move(remaining);
15107 for (auto& entry : after_close) {
15108 entry.snapshot.paired_reversal_parent = {};
15109 entry.snapshot.market_admission = {};
15110 entry.request.owner = native_order::Independent{};
15111 // ab9714be pine_fills.cpp:8013-8018: a same-pass close flattens
15112 // first. A following opposite stop that the close's fill has not
15113 // gapped through stays resting and fills at the stop level on
15114 // the remaining path, not at the close's open print.
15115 const bool priced_stop = finite_positive(entry.snapshot.exit_levels.stop)
15116 && !finite_positive(entry.snapshot.exit_levels.limit)
15117 && !finite_positive(entry.snapshot.exit_levels.trail_points)
15118 && !finite_positive(entry.snapshot.exit_levels.trail_price)
15119 && !finite_positive(entry.snapshot.exit_levels.trail_offset);
15120 const bool marketable_now = !priced_stop
15121 || pure_stop_entry_marketable_at(
15122 entry.snapshot, event.resolved_price);
15123 if (marketable_now) {
15124 entry.snapshot.forced_execution_price = event.resolved_price;
15125 // ab9714be pine_fills.cpp:3848-3856 orders the full close
15126 // first, but each market fill still books its own side's
15127 // slippage off the shared open print (engine.hpp:1207-1210).
15128 // A released entry on the closed position's own side is a
15129 // buy after a sell (or the reverse), so it cannot reuse the
15130 // close's slipped price.
15131 const auto closed_side = static_cast<PositionSide>(
15132 placement_snapshot->projection_position_side);
15133 if (!priced_stop && config_.slippage != 0
15134 && closed_side != PositionSide::FLAT
15135 && entry.snapshot.is_long == (closed_side == PositionSide::LONG)) {
15136 entry.snapshot.forced_execution_price =
15137 nearest_tick(event.raw_price, staged_.syminfo.mintick)
15138 + (entry.snapshot.is_long ? 1.0 : -1.0) * config_.slippage
15139 * staged_.syminfo.mintick;
15140 }
15141 }
15142 native_order::RequestHandle prior_flip_stop{};
15143 const bool self_touched = priced_stop && policy_script_bar_valid_
15144 && (entry.snapshot.is_long
15145 ? policy_script_bar_.high >= entry.snapshot.exit_levels.stop
15146 : policy_script_bar_.low <= entry.snapshot.exit_levels.stop);
15147 if (priced_stop && !marketable_now && self_touched) {
15148 std::uint64_t prior_seq = std::numeric_limits<std::uint64_t>::max();
15149 for (const auto& handle : live_handles_) {
15150 const auto found = placement_.find(handle.incarnation);
15151 if (found == placement_.end()) continue;
15152 const auto& prior = found->second;
15153 if (!prior.opening || prior.family != PineOrderFamily::Entry
15154 || prior.is_long != entry.snapshot.is_long
15155 || !finite_positive(prior.exit_levels.stop)
15156 || finite_positive(prior.exit_levels.limit)
15157 || prior.command_sequence >= entry.snapshot.command_sequence) {
15158 continue;
15159 }
15160 const auto created = static_cast<PositionSide>(
15161 prior.projection_position_side);
15162 if (created == PositionSide::FLAT
15163 || (created == PositionSide::LONG) == prior.is_long) {
15164 continue;
15165 }
15166 const bool prior_touched = prior.is_long
15167 ? policy_script_bar_.high >= prior.exit_levels.stop
15168 : policy_script_bar_.low <= prior.exit_levels.stop;
15169 if (!prior_touched || prior.command_sequence >= prior_seq) continue;
15170 prior_seq = prior.command_sequence;
15171 prior_flip_stop = handle;
15172 }
15173 }
15174 if (prior_flip_stop.incarnation != 0) {
15175 const double units = finite_positive(entry.snapshot.requested_qty)
15176 ? std::abs(entry.snapshot.requested_qty)
15177 : (finite_positive(entry.snapshot.sizing.frozen_units)
15178 ? entry.snapshot.sizing.frozen_units
15179 : std::abs(config_.default_qty_value));
15180 entry.request.intent = native_order::Transact{
15181 entry.snapshot.is_long ? units : -units};
15182 entry.request.owner = native_order::WaitForApplied{prior_flip_stop};
15183 }
15184 const auto accepted = submit_or_replace(
15185 std::move(entry.request), std::move(entry.snapshot), true,
15186 entry.replacement_key);
15187 if (accepted && marketable_now && prior_flip_stop.incarnation == 0) {
15188 const auto outcome = require_host().execute_current(
15189 {*accepted, NativeCurrentPriceRule::NearestTick});
15190 (void)outcome;
15191 }
15192 }
15193 }
15194 bool preclose_intraday_loss = false;
15195 if (event.closed_trade_count > 0 && risk_.max_intraday_loss > 0.0
15196 && !intraday_loss_orders_blocked()
15197 && std::isfinite(day_ledger_.intraday_start_equity)) {
15198 double closed_pnl = 0.0;
15199 const auto& host = require_host();
15200 for (std::size_t i = 0; i < event.closed_trade_count; ++i) {
15201 const auto index = event.first_trade_index + i;
15202 if (index < static_cast<std::size_t>(host.trade_count()))
15203 closed_pnl += host.get_trade(static_cast<int>(index)).pnl;
15204 }
15205 const double after = host.native_marked_equity(event.resolved_price);
15206 const double before = after - closed_pnl;
15207 const double loss = day_ledger_.intraday_start_equity - before;
15208 const double threshold = risk_.max_intraday_loss_percent
15209 ? day_ledger_.intraday_start_equity * risk_.max_intraday_loss / 100.0
15210 : risk_.max_intraday_loss;
15211 const double epsilon = 1e-9 * std::max(1.0, std::abs(threshold));
15212 preclose_intraday_loss = std::isfinite(before) && threshold > 0.0
15213 && loss > 0.0 && loss + epsilon >= threshold;
15214 }
15215 last_applied_ordinal_ = event.ordinal;
15216 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::Entry
15217 && std::abs(event.opened_units) > 0.0) {
15218 entry_openings_this_interval_ += 1;
15219 if (!throttled_reopen_rearm_.empty())
15220 rearm_throttled_reopens();
15221 }
15222 const double live_position = require_host().physical_position().signed_units;
15223 const int next_sign = live_position > 0.0 ? 1 : (live_position < 0.0 ? -1 : 0);
15224 const bool flipped_position = current_position_sign_ != 0 && next_sign != 0
15225 && current_position_sign_ != next_sign;
15226 if (flipped_position && event.closed_units > 0.0) {
15227 // A reversal retires the complete prior physical side before opening
15228 // the new one. The generic settlement event owns that fact; mirror
15229 // it into every source cohort so a later reuse of an old entry id
15230 // cannot reserve against already-closed exposure.
15231 std::vector<SourceId> closed_cohorts;
15232 for (const auto& cohort : cohorts_by_id_) {
15233 const bool nested_new_side_opening = std::any_of(
15234 live_handles_.begin(), live_handles_.end(),
15235 [&](const native_order::RequestHandle& handle) {
15236 const auto pending = placement_.find(handle.incarnation);
15237 return pending != placement_.end()
15238 && pending->second.opening
15239 && pending->second.family == PineOrderFamily::Entry
15240 && pending->second.source_id == cohort.first
15241 && pending->second.is_long == (live_position > 0.0);
15242 });
15243 if ((!placement_snapshot
15244 || cohort.first != placement_snapshot->source_id)
15245 && !nested_new_side_opening) {
15246 closed_cohorts.push_back(cohort.first);
15247 }
15248 }
15249 std::sort(closed_cohorts.begin(), closed_cohorts.end());
15250 for (const auto& id : closed_cohorts)
15251 cancel_exit_orders_for_full_close(id);
15252 for (auto& cohort : cohorts_by_id_) {
15253 cohort.second.opened.clear();
15254 cohort.second.live_units_by_origin.clear();
15255 }
15256 // ab9714be pine_strategy_host.cpp:244 / :261
15257 // reset_source_open_position_ledgers_before_book clears the close
15258 // ledgers when a position is opened or reversed, so unclosed units of
15259 // the prior position side cannot survive to admit a later close of that
15260 // side against the new position.
15261 close_logical_units_.clear();
15262 close_reserved_units_.clear();
15263 close_first_units_.clear();
15264 close_callsite_reserved_units_.clear();
15265 close_callsite_first_units_.clear();
15266 }
15267 if (next_sign != 0 && (current_position_sign_ == 0 || current_position_sign_ != next_sign)) {
15268 ++current_position_cycle_;
15269 position_open_script_bar_ = context.script_bar_open_ms;
15270 position_open_epoch_ = broker_open_epoch_;
15271 position_open_bar_index_ = context.coordinate.interval_index;
15272 position_open_phase_ = context.coordinate.path_phase;
15273 position_open_priced_ = placement_snapshot
15274 && (finite_positive(placement_snapshot->exit_levels.limit)
15275 || finite_positive(placement_snapshot->exit_levels.stop)
15276 || placement_snapshot->family == PineOrderFamily::Order);
15277 }
15278 current_position_sign_ = next_sign;
15279 if (placement_snapshot
15280 && (placement_snapshot->family == PineOrderFamily::ExitLimit
15281 || placement_snapshot->family == PineOrderFamily::ExitStop
15282 || placement_snapshot->family == PineOrderFamily::ExitTrail)
15283 && event.closed_units > 0.0 && live_position != 0.0
15284 // ab9714be pine_orders.cpp:330-337: execute_partial_exit_qty recognizes explicit requested quantity reductions
15285 && ((std::isfinite(placement_snapshot->requested_qty) && placement_snapshot->requested_qty > 0.0)
15286 || (std::isfinite(placement_snapshot->qty_percent) && placement_snapshot->qty_percent < 100.0 - 1e-9))) {
15287 const bool sibling_leg_still_live = std::any_of(
15288 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
15289 if (handle == event.handle()) return false;
15290 const auto sibling = placement_.find(handle.incarnation);
15291 if (sibling == placement_.end()) return false;
15292 const auto family = sibling->second.family;
15293 // ab9714be pine_fills.cpp:7012-7024 counts other PENDING
15294 // ORDERS with the id (a second binding); the stop/limit/trail
15295 // legs of one strategy.exit call are one owner order, so a
15296 // same-bracket leg the fill is about to OCA-retire is not a
15297 // surviving sibling.
15298 if (sibling->second.command_sequence == placement_snapshot->command_sequence
15299 && sibling->second.bracket_origin == placement_snapshot->bracket_origin)
15300 return false;
15301 return sibling->second.source_id == placement_snapshot->source_id
15302 && sibling->second.from_entry == placement_snapshot->from_entry
15303 && (family == PineOrderFamily::ExitLimit
15304 || family == PineOrderFamily::ExitStop
15305 || family == PineOrderFamily::ExitTrail);
15306 });
15307 if (!sibling_leg_still_live) {
15308 consumed_partial_exit_cycles_[placement_snapshot->source_id + "\x1f"
15309 + placement_snapshot->from_entry] =
15310 current_position_cycle_;
15311 }
15312 }
15313 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::Order
15314 && placement_snapshot->oca_type == 1 && !placement_snapshot->oca_name.empty()) {
15315 const bool fully_filled = !std::isfinite(placement_snapshot->requested_qty)
15316 || event.filled_working >= placement_snapshot->requested_qty;
15317 if (fully_filled) {
15318 std::vector<native_order::RequestHandle> siblings;
15319 for (const auto& handle : live_handles_) {
15320 if (handle == event.handle()) continue;
15321 const auto peer = placement_.find(handle.incarnation);
15322 if (peer != placement_.end()
15323 && (peer->second.family == PineOrderFamily::Order
15324 || peer->second.family == PineOrderFamily::Entry)
15325 && peer->second.oca_type == 1
15326 && peer->second.oca_name == placement_snapshot->oca_name) {
15327 siblings.push_back(handle);
15328 }
15329 }
15330 for (const auto& sibling : siblings) {
15331 const auto result = require_host().cancel(sibling);
15332 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
15333 }
15334 }
15335 }
15336 if (placement_snapshot && placement_snapshot->opening
15337 && std::abs(event.opened_units) > 0.0) {
15338 // Explicit brackets armed while their same-id parent was still flat
15339 // use origin zero as a temporary source binding. Once that parent
15340 // applies, bind those live legs to its actual incarnation and move
15341 // their replacement keys with it. A later reissue must replace the
15342 // carried legs before adding legs for another pending instance
15343 // (ab9714be:test_exit_bracket_pending_entry_leg.cpp:10-18).
15344 for (const auto& handle : live_handles_) {
15345 auto child = placement_.find(handle.incarnation);
15346 if (child == placement_.end()) continue;
15347 auto& snapshot = child->second;
15348 const bool exit = snapshot.family == PineOrderFamily::ExitLimit
15349 || snapshot.family == PineOrderFamily::ExitStop
15350 || snapshot.family == PineOrderFamily::ExitTrail;
15351 if (!exit || snapshot.from_entry != placement_snapshot->source_id
15352 || snapshot.bracket_origin.incarnation != 0
15353 || !std::isfinite(snapshot.requested_qty)) {
15354 continue;
15355 }
15356 const auto replacement = [&](std::uint64_t origin) {
15357 return snapshot.source_id + "\x1f" + snapshot.from_entry + "\x1f"
15358 + std::to_string(static_cast<int>(snapshot.family)) + "\x1f"
15359 + std::to_string(origin);
15360 };
15361 const auto old_key = key_for(replacement(0));
15362 const auto old = live_by_source_key_.find(old_key);
15363 if (old != live_by_source_key_.end() && old->second == handle)
15364 live_by_source_key_.erase(old);
15365 snapshot.bracket_origin = event.handle();
15366 live_by_source_key_[key_for(replacement(event.handle().incarnation))] = handle;
15367 }
15368 {
15369 auto& facts = cohorts_by_id_[placement_snapshot->source_id];
15370 facts.cycle = event.cycle_after;
15371 if (std::find(facts.opened.begin(), facts.opened.end(), event.handle()) == facts.opened.end())
15372 facts.opened.push_back(event.handle());
15373 facts.live_units_by_origin[event.handle().incarnation]
15374 += std::abs(event.opened_units);
15375 }
15376 close_logical_units_[placement_snapshot->source_id]
15377 += std::abs(event.opened_units);
15378 record_opening_fee(*placement_snapshot, event);
15379 materialize_pending_bracket_legs(event);
15380 const auto created_side = static_cast<PositionSide>(
15381 placement_snapshot->projection_position_side);
15382 const bool consumed_deferred_carry =
15383 finite_positive(placement_snapshot->projection_tv_carry_qty)
15384 && created_side != PositionSide::FLAT
15385 && ((created_side == PositionSide::LONG) != placement_snapshot->is_long);
15386 if (consumed_deferred_carry) {
15387 for (const auto& handle : live_handles_) {
15388 if (handle == event.handle()) continue;
15389 const auto sibling = placement_.find(handle.incarnation);
15390 if (sibling == placement_.end()) continue;
15391 auto& candidate = sibling->second;
15392 if (!candidate.opening
15393 || candidate.source_id == placement_snapshot->source_id
15394 || candidate.projection_position_side
15395 != placement_snapshot->projection_position_side
15396 || candidate.projection_created_bar
15397 > placement_snapshot->projection_created_bar) {
15398 continue;
15399 }
15400 candidate.projection_tv_carry_qty = 0.0;
15401 }
15402 }
15403 materialize_relative_exits(*placement_snapshot, event);
15404 const bool true_paired_transaction =
15405 placement_snapshot->paired_flat_market_candidate
15406 && finite_positive(placement_snapshot->paired_flat_market_own_qty)
15407 && finite_positive(placement_snapshot->paired_flat_market_transaction_qty)
15408 && placement_snapshot->paired_flat_market_transaction_qty
15409 > placement_snapshot->paired_flat_market_own_qty + 1e-10;
15410 if (!true_paired_transaction) {
15411 reconcile_deferred_exit_reservations(
15412 placement_snapshot->source_id,
15413 cohort_exposure_for(placement_snapshot->source_id));
15414 }
15415 flush_pending_bracket_legs(event.handle(), false);
15416 // ab9714be pine_fills.cpp:7652-7672 (finding-347): a leg re-issued for
15417 // a carried, already opened origin draws on the live position; it is
15418 // not this parent's pre-armed bracket and never waits for it.
15419 const auto carried_origin_leg = [&](const PlacementSnapshot& row) {
15420 return row.bracket_origin.incarnation != 0
15421 && row.bracket_origin != event.handle();
15422 };
15423 const bool partial_prearmed_parent = std::isfinite(
15424 [&]() {
15425 double smallest = kNaN;
15426 for (const auto& handle : live_handles_) {
15427 const auto pending = placement_.find(handle.incarnation);
15428 if (pending == placement_.end()) continue;
15429 const auto& row = pending->second;
15430 if ((row.family == PineOrderFamily::ExitStop
15431 || row.family == PineOrderFamily::ExitLimit)
15432 && row.from_entry == placement_snapshot->source_id
15433 && row.projection_created_bar
15434 == placement_snapshot->projection_created_bar
15435 && !carried_origin_leg(row)
15436 && std::isfinite(row.requested_qty)) {
15437 smallest = std::isfinite(smallest)
15438 ? std::min(smallest, row.requested_qty)
15439 : row.requested_qty;
15440 }
15441 }
15442 return smallest;
15443 }())
15444 && [&]() {
15445 for (const auto& handle : live_handles_) {
15446 const auto pending = placement_.find(handle.incarnation);
15447 if (pending == placement_.end()) continue;
15448 const auto& row = pending->second;
15449 if ((row.family == PineOrderFamily::ExitStop
15450 || row.family == PineOrderFamily::ExitLimit)
15451 && row.from_entry == placement_snapshot->source_id
15452 && row.projection_created_bar
15453 == placement_snapshot->projection_created_bar
15454 && !carried_origin_leg(row)
15455 && std::isfinite(row.requested_qty)
15456 && row.requested_qty < std::abs(event.opened_units)) {
15457 return true;
15458 }
15459 }
15460 return false;
15461 }();
15462 const bool multiple_prearmed_parents = std::any_of(
15463 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
15464 if (handle == event.handle()) return false;
15465 const auto pending = placement_.find(handle.incarnation);
15466 return pending != placement_.end() && pending->second.opening
15467 && pending->second.family == PineOrderFamily::Entry
15468 && pending->second.projection_created_bar
15469 == placement_snapshot->projection_created_bar;
15470 });
15471 // A same-bar leg whose level is already marketable at the parent's
15472 // fill is a wrong-side scratch candidate; legacy scratches it at the
15473 // open for a sole MARKET parent only (test_prearmed_market_parent_gap_exit).
15474 const auto marketable_at_fill = [&](const PlacementSnapshot& row) {
15475 const bool long_position = event.opened_units > 0.0;
15476 const bool stop_marketable = finite_positive(row.exit_levels.stop)
15477 && (long_position ? event.resolved_price <= row.exit_levels.stop
15478 : event.resolved_price >= row.exit_levels.stop);
15479 const bool limit_marketable = finite_positive(row.exit_levels.limit)
15480 && (long_position ? event.resolved_price >= row.exit_levels.limit
15481 : event.resolved_price <= row.exit_levels.limit);
15482 return stop_marketable || limit_marketable;
15483 };
15484 // ab9714be pine_fills.cpp:7679-7700: TradingView evaluates a filled
15485 // parent's priced exits on the entry bar itself. With sibling pre-armed
15486 // parents on the same bar, only the wrong-side (marketable) legs wait
15487 // for the next opening; the right-side legs join the entry bar
15488 // (L9g; delta-3 P0-N4). A partial-quantity bracket waits entirely.
15489 if (partial_prearmed_parent || multiple_prearmed_parents) {
15490 std::vector<native_order::RequestHandle> delayed_legs;
15491 for (const auto& handle : live_handles_) {
15492 const auto pending = placement_.find(handle.incarnation);
15493 if (pending == placement_.end()) continue;
15494 const auto& row = pending->second;
15495 const bool matches_parent = row.from_entry.empty()
15496 || row.from_entry == placement_snapshot->source_id;
15497 if ((row.family == PineOrderFamily::ExitStop
15498 || row.family == PineOrderFamily::ExitLimit)
15499 && matches_parent
15500 && row.projection_created_bar
15501 == placement_snapshot->projection_created_bar
15502 && !carried_origin_leg(row)
15503 && (partial_prearmed_parent || marketable_at_fill(row))) {
15504 delayed_legs.push_back(handle);
15505 }
15506 }
15507 for (const auto& handle : delayed_legs) {
15508 const auto pending = placement_.find(handle.incarnation);
15509 if (pending == placement_.end()) continue;
15510 const PlacementSnapshot row = pending->second;
15511 native_order::Request request;
15513 native_order::HostSizedKind::Close, std::nullopt};
15514 request.label = row.source_id;
15515 request.comment = row.comment;
15516 request.owner = owner_for_close(row.from_entry, true);
15517 const bool exit_is_buy = event.opened_units < 0.0;
15518 if (row.family == PineOrderFamily::ExitStop) {
15519 request.trigger = native_order::Stop{source_trigger_threshold(
15520 row.exit_levels.stop, staged_.syminfo.mintick,
15521 exit_is_buy, false)};
15522 } else {
15523 request.trigger = native_order::Limit{source_trigger_threshold(
15524 row.exit_levels.limit, staged_.syminfo.mintick,
15525 exit_is_buy, true)};
15526 }
15527 const bool explicit_origin = std::isfinite(row.requested_qty)
15528 && row.bracket_origin.incarnation != 0;
15529 const std::string origin_suffix = explicit_origin
15530 ? "\x1f" + std::to_string(row.bracket_origin.incarnation) : "";
15531 const std::string group_name = row.oca_name.empty()
15532 ? row.source_id + "\x1f" + row.from_entry + origin_suffix
15533 : row.oca_name;
15534 request.group = group_for(group_name, 1);
15535 const std::string replacement_key = row.source_id + "\x1f"
15536 + row.from_entry + "\x1f"
15537 + std::to_string(static_cast<int>(row.family)) + origin_suffix;
15538 const auto cancelled = require_host().cancel(handle);
15539 if (cancelled.status != native_order::CancelStatus::Cancelled)
15540 continue;
15541 retire(handle);
15542 delayed_market_orders_.push_back({
15543 std::move(request), row, replacement_key,
15544 broker_open_epoch_ + 1U});
15545 }
15546 }
15547 if (!partial_prearmed_parent) {
15548 std::optional<PlacementSnapshot> rearm;
15549 for (const auto& handle : live_handles_) {
15550 const auto pending = placement_.find(handle.incarnation);
15551 if (pending == placement_.end()) continue;
15552 const auto& row = pending->second;
15553 const bool matches_parent = row.from_entry.empty()
15554 || row.from_entry == placement_snapshot->source_id;
15555 if ((row.family != PineOrderFamily::ExitStop
15556 && row.family != PineOrderFamily::ExitLimit)
15557 || !matches_parent
15558 || row.projection_created_bar
15559 != placement_snapshot->projection_created_bar
15560 || carried_origin_leg(row)) {
15561 continue;
15562 }
15563 const bool long_position = event.opened_units > 0.0;
15564 const bool stop_marketable = finite_positive(row.exit_levels.stop)
15565 && (long_position ? event.resolved_price <= row.exit_levels.stop
15566 : event.resolved_price >= row.exit_levels.stop);
15567 const bool limit_marketable = finite_positive(row.exit_levels.limit)
15568 && (long_position ? event.resolved_price >= row.exit_levels.limit
15569 : event.resolved_price <= row.exit_levels.limit);
15570 const bool equal_limit = finite_positive(row.exit_levels.limit)
15571 && event.resolved_price == row.exit_levels.limit;
15572 if ((!stop_marketable && !limit_marketable) || equal_limit) {
15573 rearm = row;
15574 break;
15575 }
15576 }
15577 if (rearm) {
15578 // ab9714be pine_fills.cpp:7788-7800: a full prearmed MARKET
15579 // parent bracket that is not an immediate wrong-side scratch
15580 // joins the remaining entry-bar path. Reissuing from the
15581 // parent's Applied callback gives the generic requests that
15582 // exact birth floor and the now-known close direction. With
15583 // sibling parents the wrong-side leg was parked above, so
15584 // only the right-side levels are reissued here (L9g).
15585 const bool long_position = event.opened_units > 0.0;
15586 const bool stop_wrong_side = multiple_prearmed_parents
15587 && finite_positive(rearm->exit_levels.stop)
15588 && (long_position ? event.resolved_price <= rearm->exit_levels.stop
15589 : event.resolved_price >= rearm->exit_levels.stop);
15590 const bool limit_wrong_side = multiple_prearmed_parents
15591 && finite_positive(rearm->exit_levels.limit)
15592 && (long_position ? event.resolved_price >= rearm->exit_levels.limit
15593 : event.resolved_price <= rearm->exit_levels.limit)
15594 && event.resolved_price != rearm->exit_levels.limit;
15595 exit(rearm->source_id, rearm->from_entry,
15596 limit_wrong_side ? kNaN : rearm->exit_levels.limit,
15597 stop_wrong_side ? kNaN : rearm->exit_levels.stop,
15598 rearm->exit_levels.trail_points,
15599 rearm->exit_levels.trail_offset,
15600 rearm->exit_levels.trail_price,
15601 rearm->qty_percent, rearm->comment,
15602 rearm->requested_qty, rearm->oca_name,
15603 rearm->exit_levels.profit_ticks,
15604 rearm->exit_levels.loss_ticks);
15605 }
15606 }
15607 if (event.closed_units > 0.0 && event.opened_units > 0.0
15608 && config_.default_qty_type
15609 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
15610 && config_.default_qty_value < 100.0) {
15611 // ab9714be pine_fills.cpp:3670-3780, famx NIFTY admit90:
15612 // after the opposite MARKET wins the gapped-open arbitration,
15613 // its newly materialized relative trail does not scratch the
15614 // admitted reversal at that same opening point.
15615 std::vector<native_order::RequestHandle> prearmed_trails;
15616 for (const auto& handle : live_handles_) {
15617 const auto pending = placement_.find(handle.incarnation);
15618 if (pending != placement_.end()
15619 && pending->second.family == PineOrderFamily::ExitTrail
15620 && pending->second.from_entry == placement_snapshot->source_id
15621 && pending->second.projection_created_bar
15622 == placement_snapshot->projection_created_bar) {
15623 prearmed_trails.push_back(handle);
15624 }
15625 }
15626 for (const auto& handle : prearmed_trails) {
15627 const auto result = require_host().cancel(handle);
15628 if (result.status == native_order::CancelStatus::Cancelled)
15629 retire(handle);
15630 }
15631 }
15632 // The generic cohort is already the quantity authority. Rebind only
15633 // adapter lifecycle/reservation receipts after the opening becomes a
15634 // live physical fact; no request is resized or resubmitted here.
15635 const auto handles = live_handles_;
15636 for (const auto& handle : handles) {
15637 const auto pending = placement_.find(handle.incarnation);
15638 if (pending == placement_.end()) continue;
15639 auto& candidate = pending->second;
15640 const bool exit = candidate.family == PineOrderFamily::ExitLimit
15641 || candidate.family == PineOrderFamily::ExitStop
15642 || candidate.family == PineOrderFamily::ExitTrail;
15643 if (!exit) continue;
15644 if (candidate.from_entry == placement_snapshot->source_id) {
15645 if (candidate.legs.target().incarnation
15646 && candidate.legs.target().owner != current_position_cycle_) {
15647 const exit_legs::Frame cause{event.ordinal,
15648 context.coordinate.interval_index,
15649 context.sub_count > 1 ? exit_legs::Domain::MagnifierFillRecalc
15650 : exit_legs::Domain::FillRecalc,
15651 exit_legs::Phase::Observation};
15652 const exit_legs::Action bind{candidate.legs.target(),
15653 candidate.legs.revision(), cause,
15654 exit_legs::BindOwner{current_position_cycle_}};
15655 (void)candidate.legs.apply(candidate.legs.target(), bind);
15656 }
15657 initialize_l4c_policy(candidate, handle);
15658 }
15659 const bool bound_preexit_add = placement_snapshot
15660 && placement_snapshot->opening && event.opened_units > 0.0
15661 && placement_snapshot->command_sequence < candidate.command_sequence;
15662 const bool selected_growth_source = placement_snapshot
15663 && placement_snapshot->reservation_growth_source.reservation_owner()
15664 && *placement_snapshot->reservation_growth_source.reservation_owner()
15665 == handle.incarnation;
15666 if (candidate.reservation_expansion.capture() && handle != event.handle()
15667 && selected_growth_source
15668 && (candidate.reservation_expansion.population_open() || bound_preexit_add)) {
15669 if (bound_preexit_add && std::isfinite(candidate.projection_remaining_qty)) {
15670 candidate.projection_remaining_qty += std::abs(event.opened_units);
15671 }
15672 if (candidate.reservation_expansion.population_open()) {
15673 candidate.pooc_global_full_exit_dynamic_qty = false;
15674 candidate.pooc_global_full_exit_tracks_bound_adds = false;
15675 candidate.reservation_expansion.close_population(event.handle().incarnation);
15676 candidate.pooc_global_full_exit_bound_add = bound_preexit_add;
15677 }
15678 }
15679 }
15680 }
15681 if (placement_snapshot && placement_snapshot->opening
15682 && placement_snapshot->reverse_to && event.closed_units > 0.0
15683 && event.opened_units > 0.0) {
15684 purge_brackets_after_applied_reversal(*placement_snapshot);
15685 }
15686 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::Margin
15687 && event.closed_units > 0.0) {
15688 refresh_pending_sizing_after_margin(event, context);
15689 // pine_fills.cpp:6399-6434's narrow MC-surplus receipt. It is not
15690 // inferred from an arbitrary requested-minus-live quantity: the
15691 // source entry must have been reduced by this one-unit margin event
15692 // after its close-only placement, while its original cycle remains the
15693 // sole live long lot. Snapshot mutation at an Applied boundary is a
15694 // pinned P5 write boundary.
15695 const auto physical = require_host().physical_position();
15696 std::uint64_t sole_live_entry_incarnation = 0;
15697 std::size_t live_entry_count = 0;
15698 for (const auto& id : cohort_order_) {
15699 const auto cohort = cohorts_by_id_.find(id);
15700 if (cohort == cohorts_by_id_.end()) continue;
15701 for (const auto& opening : cohort->second.opened) {
15702 const auto units = cohort->second.live_units_by_origin.find(
15703 opening.incarnation);
15704 if (units == cohort->second.live_units_by_origin.end()
15705 || !(units->second > 0.0)) {
15706 continue;
15707 }
15708 sole_live_entry_incarnation = opening.incarnation;
15709 ++live_entry_count;
15710 }
15711 }
15712 const std::uint64_t source_fill_sequence =
15713 static_cast<PineStrategyHost&>(require_host()).broker_fill_event_seq_;
15714 if (live_entry_count == 1U && sole_live_entry_incarnation != 0
15715 && physical.signed_units > 0.0 && physical.lot_count == 1U
15716 && std::abs(event.closed_units - 1.0) < 1e-6
15717 && config_.default_qty_type
15718 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
15719 && std::abs(config_.default_qty_value - 100.0) < 1e-12
15720 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
15721 && config_.commission_value == 0.0 && config_.slippage == 0
15722 && source_fill_sequence != 0) {
15723 signal_close_mc_event_bar_ = context.coordinate.interval_index;
15724 signal_close_mc_position_cycle_ = current_position_cycle_;
15725 signal_close_mc_entry_incarnation_ = sole_live_entry_incarnation;
15726 signal_close_mc_fill_seq_ = source_fill_sequence;
15727 signal_close_mc_remaining_qty_ = std::abs(physical.signed_units);
15728 signal_close_mc_before_qty_ = signal_close_mc_remaining_qty_
15729 + std::abs(event.closed_units);
15730 }
15731 std::uint64_t sole_entry_incarnation = 0;
15732 int live_entry_origins = 0;
15733 if (physical.signed_units != 0.0 && physical.lot_count == 1U) {
15734 for (const auto& cohort_id : cohort_order_) {
15735 const auto cohort = cohorts_by_id_.find(cohort_id);
15736 if (cohort == cohorts_by_id_.end()) continue;
15737 for (const auto& origin : cohort->second.opened) {
15738 const auto units = cohort->second.live_units_by_origin.find(
15739 origin.incarnation);
15740 if (units == cohort->second.live_units_by_origin.end()
15741 || !(units->second > 0.0)) {
15742 continue;
15743 }
15744 const auto opening = placement_.find(origin.incarnation);
15745 if (opening == placement_.end()
15746 || !opening->second.opening
15747 || opening->second.is_long != (physical.signed_units > 0.0)) {
15748 continue;
15749 }
15750 sole_entry_incarnation = origin.incarnation;
15751 ++live_entry_origins;
15752 }
15753 }
15754 }
15755 if (live_entry_origins != 1) sole_entry_incarnation = 0;
15756 last_margin_call_script_bar_ = context.script_bar_open_ms;
15757 last_margin_call_event_ordinal_ = event.ordinal;
15758 last_margin_call_entry_incarnation_ = sole_entry_incarnation;
15759 last_margin_call_position_cycle_ = current_position_cycle_;
15760 last_margin_call_at_script_close_ = policy_script_bar_valid_
15761 && policy_script_bar_.timestamp == context.script_bar_open_ms
15762 && nearest_tick(event.resolved_price, staged_.syminfo.mintick)
15763 == nearest_tick(policy_script_bar_.close, staged_.syminfo.mintick);
15764 last_margin_call_closed_units_ = event.closed_units;
15765 last_margin_call_remaining_units_ = std::abs(physical.signed_units);
15766 const auto state = require_host().native_state();
15767 const auto* pine_host = dynamic_cast<const PineStrategyHost*>(&require_host());
15768 const bool magnifier = pine_host
15769 && pine_host->scheduler_.bar_magnifier_enabled();
15770 const bool ordinary_margin_receipt = !config_.process_orders_on_close
15771 && !config_.calc_on_order_fills && !coof_recalc_active_
15772 && !magnifier && state.phase == NativeRunPhase::Batch
15773 && last_margin_call_at_script_close_;
15774 for (const auto& handle : live_handles_) {
15775 const auto found = placement_.find(handle.incarnation);
15776 if (found == placement_.end()) continue;
15777 auto& candidate = found->second;
15778 const double close_surplus = candidate.projection_tv_carry_qty
15779 - std::abs(physical.signed_units);
15780 const bool exact_margin_receipt = candidate.family == PineOrderFamily::Entry
15781 && candidate.affordability_close_only && !candidate.is_long
15782 && candidate.rounded_signal_cost_close_only
15783 && candidate.deferred_cohort
15784 && ordinary_margin_receipt
15785 && !std::isfinite(candidate.requested_qty)
15786 && !finite_positive(candidate.exit_levels.limit)
15787 && !finite_positive(candidate.exit_levels.stop)
15788 && !candidate.projection_after_close
15789 && candidate.projection_created_bar
15790 == context.coordinate.interval_index
15791 && candidate.projection_position_side
15792 == static_cast<std::int32_t>(PositionSide::LONG)
15793 && candidate.placement_cycle == current_position_cycle_
15794 && physical.signed_units > 0.0 && physical.lot_count == 1U
15795 && sole_entry_incarnation != 0
15796 && std::isfinite(close_surplus) && std::abs(close_surplus - 1.0) < 1e-6
15797 && std::abs(event.closed_units - 1.0) < 1e-6;
15798 if (exact_margin_receipt) {
15799 candidate.affordability_keep_mc_close_surplus = true;
15800 candidate.signal_close_mc_bar = candidate.projection_created_bar;
15801 candidate.signal_close_mc_entry_incarnation = sole_entry_incarnation;
15802 candidate.signal_close_mc_fill_seq = pine_host
15803 ? pine_host->adapter_broker_fill_event_sequence() : 0;
15804 candidate.signal_close_mc_remaining_qty =
15805 std::abs(physical.signed_units);
15806 if (live_entry_count == 1U && sole_live_entry_incarnation != 0) {
15807 candidate.signal_close_mc_bar = candidate.projection_created_bar;
15808 candidate.signal_close_mc_entry_incarnation =
15809 sole_live_entry_incarnation;
15810 candidate.signal_close_mc_fill_seq = source_fill_sequence;
15811 candidate.signal_close_mc_remaining_qty =
15812 std::abs(physical.signed_units);
15813 }
15814 }
15815 }
15816 revive_brackets_after_margin(event, context);
15817 }
15818 if (event.closed_units > 0.0) {
15819 const SourceId* fee_source = nullptr;
15820 if (placement_snapshot && !placement_snapshot->from_entry.empty())
15821 fee_source = &placement_snapshot->from_entry;
15822 consume_opening_fees(event, fee_source);
15823 }
15824 const bool current_debit_observed =
15825 current_debited_applied_ordinals_.erase(event.ordinal) != 0;
15826 if (!current_debit_observed && event.closed_trade_count > 0)
15827 consume_closed_trade_rows(event,
15828 placement_snapshot ? &*placement_snapshot : nullptr);
15829 // ab9714be pine_fills.cpp:5883-5899 / pine_strategy_commands.cpp:
15830 // 2739-2811: once an opening applies, every deferred bracket family for
15831 // a now-live source cohort receives its reservation. This also covers
15832 // the second half of a flat MARKET/MARKET transaction pair; its Applied
15833 // shape is not required to expose closed_units for the source reservation
15834 // boundary to be observable.
15835 if (placement_snapshot && placement_snapshot->opening) {
15836 const double physical_exposure =
15837 std::abs(require_host().physical_position().signed_units);
15838 for (const auto& id : cohort_order_) {
15839 const double exposure = cohort_exposure_for(id);
15840 const bool paired_source_transaction =
15841 placement_snapshot->paired_flat_market_candidate
15842 && finite_positive(
15843 placement_snapshot->paired_flat_market_own_qty)
15844 && finite_positive(
15845 placement_snapshot->paired_flat_market_transaction_qty)
15846 && placement_snapshot->paired_flat_market_transaction_qty
15847 > placement_snapshot->paired_flat_market_own_qty + 1e-10;
15848 const double source_own = paired_source_transaction
15849 ? placement_snapshot->paired_flat_market_own_qty
15850 : physical_exposure;
15851 const bool intermediate_paired_gross = config_.pyramiding == 2
15852 && source_own > 0.0 && exposure > source_own + 1e-10;
15853 if (exposure > 0.0 && !intermediate_paired_gross)
15854 reconcile_deferred_exit_reservations(id, exposure);
15855 }
15856 }
15857 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::CloseAll
15858 && event.closed_units > 0.0) {
15859 for (auto& cohort : cohorts_by_id_) cohort.second.live_units_by_origin.clear();
15860 }
15861 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::Margin
15862 && event.closed_units > 0.0
15863 && require_host().physical_position().signed_units != 0.0) {
15864 const auto physical = require_host().physical_position();
15865 auto revival = std::find_if(
15866 pending_margin_revivals_.begin(), pending_margin_revivals_.end(),
15867 [&](const PendingMarginRevival& pending) {
15868 const auto& row = pending.snapshot;
15869 const bool same_side = row.projection_position_side
15870 == static_cast<std::int32_t>(PositionSide::LONG)
15871 ? physical.signed_units > 0.0 : physical.signed_units < 0.0;
15872 const bool exposed = !row.from_entry.empty()
15873 && cohort_exposure_for(row.from_entry) > 0.0;
15874 return same_side && exposed;
15875 });
15876 if (revival != pending_margin_revivals_.end()) {
15877 PlacementSnapshot snapshot = std::move(revival->snapshot);
15878 pending_margin_revivals_.erase(revival);
15879 const bool reached = physical.signed_units > 0.0
15880 ? event.resolved_price <= snapshot.exit_levels.stop
15881 : event.resolved_price >= snapshot.exit_levels.stop;
15882 native_order::Request request;
15883 request.intent = native_order::Reduce{
15884 native_order::ExplicitUnits{std::abs(physical.signed_units)}};
15885 request.label = snapshot.source_id;
15886 request.comment = snapshot.comment;
15887 if (!reached) {
15888 const bool exit_is_buy = physical.signed_units < 0.0;
15889 request.trigger = native_order::Stop{source_trigger_threshold(
15890 snapshot.exit_levels.stop, staged_.syminfo.mintick,
15891 exit_is_buy, false)};
15892 }
15894 snapshot.restored_after_margin = true;
15895 snapshot.requested_qty = std::abs(physical.signed_units);
15896 snapshot.qty_percent = 100.0;
15897 snapshot.projection_remaining_qty = kNaN;
15898 snapshot.fixed_exit_reservation = false;
15899 snapshot.forced_execution_price = reached
15900 ? event.resolved_price : kNaN;
15901 snapshot.market_admission = {};
15902 snapshot.cancellation = {};
15903 const auto family_key = key_for(snapshot.source_id, snapshot.from_entry);
15904 const SourceId replacement_key = snapshot.source_id + "\x1f"
15905 + snapshot.from_entry + "\x1fmargin-revival";
15906 const auto accepted = submit_or_replace(
15907 std::move(request), std::move(snapshot), false, replacement_key);
15908 if (accepted) {
15909 bracket_families_[family_key].push_back(*accepted);
15910 if (reached) {
15911 (void)require_host().execute_current(
15912 {*accepted, NativeCurrentPriceRule::NearestTick});
15913 }
15914 }
15915 }
15916 }
15917 if (require_host().physical_position().signed_units == 0.0) {
15918 std::vector<SourceId> exit_owners;
15919 const auto collect_owner = [&](const PlacementSnapshot& candidate) {
15920 const bool exit = candidate.family == PineOrderFamily::ExitLimit
15921 || candidate.family == PineOrderFamily::ExitStop
15922 || candidate.family == PineOrderFamily::ExitTrail;
15923 if (exit) exit_owners.push_back(candidate.from_entry);
15924 };
15925 for (const auto& handle : live_handles_) {
15926 const auto found = placement_.find(handle.incarnation);
15927 if (found != placement_.end()) collect_owner(found->second);
15928 }
15929 for (const auto& pending : pending_bracket_legs_) collect_owner(pending.snapshot);
15930 for (const auto& pending : pending_coof_requests_) collect_owner(pending.snapshot);
15931 for (const auto& shadow : source_shadow_pending_) collect_owner(shadow.snapshot);
15932 std::sort(exit_owners.begin(), exit_owners.end());
15933 exit_owners.erase(std::unique(exit_owners.begin(), exit_owners.end()),
15934 exit_owners.end());
15935 const auto pending_parent = [&](const SourceId& owner) {
15936 if (placement_snapshot && placement_snapshot->opening
15937 && placement_snapshot->source_id == owner) {
15938 return true;
15939 }
15940 for (const auto& handle : live_handles_) {
15941 if (event.terminal && handle == event.handle()) continue;
15942 const auto found = placement_.find(handle.incarnation);
15943 if (found != placement_.end() && found->second.opening
15944 && found->second.family == PineOrderFamily::Entry
15945 && found->second.source_id == owner) {
15946 return true;
15947 }
15948 }
15949 return std::any_of(pending_entries_.begin(), pending_entries_.end(),
15950 [&](const PendingEntry& entry) {
15951 return entry.snapshot.opening
15952 && entry.snapshot.source_id == owner;
15953 });
15954 };
15955 for (const auto& owner : exit_owners) {
15956 // A transient flat between two same-point reversal transactions
15957 // does not end the pending parent's lifecycle. Keep only that
15958 // parent's brackets; stale owners still retire immediately.
15959 if (!pending_parent(owner)) cancel_exit_orders_for_full_close(owner);
15960 }
15961 // ab9714be pine_fills.cpp:7461-7468 / 7667-7673: the owner scan above
15962 // only sees exits that are live or still pending, so a bracket that
15963 // already went dormant survives the flat in its projection row and is
15964 // revived against the next position that reuses its entry id. The
15965 // legacy book Removes every in-position exit at the flat whatever its
15966 // current lifecycle state.
15967 retire_in_position_exits_at_flat(/*preserve_pending_parents=*/true,
15968 /*dormant_rows_only=*/true,
15969 /*paired_close=*/nullptr);
15970 position_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
15971 position_open_bar_index_ = -1;
15972 position_open_phase_ = NativePathPhase::None;
15973 position_open_priced_ = false;
15974 open_entry_fees_.clear();
15975 close_logical_units_.clear();
15976 close_reserved_units_.clear();
15977 close_first_units_.clear();
15978 close_callsite_reserved_units_.clear();
15979 close_callsite_first_units_.clear();
15980 std::vector<SourceId> ended_sources;
15981 pending_margin_revivals_.clear();
15982 for (auto& cohort : cohorts_by_id_) {
15983 if (!cohort.second.opened.empty()) ended_sources.push_back(cohort.first);
15984 cohort.second.opened.clear();
15985 cohort.second.live_units_by_origin.clear();
15986 }
15987 const auto handles = live_handles_;
15988 for (const auto& handle : handles) {
15989 const auto found = placement_.find(handle.incarnation);
15990 if (found == placement_.end()) continue;
15991 const auto family = found->second.family;
15992 // ab9714be pine_orders.cpp:530-534: purge_exit_orders retires all exit and close orders when position flattens
15993 if (family == PineOrderFamily::ExitLimit
15994 || family == PineOrderFamily::ExitStop
15995 || family == PineOrderFamily::ExitTrail
15996 || family == PineOrderFamily::Close
15997 || family == PineOrderFamily::CloseAll
15998 // The slice is a full close of its from_entry's cycle: the owner
15999 // clears cycle_filled_entry_ids_ when the book goes flat
16000 // (ab9714be pine_strategy_host.cpp:245-251), so the bracket
16001 // retires with the rest instead of surviving into the next lot
16002 // that reuses the id.
16003 || family == PineOrderFamily::Margin) {
16004 if (!found->second.from_entry.empty()
16005 && std::find(ended_sources.begin(), ended_sources.end(),
16006 found->second.from_entry) == ended_sources.end()) {
16007 continue;
16008 }
16009 (void)require_host().cancel(handle);
16010 retire(handle);
16011 }
16012 }
16013 bracket_shadowed_openings_.clear();
16014 }
16015 if (short_seed_.final_short.incarnation != 0 && event.handle() == short_seed_.final_short
16016 && config_.default_qty_type != static_cast<int>(QtyType::FIXED)) {
16017 short_seed_.report_swap_pending = true;
16018 }
16019 update_l4c_lifecycle(event, context);
16020 if (event.terminal) retire(event.handle());
16021 if (event.ordinal != day_ledger_.observed_applied_ordinal) {
16022 day_ledger_.observed_applied_ordinal = event.ordinal;
16023 const auto day = chart_day_key(context.sub_bar_open_ms);
16024 if (event.closed_trade_count > 0) {
16025 const auto& host = require_host();
16026 for (std::size_t i = 0; i < event.closed_trade_count; ++i) {
16027 const auto index = event.first_trade_index + i;
16028 if (index >= static_cast<std::size_t>(host.trade_count())) continue;
16029 const double pnl = host.get_trade(static_cast<int>(index)).pnl;
16030 day_ledger_.intraday_realized += pnl;
16031 if (pnl < 0.0 && day != day_ledger_.last_loss_day) {
16032 day_ledger_.last_loss_day = day;
16033 if (day_ledger_.consecutive_loss_days
16034 == std::numeric_limits<int>::max()) {
16035 throw std::overflow_error("closed trade counter exhausted");
16036 }
16037 ++day_ledger_.consecutive_loss_days;
16038 } else if (pnl > 0.0) {
16039 day_ledger_.consecutive_loss_days = 0;
16040 }
16041 }
16042 }
16043 }
16044 if (placement_snapshot) {
16045 observe_intraday_cap(event, *placement_snapshot, context);
16046 if (placement_snapshot->close_batch_calls != 0)
16047 observe_close_policy(event, *placement_snapshot);
16048 if (placement_snapshot->family == PineOrderFamily::Margin
16049 && event.closed_units > 0.0) {
16050 last_margin_call_script_bar_ = context.script_bar_open_ms;
16051 const auto after_margin = require_host().physical_position();
16052 const bool one_x_long = after_margin.signed_units > 0.0
16053 && std::abs(config_.margin_long - 100.0) < 1e-12;
16054 if (context.coordinate.path_phase == NativePathPhase::Open
16055 && policy_script_bar_valid_
16056 && policy_script_bar_.timestamp == context.script_bar_open_ms
16057 && after_margin.signed_units != 0.0 && !one_x_long) {
16058 // ab9714be pine_fills.cpp:2525-2678 then :1266-1751:
16059 // after an opening slice, the survivor is checked over the
16060 // unconsumed bar suffix. Submission from this Applied point
16061 // uses A35 remaining-path eligibility and sizes from the
16062 // already-reduced physical book.
16063 (void)schedule_margin_call_path(policy_script_bar_, context);
16064 }
16065 }
16066 if (pooc_close_checkpoint_deferred_ms_ == context.script_bar_open_ms
16067 && context.coordinate.path_phase == NativePathPhase::Close
16068 && placement_snapshot->family != PineOrderFamily::Margin
16069 && !market_orders_pending_at_close(context, event.handle().incarnation)) {
16070 // Last of this bar's close market fills: run the deferred
16071 // carried-POOC-short checkpoint on the post-fill book.
16072 pooc_close_checkpoint_deferred_ms_ = std::numeric_limits<std::int64_t>::min();
16073 const auto after_fill = require_host().physical_position();
16074 if (after_fill.signed_units < 0.0
16075 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()
16076 && position_open_script_bar_ != context.script_bar_open_ms
16077 && last_margin_call_script_bar_ != context.script_bar_open_ms
16078 && policy_script_bar_valid_
16079 && policy_script_bar_.timestamp == context.script_bar_open_ms
16080 && finite_positive(policy_script_bar_.high)) {
16081 (void)submit_margin_call_slice(policy_script_bar_.high, context);
16082 }
16083 }
16084 if (placement_snapshot->family == PineOrderFamily::Risk
16085 && event.closed_units > 0.0) {
16086 risk_.intraday_block_day = chart_day_key(context.sub_bar_open_ms);
16087 risk_.intraday_cancel_pending = true;
16088 }
16089 }
16090 if (preclose_intraday_loss) {
16091 risk_.intraday_block_day = chart_day_key(context.sub_bar_open_ms);
16092 risk_.intraday_cancel_pending = true;
16093 if (require_host().physical_position().signed_units != 0.0) {
16094 native_order::Request request;
16095 request.intent = native_order::Flatten{};
16096 request.comment = kIntradayLossComment;
16097 PlacementSnapshot snapshot;
16098 snapshot.family = PineOrderFamily::Risk;
16099 snapshot.source_id = "__intraday_loss__";
16100 snapshot.comment = request.comment;
16101 snapshot.sizing = sizing_snapshot();
16102 const auto accepted = submit_or_replace(
16103 std::move(request), std::move(snapshot), false,
16104 "__intraday_loss_close__");
16105 if (accepted) {
16106 (void)require_host().execute_current(
16107 {*accepted, NativeCurrentPriceRule::NearestTick});
16108 }
16109 }
16110 }
16111 if (risk_.intraday_cancel_pending) {
16112 risk_.intraday_cancel_pending = false;
16113 cancel_all();
16114 }
16115 if (placement_snapshot && placement_snapshot->opening
16116 && std::abs(event.opened_units) > 0.0 && policy_script_bar_valid_) {
16117 // Legacy processes the opening-affordability checkpoint at the
16118 // matched entry price before the remainder of that bar's path. The
16119 // native callback is at exactly that current execution point, so the
16120 // adapter can issue the generic reduction synchronously without a
16121 // second matching loop.
16122 const bool commissioned_short_opening =
16123 require_host().physical_position().signed_units < 0.0
16124 && config_.margin_short == 100.0
16125 && config_.commission_type == static_cast<int>(CommissionType::PERCENT)
16126 && config_.commission_value > 0.0
16127 && finite_positive(placement_snapshot->requested_qty);
16128 const auto opened_position = require_host().physical_position();
16129 const double opening_margin = opened_position.signed_units < 0.0
16130 ? config_.margin_short : config_.margin_long;
16131 const bool full_margin_opening = opened_position.signed_units != 0.0
16132 && std::abs(opening_margin - 100.0) < 1e-12;
16133 const bool preopen_margin_already_scheduled = std::any_of(
16134 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
16135 const auto pending = placement_.find(handle.incarnation);
16136 return pending != placement_.end()
16137 && pending->second.family == PineOrderFamily::Margin
16138 && pending->second.from_entry == placement_snapshot->source_id
16139 && pending->second.source_id.rfind("__margin_preopen__", 0) == 0;
16140 });
16141 const bool flat_dual_stop_member = last_bar_dual_entry_path_ != 0
16142 && placement_snapshot->projection_position_side
16143 == static_cast<std::int32_t>(PositionSide::FLAT)
16144 && finite_positive(placement_snapshot->exit_levels.stop)
16145 && !finite_positive(placement_snapshot->exit_levels.limit)
16146 && std::any_of(placement_.begin(), placement_.end(),
16147 [&](const auto& row) {
16148 const auto& peer = row.second;
16149 return row.first != event.handle().incarnation && peer.opening
16150 && peer.family == PineOrderFamily::Entry
16151 && peer.projection_position_side
16152 == static_cast<std::int32_t>(PositionSide::FLAT)
16153 && peer.projection_created_bar
16154 == placement_snapshot->projection_created_bar
16155 && peer.is_long != placement_snapshot->is_long
16156 && finite_positive(peer.exit_levels.stop)
16157 && !finite_positive(peer.exit_levels.limit);
16158 });
16159 // Timestamped FX has its own base-equivalent opening checkpoint
16160 // (apply_fx_opening_margin_slice). A generic fill-price retry here
16161 // would replay a rate epoch that was consumed while the host was
16162 // flat, producing a false margin row on the subsequent opening.
16163 const bool stable_opening_fx = staged_.account_fx_effective_from_ms.empty()
16164 || placement_snapshot->sizing.fx
16165 == active_staged_fx(context.sub_bar_open_ms);
16166 const bool zero_fee_true_flat_default =
16167 config_.default_qty_type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
16168 && std::abs(config_.default_qty_value - 100.0) < 1e-12
16169 && config_.commission_value == 0.0
16170 && !std::isfinite(placement_snapshot->requested_qty)
16171 && placement_snapshot->projection_position_side
16172 == static_cast<std::int32_t>(PositionSide::FLAT)
16173 && !placement_snapshot->projection_after_close
16174 && std::holds_alternative<native_order::Market>(event.request().trigger)
16175 && placement_snapshot->sizing.price == event.resolved_price;
16176 if ((full_margin_opening || placement_snapshot->has_full_entry_bracket)
16177 && !commissioned_short_opening && !preopen_margin_already_scheduled
16178 && stable_opening_fx) {
16179 const bool long_full_margin = opened_position.signed_units > 0.0
16180 && std::abs(config_.margin_long - 100.0) < 1e-12;
16181 const bool prearmed_entry_bar_margin = std::any_of(
16182 live_handles_.begin(), live_handles_.end(), [&](const auto& handle) {
16183 const auto pending = placement_.find(handle.incarnation);
16184 return pending != placement_.end()
16185 && pending->second.family == PineOrderFamily::Margin
16186 && pending->second.from_entry == placement_snapshot->source_id;
16187 });
16188 // ab9714be pine_fills.cpp:2150-2169: a 1x short does not run an
16189 // opening trim before a priced exit that touches on the entry bar.
16190 const bool short_preempted_by_priced_exit = opened_position.signed_units < 0.0
16191 && [&]() {
16192 for (const auto& handle : live_handles_) {
16193 const auto found = placement_.find(handle.incarnation);
16194 if (found == placement_.end()) continue;
16195 const auto& row = found->second;
16196 if (row.family != PineOrderFamily::ExitLimit
16197 && row.family != PineOrderFamily::ExitStop) {
16198 continue;
16199 }
16200 if (row.projection_created_bar < 0
16201 || row.projection_created_bar > context.coordinate.interval_index) {
16202 continue;
16203 }
16204 // ab9714be pine_fills.cpp:7671-7674 removes an exit
16205 // whose from_entry has not filled in the live cycle
16206 // (the long a direct reversal just closed), so it
16207 // cannot fill and must not preempt the opening
16208 // checkpoint (pine_fills.cpp:6069-6102, 1386-1482).
16209 if (!row.from_entry.empty()
16210 && !from_entry_filled_this_cycle(row.from_entry)) {
16211 continue;
16212 }
16213 if (std::isfinite(row.exit_levels.stop)
16214 && policy_script_bar_.high >= row.exit_levels.stop) {
16215 return true;
16216 }
16217 if (std::isfinite(row.exit_levels.limit)
16218 && policy_script_bar_.low <= row.exit_levels.limit) {
16219 return true;
16220 }
16221 }
16222 return false;
16223 }();
16224 // The 10-significant-digit long residual is same-currency,
16225 // pointvalue-one policy. A non-unit point value does not inherit
16226 // an exact-money opening slice merely because the generic
16227 // floating ledger rounds its fill cost differently.
16228 if (!zero_fee_true_flat_default
16229 && !short_preempted_by_priced_exit
16230 && !flat_dual_stop_member && !prearmed_entry_bar_margin
16231 && !(long_full_margin
16232 && std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12)) {
16233 const double opening_exact_required = std::abs(opened_position.signed_units)
16234 * event.resolved_price * staged_.syminfo.pointvalue
16235 * active_staged_fx(context.sub_bar_open_ms);
16236 const double opening_equity = require_host().native_marked_equity(
16237 event.resolved_price);
16238 const bool defer_slipped_pooc_rounding =
16239 slipped_pooc_opening_money_scope(policy_script_bar_, context)
16240 && std::isfinite(opening_exact_required)
16241 && std::isfinite(opening_equity)
16242 && opening_equity >= opening_exact_required
16243 && opening_equity < source_money_round(opening_exact_required);
16244 // pine_fills.cpp:1441-1450 at ab9714be leaves this exact-funded
16245 // terminal POOC residual intact until the next opening print.
16246 // Genuine opening deficits retain the immediate checkpoint.
16247 if (!defer_slipped_pooc_rounding) {
16248 (void)submit_margin_call_slice(
16249 event.resolved_price, context, true);
16250 }
16251 }
16252 const bool terminal_pooc_open = config_.process_orders_on_close
16253 && position_open_phase_ == NativePathPhase::Close;
16254 if (!terminal_pooc_open && !flat_dual_stop_member
16255 && !prearmed_entry_bar_margin) {
16256 if (long_full_margin) {
16257 (void)schedule_tv_money_long_margin_before_trail(
16258 policy_script_bar_, context);
16259 } else {
16260 schedule_margin_call_path(policy_script_bar_, context);
16261 }
16262 }
16263 }
16264 schedule_intraday_loss_path(policy_script_bar_, context);
16265 }
16266 if (placement_snapshot && placement_snapshot->family == PineOrderFamily::Close
16267 && context.coordinate.path_phase == NativePathPhase::Open
16268 && require_host().physical_position().signed_units != 0.0
16269 && policy_script_bar_valid_ && !config_.process_orders_on_close
16270 && staged_.account_fx_effective_from_ms.empty()) {
16271 // The retired ordinary scheduler settled old MARKET closes before its
16272 // pre-script adverse-margin checkpoint. Recompute the path slice from
16273 // the post-close physical book so a partial close cannot leave behind
16274 // a fixed liquidation request sized on the larger pre-open position.
16275 std::vector<native_order::RequestHandle> stale_margin_requests;
16276 for (const auto& handle : live_handles_) {
16277 const auto pending = placement_.find(handle.incarnation);
16278 if (pending != placement_.end()
16279 && pending->second.family == PineOrderFamily::Margin) {
16280 stale_margin_requests.push_back(handle);
16281 }
16282 }
16283 for (const auto& handle : stale_margin_requests) {
16284 const auto cancelled = require_host().cancel(handle);
16285 if (cancelled.status == native_order::CancelStatus::Cancelled)
16286 retire(handle);
16287 }
16288 (void)schedule_margin_call_path(policy_script_bar_, context);
16289 }
16290 if (placement_snapshot && event.closed_units > 0.0
16291 && (placement_snapshot->family == PineOrderFamily::ExitLimit
16292 || placement_snapshot->family == PineOrderFamily::ExitStop
16293 || placement_snapshot->family == PineOrderFamily::ExitTrail)
16294 && policy_script_bar_valid_
16295 && policy_script_bar_.timestamp == context.script_bar_open_ms
16296 && !config_.process_orders_on_close && !config_.calc_on_order_fills
16297 && staged_.account_fx_effective_from_ms.empty()) {
16298 // ab9714be pine_scheduler.cpp:267-282: the ordinary margin call runs
16299 // once after every order of the bar, on the post-fill book; only an
16300 // adverse extreme that strictly precedes the priced exit's fill takes
16301 // the slice ahead of it (pine_fills.cpp:2224), and that slice has
16302 // already settled by now, as has a carried open-price slice
16303 // (pine_scheduler.cpp:177-183). A path slice still live here was sized on
16304 // the pre-exit position, so re-size it from the reduced book. The
16305 // 1x-long TV-money slice keeps its own scheduler.
16306 const auto after_exit = require_host().physical_position();
16307 const bool one_x_long = after_exit.signed_units > 0.0
16308 && std::abs(config_.margin_long - 100.0) < 1e-12;
16309 std::vector<native_order::RequestHandle> stale_margin_requests;
16310 for (const auto& handle : live_handles_) {
16311 const auto pending = placement_.find(handle.incarnation);
16312 if (pending != placement_.end()
16313 && pending->second.family == PineOrderFamily::Margin) {
16314 stale_margin_requests.push_back(handle);
16315 }
16316 }
16317 if (!one_x_long && !stale_margin_requests.empty()) {
16318 for (const auto& handle : stale_margin_requests) {
16319 const auto cancelled = require_host().cancel(handle);
16320 if (cancelled.status == native_order::CancelStatus::Cancelled)
16321 retire(handle);
16322 }
16323 if (after_exit.signed_units != 0.0)
16324 (void)schedule_margin_call_path(policy_script_bar_, context);
16325 } else if (!one_x_long) {
16326 // R5: under the kernel margin model the live path slice is the
16327 // kernel's, so there is no source row here to cancel and the
16328 // scan above finds nothing. The re-size still has to happen --
16329 // a slice sized on the pre-exit book must not survive the exit
16330 // and execute against the remainder -- so admit the kernel's own
16331 // check point for this driver point instead. Gated on a resting
16332 // slice in margin_check_allowed, so a point where the legacy
16333 // broker did nothing stays a point where nothing is done.
16334 kernel_margin_resize_point_ = context.coordinate.ordinal;
16335 if (after_exit.signed_units != 0.0)
16336 (void)schedule_margin_call_path(policy_script_bar_, context);
16337 }
16338 }
16339 apply_fx_opening_margin_slice(event, context);
16340 refresh_pending_view();
16341 // ab9714be pine_fills.cpp:7026-7033 and pine_orders.cpp:444-480 (KI-62):
16342 // after a priced from_entry bracket leg fills, every same-id MARKET
16343 // pyramid add opened on this bar is still open behind the leg's own
16344 // (FIFO) reduction; the owner covers it at the leg's booked price as a
16345 // second fill of the same order.
16346 if (placement_snapshot && event.closed_units > 0.0
16347 && (placement_snapshot->family == PineOrderFamily::ExitLimit
16348 || placement_snapshot->family == PineOrderFamily::ExitStop
16349 || placement_snapshot->family == PineOrderFamily::ExitTrail)
16350 && !placement_snapshot->from_entry.empty()
16351 && require_host().physical_position().signed_units != 0.0
16352 && current_position_cycle_ > 0) {
16353 auto* pine = dynamic_cast<PineStrategyHost*>(&require_host());
16354 const auto cohort = cohorts_by_id_.find(placement_snapshot->from_entry);
16355 if (pine && cohort != cohorts_by_id_.end()) {
16356 const int bar = pine->scheduler_.bar_magnifier_enabled()
16357 ? pine->scheduler_.source_bar_index_for(context)
16358 : context.coordinate.interval_index;
16359 std::vector<native_order::RequestHandle> adds;
16360 double units = 0.0;
16361 for (const auto& lot : pine->pyramid_entries_) {
16362 if (!market_pyramid_add(lot.entry_incarnation)
16363 || lot.entry_bar_index != bar
16364 || lot.entry_id != placement_snapshot->from_entry
16365 || !(lot.qty > internal::kQtyEpsilon)) {
16366 continue;
16367 }
16368 const auto opened = std::find_if(
16369 cohort->second.opened.begin(), cohort->second.opened.end(),
16370 [&](const native_order::RequestHandle& handle) {
16371 return handle.incarnation == lot.entry_incarnation;
16372 });
16373 if (opened == cohort->second.opened.end()) continue;
16374 adds.push_back(*opened);
16375 units += lot.qty;
16376 }
16377 if (!adds.empty() && units > 0.0) {
16378 native_order::Request request;
16380 request.label = placement_snapshot->source_id;
16381 request.comment = placement_snapshot->comment;
16382 request.trigger = native_order::Market{};
16384 std::move(adds), current_position_cycle_};
16385 request.group = native_order::NoGroup{};
16386 PlacementSnapshot cover = *placement_snapshot;
16387 cover.requested_qty = units;
16388 cover.immediately = true;
16389 cover.deferred_cohort = false;
16390 cover.forced_execution_price = event.resolved_price;
16391 const auto accepted = submit_or_replace(
16392 std::move(request), std::move(cover), false,
16393 placement_snapshot->source_id + "\x1f" + placement_snapshot->from_entry
16394 + "\x1f" + "cover");
16395 if (accepted) {
16396 (void)require_host().execute_current(
16397 {*accepted, NativeCurrentPriceRule::NearestTick});
16398 }
16399 }
16400 }
16401 }
16402}
16403
16405 // Only the plan actually selected at the qualifying broker open projects
16406 // executable roles. Retained immutable handles are lifecycle/hash facts,
16407 // not historical role authority.
16408 if (!short_seed_.active) return 0;
16409 if (handle == short_seed_.long_entry) return 1;
16410 if (handle == short_seed_.materialize_long) return 2;
16411 if (handle == short_seed_.final_short) return 3;
16412 return 0;
16413}
16414
16415bool PineExecutionAdapter::take_intraday_loss_relabel(std::uint64_t ordinal) noexcept {
16416 return intraday_loss_relabel_ordinals_.erase(ordinal) != 0;
16417}
16418
16419std::vector<PineExecutionAdapter::FixturePendingSnapshot>
16421 std::vector<FixturePendingSnapshot> rows;
16422 rows.reserve(live_handles_.size() + pending_entries_.size()
16423 + pending_bracket_legs_.size()
16424 + pending_same_bar_commands_.size()
16425 + pending_coof_requests_.size()
16426 + source_shadow_pending_.size());
16427 std::uint64_t next_incarnation = 1;
16428 for (const auto& placement : placement_)
16429 next_incarnation = std::max(next_incarnation, placement.first + 1);
16430 struct StagedParentProjection {
16431 SourceId id;
16432 std::uint64_t command_sequence = 0;
16433 std::uint64_t incarnation = 0;
16434 };
16435 std::vector<StagedParentProjection> staged_parents;
16436 for (const auto& handle : live_handles_) {
16437 const auto found = placement_.find(handle.incarnation);
16438 if (found != placement_.end()) rows.push_back({handle.incarnation, found->second, false});
16439 }
16440 for (const auto& pending : pending_entries_) {
16441 const std::uint64_t incarnation = next_incarnation++;
16442 rows.push_back({incarnation, pending.snapshot, true});
16443 staged_parents.push_back({pending.snapshot.source_id,
16444 pending.snapshot.command_sequence,
16445 incarnation});
16446 }
16447 std::vector<std::uint64_t> pending_bracket_families;
16448 for (const auto& pending : pending_bracket_legs_) {
16449 if (std::find(pending_bracket_families.begin(), pending_bracket_families.end(),
16450 pending.family_key) != pending_bracket_families.end()) {
16451 continue;
16452 }
16453 pending_bracket_families.push_back(pending.family_key);
16454 std::uint64_t incarnation = 0;
16455 if (pending.snapshot.defer_until_post_parent_calculation
16456 && pending.snapshot.legs.target().incarnation != 0) {
16457 incarnation = pending.snapshot.legs.target().incarnation;
16458 } else {
16459 const auto parent = std::find_if(
16460 staged_parents.begin(), staged_parents.end(),
16461 [&](const StagedParentProjection& row) {
16462 return row.id == pending.snapshot.from_entry
16463 && pending.snapshot.command_sequence >= row.command_sequence;
16464 });
16465 if (parent != staged_parents.end()) {
16466 incarnation = parent->incarnation
16467 + (pending.snapshot.command_sequence - parent->command_sequence);
16468 next_incarnation = std::max(next_incarnation, incarnation + 1);
16469 } else {
16470 incarnation = next_incarnation++;
16471 }
16472 }
16473 rows.push_back({incarnation, pending.snapshot, true});
16474 }
16475 for (const auto& pending : pending_same_bar_commands_)
16476 rows.push_back({0, pending.snapshot, true});
16477 for (const auto& pending : pending_coof_requests_)
16478 rows.push_back({0, pending.snapshot, true});
16479 for (const auto& shadow : source_shadow_pending_)
16480 rows.push_back({0, shadow.snapshot, true});
16481 return rows;
16482}
16483
16485 const SourceId& id) const noexcept {
16486 const auto found = close_logical_units_.find(id);
16487 return found == close_logical_units_.end() ? 0.0 : found->second;
16488}
16489
16491 const SourceId& id) const noexcept {
16492 const auto found = close_reserved_units_.find(id);
16493 return found == close_reserved_units_.end() ? 0.0 : found->second;
16494}
16495
16497 const SourceId& id) const noexcept {
16498 const auto found = close_first_units_.find(id);
16499 return found == close_first_units_.end() ? 0.0 : found->second;
16500}
16501
16503 std::uint64_t token, const SourceId& id) const noexcept {
16504 const auto owner = close_callsite_reserved_units_.find(token);
16505 if (owner == close_callsite_reserved_units_.end()) return 0.0;
16506 const auto found = owner->second.find(id);
16507 return found == owner->second.end() ? 0.0 : found->second;
16508}
16509
16511 std::uint64_t token, const SourceId& id) const noexcept {
16512 const auto owner = close_callsite_first_units_.find(token);
16513 if (owner == close_callsite_first_units_.end()) return 0.0;
16514 const auto found = owner->second.find(id);
16515 return found == owner->second.end() ? 0.0 : found->second;
16516}
16517
16519 return close_reserved_units_.size();
16520}
16521
16523 return close_first_units_.size();
16524}
16525
16527 std::size_t count = 0;
16528 for (const auto& owner : close_callsite_reserved_units_) count += owner.second.size();
16529 return count;
16530}
16531
16533 std::size_t count = 0;
16534 for (const auto& owner : close_callsite_first_units_) count += owner.second.size();
16535 return count;
16536}
16537
16539 double total = 0.0;
16540 for (const auto& owner : close_callsite_reserved_units_)
16541 for (const auto& claim : owner.second) total += claim.second;
16542 return total;
16543}
16544
16545std::vector<PineExecutionAdapter::FixtureCloseCallsite>
16547 std::vector<FixtureCloseCallsite> result;
16548 result.reserve(close_batch_callsites_.size());
16549 for (const auto& row : close_batch_callsites_) {
16550 const auto& site = row.second;
16551 result.push_back({site.token, site.active, site.target, site.calls,
16552 site.id, site.comment, site.queue_sequence});
16553 }
16554 return result;
16555}
16556
16559 if (index < 0 || index >= static_cast<int>(live_handles_.size())) return nullptr;
16560 const auto handle = live_handles_[static_cast<std::size_t>(index)];
16561 const auto found = placement_.find(handle.incarnation);
16562 return found == placement_.end() ? nullptr : &found->second.cancellation;
16563}
16564
16566 const SourceId& id) {
16567 pending_entries_.erase(std::remove_if(
16568 pending_entries_.begin(), pending_entries_.end(),
16569 [&](const PendingEntry& pending) {
16570 return pending.snapshot.family == PineOrderFamily::Entry
16571 && pending.snapshot.source_id == id;
16572 }), pending_entries_.end());
16573 std::vector<native_order::RequestHandle> matches;
16574 for (const auto& handle : live_handles_) {
16575 const auto found = placement_.find(handle.incarnation);
16576 if (found != placement_.end()
16577 && found->second.family == PineOrderFamily::Entry
16578 && found->second.source_id == id) {
16579 matches.push_back(handle);
16580 }
16581 }
16582 for (const auto& handle : matches) {
16583 const auto result = require_host().cancel(handle);
16584 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
16585 }
16586 named_entry_cancel_tokens_.erase(id);
16587}
16588
16589void PineExecutionAdapter::set_risk_direction(int direction) noexcept { risk_.direction = direction; }
16590void PineExecutionAdapter::set_risk_max_cons_loss_days(int value) noexcept { risk_.max_cons_loss_days = value; }
16591void PineExecutionAdapter::set_risk_max_drawdown(double value, bool percent) noexcept {
16592 risk_.max_drawdown = value;
16593 if (percent) risk_.max_drawdown_percent = true;
16594}
16595void PineExecutionAdapter::set_risk_max_intraday_loss(double value, bool percent) noexcept {
16596 risk_.max_intraday_loss = value;
16597 if (percent) risk_.max_intraday_loss_percent = true;
16598}
16599void PineExecutionAdapter::set_risk_max_position_size(double value) noexcept { risk_.max_position_size = value; }
16601 source_margin_call_enabled_ = enabled;
16602}
16603void PineExecutionAdapter::mark_market_pyramid_add(std::uint64_t incarnation) {
16604 market_pyramid_adds_.insert(incarnation);
16605}
16608
16609std::vector<native_order::RequestHandle> PineExecutionAdapter::take_first_open_newborns() {
16610 auto result = std::move(first_open_newborns_);
16611 first_open_newborns_.clear();
16612 return result;
16613}
16614
16615void PineExecutionAdapter::refresh_pending_view() noexcept {
16616 // PendingIntentView is a read-only projection of the current live-handle
16617 // roster. It used to duplicate that roster after every submit, replace and
16618 // retirement; expose the live roster directly instead. hash_state keeps
16619 // the historical two-vector encoding by folding the same roster twice.
16620}
16621
16622int PineExecutionAdapter::projected_raw_pending_size() const noexcept {
16623 const std::size_t total = live_handles_.size()
16624 + pending_same_bar_commands_.size() + pending_entries_.size()
16625 + pending_bracket_legs_.size() + pending_coof_requests_.size()
16626 + source_shadow_pending_.size();
16627 return total > static_cast<std::size_t>(std::numeric_limits<int>::max())
16628 ? std::numeric_limits<int>::max() : static_cast<int>(total);
16629}
16630
16631bool PineExecutionAdapter::projected_raw_pending_at(
16632 int index, const PlacementSnapshot*& snapshot,
16633 native_order::RequestHandle& handle) const noexcept {
16634 snapshot = nullptr;
16635 handle = {};
16636 if (index < 0) return false;
16637 std::size_t offset = static_cast<std::size_t>(index);
16638 if (offset < live_handles_.size()) {
16639 handle = live_handles_[offset];
16640 const auto found = placement_.find(handle.incarnation);
16641 if (found == placement_.end()) return false;
16642 snapshot = &found->second;
16643 return true;
16644 }
16645 offset -= live_handles_.size();
16646 const auto locate = [&](const auto& rows, auto read) {
16647 if (offset >= rows.size()) {
16648 offset -= rows.size();
16649 return false;
16650 }
16651 snapshot = &read(rows[offset]);
16652 return true;
16653 };
16654 if (locate(pending_same_bar_commands_,
16655 [](const PendingSameBarCommand& row) -> const PlacementSnapshot& {
16656 return row.snapshot;
16657 })) return true;
16658 if (locate(pending_entries_,
16659 [](const PendingEntry& row) -> const PlacementSnapshot& {
16660 return row.snapshot;
16661 })) return true;
16662 if (locate(pending_bracket_legs_,
16663 [](const PendingBracketLeg& row) -> const PlacementSnapshot& {
16664 return row.snapshot;
16665 })) return true;
16666 if (locate(pending_coof_requests_,
16667 [](const PendingCoofRequest& row) -> const PlacementSnapshot& {
16668 return row.snapshot;
16669 })) return true;
16670 return locate(source_shadow_pending_,
16671 [](const SourceShadowPending& row) -> const PlacementSnapshot& {
16672 return row.snapshot;
16673 });
16674}
16675
16676bool PineExecutionAdapter::same_projected_order(
16677 const PlacementSnapshot& left,
16678 const PlacementSnapshot& right) noexcept {
16679 const auto is_exit = [](PineOrderFamily family) {
16680 return family == PineOrderFamily::ExitLimit
16681 || family == PineOrderFamily::ExitStop
16682 || family == PineOrderFamily::ExitTrail;
16683 };
16684 if (!is_exit(left.family) || !is_exit(right.family)) return false;
16685 return left.source_id == right.source_id
16686 && left.from_entry == right.from_entry
16687 && left.bracket_origin == right.bracket_origin;
16688}
16689
16690int PineExecutionAdapter::projected_pending_size() const noexcept {
16691 const int raw_count = projected_raw_pending_size();
16692 int count = 0;
16693 for (int index = 0; index < raw_count; ++index) {
16694 const PlacementSnapshot* candidate = nullptr;
16695 native_order::RequestHandle handle;
16696 if (!projected_raw_pending_at(index, candidate, handle) || !candidate) continue;
16697 bool duplicate = false;
16698 for (int prior = 0; prior < index; ++prior) {
16699 const PlacementSnapshot* earlier = nullptr;
16700 native_order::RequestHandle earlier_handle;
16701 if (projected_raw_pending_at(prior, earlier, earlier_handle) && earlier
16702 && same_projected_order(*candidate, *earlier)) {
16703 duplicate = true;
16704 break;
16705 }
16706 }
16707 if (!duplicate && count != std::numeric_limits<int>::max()) ++count;
16708 }
16709 return count;
16710}
16711
16712bool PineExecutionAdapter::projected_pending_at(
16713 int index, const PlacementSnapshot*& snapshot,
16714 native_order::RequestHandle& handle) const noexcept {
16715 snapshot = nullptr;
16716 handle = {};
16717 if (index < 0) return false;
16718 const int raw_count = projected_raw_pending_size();
16719 int projected = 0;
16720 for (int raw = 0; raw < raw_count; ++raw) {
16721 const PlacementSnapshot* candidate = nullptr;
16722 native_order::RequestHandle candidate_handle;
16723 if (!projected_raw_pending_at(raw, candidate, candidate_handle) || !candidate) continue;
16724 bool duplicate = false;
16725 for (int prior = 0; prior < raw; ++prior) {
16726 const PlacementSnapshot* earlier = nullptr;
16727 native_order::RequestHandle earlier_handle;
16728 if (projected_raw_pending_at(prior, earlier, earlier_handle) && earlier
16729 && same_projected_order(*candidate, *earlier)) {
16730 duplicate = true;
16731 break;
16732 }
16733 }
16734 if (duplicate) continue;
16735 if (projected++ == index) {
16736 snapshot = candidate;
16737 handle = candidate_handle;
16738 return true;
16739 }
16740 }
16741 return false;
16742}
16743
16744int PendingIntentView::size() const noexcept {
16745 return owner_ ? owner_->projected_pending_size() : 0;
16746}
16747
16748int PendingIntentView::probe_fill_qty(int index, double fill_price, double* qty,
16749 int* close_only, int* partition) const noexcept {
16750 if (!owner_ || !qty || !close_only || !partition) return -1;
16751 const PlacementSnapshot* row = nullptr;
16753 if (!owner_->projected_pending_at(index, row, handle) || !row) return -1;
16754 const auto& snapshot = *row;
16755 *qty = kNaN;
16756 *close_only = 0;
16757 *partition = -1;
16758 if (!snapshot.opening) return 1;
16759
16760 const auto physical = owner_->require_host().physical_position();
16761 const bool opposite = physical.signed_units != 0.0
16762 && ((physical.signed_units > 0.0) != snapshot.is_long);
16763 bool kernel_close_only = false;
16764 bool sized = false;
16765 if (snapshot.frozen_market_instruction
16766 && finite_positive(snapshot.frozen_market_transaction_units)) {
16767 if (opposite) {
16768 *qty = std::max(0.0, snapshot.frozen_market_transaction_units
16769 - std::abs(physical.signed_units));
16770 *partition = 1;
16771 kernel_close_only = !(*qty > 1e-10);
16772 sized = true;
16773 } else if (physical.signed_units != 0.0
16774 && ((physical.signed_units > 0.0) == snapshot.is_long)
16775 && snapshot.projection_over_pyramiding) {
16776 *qty = snapshot.frozen_market_transaction_units;
16777 *partition = 1;
16778 sized = true;
16779 } else if (physical.signed_units == 0.0
16780 && finite_positive(snapshot.frozen_market_own_units)
16781 && snapshot.frozen_market_transaction_units
16782 > snapshot.frozen_market_own_units + 1e-10) {
16783 *qty = snapshot.frozen_market_transaction_units;
16784 *partition = 1;
16785 sized = true;
16786 }
16787 }
16788 const bool default_stop_shape = snapshot.family == PineOrderFamily::Entry
16789 && !std::isnan(snapshot.exit_levels.stop)
16790 && std::isnan(snapshot.exit_levels.limit)
16791 && std::isnan(snapshot.requested_qty)
16792 && owner_->config_.default_qty_type
16793 == static_cast<int>(QtyType::PERCENT_OF_EQUITY)
16794 && owner_->config_.default_qty_value <= 100.0
16795 && finite_positive(snapshot.sizing.frozen_units);
16796 const bool default_stop = default_stop_shape
16797 && finite_positive(fill_price)
16798 && snapshot.projection_position_side
16799 == static_cast<std::int32_t>(PositionSide::FLAT);
16800 const bool unpriced_market = std::isnan(snapshot.exit_levels.limit)
16801 && std::isnan(snapshot.exit_levels.stop)
16802 && std::isnan(snapshot.exit_levels.trail_offset)
16803 && std::isnan(snapshot.exit_levels.trail_price);
16804 if (!sized && finite_positive(snapshot.requested_qty)) {
16805 *qty = snapshot.requested_qty;
16806 *partition = 0;
16807 sized = true;
16808 } else if (!sized && default_stop) {
16809 *qty = snapshot.sizing.frozen_units;
16810 *partition = 1;
16811 sized = true;
16812 } else if (!sized && !default_stop_shape && unpriced_market
16813 && finite_positive(snapshot.sizing.frozen_units)) {
16814 *qty = snapshot.sizing.frozen_units;
16815 *partition = 1;
16816 sized = true;
16817 }
16818 if (!sized) {
16819 const bool limit_route = (snapshot.family == PineOrderFamily::Entry
16820 || snapshot.family == PineOrderFamily::Order)
16821 && !std::isnan(snapshot.exit_levels.limit);
16822 double sized_price = fill_price;
16823 if (!limit_route && std::isfinite(sized_price)) {
16824 sized_price += (snapshot.is_long ? 1.0 : -1.0)
16825 * owner_->config_.slippage * owner_->staged_.syminfo.mintick;
16826 }
16827 const int type = owner_->config_.default_qty_type;
16828 if (type == static_cast<int>(QtyType::CASH)) {
16829 const double denominator = sized_price * owner_->staged_.syminfo.pointvalue
16830 * owner_->staged_.account_fx;
16831 *qty = finite_positive(denominator)
16832 ? floor_quantity_grid(owner_->config_.default_qty_value / denominator,
16833 owner_->staged_.quantity_grid)
16834 : 0.0;
16835 } else if (type == static_cast<int>(QtyType::PERCENT_OF_EQUITY)) {
16836 const double denominator = sized_price * owner_->staged_.syminfo.pointvalue
16837 * owner_->staged_.account_fx;
16838 const double equity = finite_positive(sized_price)
16839 ? owner_->require_host().native_marked_equity(sized_price) : 0.0;
16840 *qty = finite_positive(denominator) && finite_positive(equity)
16841 ? floor_quantity_grid(equity * owner_->config_.default_qty_value
16842 / 100.0 / denominator,
16843 owner_->staged_.quantity_grid)
16844 : 0.0;
16845 } else {
16846 *qty = floor_quantity_grid(owner_->config_.default_qty_value,
16847 owner_->staged_.quantity_grid);
16848 }
16849 *partition = 3;
16850 }
16851
16852 const bool prior_cycle_close_only = snapshot.family == PineOrderFamily::Entry
16853 && opposite
16854 && snapshot.projection_position_side
16855 != (physical.signed_units > 0.0
16856 ? static_cast<std::int32_t>(PositionSide::LONG)
16857 : static_cast<std::int32_t>(PositionSide::SHORT))
16858 && !snapshot.projection_predecessor_market;
16859 *close_only = (snapshot.affordability_close_only || prior_cycle_close_only
16860 || kernel_close_only) ? 1 : 0;
16861 return 0;
16862}
16863
16864int PendingIntentView::level_resolved(int index) const noexcept {
16865 if (!owner_) return -1;
16866 const PlacementSnapshot* snapshot = nullptr;
16868 if (!owner_->projected_pending_at(index, snapshot, handle) || !snapshot) return -1;
16869 if (snapshot->from_entry.empty()) return 1;
16870 const auto cohort = owner_->cohorts_by_id_.find(snapshot->from_entry);
16871 return cohort != owner_->cohorts_by_id_.end() && !cohort->second.opened.empty() ? 1 : 0;
16872}
16873
16874int PendingIntentView::effective_levels(int index, double* stop, double* limit,
16875 double* trail_activation) const noexcept {
16876 if (!owner_ || !stop || !limit || !trail_activation) return -1;
16877 const PlacementSnapshot* row = nullptr;
16879 if (!owner_->projected_pending_at(index, row, handle) || !row) return -1;
16880 const auto& snapshot = *row;
16881 const double tick = owner_->staged_.syminfo.mintick;
16882 const auto physical = owner_->require_host().physical_position();
16883 const bool long_side = physical.signed_units > 0.0;
16884 const double entry = owner_->require_host().position_avg_price();
16885 *stop = snapshot.exit_levels.stop;
16886 *limit = snapshot.exit_levels.limit;
16887 double trail_points = snapshot.exit_levels.trail_points;
16888 double trail_price = snapshot.exit_levels.trail_price;
16889 double profit_ticks = snapshot.exit_levels.profit_ticks;
16890 double loss_ticks = snapshot.exit_levels.loss_ticks;
16891 for (int raw = 0; raw < owner_->projected_raw_pending_size(); ++raw) {
16892 const PlacementSnapshot* sibling = nullptr;
16893 native_order::RequestHandle sibling_handle;
16894 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
16895 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
16896 continue;
16897 }
16898 if (!std::isnan(sibling->exit_levels.stop)) *stop = sibling->exit_levels.stop;
16899 if (!std::isnan(sibling->exit_levels.limit)) *limit = sibling->exit_levels.limit;
16900 if (!std::isnan(sibling->exit_levels.trail_points))
16901 trail_points = sibling->exit_levels.trail_points;
16902 if (!std::isnan(sibling->exit_levels.trail_price))
16903 trail_price = sibling->exit_levels.trail_price;
16904 if (!std::isnan(sibling->exit_levels.profit_ticks))
16905 profit_ticks = sibling->exit_levels.profit_ticks;
16906 if (!std::isnan(sibling->exit_levels.loss_ticks))
16907 loss_ticks = sibling->exit_levels.loss_ticks;
16908 }
16909 // The legacy C observer reports the executable levels, not merely the
16910 // raw tick offsets retained at the command. Keep the source tick
16911 // derivation at the projection boundary where it is observable.
16912 const bool position_live = physical.signed_units != 0.0 && std::isfinite(entry);
16913 const bool resolved = position_live && level_resolved(index) == 1;
16914 const double direction = long_side ? 1.0 : -1.0;
16915 if ((snapshot.family == PineOrderFamily::ExitLimit
16916 || snapshot.family == PineOrderFamily::ExitStop
16917 || snapshot.family == PineOrderFamily::ExitTrail)
16918 && resolved) {
16919 if (std::isnan(*limit) && !std::isnan(profit_ticks)) {
16920 *limit = source_level_on_price_grid(
16921 entry + direction * profit_ticks * tick, tick);
16922 }
16923 if (std::isnan(*stop) && !std::isnan(loss_ticks)) {
16924 *stop = source_level_on_price_grid(
16925 entry - direction * loss_ticks * tick, tick);
16926 }
16927 }
16928 *trail_activation = kNaN;
16929 if (!std::isnan(trail_points)) {
16930 if (resolved) {
16931 const double ticks = compat::pine::trail_points_to_ticks(trail_points);
16933 entry + direction * ticks * tick, tick);
16934 }
16935 } else {
16936 *trail_activation = trail_price;
16937 }
16938 return 0;
16939}
16940
16941int PendingIntentView::copy_v1(int index, pf_pending_order_v1_t* out) const noexcept {
16942 if (!owner_ || !out) return -1;
16943 const PlacementSnapshot* row = nullptr;
16945 if (!owner_->projected_pending_at(index, row, handle) || !row) return -1;
16946 const PlacementSnapshot& snapshot = *row;
16947
16948 std::memset(out, 0, sizeof(*out));
16949 out->struct_version = 1;
16950 out->size = static_cast<std::uint32_t>(sizeof(*out));
16951 if (snapshot.family == PineOrderFamily::Close) {
16952 copy_pending_prefixed_string("__close__", snapshot.source_id,
16953 out->id, &out->id_truncated,
16954 &out->id_hash64);
16955 } else {
16956 copy_pending_string(snapshot.source_id, out->id,
16957 &out->id_truncated, &out->id_hash64);
16958 }
16959 copy_pending_string(snapshot.from_entry, out->from_entry, &out->from_entry_truncated,
16960 &out->from_entry_hash64);
16961 copy_pending_string(snapshot.oca_name, out->oca_name, &out->oca_name_truncated,
16962 &out->oca_name_hash64);
16963 copy_pending_string(snapshot.comment, out->comment, &out->comment_truncated,
16964 &out->comment_hash64);
16965 out->type = mirror_order_type(snapshot.family);
16966 out->is_long = snapshot.is_long ? 1U : 0U;
16967 out->limit_price = snapshot.exit_levels.limit;
16968 out->stop_price = snapshot.exit_levels.stop;
16969 out->trail_points = snapshot.exit_levels.trail_points;
16970 out->trail_price = snapshot.exit_levels.trail_price;
16971 out->trail_offset = snapshot.exit_levels.trail_offset;
16972 out->profit_ticks = snapshot.exit_levels.profit_ticks;
16973 out->loss_ticks = snapshot.exit_levels.loss_ticks;
16974 for (int raw = 0; raw < owner_->projected_raw_pending_size(); ++raw) {
16975 const PlacementSnapshot* sibling = nullptr;
16976 native_order::RequestHandle sibling_handle;
16977 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
16978 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
16979 continue;
16980 }
16981 if (!std::isnan(sibling->exit_levels.limit))
16982 out->limit_price = sibling->exit_levels.limit;
16983 if (!std::isnan(sibling->exit_levels.stop))
16984 out->stop_price = sibling->exit_levels.stop;
16985 if (!std::isnan(sibling->exit_levels.trail_points))
16986 out->trail_points = sibling->exit_levels.trail_points;
16987 if (!std::isnan(sibling->exit_levels.trail_price))
16988 out->trail_price = sibling->exit_levels.trail_price;
16989 if (!std::isnan(sibling->exit_levels.trail_offset))
16990 out->trail_offset = sibling->exit_levels.trail_offset;
16991 if (!std::isnan(sibling->exit_levels.profit_ticks))
16992 out->profit_ticks = sibling->exit_levels.profit_ticks;
16993 if (!std::isnan(sibling->exit_levels.loss_ticks))
16994 out->loss_ticks = sibling->exit_levels.loss_ticks;
16995 }
16996 out->qty = snapshot.family == PineOrderFamily::Close
16997 ? snapshot.requested_qty
16998 : (std::isfinite(snapshot.projection_remaining_qty)
16999 ? snapshot.projection_remaining_qty : snapshot.requested_qty);
17000 out->qty_type = snapshot.qty_type;
17001 out->qty_percent = snapshot.qty_percent;
17002 out->oca_type = snapshot.oca_type;
17003 out->created_bar = snapshot.projection_created_bar;
17004 out->created_seq = static_cast<std::int64_t>(snapshot.source_sequence);
17005 out->incarnation = handle.incarnation;
17006 out->created_by_same_id_replacement = snapshot.projection_predecessor != 0
17007 && snapshot.family != PineOrderFamily::Order ? 1U : 0U;
17008 out->replaced_default_market_incarnation = snapshot.projection_predecessor_market
17009 ? snapshot.projection_predecessor : 0;
17010 out->declined_by_replaced_short_market = snapshot.cancellation.cause
17012 out->replaced_exit_order_incarnation = snapshot.projection_predecessor_exit
17013 ? snapshot.projection_predecessor : 0;
17014 out->recreated_after_named_cancelled_entry_incarnation =
17016 out->named_cancel_surviving_exit_incarnation = snapshot.named_cancel_surviving_exit_incarnation;
17017 out->stop_limit_activated = snapshot.stop_limit_activated ? 1U : 0U;
17018 out->coof_suppress_stop_on_entry_bar = snapshot.exit_activation.holds_stop() ? 1U : 0U;
17019 out->coof_suppress_limit_on_entry_bar = snapshot.exit_activation.holds_limit() ? 1U : 0U;
17020 out->created_during_coof_recalc = snapshot.birth.from_fill() ? 1U : 0U;
17021 out->coof_born_at_close_recalc = snapshot.birth.at_terminal_fill() ? 1U : 0U;
17022 out->coof_born_mid_bar = compat::pine::historical_cascade_reach(snapshot.birth_reach) ? 1U : 0U;
17023 out->coof_cascade_seg_i = snapshot.coof_cascade_seg_i;
17024 out->coof_cascade_inflight_fires = snapshot.coof_cascade_inflight_fires ? 1U : 0U;
17025 out->created_position_side = snapshot.projection_position_side;
17026 out->created_position_cycle_seq = snapshot.placement_cycle;
17027 out->created_after_position_close_in_bar = snapshot.projection_after_close ? 1U : 0U;
17028 out->over_pyramiding_cap_at_placement = snapshot.projection_over_pyramiding ? 1U : 0U;
17029 out->same_id_stop_deferred_close_all_bar = snapshot.cancellation.cause
17031 out->same_id_stop_deferred_close_all_incarnation = snapshot.cancellation.cause
17033 out->reverses_same_bar_market_from_flat =
17034 snapshot.projection_opposite_market_predecessor ? 1U : 0U;
17035 out->paired_flat_market_candidate = snapshot.paired_flat_market_candidate ? 1U : 0U;
17036 out->paired_flat_market_own_qty = snapshot.paired_flat_market_own_qty;
17037 out->paired_flat_market_signal_close = snapshot.paired_flat_market_signal_close;
17038 out->paired_flat_market_signal_equity = snapshot.paired_flat_market_signal_equity;
17039 out->paired_flat_market_signal_margin_pct = snapshot.paired_flat_market_signal_margin_pct;
17040 out->paired_flat_market_signal_pointvalue = snapshot.paired_flat_market_signal_pointvalue;
17041 out->paired_flat_market_signal_fx = snapshot.paired_flat_market_signal_fx;
17042 out->paired_flat_market_peer_seq = snapshot.paired_flat_market_peer_seq;
17043 out->paired_flat_market_transaction_qty = snapshot.paired_flat_market_transaction_qty;
17044 out->default_flat_market_gross_candidate = snapshot.paired_flat_market_candidate
17045 && !std::isfinite(snapshot.requested_qty) ? 1U : 0U;
17046 out->tv_carry_qty = snapshot.projection_tv_carry_qty;
17047 out->frozen_default_qty = snapshot.sizing.frozen_units;
17048 out->default_stop_placement_qty = snapshot.sizing.frozen_units;
17049 out->default_stop_placement_equity = snapshot.projection_default_stop_equity;
17050 out->default_stop_placement_signal_close = snapshot.projection_default_stop_signal_close;
17051 out->default_stop_sizing_price = snapshot.sizing.price;
17052 out->sizing_equity = snapshot.sizing.equity;
17053 out->sizing_price = snapshot.sizing.price;
17054 out->sizing_fx = snapshot.sizing.fx;
17055 out->sizing_mark = snapshot.sizing.mark;
17056 out->opening_affordability_exemption_candidate = snapshot.opening
17057 && !std::isfinite(snapshot.requested_qty)
17058 && snapshot.projection_position_side == static_cast<std::int32_t>(PositionSide::FLAT) ? 1U : 0U;
17059 out->explicit_flat_admission_candidate = snapshot.opening
17060 && std::isfinite(snapshot.requested_qty)
17061 && snapshot.projection_position_side == static_cast<std::int32_t>(PositionSide::FLAT) ? 1U : 0U;
17062 out->explicit_placement_equity = snapshot.projection_explicit_equity;
17063 out->explicit_slipped_signal_close = snapshot.projection_explicit_signal_close;
17064 out->affordability_placement_equity = snapshot.projection_affordability_equity;
17065 out->affordability_signal_price = snapshot.projection_affordability_signal_price;
17066 out->affordability_held_qty = snapshot.projection_affordability_held_qty;
17067 out->affordability_close_only = snapshot.affordability_close_only ? 1U : 0U;
17068 out->rounded_signal_cost_close_only = snapshot.rounded_signal_cost_close_only ? 1U : 0U;
17069 // The source command boundary itself is the truthful placement
17070 // observation after the legacy admission draft owner was retired. Its
17071 // original sizing tuple is the immutable adapter snapshot captured by
17072 // entry()/order(); no allocation or reconstructed executable book is
17073 // involved in this projection.
17074 out->market_admission_observation_present = snapshot.opening ? 1U : 0U;
17075 out->market_admission_observation_original_sizing_present =
17076 snapshot.opening ? 1U : 0U;
17077 out->market_admission_observation_requested_quantity = snapshot.requested_qty;
17078 out->market_admission_observation_quantity_type = snapshot.qty_type;
17079 out->market_admission_observation_buy = snapshot.is_long ? 1U : 0U;
17080 out->market_admission_observation_prices_limit = snapshot.exit_levels.limit;
17081 out->market_admission_observation_prices_stop = snapshot.exit_levels.stop;
17082 copy_pending_string(snapshot.source_id,
17083 out->market_admission_observation_id,
17084 &out->market_admission_observation_id_truncated,
17085 &out->market_admission_observation_id_hash64);
17086 copy_pending_string(snapshot.oca_name,
17087 out->market_admission_observation_oca_name,
17088 &out->market_admission_observation_oca_name_truncated,
17089 &out->market_admission_observation_oca_name_hash64);
17090 out->market_admission_observation_placement_equity = snapshot.sizing.equity;
17091 out->market_admission_observation_signal_close = snapshot.sizing.price;
17092 out->market_admission_observation_original_sizing_quantity =
17093 snapshot.requested_qty;
17094 out->market_admission_observation_original_sizing_equity = snapshot.sizing.equity;
17095 out->market_admission_observation_original_sizing_price = snapshot.sizing.price;
17096 out->market_admission_observation_original_sizing_mark = snapshot.sizing.mark;
17097 out->market_admission_observation_original_sizing_fx = snapshot.sizing.fx;
17098 out->signal_close_mc_bar = snapshot.signal_close_mc_bar;
17099 out->signal_close_mc_entry_incarnation = snapshot.signal_close_mc_entry_incarnation;
17100 out->signal_close_mc_fill_seq = snapshot.signal_close_mc_fill_seq;
17101 out->signal_close_mc_remaining_qty = snapshot.signal_close_mc_remaining_qty;
17102 out->requested_partial = (!snapshot.opening && std::isfinite(snapshot.qty_percent)
17103 && snapshot.qty_percent < 100.0) || (!snapshot.opening
17104 && std::isfinite(snapshot.requested_qty)) ? 1U : 0U;
17105 out->full_percent_exit_request = !snapshot.opening && !std::isfinite(snapshot.requested_qty)
17106 && (!std::isfinite(snapshot.qty_percent) || snapshot.qty_percent == 100.0) ? 1U : 0U;
17107 out->pooc_global_full_exit_dynamic_qty = snapshot.pooc_global_full_exit_dynamic_qty ? 1U : 0U;
17108 out->pooc_global_full_exit_tracks_bound_adds = snapshot.pooc_global_full_exit_tracks_bound_adds ? 1U : 0U;
17109 out->pooc_global_full_exit_bound_add = snapshot.pooc_global_full_exit_bound_add ? 1U : 0U;
17110 out->created_while_in_position = !snapshot.opening
17111 && snapshot.projection_position_side != static_cast<std::int32_t>(PositionSide::FLAT)
17112 ? 1U : 0U;
17113 out->sbmt_member = snapshot.frozen_market_instruction ? 1U : 0U;
17114 out->sbmt_own_qty = snapshot.frozen_market_instruction
17115 ? snapshot.frozen_market_own_units : kNaN;
17116 out->sbmt_tx_qty = snapshot.frozen_market_instruction
17117 ? snapshot.frozen_market_transaction_units : kNaN;
17118 out->sbmt_kept_over_cap = snapshot.frozen_market_instruction
17119 && snapshot.projection_over_pyramiding ? 1U : 0U;
17120 out->sbmt_close_qty = snapshot.frozen_market_targeted_close
17121 ? snapshot.requested_qty : kNaN;
17122 out->sbmt_close_buy = snapshot.frozen_market_targeted_close
17123 && snapshot.projection_position_side == static_cast<std::int32_t>(PositionSide::SHORT)
17124 ? 1U : 0U;
17125 out->suppress_as_declined_reversal_close = snapshot.cancellation.cause
17127 out->dormant_bracket = snapshot.legs.dormant() ? 1U : 0U;
17128 out->dormant_reissue_pending = snapshot.legs.pending_replacement() ? 1U : 0U;
17129 out->dormant_original_stop_price = snapshot.legs.original_stop();
17130 out->dormant_hold_bar = snapshot.legs.hold_bar();
17131 out->dormant_reversal_kill_bar = snapshot.legs.excluded_bar();
17132 out->dormant_trail_best = snapshot.legs.trail_best();
17133 out->dormant_trail_best_start = snapshot.legs.trail_prefix();
17134 out->dormant_trail_leg_dead = snapshot.legs.retired(exit_legs::Leg::Trail) ? 1U : 0U;
17135 out->suppressed_close_consumed_ledger_qty = snapshot.cancellation.close_claim_consumed;
17136 out->suppressed_close_retired_ledger_qty = snapshot.cancellation.close_claim_retired;
17137 out->short_seed_collision_role = short_seed_collision_role(index);
17138 out->replaced_order_incarnation = snapshot.projection_predecessor;
17139 out->birth_timestamp = snapshot.birth.timestamp();
17140 out->birth_cause = static_cast<std::int32_t>(snapshot.birth.cause());
17141 out->birth_bar = snapshot.birth.bar();
17142 out->birth_cursor_domain = static_cast<std::int32_t>(snapshot.birth.cursor().domain());
17143 out->birth_cursor_position = static_cast<std::int32_t>(snapshot.birth.cursor().position());
17144 out->birth_cursor_index = snapshot.birth.cursor().index();
17145 out->birth_cursor_count = snapshot.birth.cursor().count();
17146 out->birth_cursor_price = snapshot.birth.cursor_price();
17147 out->birth_first_fill = snapshot.birth.first_fill();
17148 out->birth_last_fill = snapshot.birth.last_fill();
17149 out->birth_evaluation_ordinal = snapshot.birth.evaluation_ordinal();
17150 out->pine_birth_reach = static_cast<std::int32_t>(snapshot.birth_reach);
17151 out->pine_frozen_market_instruction_kind = snapshot.frozen_market_instruction ? 1U : 0U;
17152 out->pine_frozen_market_instruction_own_units = snapshot.frozen_market_own_units;
17153 out->pine_frozen_market_instruction_transaction_units =
17155 copy_pending_string(snapshot.frozen_market_targeted_close
17156 ? std::string_view(snapshot.source_id) : std::string_view{},
17157 out->pine_frozen_market_instruction_target_id,
17158 &out->pine_frozen_market_instruction_target_id_truncated,
17159 &out->pine_frozen_market_instruction_target_id_hash64);
17160 const bool explicit_units = std::isfinite(snapshot.requested_qty);
17161 const bool percentage = !explicit_units && std::isfinite(snapshot.qty_percent);
17162 out->quantity_intent_kind = explicit_units ? 2U : (percentage ? 3U : 1U);
17163 out->quantity_intent_units = explicit_units ? snapshot.requested_qty : 0.0;
17164 out->quantity_intent_numerator = percentage ? snapshot.qty_percent : 0.0;
17165 out->quantity_intent_denominator = percentage ? 100.0 : 0.0;
17166 const double exposure = snapshot.from_entry.empty() ? 0.0
17167 : owner_->cohort_exposure_for(snapshot.from_entry);
17168 out->quantity_reservation_present = snapshot.deferred_cohort && exposure > 0.0 ? 1U : 0U;
17169 out->quantity_reservation_units = out->quantity_reservation_present ? exposure : 0.0;
17170 out->quantity_reservation_basis_units = out->quantity_reservation_present ? exposure : 0.0;
17171 out->leg_activation_present = snapshot.leg_activation.bounds() ? 1U : 0U;
17172 out->leg_activation_owner_cycle = snapshot.leg_activation.bounds()
17173 ? snapshot.leg_activation.bounds()->position_cycle : 0;
17174 out->leg_activation_stop_first_bar = snapshot.leg_activation.bounds()
17175 ? snapshot.leg_activation.bounds()->stop_first_bar : 0;
17176 out->leg_activation_limit_first_bar = snapshot.leg_activation.bounds()
17177 ? snapshot.leg_activation.bounds()->limit_first_bar : 0;
17178 const auto& activation = snapshot.exit_activation.evidence();
17179 out->pine_exit_activation_present = activation ? 1U : 0U;
17180 out->pine_exit_activation_owner_cycle_at_birth = activation ? activation->position_cycle : 0;
17181 out->pine_exit_activation_entry_bar_at_birth = activation ? activation->entry_bar : 0;
17182 out->pine_exit_activation_direction_at_birth = activation ? activation->direction : 0;
17183 out->pine_exit_activation_cursor_price_at_birth = activation ? activation->cursor_price : 0.0;
17184 out->pine_exit_activation_stop_level_at_birth = activation ? activation->stop_level : 0.0;
17185 out->pine_exit_activation_limit_level_at_birth = activation ? activation->limit_level : 0.0;
17186 out->pine_exit_activation_limit_continuation_present = activation
17187 && activation->limit_continuation ? 1U : 0U;
17188 out->pine_exit_activation_limit_continuation_cause = activation
17189 && activation->limit_continuation
17190 ? static_cast<std::int32_t>(activation->limit_continuation->cause) : 0;
17191 out->pine_exit_activation_limit_continuation_fill = activation
17192 && activation->limit_continuation
17193 ? activation->limit_continuation->observed_fill_sequence : 0;
17194 const auto& expansion = snapshot.reservation_expansion.capture();
17195 out->reservation_expansion_present = expansion ? 1U : 0U;
17196 out->reservation_expansion_position_cycle = expansion ? expansion->position_cycle : 0;
17197 out->reservation_expansion_side = expansion ? static_cast<std::int32_t>(expansion->side) : 0;
17198 out->reservation_expansion_first_later_admission_present = expansion
17199 && expansion->first_later_admission ? 1U : 0U;
17200 out->reservation_expansion_first_later_admission = expansion
17201 && expansion->first_later_admission ? *expansion->first_later_admission : 0;
17202 out->reservation_growth_source_present = snapshot.reservation_growth_source.reservation_owner()
17203 ? 1U : 0U;
17204 out->reservation_growth_source_reservation_owner = snapshot.reservation_growth_source.reservation_owner()
17206
17207 const auto target = snapshot.legs.target();
17208 const auto& definition = snapshot.legs.current_definition();
17209 out->legs_target_incarnation = target.incarnation;
17210 out->legs_target_owner = target.owner;
17211 out->legs_revision = snapshot.legs.revision();
17212 out->legs_definition_incarnation = definition.incarnation();
17213 out->legs_definition_revision = definition.revision();
17214 out->legs_definition_value_present = definition.has_value() ? 1U : 0U;
17215 out->legs_definition_limit_price = definition.has_value() ? definition.prices().limit_price : kNaN;
17216 out->legs_definition_stop_price = definition.has_value() ? definition.prices().stop_price : kNaN;
17217 out->legs_definition_trail_points = definition.has_value() ? definition.prices().trail_points : kNaN;
17218 out->legs_definition_trail_price = definition.has_value() ? definition.prices().trail_price : kNaN;
17219 out->legs_definition_trail_offset = definition.has_value() ? definition.prices().trail_offset : kNaN;
17220 out->legs_definition_profit_ticks = definition.has_value() ? definition.prices().profit_ticks : kNaN;
17221 out->legs_definition_loss_ticks = definition.has_value() ? definition.prices().loss_ticks : kNaN;
17222 const auto& retirements = snapshot.legs.retirements();
17223 const auto copy_retirement = [&](std::size_t number, std::uint64_t& generation,
17224 std::uint8_t& present, std::uint64_t& receipt_generation,
17225 std::uint64_t& event, std::int64_t& bar,
17226 std::uint32_t& domain, std::uint32_t& phase) {
17227 const auto leg = static_cast<exit_legs::Leg>(number);
17228 generation = snapshot.legs.generation(leg);
17229 const auto& receipt = retirements[number];
17230 present = receipt ? 1U : 0U;
17231 receipt_generation = receipt ? receipt->generation : 0;
17232 event = receipt ? receipt->cause.event : 0;
17233 bar = receipt ? receipt->cause.bar : 0;
17234 domain = receipt ? static_cast<std::uint32_t>(receipt->cause.domain) : 0U;
17235 phase = receipt ? static_cast<std::uint32_t>(receipt->cause.phase) : 0U;
17236 };
17237 copy_retirement(0, out->legs_generation0, out->legs_retirement0_present,
17238 out->legs_retirement0_generation, out->legs_retirement0_cause_event,
17239 out->legs_retirement0_cause_bar, out->legs_retirement0_cause_domain,
17240 out->legs_retirement0_cause_phase);
17241 copy_retirement(1, out->legs_generation1, out->legs_retirement1_present,
17242 out->legs_retirement1_generation, out->legs_retirement1_cause_event,
17243 out->legs_retirement1_cause_bar, out->legs_retirement1_cause_domain,
17244 out->legs_retirement1_cause_phase);
17245 copy_retirement(2, out->legs_generation2, out->legs_retirement2_present,
17246 out->legs_retirement2_generation, out->legs_retirement2_cause_event,
17247 out->legs_retirement2_cause_bar, out->legs_retirement2_cause_domain,
17248 out->legs_retirement2_cause_phase);
17249 const auto& suspension = snapshot.legs.suspension();
17250 out->legs_suspension_present = suspension ? 1U : 0U;
17251 out->legs_suspension_cause_event = suspension ? suspension->cause.event : 0;
17252 out->legs_suspension_cause_bar = suspension ? suspension->cause.bar : 0;
17253 out->legs_suspension_cause_domain = suspension
17254 ? static_cast<std::uint32_t>(suspension->cause.domain) : 0U;
17255 out->legs_suspension_cause_phase = suspension
17256 ? static_cast<std::uint32_t>(suspension->cause.phase) : 0U;
17257 out->legs_suspension_legs_count = suspension
17258 ? static_cast<std::uint32_t>(suspension->legs.size()) : 0U;
17259 const auto suspended_leg = [&](std::size_t number) -> std::uint32_t {
17260 return suspension && suspension->legs.size() > number
17261 ? static_cast<std::uint32_t>(suspension->legs[number]) : UINT32_MAX;
17262 };
17263 out->legs_suspension_legs_item0 = suspended_leg(0);
17264 out->legs_suspension_legs_item1 = suspended_leg(1);
17265 out->legs_suspension_legs_item2 = suspended_leg(2);
17266 out->legs_suspension_hold_present = suspension && suspension->hold ? 1U : 0U;
17267 if (suspension && suspension->hold) {
17268 const auto& hold = *suspension->hold;
17269 out->legs_suspension_hold_requested_event = hold.requested.event;
17270 out->legs_suspension_hold_requested_bar = hold.requested.bar;
17271 out->legs_suspension_hold_requested_domain = static_cast<std::uint32_t>(hold.requested.domain);
17272 out->legs_suspension_hold_requested_phase = static_cast<std::uint32_t>(hold.requested.phase);
17273 out->legs_suspension_hold_target_incarnation = hold.target.incarnation;
17274 out->legs_suspension_hold_target_owner = hold.target.owner;
17275 out->legs_suspension_hold_revision = hold.revision;
17276 }
17277 out->legs_suspension_revival_definition_limit_price = kNaN;
17278 out->legs_suspension_revival_definition_stop_price = kNaN;
17279 out->legs_suspension_revival_definition_trail_points = kNaN;
17280 out->legs_suspension_revival_definition_trail_price = kNaN;
17281 out->legs_suspension_revival_definition_trail_offset = kNaN;
17282 out->legs_suspension_revival_definition_profit_ticks = kNaN;
17283 out->legs_suspension_revival_definition_loss_ticks = kNaN;
17284 const auto* revival = suspension && suspension->revival_definition
17285 ? &*suspension->revival_definition : nullptr;
17286 out->legs_suspension_revival_definition_present = revival ? 1U : 0U;
17287 if (revival) {
17288 out->legs_suspension_revival_definition_incarnation = revival->incarnation();
17289 out->legs_suspension_revival_definition_revision = revival->revision();
17290 out->legs_suspension_revival_definition_value_present = revival->has_value() ? 1U : 0U;
17291 if (revival->has_value()) {
17292 const auto& prices = revival->prices();
17293 out->legs_suspension_revival_definition_limit_price = prices.limit_price;
17294 out->legs_suspension_revival_definition_stop_price = prices.stop_price;
17295 out->legs_suspension_revival_definition_trail_points = prices.trail_points;
17296 out->legs_suspension_revival_definition_trail_price = prices.trail_price;
17297 out->legs_suspension_revival_definition_trail_offset = prices.trail_offset;
17298 out->legs_suspension_revival_definition_profit_ticks = prices.profit_ticks;
17299 out->legs_suspension_revival_definition_loss_ticks = prices.loss_ticks;
17300 }
17301 }
17302 out->legs_suspension_replacement_revival_definition_limit_price = kNaN;
17303 out->legs_suspension_replacement_revival_definition_stop_price = kNaN;
17304 out->legs_suspension_replacement_revival_definition_trail_points = kNaN;
17305 out->legs_suspension_replacement_revival_definition_trail_price = kNaN;
17306 out->legs_suspension_replacement_revival_definition_trail_offset = kNaN;
17307 out->legs_suspension_replacement_revival_definition_profit_ticks = kNaN;
17308 out->legs_suspension_replacement_revival_definition_loss_ticks = kNaN;
17309 const auto* replacement = suspension && suspension->replacement
17310 ? &*suspension->replacement : nullptr;
17311 out->legs_suspension_replacement_present = replacement ? 1U : 0U;
17312 if (replacement) {
17313 out->legs_suspension_replacement_queue_predecessor = replacement->queue_predecessor;
17314 const auto& definition = replacement->revival_definition;
17315 out->legs_suspension_replacement_revival_definition_incarnation = definition.incarnation();
17316 out->legs_suspension_replacement_revival_definition_revision = definition.revision();
17317 out->legs_suspension_replacement_revival_definition_value_present =
17318 definition.has_value() ? 1U : 0U;
17319 if (definition.has_value()) {
17320 const auto& prices = definition.prices();
17321 out->legs_suspension_replacement_revival_definition_limit_price = prices.limit_price;
17322 out->legs_suspension_replacement_revival_definition_stop_price = prices.stop_price;
17323 out->legs_suspension_replacement_revival_definition_trail_points = prices.trail_points;
17324 out->legs_suspension_replacement_revival_definition_trail_price = prices.trail_price;
17325 out->legs_suspension_replacement_revival_definition_trail_offset = prices.trail_offset;
17326 out->legs_suspension_replacement_revival_definition_profit_ticks = prices.profit_ticks;
17327 out->legs_suspension_replacement_revival_definition_loss_ticks = prices.loss_ticks;
17328 }
17329 const auto& release = replacement->release;
17330 out->legs_suspension_replacement_release_requested_event = release.requested.event;
17331 out->legs_suspension_replacement_release_requested_bar = release.requested.bar;
17332 out->legs_suspension_replacement_release_requested_domain =
17333 static_cast<std::uint32_t>(release.requested.domain);
17334 out->legs_suspension_replacement_release_requested_phase =
17335 static_cast<std::uint32_t>(release.requested.phase);
17336 out->legs_suspension_replacement_release_target_incarnation = release.target.incarnation;
17337 out->legs_suspension_replacement_release_target_owner = release.target.owner;
17338 out->legs_suspension_replacement_release_revision = release.revision;
17339 }
17340 out->legs_suspension_window_present = suspension && suspension->window ? 1U : 0U;
17341 out->legs_suspension_window_best = kNaN;
17342 out->legs_suspension_window_prefix = kNaN;
17343 if (suspension && suspension->window) {
17344 const auto& window = *suspension->window;
17345 out->legs_suspension_window_excluded_event = window.excluded.event;
17346 out->legs_suspension_window_excluded_bar = window.excluded.bar;
17347 out->legs_suspension_window_excluded_domain = static_cast<std::uint32_t>(window.excluded.domain);
17348 out->legs_suspension_window_excluded_phase = static_cast<std::uint32_t>(window.excluded.phase);
17349 out->legs_suspension_window_best = window.best;
17350 out->legs_suspension_window_prefix = window.prefix;
17351 }
17352 const auto& last = snapshot.legs.last_action();
17353 out->legs_last_present = last ? 1U : 0U;
17354 out->legs_last_suspend_legs_item0 = UINT32_MAX;
17355 out->legs_last_suspend_legs_item1 = UINT32_MAX;
17356 out->legs_last_suspend_legs_item2 = UINT32_MAX;
17357 out->legs_last_suspend_window_best = kNaN;
17358 out->legs_last_suspend_window_prefix = kNaN;
17359 out->legs_last_suspend_retire_item0 = UINT32_MAX;
17360 out->legs_last_suspend_retire_item1 = UINT32_MAX;
17361 out->legs_last_suspend_retire_item2 = UINT32_MAX;
17362 out->legs_last_stage_revival_definition_limit_price = kNaN;
17363 out->legs_last_stage_revival_definition_stop_price = kNaN;
17364 out->legs_last_stage_revival_definition_trail_points = kNaN;
17365 out->legs_last_stage_revival_definition_trail_price = kNaN;
17366 out->legs_last_stage_revival_definition_trail_offset = kNaN;
17367 out->legs_last_stage_revival_definition_profit_ticks = kNaN;
17368 out->legs_last_stage_revival_definition_loss_ticks = kNaN;
17369 out->legs_last_restore_legs_item0 = UINT32_MAX;
17370 out->legs_last_restore_legs_item1 = UINT32_MAX;
17371 out->legs_last_restore_legs_item2 = UINT32_MAX;
17372 out->legs_last_observe_high = kNaN;
17373 out->legs_last_observe_low = kNaN;
17374 out->legs_last_cancel_legs_item0 = UINT32_MAX;
17375 out->legs_last_cancel_legs_item1 = UINT32_MAX;
17376 out->legs_last_cancel_legs_item2 = UINT32_MAX;
17377 if (last) {
17378 out->legs_last_target_incarnation = last->target.incarnation;
17379 out->legs_last_target_owner = last->target.owner;
17380 out->legs_last_expected_revision = last->expected_revision;
17381 out->legs_last_cause_event = last->cause.event;
17382 out->legs_last_cause_bar = last->cause.bar;
17383 out->legs_last_cause_domain = static_cast<std::uint32_t>(last->cause.domain);
17384 out->legs_last_cause_phase = static_cast<std::uint32_t>(last->cause.phase);
17385 out->legs_last_operation = static_cast<std::uint32_t>(last->operation.index());
17386 if (const auto* bind = std::get_if<exit_legs::BindOwner>(&last->operation)) {
17387 out->legs_last_bind_owner = bind->owner;
17388 } else if (const auto* suspended = std::get_if<exit_legs::Suspend>(&last->operation)) {
17389 const auto copy_leg = [](const std::vector<exit_legs::Leg>& legs,
17390 std::size_t index) -> std::uint32_t {
17391 return index < legs.size() ? static_cast<std::uint32_t>(legs[index])
17392 : UINT32_MAX;
17393 };
17394 out->legs_last_suspend_legs_count =
17395 static_cast<std::uint32_t>(suspended->legs.size());
17396 out->legs_last_suspend_legs_item0 = copy_leg(suspended->legs, 0);
17397 out->legs_last_suspend_legs_item1 = copy_leg(suspended->legs, 1);
17398 out->legs_last_suspend_legs_item2 = copy_leg(suspended->legs, 2);
17399 out->legs_last_suspend_hold_present = suspended->hold ? 1U : 0U;
17400 if (suspended->hold) {
17401 const auto& hold = *suspended->hold;
17402 out->legs_last_suspend_hold_requested_event = hold.requested.event;
17403 out->legs_last_suspend_hold_requested_bar = hold.requested.bar;
17404 out->legs_last_suspend_hold_requested_domain =
17405 static_cast<std::uint32_t>(hold.requested.domain);
17406 out->legs_last_suspend_hold_requested_phase =
17407 static_cast<std::uint32_t>(hold.requested.phase);
17408 out->legs_last_suspend_hold_target_incarnation = hold.target.incarnation;
17409 out->legs_last_suspend_hold_target_owner = hold.target.owner;
17410 out->legs_last_suspend_hold_revision = hold.revision;
17411 }
17412 out->legs_last_suspend_window_present = suspended->window ? 1U : 0U;
17413 if (suspended->window) {
17414 const auto& window = *suspended->window;
17415 out->legs_last_suspend_window_excluded_event = window.excluded.event;
17416 out->legs_last_suspend_window_excluded_bar = window.excluded.bar;
17417 out->legs_last_suspend_window_excluded_domain =
17418 static_cast<std::uint32_t>(window.excluded.domain);
17419 out->legs_last_suspend_window_excluded_phase =
17420 static_cast<std::uint32_t>(window.excluded.phase);
17421 out->legs_last_suspend_window_best = window.best;
17422 out->legs_last_suspend_window_prefix = window.prefix;
17423 }
17424 out->legs_last_suspend_retire_count =
17425 static_cast<std::uint32_t>(suspended->retire.size());
17426 out->legs_last_suspend_retire_item0 = copy_leg(suspended->retire, 0);
17427 out->legs_last_suspend_retire_item1 = copy_leg(suspended->retire, 1);
17428 out->legs_last_suspend_retire_item2 = copy_leg(suspended->retire, 2);
17429 } else if (const auto* stage =
17430 std::get_if<exit_legs::StageReplacement>(&last->operation)) {
17431 const auto& relation = stage->relation;
17432 out->legs_last_stage_queue_predecessor = relation.queue_predecessor;
17433 const auto& definition = relation.revival_definition;
17434 out->legs_last_stage_revival_definition_incarnation = definition.incarnation();
17435 out->legs_last_stage_revival_definition_revision = definition.revision();
17436 out->legs_last_stage_revival_definition_value_present =
17437 definition.has_value() ? 1U : 0U;
17438 if (definition.has_value()) {
17439 const auto& prices = definition.prices();
17440 out->legs_last_stage_revival_definition_limit_price = prices.limit_price;
17441 out->legs_last_stage_revival_definition_stop_price = prices.stop_price;
17442 out->legs_last_stage_revival_definition_trail_points = prices.trail_points;
17443 out->legs_last_stage_revival_definition_trail_price = prices.trail_price;
17444 out->legs_last_stage_revival_definition_trail_offset = prices.trail_offset;
17445 out->legs_last_stage_revival_definition_profit_ticks = prices.profit_ticks;
17446 out->legs_last_stage_revival_definition_loss_ticks = prices.loss_ticks;
17447 }
17448 const auto& release = relation.release;
17449 out->legs_last_stage_release_requested_event = release.requested.event;
17450 out->legs_last_stage_release_requested_bar = release.requested.bar;
17451 out->legs_last_stage_release_requested_domain =
17452 static_cast<std::uint32_t>(release.requested.domain);
17453 out->legs_last_stage_release_requested_phase =
17454 static_cast<std::uint32_t>(release.requested.phase);
17455 out->legs_last_stage_release_target_incarnation = release.target.incarnation;
17456 out->legs_last_stage_release_target_owner = release.target.owner;
17457 out->legs_last_stage_release_revision = release.revision;
17458 } else if (const auto* restore = std::get_if<exit_legs::Restore>(&last->operation)) {
17459 out->legs_last_restore_legs_count =
17460 static_cast<std::uint32_t>(restore->legs.size());
17461 const auto copy_leg = [&](std::size_t index) -> std::uint32_t {
17462 return index < restore->legs.size()
17463 ? static_cast<std::uint32_t>(restore->legs[index]) : UINT32_MAX;
17464 };
17465 out->legs_last_restore_legs_item0 = copy_leg(0);
17466 out->legs_last_restore_legs_item1 = copy_leg(1);
17467 out->legs_last_restore_legs_item2 = copy_leg(2);
17468 } else if (const auto* complete =
17469 std::get_if<exit_legs::CompleteBarrier>(&last->operation)) {
17470 out->legs_last_complete_completed_event = complete->completed.event;
17471 out->legs_last_complete_completed_bar = complete->completed.bar;
17472 out->legs_last_complete_completed_domain =
17473 static_cast<std::uint32_t>(complete->completed.domain);
17474 out->legs_last_complete_completed_phase =
17475 static_cast<std::uint32_t>(complete->completed.phase);
17476 out->legs_last_complete_requested_present = complete->requested ? 1U : 0U;
17477 if (complete->requested) {
17478 const auto& requested = *complete->requested;
17479 out->legs_last_complete_requested_requested_event = requested.requested.event;
17480 out->legs_last_complete_requested_requested_bar = requested.requested.bar;
17481 out->legs_last_complete_requested_requested_domain =
17482 static_cast<std::uint32_t>(requested.requested.domain);
17483 out->legs_last_complete_requested_requested_phase =
17484 static_cast<std::uint32_t>(requested.requested.phase);
17485 out->legs_last_complete_requested_target_incarnation =
17486 requested.target.incarnation;
17487 out->legs_last_complete_requested_target_owner = requested.target.owner;
17488 out->legs_last_complete_requested_revision = requested.revision;
17489 }
17490 } else if (const auto* observe = std::get_if<exit_legs::Observe>(&last->operation)) {
17491 out->legs_last_observe_high = observe->high;
17492 out->legs_last_observe_low = observe->low;
17493 out->legs_last_observe_direction = observe->direction;
17494 out->legs_last_observe_fold = static_cast<std::uint32_t>(observe->fold);
17495 } else if (const auto* cancel = std::get_if<exit_legs::Cancel>(&last->operation)) {
17496 out->legs_last_cancel_legs_count =
17497 static_cast<std::uint32_t>(cancel->legs.size());
17498 const auto copy_leg = [&](std::size_t index) -> std::uint32_t {
17499 return index < cancel->legs.size()
17500 ? static_cast<std::uint32_t>(cancel->legs[index]) : UINT32_MAX;
17501 };
17502 out->legs_last_cancel_legs_item0 = copy_leg(0);
17503 out->legs_last_cancel_legs_item1 = copy_leg(1);
17504 out->legs_last_cancel_legs_item2 = copy_leg(2);
17505 }
17506 }
17507 const auto& admission = snapshot.market_admission;
17508 const auto& observation = admission.observation();
17509 if (observation) out->market_admission_observation_present = 1U;
17510 if (observation) {
17511 out->market_admission_observation_command = observation->command;
17512 out->market_admission_observation_kind = static_cast<std::int64_t>(observation->kind);
17513 out->market_admission_observation_birth_cause =
17514 static_cast<std::int64_t>(observation->birth.cause());
17515 out->market_admission_observation_birth_bar = observation->birth.bar();
17516 out->market_admission_observation_birth_timestamp = observation->birth.timestamp();
17517 out->market_admission_observation_birth_cursor_domain =
17518 static_cast<std::int64_t>(observation->birth.cursor().domain());
17519 out->market_admission_observation_birth_cursor_position =
17520 static_cast<std::int64_t>(observation->birth.cursor().position());
17521 out->market_admission_observation_birth_cursor_index = observation->birth.cursor().index();
17522 out->market_admission_observation_birth_cursor_count = observation->birth.cursor().count();
17523 out->market_admission_observation_birth_cursor_price = observation->birth.cursor_price();
17524 out->market_admission_observation_birth_first_fill = observation->birth.first_fill();
17525 out->market_admission_observation_birth_last_fill = observation->birth.last_fill();
17526 out->market_admission_observation_birth_evaluation_ordinal =
17527 observation->birth.evaluation_ordinal();
17528 copy_pending_string(observation->id, out->market_admission_observation_id,
17529 &out->market_admission_observation_id_truncated,
17530 &out->market_admission_observation_id_hash64);
17531 out->market_admission_observation_requested_quantity = observation->requested_quantity;
17532 out->market_admission_observation_quantity_type = observation->quantity_type;
17533 out->market_admission_observation_buy = observation->buy ? 1U : 0U;
17534 out->market_admission_observation_prices_limit = observation->prices.limit;
17535 out->market_admission_observation_prices_stop = observation->prices.stop;
17536 copy_pending_string(observation->oca_name, out->market_admission_observation_oca_name,
17537 &out->market_admission_observation_oca_name_truncated,
17538 &out->market_admission_observation_oca_name_hash64);
17539 out->market_admission_observation_oca_type = observation->oca_type;
17540 const auto& configuration = observation->configuration;
17541 out->market_admission_observation_configuration_process_on_close =
17542 configuration.process_on_close ? 1U : 0U;
17543 out->market_admission_observation_configuration_calc_on_fills =
17544 configuration.calc_on_fills ? 1U : 0U;
17545 out->market_admission_observation_configuration_magnifier =
17546 configuration.magnifier ? 1U : 0U;
17547 out->market_admission_observation_configuration_fill_recalculation =
17548 configuration.fill_recalculation ? 1U : 0U;
17549 out->market_admission_observation_configuration_scheduler =
17550 configuration.scheduler ? 1U : 0U;
17551 out->market_admission_observation_configuration_slippage = configuration.slippage;
17552 out->market_admission_observation_configuration_pyramiding = configuration.pyramiding;
17553 out->market_admission_observation_configuration_default_quantity_type =
17554 configuration.default_quantity_type;
17555 out->market_admission_observation_configuration_default_quantity_value =
17556 configuration.default_quantity_value;
17557 out->market_admission_observation_configuration_long_margin = configuration.long_margin;
17558 out->market_admission_observation_configuration_short_margin = configuration.short_margin;
17559 out->market_admission_observation_configuration_commission_value =
17560 configuration.commission_value;
17561 out->market_admission_observation_configuration_commission_type =
17562 configuration.commission_type;
17563 out->market_admission_observation_configuration_pointvalue = configuration.pointvalue;
17564 out->market_admission_observation_configuration_fx = configuration.fx;
17565 out->market_admission_observation_configuration_quantity_step =
17566 configuration.quantity_step;
17567 out->market_admission_observation_configuration_mintick = configuration.mintick;
17568 out->market_admission_observation_configuration_risk_direction =
17569 configuration.risk_direction;
17570 out->market_admission_observation_configuration_loss_days_limit =
17571 configuration.loss_days_limit;
17572 out->market_admission_observation_configuration_drawdown_limit =
17573 configuration.drawdown_limit;
17574 out->market_admission_observation_configuration_intraday_loss_limit =
17575 configuration.intraday_loss_limit;
17576 out->market_admission_observation_configuration_position_limit =
17577 configuration.position_limit;
17578 out->market_admission_observation_configuration_fill_cap_active =
17579 configuration.fill_cap_active ? 1U : 0U;
17580 out->market_admission_observation_configuration_risk_halted =
17581 configuration.risk_halted ? 1U : 0U;
17582 out->market_admission_observation_bar = observation->bar;
17583 out->market_admission_observation_placement_side = observation->placement_side;
17584 out->market_admission_observation_placement_cycle = observation->placement_cycle;
17585 out->market_admission_observation_prior_close_quantity =
17586 observation->prior_close_quantity;
17587 out->market_admission_observation_held_quantity = observation->held_quantity;
17588 out->market_admission_observation_held_entries = observation->held_entries;
17589 out->market_admission_observation_realized_equity = observation->realized_equity;
17590 out->market_admission_observation_placement_equity = observation->placement_equity;
17591 out->market_admission_observation_signal_close = observation->signal_close;
17592 out->market_admission_observation_quantized_fixed_quantity =
17593 observation->quantized_fixed_quantity;
17594 out->market_admission_observation_original_sizing_present =
17595 observation->original_sizing ? 1U : 0U;
17596 if (observation->original_sizing) {
17597 out->market_admission_observation_original_sizing_quantity =
17598 observation->original_sizing->quantity;
17599 out->market_admission_observation_original_sizing_equity =
17600 observation->original_sizing->equity;
17601 out->market_admission_observation_original_sizing_price =
17602 observation->original_sizing->price;
17603 out->market_admission_observation_original_sizing_mark =
17604 observation->original_sizing->mark;
17605 out->market_admission_observation_original_sizing_fx =
17606 observation->original_sizing->fx;
17607 }
17608 out->market_admission_observation_explicit_equity = observation->explicit_equity;
17609 out->market_admission_observation_explicit_price = observation->explicit_price;
17610 }
17611 out->market_admission_review_present = admission.review() ? 1U : 0U;
17612 if (admission.review()) {
17613 out->market_admission_review_sequence = admission.review()->sequence;
17614 out->market_admission_review_checkpoint =
17615 static_cast<std::int64_t>(admission.review()->checkpoint);
17616 out->market_admission_review_bar = admission.review()->bar;
17617 out->market_admission_review_target_command = admission.review()->target_command;
17618 }
17619 out->market_admission_sizing_revision_present = admission.sizing_revision() ? 1U : 0U;
17620 if (admission.sizing_revision()) {
17621 out->market_admission_sizing_revision_sequence = admission.sizing_revision()->sequence;
17622 out->market_admission_sizing_revision_cause_fill =
17623 admission.sizing_revision()->cause_fill;
17624 out->market_admission_sizing_revision_bar = admission.sizing_revision()->bar;
17625 out->market_admission_sizing_revision_target_command =
17626 admission.sizing_revision()->target_command;
17627 }
17628 out->cancellation_cause = static_cast<std::int32_t>(snapshot.cancellation.cause);
17629 out->cancellation_state = snapshot.cancellation.state;
17630 out->cancellation_close_claim_release = snapshot.cancellation.close_claim_release;
17631 out->cancellation_source_incarnation = snapshot.cancellation.source_incarnation;
17632 out->cancellation_source_sequence = snapshot.cancellation.source_sequence;
17633 out->cancellation_target_incarnation = snapshot.cancellation.target_incarnation;
17634 out->cancellation_target_owner = snapshot.cancellation.target_owner;
17635 out->cancellation_target_revision = snapshot.cancellation.target_revision;
17636 out->cancellation_close_claim_consumed = snapshot.cancellation.close_claim_consumed;
17637 out->cancellation_close_claim_retired = snapshot.cancellation.close_claim_retired;
17638 return 0;
17639}
17640
17642 if (!owner_) return -1;
17643 const PlacementSnapshot* snapshot = nullptr;
17645 if (!owner_->projected_pending_at(index, snapshot, handle) || !snapshot) return -1;
17646 if (!owner_->short_seed_.active) return 0;
17647 return owner_->short_seed_collision_role_v1(handle);
17648}
17649int PendingIntentView::last_bar_dual_entry_path() const noexcept { return owner_ ? owner_->last_bar_dual_entry_path_ : 0; }
17651 return owner_ && owner_->host_ ? owner_->host_->trail_best_price() : kNaN;
17652}
17653
17654} // namespace pineforge::source
static BirthCursor point(BirthCursorDomain domain, int index, int count)
const std::optional< ExitLegActivationBounds > & bounds() const
uint64_t first_fill() const
bool at_terminal_fill() const
static OrderBirth direct_command(int bar, int64_t timestamp)
static OrderBirth chart_evaluation(int bar, int64_t timestamp)
static OrderBirth fill_evaluation(int bar, int64_t timestamp, BirthCursor cursor, double price, uint64_t first_fill, uint64_t last_fill, uint64_t evaluation_ordinal)
int64_t timestamp() const
OrderBirthCause cause() const
uint64_t evaluation_ordinal() const
double cursor_price() const
const BirthCursor & cursor() const
uint64_t last_fill() const
const std::optional< ExitPlacementEvidence > & evidence() const noexcept
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
NativePhysicalPosition physical_position() const
The book as one aggregate, copied out.
std::optional< NativeCurrentPointView > current_execution_point() const
The active callback's quote and calendar-derived decision context, as an owning value.
NativeStateView native_state() const
The whole run state as one owning read.
native_order::CohortHandle cohort_open()
Open a roster a later close can bind to.
const std::array< std::optional< Retirement >, 3 > & retirements() const
const std::optional< Action > & last_action() const
const std::optional< Suspension > & suspension() const
void capture(std::uint64_t receiver, std::int64_t cycle, PositionSide side, double capacity)
const std::optional< std::uint64_t > & reservation_owner() const noexcept
double trail_best_price() const noexcept
int level_resolved(int index) const noexcept
int copy_v1(int index, pf_pending_order_v1_t *out) const noexcept
int effective_levels(int index, double *stop, double *limit, double *trail_activation) const noexcept
int probe_fill_qty(int index, double fill_price, double *qty, int *close_only, int *partition) const noexcept
int short_seed_collision_role(int index) const noexcept
int last_bar_dual_entry_path() const noexcept
std::optional< double > resolve_anchored_level(const NativeAnchoredLevelView &) const
void set_risk_max_drawdown(double value, bool percent) noexcept
std::size_t fixture_close_reservation_count() const noexcept
std::size_t fixture_callsite_close_first_count() const noexcept
void set_configuration(const PineStrategyConfig &config) noexcept
void fixture_remove_entry_without_named_cancel(const SourceId &)
void set_margin_call_enabled(bool enabled) noexcept
void begin_coof_recalc(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &, bool first_open, std::uint64_t source_fill_sequence)
PineExecutionAdapter(compat::pine::CapAttachment attachment=compat::pine::CapAttachment::None)
void on_bar_open(const Bar &, const NativeDecisionContext &)
void set_risk_direction(int direction) noexcept
void permute_exit_phases(std::size_t start, const std::vector< std::size_t > &indices)
std::optional< NativeMarginDecision > resolve_margin_requirement(const NativeMarginRequirementView &) const
std::size_t fixture_callsite_close_reservation_count() const noexcept
void set_begin_mode(bool is_stream, bool bar_magnifier=false) noexcept
native_order::ExecutionTerms resolve_terms(const NativeExecutionTermsFacts &) const
NativePrecommitVerdict validate_precommit(const NativePrecommitView &) const
void set_risk_max_position_size(double value) noexcept
void on_tick(const Bar &, const NativeTickContext &)
void exit_cancel_bracket(const SourceId &exit_id, const SourceId &from_entry, const std::string &comment={})
std::vector< native_order::RequestHandle > take_first_open_newborns()
static bool source_kernel_liquidation(const native_order::DefinitionRef &) noexcept
double source_margin_units(const SourceMarginMoney &, bool opening_checkpoint) const
std::uint64_t command_sequence_for_exit(const SourceId &exit_id, const SourceId &from_entry={}) const noexcept
double fixture_callsite_close_first_units(std::uint64_t, const SourceId &) const 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())
void set_risk_max_cons_loss_days(int value) noexcept
void set_staged_configuration(const StagedConfiguration &staged)
bool source_margin_exit(std::uint64_t incarnation) const noexcept
double fixture_callsite_close_reserved_total() const noexcept
bool source_post_parent_calc_level_fill(std::uint64_t incarnation) const noexcept
double source_margin_fill_price(double fire, bool close_is_buy) const
bool is_open_phase_exit(std::size_t trade_index) const noexcept
double fixture_close_reserved_units(const SourceId &) const noexcept
std::int64_t chart_day_key(std::int64_t timestamp_ms) const noexcept
void set_risk_max_intraday_loss(double value, bool percent) noexcept
void bind(NativeStrategyHost &host) noexcept
std::vector< FixtureCloseCallsite > fixture_close_callsites() const
void release_delayed_orders(bool explicit_brackets_only=false, double current_open=std::numeric_limits< double >::quiet_NaN())
SourceMarginMoney source_margin_money(double mark_price, std::int64_t sub_bar_open_ms) const
void close(const SourceId &id, const std::string &comment={}, double qty=std::numeric_limits< double >::quiet_NaN(), double qty_percent=std::numeric_limits< double >::quiet_NaN(), bool immediately=false, std::uint64_t callsite_token=0)
std::vector< FixturePendingSnapshot > fixture_pending_snapshots() const
compat::pine::OrderPriority priority
bool take_intraday_loss_relabel(std::uint64_t ordinal) noexcept
NativeRunSpec project(const PineStrategyConfig &, const StagedConfiguration &, const NativeBeginArgs &, NativePathOrder path_order=NativePathOrder::Auto) const
bool core_sizes_default_opening(bool is_long) const
void on_applied(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &)
void flush_pending_bracket_legs(native_order::RequestHandle just_applied={}, bool post_calculation=true, bool pre_script_drain=false)
double fixture_close_logical_units(const SourceId &) const noexcept
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)
bool source_priced_exit(std::uint64_t incarnation) const noexcept
std::optional< double > resolve_margin_call_units(const NativeMarginCallView &) const
bool margin_check_allowed(const NativeMarginCheckPoint &) const
double fixture_callsite_close_reserved_units(std::uint64_t, const SourceId &) const noexcept
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)
bool has_pending_market_exit(int current_interval_index=-1) const noexcept
PineCancellationReceipt * fixture_mutable_cancellation(int index) noexcept
std::size_t fixture_close_first_count() const noexcept
bool carried_long_money_precedes_priced_exit(const NativePrecommitView &, double held_units) const
std::optional< double > source_trail_offset_ticks(std::uint64_t incarnation) const noexcept
int short_seed_collision_role_v1(native_order::RequestHandle) const noexcept
void on_bar_close(const Bar &, const NativeDecisionContext &)
bool market_pyramid_add(std::uint64_t incarnation) const noexcept
bool suppress_grouped_stop_recalc(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &) const noexcept
void mark_market_pyramid_add(std::uint64_t incarnation)
void set_path_order(NativePathOrder path_order) noexcept
double fixture_close_first_units(const SourceId &) const noexcept
double trail_offset_to_ticks(double trail_offset)
exit_legs::Operation select_pair_hold(const exit_legs::Lifecycle &, exit_legs::Frame)
bool explicit_qualification(const admission::Draft &draft)
bool admits_reservation_expansion(const std::vector< std::uint64_t > &selected, bool partial, double reserved, double live) noexcept
double trail_points_to_ticks(double trail_points)
double snap_trail_level_to_tick_grid(double price, double mintick)
int last_rejected_command_bar(const admission::Journal &journal)
std::vector< std::uint64_t > select_reservation_growth_sources(const std::vector< ReservationGrowthCandidate > &candidates, const std::string &from_entry, bool process_on_close, bool effectively_flat, double percent, int bar, PositionSide side)
bool awaits_pair_review(const admission::Draft &draft)
double select_margin_revival_stop(const exit_legs::Lifecycle &)
HistoricalBirthReach select_historical_birth_reach(const OrderBirth &birth, bool requested_trailing_exit) noexcept
std::optional< exit_legs::Operation > select_exit_completion(const exit_legs::Lifecycle &, exit_legs::Frame completed)
bool awaits_default_review(const admission::Draft &draft)
std::optional< exit_legs::Operation > select_exit_suspension(const exit_legs::Lifecycle &, const ExitSuspensionContext &)
bool historical_cascade_reach(HistoricalBirthReach reach) noexcept
ExitActivationPolicy select_exit_activation(const ExitActivationRequest &request, double stop, double limit, const ExitActivationContext &context)
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
bool entry_stop_first_touch(const Bar &bar, double stop_level, bool is_long, double *out_pos)
NativePathPhase
Which leg of a modeled OHLC walk a point sits on.
std::variant< Market, Limit, Stop, StopLimit, Trail > Trigger
bool quantity_on_grid(double q, double step) noexcept
v2 exact binary64 grid: r=abs(q)/s, n=round(r) half away from zero, g=n*s.
std::variant< NoGroup, Member > Group
std::variant< Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort > Owner
std::shared_ptr< const RequestDefinition > DefinitionRef
order_action::Transact Transact
A signed book transaction: finite nonzero units on the side their sign names.
std::variant< Flatten, Reduce, Transact, ReverseTo, HostSized, Sized > OrderIntent
What a request does.
NativePathOrder
Generic ordering for a modeled OHLC path.
NativeOpenDirections
Which opening directions the run admits at all.
NativeFeedTolerance
Explicit, opt-in admission exceptions for a tolerated input-feed shape.
NativeFeeKind
Encodings coincide with the versioned native-v1 C transport.
NativeRunSpecValidation validate_native_run_spec(const NativeRunSpec &spec) noexcept
Complete validation, with deterministic first-error field order.
std::optional< Plan > plan(double signed_position, Reduce request) noexcept
constexpr std::uint32_t kCoofLoopGuard
constexpr char kMarginCallLabel[]
constexpr char kIntradayLossComment[]
Bar margin_call_sample_bar(const Bar &bar, double fire_price, bool prefix_sample, bool high_first, double mintick=0.0, int slippage=0)
std::string SourceId
void sample_open_trade_extremes(std::vector< PyramidEntry > &lots, PositionSide side, int bar_index, const Bar &bar)
int tf_ratio(const std::string &input_tf, const std::string &target_tf)
Compute how many input bars fit into one target bar.
std::string detect_timeframe(const Bar *bars, int n, int max_samples=100)
Detect the timeframe string from an array of bars by computing the median timestamp delta and mapping...
double open
Definition bar.hpp:7
double close
Definition bar.hpp:7
double low
Definition bar.hpp:7
int64_t timestamp
Definition bar.hpp:8
double high
Definition bar.hpp:7
std::string timezone
Definition engine.hpp:292
std::string tickerid
Definition engine.hpp:288
std::string ticker
Definition engine.hpp:287
std::string basecurrency
Definition engine.hpp:290
std::string volumetype
Definition engine.hpp:294
std::string type
Definition engine.hpp:291
std::string description
Definition engine.hpp:295
std::string currency
Definition engine.hpp:289
std::string session
Definition engine.hpp:293
Ephemeral read-only facts of one anchored-leg materialization (L7b), offered to the host exactly once...
Borrowed begin-call facts.
Read-only owning-value facts for one candidate.
native_order::NativeCandidatePriceKind price_kind
Ephemeral factual view of one kernel-issued liquidation before its units are fixed.
Ephemeral factual view of one kernel check point, offered to the host before the check runs.
bool liquidation_resting
Whether the model already holds a liquidation resting from an earlier admitted point.
The host's answer to one requirement view.
Ephemeral factual view of the numbers the kernel is about to compare, at one check point,...
Ephemeral factual view of one prepared execution before any physical effect.
One accepted realtime print before native matching at its current decision point.
Bind to a host-built roster (cohort_open / cohort_add / cohort_remove), read at the match rather than...
Bind to a fixed cohort of already-live openings.
A host-maintained, run-scoped roster identity.
One committed execution: the definition, the cursor, the resolved price and units,...
A reduction of exactly these units, finite and positive.
Defer a level to the owner's fill: at the arm the level becomes fill + offset, with offset signed (ad...
An opening or a closing whose quantity the HOST resolves, in NativeStrategyHost::resolve_execution_te...
No owner: the request stands on its own book authority.
fill_through makes the limit a touch trigger (market-if-touched): the level still gates when the requ...
Match at the next eligible matching point, with no level to reach.
A candidate the run refused, with its MatchRejectReason and the cursor it was refused at.
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...
Exact target exposure for an explicit reversal request.
A stop that, once reached, becomes a limit at limit.
A stop trigger: the modeled path has to reach price from the adverse side.
The one owner relation that arms (the ArmedEvent).
@ ContinuousSegments
Native hosts retain continuous matching between generated samples unless they explicitly request poin...
A generic per-side broker margin model (L4).
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
NativeSlotLabelPolicy slot_label_policy
Strict native hosts retain the canonical slot-label rule.
NativeCalculationTrigger calculation
Calculation timing.
std::optional< NativeMarginModel > margin
Opt-in generic margin model.
bool timeframe_undetected
A public begin with fewer than two bars may not establish a timeframe.
std::uint64_t recreated_after_named_cancelled_entry_incarnation
native_order::RequestHandle paired_reversal_parent
ReservationGrowthSource reservation_growth_source
native_order::RequestHandle bracket_origin
compat::pine::HistoricalBirthReach birth_reach
PineCancellationReceipt cancellation
ReservationExpansion reservation_expansion
compat::pine::ExitActivationPolicy exit_activation
std::optional< double > quantity_grid