9#include "../engine_internal.hpp"
11#include "../timezone.hpp"
29constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
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;
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);
49bool finite_positive(
double value)
noexcept {
50 return std::isfinite(value) && value > 0.0;
53bool finite_non_negative(
double value)
noexcept {
54 return std::isfinite(value) && value >= 0.0;
68template <
typename Handles,
typename Placement,
typename FromEntryFilled>
69bool opening_slice_precedes_priced_exit_fill(
const Handles& handles,
70 const Placement& placement,
76 std::int64_t position_cycle,
77 const FromEntryFilled& from_entry_filled)
noexcept {
88 const auto expected_side = is_long
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;
100 if (position_cycle > 0 && row.placement_cycle != 0
101 && row.placement_cycle != position_cycle) {
104 if (row.projection_position_side != expected_side
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) {
118 if (is_long && bar.low <= row.exit_levels.stop)
return true;
119 if (!is_long && bar.high >= row.exit_levels.stop)
return true;
122 if (is_long && bar.high >= row.exit_levels.limit)
return true;
123 if (!is_long && bar.low <= row.exit_levels.limit)
return true;
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;
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;
144bool price_present(
double value)
noexcept {
return !std::isnan(value); }
146bool pure_stop_entry_marketable_at(
const PlacementSnapshot& snapshot,
double open)
noexcept {
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)) {
155 return snapshot.is_long ? open >= snapshot.exit_levels.stop
156 : open <= snapshot.exit_levels.stop;
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);
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;
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:
184 case NativePathPhase::None:
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) {
194 value *= 1099511628211ULL;
199std::uint64_t fnv_string(std::string_view value)
noexcept {
200 return fnv_append(1469598103934665603ULL, value.data(), value.size());
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);
209 *truncated = value.size() > size ? 1U : 0U;
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 {
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);
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;
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());
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;
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;
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);
285 if (k * tick == value)
return value;
286 return source_decimal_tick(value, tick);
292bool source_same_point(
double booked,
double raw,
double tick)
noexcept {
294 || (finite_positive(tick) && source_bar_fill_tick(raw, tick) == raw
295 && nearest_tick(raw, tick) == booked);
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;
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;
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;
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;
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;
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;
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;
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());
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;
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);
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;
394 return floored < units ? floored : units;
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;
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;
410 const double cent_candidate = std::floor(units * 100.0) * *grid;
411 if (cent_candidate > floored && cent_candidate <= units) floored = cent_candidate;
413 return floored < units ? floored : units;
421 default:
return NativeFeeKind::Percent;
426 if (direction > 0)
return NativeOpenDirections::Long;
427 if (direction < 0)
return NativeOpenDirections::Short;
428 return NativeOpenDirections::Both;
435bool throttled_rearm_already_queued(
436 const std::vector<PlacementSnapshot>& queue,
438 for (
const auto& queued : queue) {
439 if (queued.source_id == source.source_id
440 && queued.source_sequence == source.source_sequence) {
457 bool high_first,
double mintick,
int slippage) {
458 if (!prefix_sample || !std::isfinite(fire_price))
return bar;
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) {
466 prefix.
close = fire_price;
469 const double path[4] = {
471 high_first ? bar.
high : bar.
low,
472 high_first ? bar.
low : bar.
high,
479 for (
int i = 0; i < 4 && fire < 0; ++i) {
480 if (same_double_bits(path[i], fire_price)) fire = i;
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;
485 if (fire < 0) fire = 3;
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]);
492 prefix.
close = fire_price;
504 if (!std::isfinite(bar.
high) || !std::isfinite(bar.
low)
505 || !std::isfinite(bar.
close)) {
509 for (
auto& pe : lots) {
510 double pe_hi = bar.
high;
511 double pe_lo = bar.
low;
513 if (pe.skip_entry_bar_high) pe_hi = pe.price;
514 if (pe.skip_entry_bar_low) pe_lo = pe.price;
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;
534 pending_view_.owner_ =
this;
546 if (!host_)
throw std::logic_error(
"Pine execution adapter is not bound to a native host");
550OrderBirth PineExecutionAdapter::capture_order_birth()
const {
553 const int bar = point->decision.coordinate.interval_index;
554 const std::int64_t timestamp = point->decision.sub_bar_open_ms;
557 const bool magnified = point->decision.sub_count > 1;
560 const int count = magnified ? std::max(1, point->decision.sub_count) : 4;
561 int index = magnified ? point->decision.sub_index : 0;
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;
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;
577 index = std::max(0, std::min(index, count - 1));
579 const std::uint64_t ordinal = std::max<std::uint64_t>(1, last_applied_ordinal_);
581 ordinal, ordinal, ordinal);
585 native_order::RequestHandle handle) {
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);
592 if (coof_recalc_active_) {
593 snapshot.coof_cascade_seg_i = coof_context_.coordinate.interval_index;
594 snapshot.coof_cascade_inflight_fires =
true;
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);
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;
627 int historical_point = 0;
628 double waypoint = activation_bar.open;
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;
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;
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)) {
661 first_lot_incarnation = opening.incarnation;
664 if (first_lot_incarnation != 0)
break;
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;
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_));
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);
744 const auto is_entry_like = [](
const PlacementSnapshot& candidate) {
748 struct CandidateRef {
749 PlacementSnapshot* snapshot =
nullptr;
750 std::uint64_t incarnation = 0;
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;
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});
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);
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);
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) {
797 snapshot.reservation_expansion.capture(handle.incarnation, current_position_cycle_,
800 std::abs(physical.signed_units));
801 }
catch (
const std::invalid_argument&) {
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) {
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&) {
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;
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
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));
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;
879 std::stable_sort(live_handles_.begin(), live_handles_.end(),
880 [&](
const auto& left,
const auto& right) { return rank(left) < rank(right); });
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;
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)
898 const exit_legs::Frame cause{
event.ordinal, context.coordinate.interval_index,
899 domain, exit_legs::Phase::AfterMargin};
901 const exit_legs::Action action{snapshot.legs.target(), snapshot.legs.revision(),
903 (
void)snapshot.legs.apply(snapshot.legs.target(), action);
905 if (require_host().physical_position().signed_units == 0.0) snapshot.leg_activation.unbind();
908bool PineExecutionAdapter::is_declined_market_reversal(
910 if (event.reason != native_order::MatchRejectReason::HostPrecommit
911 && event.reason != native_order::MatchRejectReason::InitialMargin) {
914 const auto found = placement_.find(event.handle().incarnation);
915 if (found == placement_.end())
return false;
916 const auto&
source = found->second;
918 return source.opening &&
source.family == PineOrderFamily::Entry
919 &&
source.reverse_to && std::holds_alternative<native_order::Market>(event.request().trigger)
924bool PineExecutionAdapter::follows_same_bar_declined_reversal(
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)) {
935 const auto reversal = placement_.find(rejected->handle().incarnation);
936 if (reversal != placement_.end()
937 && bracket_belongs_to_reversal(
exit, reversal->second)) {
944bool PineExecutionAdapter::bracket_belongs_to_reversal(
947 if (bracket.projection_position_side == reversal.projection_position_side)
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
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) {
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);
976void PineExecutionAdapter::suspend_brackets_for_reversal(
979 const int direction = reversal.projection_position_side
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)
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);
995 for (
auto row : placement_) {
996 auto& candidate = row.second;
1000 if (!
exit || candidate.from_entry.empty() || candidate.legs.dormant()
1001 || !candidate.legs.target().incarnation
1002 || !belongs(candidate)) {
1005 const compat::pine::ExitSuspensionContext context{
1006 cause, direction, require_host().position_avg_price(), staged_.syminfo.mintick,
1008 candidate.legs.trail_best(),
false,
true};
1010 if (!operation)
continue;
1011 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
1013 (void)candidate.legs.apply(candidate.legs.target(), action);
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);
1032 || !reversal.reverse_to || !opposite || !market_entry) {
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);
1056void PineExecutionAdapter::hold_reversal_pair_brackets(
const SourceId& from_entry) {
1057 const auto point = require_host().current_execution_point();
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;
1068 if (!
exit || candidate.from_entry != from_entry || candidate.legs.dormant()
1069 || !candidate.legs.target().incarnation) {
1073 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
1075 (void)candidate.legs.apply(candidate.legs.target(), action);
1079void PineExecutionAdapter::purge_brackets_after_applied_reversal(
1081 const bool prior_long = reversal.projection_position_side
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;
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;
1105 if ((
exit && targets_prior) || stale_margin) stale.push_back(handle);
1107 for (
const auto& handle : stale) {
1108 (void)require_host().cancel(handle);
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) {
1122 cohort.second.opened.clear();
1123 cohort.second.live_units_by_origin.clear();
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;
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);
1141 && rejected->cursor.point.interval_index == context.coordinate.interval_index
1142 && is_declined_market_reversal(*rejected)) {
1143 suspend_declined_reversal_brackets(*rejected);
1146 const auto domain = context.sub_count > 1
1147 ? (config_.calc_on_order_fills ? exit_legs::Domain::MagnifierFillRecalc
1148 : exit_legs::Domain::Magnifier)
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_) {
1160 if (row.second.placement_cycle != current_position_cycle_)
continue;
1161 auto& candidate = row.second;
1165 if (!
exit || !candidate.legs.dormant() || candidate.from_entry.empty()
1166 || !(cohort_exposure_for(candidate.from_entry) > 0.0)
1167 || !candidate.legs.target().incarnation) {
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)) {
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)) {
1206 if (superseded)
continue;
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) {
1216 candidate.restored_after_margin =
true;
1217 const double held = std::abs(physical.signed_units);
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};
1240 if (!marketable)
return;
1241 if (marketable_handle.incarnation) {
1242 (void)require_host().cancel(marketable_handle);
1243 retire(marketable_handle);
1245 native_order::Request request;
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;
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));
1261 (void)require_host().execute_current(
1262 {*accepted, NativeCurrentPriceRule::NearestTick});
1268 cohorts_by_id_.clear();
1269 cohort_order_.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;
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;
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;
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;
1380 refresh_pending_view();
1386 stream_mode_ = is_stream;
1387 bar_magnifier_ = bar_magnifier;
1390 path_order_ = path_order;
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;
1403 if (run_counter_ == std::numeric_limits<std::uint64_t>::max()) {
1404 throw std::overflow_error(
"Pine native run counter exhausted");
1410 spec.
identity = {session +
"@" + timezone, ++run_counter_};
1424 std::string effective_input = args.
input_tf;
1425 if (effective_input.empty() && args.
n >= 2 && args.
bars !=
nullptr) {
1428 spec.
input_tf = std::move(effective_input);
1467 |
static_cast<std::uint32_t
>(NativeFeedTolerance::WarmupNonNegativeOHLC));
1471 ? NativeCloseExecution::AfterCalculation : NativeCloseExecution::NextEligiblePoint;
1480 spec.
calculation = NativeCalculationTrigger::BarCloseAndFills;
1509 spec.
report_policy = NativeReportPolicy::KernelRecordedAtHostMarks;
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) {
1564 margin.
check = NativeLiquidationCheck::PathAdverseExtremeMark;
1571 ? NativeMarginEquityBasis::MarkedEquity
1572 : NativeMarginEquityBasis::MarkedEquityBeforeOpenCommission;
1574 margin.
level_base = NativeLiquidationLevelBase::RealizedOnly;
1588 const int volume_weighted_cap =
1592 IntrabarPath::synthesized path;
1597 path.volume_weighted_max_samples = volume_weighted_cap;
1603 IntrabarPath::lower_tf path;
1604 if (args.
bars && args.
n > 0) path.bars.assign(args.
bars, args.
bars + args.
n);
1610 path.volume_weighted_max_samples = volume_weighted_cap;
1626 IntrabarPath::lower_tf inert;
1630 inert.volume_weighted =
false;
1631 inert.volume_weighted_min_samples = 2;
1632 inert.volume_weighted_max_samples = 64;
1638 throw std::logic_error(
"Pine adapter produced invalid native run spec field "
1639 + std::to_string(
static_cast<unsigned>(validation.field)));
1644std::uint64_t PineExecutionAdapter::key_for(
const SourceId& left,
const SourceId& right)
const noexcept {
1645 return source_key(left, right);
1649 auto found = cohorts_by_id_.find(
id);
1650 if (found != cohorts_by_id_.end())
return found->second.handle;
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);
1660PineSizingSnapshot PineExecutionAdapter::sizing_snapshot()
const {
1661 PineSizingSnapshot snapshot;
1662 const auto& host = require_host();
1663 if (
const auto point = host.current_execution_point()) {
1667 snapshot.mark = nearest_tick(point->price, staged_.
syminfo.
mintick);
1668 snapshot.price = snapshot.mark;
1669 snapshot.equity = percent_commission_live_equity(snapshot.mark);
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);
1685double PineExecutionAdapter::default_sizing_cash(
1687 if (config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)) {
1688 return config_.default_qty_value;
1691 || !finite_positive(sizing.equity)) {
1694 const double equity = staged_.quantity_grid ? source_money_round(sizing.equity) : sizing.equity;
1695 return config_.default_qty_value / 100.0 * equity;
1702bool PineExecutionAdapter::default_sizing_reserves_percent_fee() const noexcept {
1705 && config_.commission_value > 0.0;
1714double PineExecutionAdapter::default_sizing_lot_floor(
double units)
const noexcept {
1716 && staged_.quantity_grid) {
1717 return source_money_floor_lot(units, staged_.quantity_grid);
1719 return floor_quantity_grid(units, staged_.quantity_grid);
1725std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_shape(
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();
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;
1751std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_intent(
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;
1763 auto sized = default_sizing_shape(sizing);
1764 if (sized) sized->side = is_long ? native_order::Side::Long : native_order::Side::Short;
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);
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);
1788bool PineExecutionAdapter::core_sizing_price_matches(
1791 if (!finite_positive(tick) || !finite_positive(sizing.
price))
return false;
1793 if (!point || !finite_positive(point->price))
return false;
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;
1807bool PineExecutionAdapter::same_bar_market_tx_scope()
const {
1815 const bool variable_short_seed = variable_default && short_seed_context_is_live();
1819 || (!fixed_default && !variable_short_seed)
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
1831 && !inert->volume_weighted && inert->volume_weighted_min_samples == 2
1832 && inert->volume_weighted_max_samples == 64
1834 return state.phase == NativeRunPhase::Batch && state.spec !=
nullptr
1835 && (state.spec->intrabar.is_none() || inactive_sampler_path);
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{};
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};
1858void PineExecutionAdapter::remember(
const native_order::RequestHandle& handle,
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;
1866 initialize_l4c_policy(snapshot, handle);
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();
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;
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_ = {};
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;
1896 refresh_pending_view();
1899void PineExecutionAdapter::maybe_activate_short_seed_plan() {
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;
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()) {
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()) {
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();
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);
1943 const auto fresh_plain = [&](
const PlacementSnapshot& row) {
1944 return row.placement_open_epoch + 1U == broker_open_epoch_
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()
1951 && !row.projection_created_during_coof
1952 && !row.reservation_expansion.capture()
1953 && row.oca_name.empty() && row.oca_type == 0;
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);
1962 && std::isnan(row.requested_qty) && row.qty_type == -1 && sizing_shape
1963 && no_level(row.exit_levels) && !row.projection_after_close;
1965 const auto exact_full_fifo_close_short = [&]() {
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;
1976 std::array<std::uint64_t, 3> incarnations{
1977 plan.long_entry.incarnation,
1978 plan.materialize_long.incarnation,
1979 plan.final_short.incarnation,
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;
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
2011 if (!(finite_positive(required) && required <= row->sizing.
equity)) {
2012 percent_rechecks_safe =
false;
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)
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;
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) {
2065 pending_short_seed_ = {};
2068std::optional<native_order::RequestHandle> PineExecutionAdapter::submit_or_replace(
2071 auto& host = require_host();
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};
2083 const NativePhysicalPosition physical = host.physical_position();
2084 snapshot.projection_position_side = physical.signed_units > 0.0
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);
2117 snapshot.projection_affordability_equity = kNaN;
2118 snapshot.projection_affordability_signal_price = kNaN;
2119 snapshot.projection_affordability_held_qty = kNaN;
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);
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);
2145 if (snapshot.placement_cycle == 0)
2146 snapshot.placement_cycle = current_position_cycle_;
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;
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()) {
2178 if (!snapshot.projection_created_bar_pinned) {
2179 snapshot.projection_created_bar = point->decision.coordinate.interval_index;
2181 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
2182 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
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)) {
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) {
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;
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_;
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");
2246 snapshot.command_sequence = ++source_command_sequence_;
2248 std::optional<admission::Allocation> admission_allocation;
2249 std::shared_ptr<const admission::CommandObservation> admission_observation;
2253 auto observed = std::make_shared<admission::CommandObservation>();
2254 observed->command = admission_allocation->sequence();
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)
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};
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);
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");
2323 member->cohort =
static_cast<std::int64_t
>(source_sequence_ + 1U);
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
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)) {
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)) {
2363 if (
const auto* limit = std::get_if<native_order::Limit>(&request.trigger)) {
2364 if (preserve_coof_birth)
2367 && same_double_bits(limit->price, prior.exit_levels.limit);
2369 if (
const auto* stop = std::get_if<native_order::Stop>(&request.trigger)) {
2370 if (preserve_coof_birth)
2373 && same_double_bits(stop->price, prior.exit_levels.stop);
2375 if (
const auto* trail = std::get_if<native_order::Trail>(&request.trigger)) {
2379 const double trail_offset = trail->ticks
2380 ? trail->ticks->ticks * staged_.syminfo.mintick : trail->offset;
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));
2389 std::optional<std::uint64_t> retained_source_sequence;
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()) {
2396 existing_handle = existing->second;
2397 if (
const auto previous = placement_.find(existing_handle->incarnation);
2398 previous != placement_.end()) {
2399 predecessor_snapshot = previous->second;
2404 if (unchanged_dynamic_exit(previous->second))
return existing_handle;
2405 const auto family = previous->second.family;
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);
2414 if (existing_handle) {
2420 if (predecessor_snapshot
2423 const auto same = [](
double left,
double right) {
2424 return (std::isnan(left) && std::isnan(right)) || left == right;
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;
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;
2450 refresh_pending_view();
2451 return existing_handle;
2453 if (std::isfinite(snapshot.sizing.price))
2454 snapshot.retained_trail_best = snapshot.sizing.price;
2456 const auto result = host.replace(*existing_handle, request);
2457 if (result.status == native_order::ReplaceStatus::Replaced && result.successor) {
2464 if (
const auto found_p = placement_.find(existing_handle->incarnation);
2465 found_p != placement_.end()) {
2466 found_p->second.legs = {};
2468 snapshot.projection_predecessor = existing_handle->incarnation;
2469 if (predecessor_snapshot) {
2470 const auto family = predecessor_snapshot->family;
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()
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;
2492 snapshot.reservation_expansion = predecessor_snapshot->reservation_expansion;
2493 snapshot.reservation_growth_source = predecessor_snapshot->reservation_growth_source;
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};
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
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;
2521 if (retained_child) {
2522 retained_source_sequence = predecessor_snapshot->source_sequence;
2528 retained_source_sequence = predecessor_snapshot->source_sequence;
2531 snapshot.projection_predecessor_exit = predecessor_exit;
2532 snapshot.projection_predecessor_market = predecessor_market;
2533 retire(*existing_handle);
2534 accepted = *result.successor;
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");
2545 member->cohort =
static_cast<std::int64_t
>(source_sequence_ + 1U);
2547 if (!accepted && materializing_relative_ && !opening) {
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;
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;
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;
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;
2587 const auto* sized = std::get_if<native_order::HostSized>(&request.intent);
2588 const bool same_close = sized && sized->kind == native_order::HostSizedKind::Close
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;
2600 const auto result = host.submit(request);
2601 if (result.status != native_order::SubmitStatus::Accepted || !result.handle)
return std::nullopt;
2602 accepted = *result.handle;
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;
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;
2622 remember(*accepted, std::move(snapshot));
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());
2640 if (key != 0) live_by_source_key_[key] = *accepted;
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);
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);
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;
2667bool PineExecutionAdapter::from_entry_filled_this_cycle(
const SourceId&
id)
const noexcept {
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_;
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;
2684 const auto& facts = found->second;
2685 bool have_previous =
false;
2686 std::size_t previous = 0;
2688 bool have_next =
false;
2689 std::size_t next = 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) {
2699 units = unit.second;
2704 if (!have_next)
break;
2705 if (std::isfinite(units) && units > 0.0) total += units;
2706 have_previous =
true;
2709 return std::isfinite(total) && total > 0.0 ? total : 0.0;
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();
2718 return total >
static_cast<std::size_t
>(std::numeric_limits<int>::max())
2719 ? std::numeric_limits<int>::max() :
static_cast<int>(total);
2722double PineExecutionAdapter::percent_commission_live_equity(
double mark)
const noexcept {
2723 if (!host_)
return std::numeric_limits<double>::quiet_NaN();
2729 && config_.commission_value > 0.0
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;
2738 return (pine->current_equity() + pine->open_profit(mark)) - paid_open_commission;
2741 const double marked = host_->native_marked_equity(mark);
2742 if (!std::isfinite(marked))
return marked;
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;
2753 return std::isfinite(restored) ? marked + restored
2754 : std::numeric_limits<double>::quiet_NaN();
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)
2766 fee = config_.commission_value * opened;
2768 const double total = opened + std::abs(event.closed_units);
2769 fee = total > 0.0 ? config_.commission_value * opened / total : 0.0;
2771 if (!std::isfinite(fee))
return;
2772 open_entry_fees_.push_back({
event.handle(), source.source_id, opened, fee});
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;
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)) {
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;
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_});
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)
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;
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);
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) {
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;
2844 if (step >= 1.0 && requested < step && available >= step) result = step;
2848bool PineExecutionAdapter::compute_exit_reservation(
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;
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;
2872 std::vector<Reservation> reservations;
2873 auto observe = [&](
const PlacementSnapshot& snapshot) {
2878 if (!
exit || snapshot.from_entry != from_entry)
return;
2881 && snapshot.placement_cycle != 0
2882 && snapshot.placement_cycle < current_position_cycle_) {
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))
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()) {
2902 next.family = family;
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));
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);
2915 row->units = std::max(row->units, units);
2917 row->explicit_units = row->explicit_units || explicit_units;
2918 row->percent = std::max(row->percent, percent);
2921 for (
const auto& handle : live_handles_) {
2922 const auto found = placement_.find(handle.incarnation);
2923 if (found != placement_.end()) observe(found->second);
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);
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;
2942 if (std::isfinite(reservation.units)) already_reserved += reservation.units;
2943 if (reservation.percent >= 100.0 - kFullPercentEpsilon) other_full_exit =
true;
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);
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);
2961 reserved_qty = std::min(requested, available);
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;
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;
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;
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;
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()) {
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));
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;
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);
3027 for (std::size_t index = 0; index < pending_bracket_legs_.size(); ++index) {
3028 const auto& snapshot = pending_bracket_legs_[index].snapshot;
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()) {
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));
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;
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);
3064 for (std::size_t index = 0; index < delayed_market_orders_.size(); ++index) {
3065 const auto& snapshot = delayed_market_orders_[index].snapshot;
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()) {
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));
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;
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);
3101 std::stable_sort(families.begin(), families.end(),
3102 [](
const Family& left,
const Family& right) {
3103 return left.command_sequence < right.command_sequence;
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);
3111 if (std::isfinite(family.explicit_requested)) {
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);
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);
3133 units = std::min(requested, available);
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;
3141 for (
const auto index : family.delayed) {
3142 if (index < delayed_market_orders_.size())
3143 delayed_market_orders_[index].snapshot.qty_percent = 0.0;
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;
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;
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;
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))
3189 for (
const auto& handle :
cancel) {
3190 const auto result = require_host().cancel(handle);
3191 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
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;
3198 pending_bracket_legs_.end());
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];
3209 return std::isfinite(rate) && rate > 0.0 ? rate : 1.0;
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);
3228 if (minimum > 0.0) {
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) {
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)
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}};
3248 request.comment =
"Margin call";
3249 PlacementSnapshot snapshot;
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});
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");
3276 submit_fx_margin_slice(bar, context, rate,
true);
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;
3287 if (!opening_snapshot || !opening_snapshot->opening
3289 || !finite_positive(opening_snapshot->sizing.frozen_units)
3292 || !(config_.commission_value > 0.0)) {
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;
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;
3306 submit_fx_margin_slice(opening, context, rate,
true);
3309void PineExecutionAdapter::schedule_preopen_margin_slice(
3310 const Bar& bar,
const NativeDecisionContext& context) {
3317 if (!source_margin_call_enabled_
3318 || require_host().physical_position().signed_units != 0.0
3320 || !finite_positive(staged_.syminfo.pointvalue)
3321 || !finite_positive(staged_.syminfo.mintick)
3322 || !finite_positive(bar.open)) {
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;
3336 if (!opening_copy)
continue;
3337 const PlacementSnapshot& opening = *opening_copy;
3339 || !finite_positive(opening.sizing.frozen_units)
3340 || !finite_positive(opening.exit_levels.stop)) {
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;
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))) {
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;
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) {
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
3389 const double unit_margin = adverse * staged_.syminfo.pointvalue * fx
3391 if (!std::isfinite(marked_equity) || !std::isfinite(required)
3392 || !finite_positive(unit_margin) || !(required > marked_equity)) {
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;
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";
3407 request.trigger = native_order::Stop{adverse_raw};
3408 request.owner = native_order::BindCohort{cohort_for(opening.source_id)};
3409 PlacementSnapshot snapshot;
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);
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;
3436 for (
const auto& handle : facts.opened) selected.push_back(handle.incarnation);
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;
3450void PineExecutionAdapter::consume_closed_trade_rows(
3451 const native_order::ExecutionAppliedEvent& event,
3453 auto& host = require_host();
3454 const auto settle_slot = [&](CohortFacts& cohort,
const Trade& trade,
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;
3481 cohort.opened.end());
3482 bracket_shadowed_openings_.erase(trade.entry_incarnation);
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);
3499 cohort.second.live_units_by_origin.erase(units);
3500 settle_slot(cohort.second, trade, drained);
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);
3515 cohort->second.live_units_by_origin.erase(units);
3516 settle_slot(cohort->second, trade, drained);
3517 if (!(remaining > 0.0))
break;
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();
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());
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);
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()) {
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};
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;
3570 if (!snapshot_copy)
return;
3571 const PlacementSnapshot& snapshot = *snapshot_copy;
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
3584 matches.push_back(candidate);
3587 for (
const auto& sibling : matches) {
3588 const auto result = require_host().cancel(sibling);
3589 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
3593void PineExecutionAdapter::cancel_exit_orders_for_full_close(
3595 const auto matches = [&](
const PlacementSnapshot& snapshot) {
3599 return exit && (from_entry.empty() ? snapshot.from_entry.empty()
3600 : snapshot.from_entry == from_entry);
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;
3620 pending_relative_exits_.end());
3621 if (!from_entry.empty()) withdraw_anchored_relative_legs(
nullptr, &from_entry);
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);
3628 for (
const auto& handle : handles) {
3629 const auto result = require_host().cancel(handle);
3630 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
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);
3637 refresh_pending_view();
3669void PineExecutionAdapter::retire_in_position_exits_at_flat(
3670 bool preserve_pending_parents,
bool dormant_rows_only,
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)) {
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
3688 && found->second.source_id == owner) {
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;
3697 const auto matches = [&](
const PlacementSnapshot& snapshot) {
3702 &&
static_cast<PositionSide>(snapshot.projection_position_side)
3704 && !pending_parent(snapshot);
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());
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);
3728 for (
const auto& handle : handles) {
3729 const auto result = require_host().cancel(handle);
3730 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
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);
3738 refresh_pending_view();
3753 return placement_.command_sequence_for(exit_id, from_entry);
3757 return trade_index < trade_exit_phase_.size()
3758 && trade_exit_phase_[trade_index] ==
static_cast<std::uint8_t
>(NativePathPhase::Open);
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);
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);
3775 trade_exit_phase_[target] = permuted[i];
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);
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)>;
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>) {
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();
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);
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};
3831 if (opening) cancel_bracket_origin(handle);
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;
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>(
3843 placement->second.projection_remaining_qty = remaining->q;
3846 }
else if constexpr (std::is_same_v<Event, native_order::ExecutionAppliedEvent>) {
3847 if (event.terminal) cancel_bracket_siblings(event.handle());
3851 if (terminal_high_water) {
3852 terminal_receipt_cursor_ = std::max(
3853 terminal_receipt_cursor_, *terminal_high_water);
3865 const auto found = cohorts_by_id_.find(
id);
3870 if (found == cohorts_by_id_.end() || !(cohort_exposure_for(
id) > 0.0)) {
3872 found == cohorts_by_id_.end()
3874 : found->second.handle};
3876 return native_order::Independent{};
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};
3886 if (dynamic || found == cohorts_by_id_.end()) {
3887 if (found == cohorts_by_id_.end())
3889 return native_order::BindCohort{found->second.handle};
3891 return native_order::BindOpenings{found->second.opened, found->second.cycle};
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;
3904 if (bracket && child.from_entry == parent_id
3905 && child.projection_position_side
3907 && child.projection_created_bar == created_bar
3908 && child.placement_script_open_ms == script_open_ms) {
3909 children.push_back(handle);
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;
3922 request.trigger = native_order::Limit{snapshot.exit_levels.limit};
3924 request.trigger = native_order::Stop{snapshot.exit_levels.stop};
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;
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;
3941 PendingBracketLeg staged{std::move(request), std::move(snapshot),
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));
3948 *queued = std::move(staged);
3951 refresh_pending_view();
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;
3965 && std::holds_alternative<native_order::Market>(event.
request().
trigger)) {
3967 if (placement != placement_.end()
3969 coof_market_entry_recalc_incarnation_ =
event.handle().incarnation;
3972 coof_context_ = context;
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;
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) {
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;
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;
4014 if (!eligible(filled->second))
return false;
4015 return std::any_of(live_handles_.begin(), live_handles_.end(),
4017 if (handle == event.handle()) return false;
4018 const auto sibling = placement_.find(handle.incarnation);
4019 return sibling != placement_.end() && eligible(sibling->second);
4023bool PineExecutionAdapter::defer_coof_tail() const noexcept {
4024 if (!coof_recalc_active_ || coof_first_open_)
return false;
4026 if (state.spec && state.spec->intrabar.lower())
return false;
4028 if (phase == NativePathPhase::Close || phase == NativePathPhase::None)
4030 if (!coof_script_bar_valid_)
return false;
4031 const bool high_first = source_path_uses_high_first(coof_script_bar_);
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);
4039bool PineExecutionAdapter::coof_fill_on_path_point() const noexcept {
4042 return coof_recalc_active_
4043 && (coof_fill_cursor_t_ == 0.0 || coof_fill_cursor_t_ == 1.0);
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;
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();
4060bool PineExecutionAdapter::source_path_uses_high_first(
const Bar& bar)
const noexcept {
4061 return source_path_high_first(bar, path_order_);
4064bool PineExecutionAdapter::coof_current_fill_was_forced_waypoint() const noexcept {
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)) {
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,
4096double PineExecutionAdapter::coof_next_waypoint(
int* path_index)
const noexcept {
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;
4112 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4114 NativePathPhase::Open,
4115 high_first ? NativePathPhase::High : NativePathPhase::Low,
4116 high_first ? NativePathPhase::Low : NativePathPhase::High,
4117 NativePathPhase::Close,
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,
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();
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];
4151 if (path_index && index < 3) *path_index = index + 1;
4152 return index < 3 ? path_price[index + 1] : kNaN;
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();
4164 if (state.spec && state.spec->intrabar.lower())
return kNaN;
4165 const bool high_first = source_path_uses_high_first(coof_script_bar_);
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;
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_)
4180 const bool high_first = source_path_uses_high_first(coof_script_bar_);
4182 NativePathPhase::Open,
4183 high_first ? NativePathPhase::High : NativePathPhase::Low,
4184 high_first ? NativePathPhase::Low : NativePathPhase::High,
4185 NativePathPhase::Close,
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,
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) {
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;
4209 if (path_price[cursor] > level) crossed_adverse =
true;
4210 else if (crossed_adverse && path_price[cursor] <= level)
return true;
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));
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);
4238 double stop_price,
double qty,
const std::string& comment,
4239 const std::string& oca_name,
int oca_type,
int qty_type) {
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;
4252 && require_host().native_state().kind == NativeLifecycleKind::Unconfigured) {
4257 request.
intent = default_sized
4259 native_order::HostSizedKind::Open,
4260 is_long ? native_order::Side::Long : native_order::Side::Short}}
4264 request.
trigger = trigger_for(limit_price, stop_price);
4265 request.
group = group_for(oca_name, oca_type);
4282 snapshot.
sizing = sizing_snapshot();
4283 pending_entries_.push_back(
4284 {std::move(request), std::move(snapshot),
id});
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;
4293 if (entry_attempts_on_bar_ != std::numeric_limits<std::uint32_t>::max())
4294 ++entry_attempts_on_bar_;
4296 bool close_precedes_entry = pending_same_bar_close_qty_ > 0.0;
4297 const double preceding_close_qty = pending_same_bar_close_qty_;
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;
4308 && row.placement_script_open_ms
4309 == source_point->decision.script_bar_open_ms) {
4310 close_precedes_entry =
true;
4311 preceding_close_request = handle;
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))) {
4324 if (pure_stop_entry && preceding_close_qty > 0.0
4325 && !pending_same_bar_commands_.empty()) {
4329 flush_pending_same_bar_commands();
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()
4335 || prior->second.placement_script_open_ms
4336 != source_point->decision.script_bar_open_ms) {
4339 preceding_close_request = *it;
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;
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);
4358 for (
const auto& handle : stale_recalc_entries) {
4359 const auto cancelled = require_host().cancel(handle);
4360 if (cancelled.status == native_order::CancelStatus::Cancelled)
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) {
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;
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;
4399 const auto result = require_host().cancel(*prior_handle);
4400 if (result.status == native_order::CancelStatus::Cancelled)
4401 retire(*prior_handle);
4408 if (explicit_fixed && normalized_qty == 0.0 && current == 0.0 && !priced) {
4423 ? source_point->decision.coordinate.interval_index : -1;
4424 shadow.
sizing = sizing_snapshot();
4425 source_shadow_pending_.push_back({std::move(shadow),
id});
4428 if (current == 0.0 && priced && explicit_fixed) {
4429 for (
const auto& pending : pending_same_bar_commands_) {
4430 const auto& candidate = pending.snapshot;
4432 || candidate.is_long == is_long
4433 || !candidate.frozen_market_instruction
4434 || !finite_positive(candidate.frozen_market_own_units)) {
4437 flat_pending_opposite_market_units += candidate.frozen_market_own_units;
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;
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;
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(),
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;
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_
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();
4487 const bool all_in_percent = default_sized && !priced && oca_name.empty()
4489 && config_.default_qty_value >= 100.0;
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
4511 || prior->second.is_long == is_long) {
4514 paired_all_in_reentry =
true;
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)) {
4525 if (source_point) observe_intraday_cap_noop(is_long, source_point->decision);
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;
4545 for (
const auto& handle : live_handles_) {
4546 const auto placement = placement_.find(handle.
incarnation);
4548 if (placement != placement_.end() && placement->second.opening
4549 && placement->second.source_id !=
id
4550 && placement->second.is_long == is_long) ++accepted_in_cycle;
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)) {
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)
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;
4579 const bool reverses = current != 0.0 && ((current > 0.0) != is_long) && !close_all_precedes;
4582 && reverses && !priced && config_.process_orders_on_close
4583 && !close_batch_callsites_.empty()) {
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;
4599 const bool affordability_reversal_candidate = reverses && !priced
4602 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
4604 && config_.default_qty_value > 100.0))
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;
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) {
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;
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);
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);
4645 const bool cash_sized = qty_type ==
static_cast<int>(
QtyType::CASH);
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)
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);
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;
4665 if (default_sized || typed_sized || direction_blocked || affordability_reversal_candidate) {
4667 is_long ? native_order::Side::Long : native_order::Side::Short};
4668 }
else if (fixed_priced_reverse || cash_priced_reverse) {
4670 is_long ? native_order::Side::Long : native_order::Side::Short};
4671 }
else if (reverses) {
4676 if (flat_pending_opposite_market_units > 0.0) {
4677 const double transaction = normalized_qty + flat_pending_opposite_market_units;
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)
4691 else if (coof_native_state.spec->path_order == NativePathOrder::LowFirst)
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);
4700 double native_limit = limit_price;
4701 double native_stop = stop_price;
4702 if (finite_positive(limit_price) && !finite_positive(stop_price)) {
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);
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()) {
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;
4749 const bool extreme_target = next_extreme_index == 1
4750 || next_extreme_index == 2;
4767 coof_market_fill = nearest_tick(next_extreme, staged_.syminfo.mintick)
4768 + (is_long ? 1.0 : -1.0) * config_.slippage
4769 * staged_.syminfo.mintick;
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) {
4775 const auto next = pine_host
4776 ? pine_host->scheduler_.next_source_bar(
4777 source_point->decision.coordinate.interval_index)
4778 : std::optional<Bar>{};
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 =
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;
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()
4806 ||
close->second.placement_script_open_ms
4807 != current_point->decision.script_bar_open_ms) {
4810 preceding_close_all = *it;
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;
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()
4836 && !prior->second.from_entry.empty()
4837 && prior->second.projection_created_bar
4838 == source_point->decision.coordinate.interval_index) {
4844 if (flat_pending_opposite_market_units > 0.0) {
4848 normalized_qty + flat_pending_opposite_market_units;
4850 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
4851 throw std::overflow_error(
"Pine source command sequence exhausted");
4859 snapshot.
birth = capture_order_birth();
4861 snapshot.
birth,
false);
4862 snapshot.
reverse_to = reverses || paired_all_in_reentry;
4864 snapshot.
sizing = sizing_snapshot();
4865 if (finite_positive(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)) {
4878 reached = is_long ? (waypoint <= limit_price && birth > limit_price)
4879 : (waypoint >= limit_price && birth < limit_price);
4882 const double slipped = waypoint + (limit_route ? 0.0
4883 : (is_long ? 1.0 : -1.0) * config_.slippage
4884 * staged_.syminfo.mintick);
4886 slipped, staged_.syminfo.mintick);
4889 if (current == 0.0 && priced && current_point) {
4890 const auto is_opposite_market_predecessor = [&](
const PlacementSnapshot& prior) {
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);
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(),
4910 const auto prior = placement_.find(handle.incarnation);
4911 return prior != placement_.end()
4912 && is_opposite_market_predecessor(prior->second);
4917 is_long ? native_order::Side::Long : native_order::Side::Short};
4921 if (default_sized && !priced && finite_positive(snapshot.
sizing.
mark)) {
4925 const auto predecessor = live_by_source_key_.find(key_for(
id));
4928 const auto prior = placement_.find(predecessor->second.incarnation);
4930 && !finite_positive(prior->second.exit_levels.limit)
4931 && !finite_positive(prior->second.exit_levels.stop);
4933 const bool special_sell_replacement = default_sized && reverses && !is_long
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;
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);
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);
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);
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;
4977 && std::find(carried_ids.begin(), carried_ids.end(), leg->second.from_entry)
4978 != carried_ids.end()) {
4979 dynamic_carried_legs.push_back(handle);
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);
4988 || (reverses && typed_sized);
4990 if (fixed_priced_reverse) {
4993 if (default_stop_scope && finite_positive(default_stop_sizing_price)) {
4998 snapshot.
sizing.
price = default_stop_sizing_price;
5000 if (default_sized && finite_positive(snapshot.
sizing.
price)) {
5002 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)) {
5006 snapshot.
sizing.
at_fill = (config_.calc_on_order_fills && coof_recalc_active_)
5007 || (priced && !default_stop_scope);
5018 if (default_sized && !priced && !snapshot.
sizing.
at_fill && !direction_blocked
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)) {
5032 const double entry_margin = is_long ? config_.margin_long : config_.margin_short;
5033 const bool tv_money_scope = default_sized && !priced
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
5041 && finite_positive(snapshot.
sizing.
fx)
5042 && finite_positive(staged_.syminfo.pointvalue)
5043 && (*staged_.quantity_grid * snapshot.
sizing.
price * staged_.syminfo.pointvalue
5049 if (tv_money_scope && !config_.process_orders_on_close) {
5051 * staged_.syminfo.pointvalue * snapshot.
sizing.
fx;
5052 const double rounded_cost = source_money_round(notional_per_price * snapshot.
sizing.
price);
5057 }
else if (reverses)
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;
5069 const bool affordability_scope = (!priced || pure_stop_entry) && (default_sized
5071 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
5073 && config_.default_qty_value > 100.0))
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) {
5084 const double denominator = signal * staged_.syminfo.pointvalue * snapshot.
sizing.
fx;
5085 own_units = finite_positive(denominator) ? normalized_qty / denominator : 0.0;
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;
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);
5099 if (reverses && margin > 0.0 && std::isfinite(required)
5103 }
else if (!reverses && margin > 0.0
5104 && (!std::isfinite(required) || !std::isfinite(snapshot.
sizing.
equity)
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;
5118 const auto result = require_host().cancel(*prior_handle);
5119 if (result.status == native_order::CancelStatus::Cancelled)
5120 retire(*prior_handle);
5128 const double margin = is_long ? config_.margin_long : config_.margin_short;
5130 * staged_.syminfo.pointvalue * snapshot.
sizing.
fx * margin / 100.0;
5136 const double stop_epsilon = std::max(
5138 if (margin > 0.0 && (!std::isfinite(required) || !std::isfinite(snapshot.
sizing.
equity)
5139 || required > snapshot.
sizing.
equity + stop_epsilon)) {
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;
5148 const auto result = require_host().cancel(*prior_handle);
5149 if (result.status == native_order::CancelStatus::Cancelled) retire(*prior_handle);
5154 if (
const auto point = require_host().current_execution_point()) {
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;
5163 || prior_snapshot.is_long != is_long
5164 || prior_snapshot.placement_script_open_ms != point->decision.script_bar_open_ms) {
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;
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);
5185 if (same_bar_market_candidate) {
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;
5198 if (!prior.opening || prior.source_id ==
id || prior.is_long == is_long) {
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;
5206 opposite_entry_pending =
true;
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) {
5219 inspect_pending(prior->second);
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;
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;
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) {
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;
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
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)));
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;
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;
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)) {
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)
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;
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));
5322 source_batch_mutated_ =
true;
5323 *existing = std::move(pending);
5329 if (preceding_close_all.incarnation != 0) {
5330 pending_entries_.push_back({std::move(request), std::move(snapshot),
id});
5333 const bool source_same_side_market_add = default_sized
5335 && !config_.calc_on_order_fills
5336 && (config_.process_orders_on_close || !opposite_opening_pending)
5338 && ((current > 0.0) == is_long)
5339 && std::holds_alternative<native_order::Market>(request.
trigger);
5340 const bool close_first_percent_add = default_sized
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);
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;
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);
5378 if (child_families.size() == 1) {
5380 token->second.entry_incarnation;
5382 token->second.surviving_exit_incarnation;
5385 if (
const auto point = require_host().current_execution_point()) {
5390 if (child_families.size() != 1) named_entry_cancel_tokens_.erase(token);
5391 pending_entries_.push_back({std::move(request), std::move(snapshot),
id});
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()) {
5403 *queued = PendingEntry{std::move(request), std::move(snapshot),
id};
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);
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});
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()
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)
5442 if (coof_flat_priced || ordinary_flat_pure_stop) {
5443 pending_entries_.push_back({std::move(request), std::move(snapshot),
id});
5446 if (coof_market_next_open) {
5447 pending_coof_requests_.push_back(
5448 {std::move(request), std::move(snapshot), id,
true, 0,
true});
5451 if (defer_coof_tail()) {
5452 pending_coof_requests_.push_back({std::move(request), std::move(snapshot), id,
true, 0});
5455 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
true,
id);
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;
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) {
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);
5480 if (default_sized && !priced && reverses && source_point
5481 && config_.default_qty_type
5483 && config_.default_qty_value <= 100.0) {
5485 if (
const auto next = pine_host->scheduler_.next_source_bar(
5486 source_point->decision.coordinate.interval_index)) {
5492 apply_reversal_gap_bracket_policy(
5493 *next, next_context,
true);
5497 }
else if (paired_all_in_reentry) {
5503 SourceShadowPending shadow;
5506 shadow.snapshot.
is_long = is_long;
5507 shadow.snapshot.
opening =
true;
5508 shadow.snapshot.
sizing = sizing_snapshot();
5510 source_shadow_pending_.push_back(std::move(shadow));
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);
5524 for (
const auto& backing : backing_by_id) {
5525 if (backing.first !=
id) total += backing.second;
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;
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;
5551 double pending_reserved = 0.0;
5552 for (
const auto& row : close_batch_callsites_) {
5553 if (row.second.active) pending_reserved += row.second.target;
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;
5564 replacement_can_reuse_own_claim = !another_targets_prior;
5566 if (same_id_reissue || replacement_can_reuse_own_claim)
5567 pending_reserved -= prior->target;
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;
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);
5587 persistent_other -= std::max(0.0, current_claim - competing_claim);
5588 persistent_other = std::max(0.0, persistent_other);
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)) {
5596 close_logical_units_.erase(
id);
5597 close_reserved_units_.erase(
id);
5598 close_first_units_.erase(
id);
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);
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;
5617 auto& site = close_batch_callsites_[token];
5623 site.first_target = target;
5625 site.comment = comment;
5626 site.target = target;
5627 site.retire_ledger_whole = retire_whole;
5628 site.queue_sequence = ++close_batch_queue_sequence_;
5631 if (site.id ==
id) {
5632 site.comment = comment;
5633 site.target = target;
5634 site.retire_ledger_whole = retire_whole;
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;
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;
5658 site.comment = comment;
5659 site.target = target;
5660 site.retire_ledger_whole = retire_whole;
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);
5674 close_callsite_reserved_units_[site.token].erase(
id);
5675 close_callsite_first_units_[site.token].erase(
id);
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;
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);
5696 std::size_t fifo_prefix_size = 0;
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;
5713 qty_closed += close_qty;
5715 if (prefix_size == 0 || prefix_size == lots.size())
return std::nullopt;
5722 double closed = 0.0;
5723 double left = qty_limit;
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;
5729 left = qty_limit - closed;
5731 if (exact && left == 0.0)
return std::nullopt;
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;
5740 for (
const auto& cohort : cohorts_by_id_) {
5741 for (
const auto& opened : cohort.second.opened)
5742 if (opened.incarnation == incarnation) receipt = &opened;
5744 if (!receipt)
return std::nullopt;
5745 selection.openings.push_back(*receipt);
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;
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));
5762 double closed = 0.0;
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;
5770 left = qty - closed;
5771 if (lot.second == 0.0) ++consumed;
5773 lots.erase(lots.begin(), lots.begin() +
static_cast<std::ptrdiff_t
>(consumed));
5775 lots.erase(std::remove_if(lots.begin(), lots.end(), [](
const auto& lot) {
5776 return lot.second <= internal::kQtyEpsilon;
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);
5787 close_callsite_reserved_units_[site.token].erase(site.first_id);
5788 close_callsite_first_units_[site.token].erase(site.first_id);
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);
5797 close_callsite_reserved_units_[site.token].erase(
id);
5798 close_callsite_first_units_[site.token].erase(
id);
5802 const auto physical = require_host().physical_position();
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);
5808 const bool closes_full = target >= available - internal::kQtyEpsilon;
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());
5828 std::optional<native_order::BindOpenings> prefix;
5829 fifo_prefix_size = 0;
5830 if (!closes_full) prefix = source_fifo_prefix_openings(fifo_lots, target);
5837 const bool off_grid = staged_.quantity_grid
5840 request.
intent = closes_full || prefix
5844 native_order::HostSizedKind::Close, std::nullopt}}
5846 request.
label =
"__close__" + site.id;
5847 request.
comment = site.comment;
5849 if (prefix) request.
owner = std::move(*prefix);
5852 if (closes_full) fifo_lots.clear();
5853 else drain_source_fifo_lots(fifo_lots, target);
5857 snapshot.
comment = site.comment;
5860 snapshot.
qty_percent = closes_full ? 100.0 : (target / available * 100.0);
5862 snapshot.
sizing = sizing_snapshot();
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);
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;
5883void PineExecutionAdapter::observe_close_policy(
5887 const double remaining_position = std::abs(
5888 require_host().physical_position().signed_units);
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);
5897 const auto erase_owner = [&](
auto& owners, std::uint64_t token,
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);
5906 close_logical_units_.erase(snapshot.
source_id);
5908 close_reserved_units_.erase(snapshot.
source_id);
5909 close_first_units_.erase(snapshot.
source_id);
5911 erase_owner(close_callsite_reserved_units_,
5913 erase_owner(close_callsite_first_units_,
5916 }
else if (remaining_position > 0.0) {
5923 const double reserved_other = close_reserved_other_units(
5925 const double capacity = std::max(0.0,
5927 const double reserve = std::min(actual_fill, capacity);
5928 if (reserve > 0.0) {
5932 auto& logical = close_logical_units_[snapshot.
source_id];
5933 logical = std::max(logical, std::min(event.
closed_units, capacity));
5936 if (reserve > 0.0) close_reserved_units_[snapshot.
source_id] = reserve;
5938 close_logical_units_.erase(snapshot.
source_id);
5939 close_reserved_units_.erase(snapshot.
source_id);
5944 close_first_units_.erase(snapshot.
source_id);
5946 if (reserve > 0.0) {
5950 close_logical_units_.erase(snapshot.
source_id);
5951 erase_owner(close_callsite_reserved_units_,
5958 erase_owner(close_callsite_first_units_,
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();
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)) {
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;
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_) {
5995 if (pending.snapshot.source_id.empty()) {
5997 empty_is_long = pending.snapshot.is_long;
6001 for (
const auto& pending : pending_same_bar_commands_) {
6003 if (pending.snapshot.is_long != empty_is_long) {
6004 opposite_entry =
true;
6009 if (empty_entry && opposite_entry) {
6010 SourceShadowPending shadow;
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));
6020 request.
label =
"__pine_close_all";
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;
6041 coof_close_all_fill}}
6043 coof_close_all_fill}};
6047 coof_close_all_fill}}
6049 coof_close_all_fill}};
6059 snapshot.
sizing = sizing_snapshot();
6063 (void)callsite_token;
6064 const auto accepted = submit_or_replace(
6065 std::move(request), std::move(snapshot),
false,
"__pine_close_all");
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) {
6076 pine_host->freeze_script_position_view();
6078 if ((immediately || pooc_ordinary_close_all) && accepted) {
6079 (void)require_host().execute_current(
6080 {*accepted, NativeCurrentPriceRule::NearestTick});
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
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)
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;
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);
6126 && !pend.projection_over_pyramiding
6127 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6128 opposite_entries.push_back(handle);
6131 for (
const auto& handle : opposite_entries) {
6132 const auto found = placement_.find(handle.incarnation);
6133 if (found == placement_.end())
continue;
6138 const auto result = require_host().cancel(handle);
6139 if (result.status == native_order::CancelStatus::Cancelled) {
6142 const double target = entry_snapshot.
is_long
6144 req.
intent = std::isnan(target)
6146 entry_snapshot.
is_long ? native_order::Side::Long : native_order::Side::Short}}
6150 pending_entries_.push_back({std::move(req), std::move(entry_snapshot), entry_snapshot.
source_id});
6153 for (
auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
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);
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)
6174 if (openings.empty() && !logical_pooc_fifo) {
6175 record_dropped_close(
id, comment, qty, qty_percent, immediately, callsite_token);
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; })
6182 pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
6183 [&](
const PendingSameBarCommand& pc) { return pc.snapshot.source_id == id; });
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);
6190 const std::uint64_t command_ordinal = ++command_ordinal_;
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);
6205 const bool close_first_percent_add =
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);
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)
6229 effective_qty = kNaN;
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;
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
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);
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;
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());
6293 const bool frozen_same_bar_close = same_bar_market_tx_scope() && !immediately
6295 const bool deferred_percentage = config_.close_entries_rule_any
6296 && std::isnan(effective_qty) && std::isfinite(requested_percent)
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);
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) {
6320 && candidate.is_long != (current > 0.0)
6321 && (!finite_positive(candidate.exit_levels.limit)
6322 && !finite_positive(candidate.exit_levels.stop));
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);
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()
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);
6344 if (closes_full_position && !reversal_pair) {
6345 cancel_exit_orders_for_full_close(
id);
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
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_) {
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;
6366 if (held_reentry && opposite_reversal) {
6372 if (short_seed_context_is_live() && current < 0.0) {
6375 native_order::HostSizedKind::Close, std::nullopt};
6376 placeholder.
label =
"__close__" + id;
6378 std::numeric_limits<double>::min()};
6379 placeholder.
owner = owner_for_close(
id,
true);
6385 placeholder_snapshot.
qty_percent = requested_percent;
6386 placeholder_snapshot.
is_long =
false;
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);
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)) {
6401 request.
label =
"__close__" + id;
6416 snapshot.
birth = capture_order_birth();
6417 snapshot.
sizing = sizing_snapshot();
6420 if (
const auto point = require_host().current_execution_point()) {
6424 const SourceId replacement_key = callsite_token == 0
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;
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();
6441 request.
intent = host_sized
6446 request.
label =
"__close__" + id;
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();
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);
6457 if (state.spec->path_order == NativePathOrder::HighFirst) high_first =
true;
6458 else if (state.spec->path_order == NativePathOrder::LowFirst) high_first =
false;
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);
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)) {
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;
6497 extreme_target && coof_close_fill != level};
6498 const bool falling = coof_close_fill < current_quote;
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);
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;
6530 snapshot.
requested_qty = exact_full_dynamic_close ? kNaN : frozen_qty;
6537 if (paired_reversal_parent && !paired_reversal_whole_drop)
6543 const bool all_in_percent = std::isnan(effective_qty)
6544 && requested_percent >= 100.0 - 1e-9
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();
6553 && candidate.is_long != held_long
6554 && (!point || candidate.placement_script_open_ms
6555 == point->decision.script_bar_open_ms);
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);
6569 if (opposite_reversal_pair) {
6575 hold_reversal_pair_brackets(
id);
6576 source_shadow_pending_.push_back({snapshot,
"__close__" +
id});
6579 bool all_in_dependent_close =
false;
6580 if (all_in_percent) {
6581 const auto point = require_host().current_execution_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
6588 || found->second.placement_script_open_ms
6589 != point->decision.script_bar_open_ms
6590 || found->second.source_id !=
id) {
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;
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;
6618 if (!earlier_opposite) reentry.reset();
6623 all_in_dependent_close =
true;
6624 hold_reversal_pair_brackets(
id);
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;
6661 const SourceId replacement_key = default_fifo_close
6662 ? (callsite_token == 0
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) {
6670 pending_coof_requests_.push_back({
6671 std::move(request), std::move(snapshot), replacement_key,
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});
6680 const bool pooc_immediate_fifo = config_.process_orders_on_close
6681 && !coof_recalc_active_
6682 && !config_.close_entries_rule_any && closes_full_position
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);
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)) {
6703 if (
const auto point = require_host().current_execution_point()) {
6704 close_all_pending_script_bar_ = point->decision.script_bar_open_ms;
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
6715 && placement->second.placement_script_open_ms == script_open) {
6716 newborns.push_back(handle);
6719 for (
const auto& handle : newborns) {
6720 const auto result = require_host().cancel(handle);
6721 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
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
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)
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;
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);
6776 && !pend.projection_over_pyramiding
6777 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
6778 opposite_entries.push_back(handle);
6781 for (
const auto& handle : opposite_entries) {
6782 const auto found = placement_.find(handle.incarnation);
6783 if (found == placement_.end())
continue;
6788 const auto result = require_host().cancel(handle);
6789 if (result.status == native_order::CancelStatus::Cancelled) {
6792 const double target = entry_snapshot.
is_long
6794 req.
intent = std::isnan(target)
6796 entry_snapshot.
is_long ? native_order::Side::Long : native_order::Side::Short}}
6800 pending_entries_.push_back({std::move(req), std::move(entry_snapshot), entry_snapshot.
source_id});
6803 for (
auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
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);
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)) {
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;
6839 for (
const auto& handle : live_handles_) {
6840 const auto parent = placement_.find(handle.incarnation);
6841 if (parent != placement_.end() && parent->second.opening
6843 && parent->second.source_id == from_entry) {
6844 recreated_parent =
true;
6851 if (!recreated_parent) named_entry_cancel_tokens_.erase(token);
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);
6869 if (!pending_same_bar_commands_.empty()
6871 && require_host().physical_position().signed_units < 0.0) {
6872 flush_pending_same_bar_commands();
6874 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
6875 throw std::overflow_error(
"Pine source command sequence exhausted");
6877 const std::uint64_t command_sequence = ++source_command_sequence_;
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);
6891 const auto physical = require_host().physical_position();
6892 const SourceId partial_exit_key = exit_id +
"\x1f" + from_entry;
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_) {
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;
6920 const bool parent_filled_this_cycle = physical.signed_units != 0.0
6921 && !from_entry.empty()
6923 const auto filled = cohorts_by_id_.find(from_entry);
6924 return filled != cohorts_by_id_.end() && !filled->second.opened.empty();
6927 if (parent_filled_this_cycle)
return;
6931 if (!from_entry.empty() && parent.source_id != from_entry) {
6940 if (cohort_exposure_for(from_entry) > 0.0
6941 && ((physical.signed_units > 0.0) == parent.is_long)) {
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;
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);
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()) {
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;
6992 known_parent_level =
false;
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,
7009 limit_price = source_level_on_price_grid(limit_price, tick);
7010 stop_price = source_level_on_price_grid(stop_price, tick);
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;
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;
7043 if (existing == pending_relative_exits_.end()) {
7044 pending_relative_exits_.push_back(std::move(pending));
7046 const auto same = [](
double left,
double right) {
7047 return same_double_bits(left, right) || (std::isnan(left) && std::isnan(right));
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;
7059 if (!unchanged) withdraw_anchored_relative_legs(&exit_id, &from_entry);
7060 *existing = std::move(pending);
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;
7068 source_shadow_pending_.end());
7074 if (finite_non_negative(limit_price) || finite_non_negative(stop_price)) {
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});
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);
7104 parent->second.has_full_entry_bracket =
true;
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;
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();
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) {
7144 return same_double_bits(prior_percent, request_percent);
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)) {
7170 && same_double_bits(prior.exit_levels.limit, level))
7172 && same_double_bits(prior.exit_levels.stop, level));
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)
7183 && (!finite_positive(stop_price)
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) {
7200 || parent.source_id != from_entry) {
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);
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;
7225 double reserved_exit_qty = kNaN;
7226 double pending_parent_units = 0.0;
7227 const auto reservation_point = require_host().current_execution_point();
7229 if (!reservation_point || !parent.opening
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) {
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;
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);
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,
7252 if (!reservation_ok) {
7260 const auto source_point = require_host().current_execution_point();
7261 const OrderBirth exit_birth = capture_order_birth();
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
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()
7277 && (!std::isfinite(qty_percent) || qty_percent >= 100.0 - 1e-9)
7278 && oca_name.empty() && !has_trail_request
7279 && finite_positive(staged_.syminfo.mintick);
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)
7285 else if (finite_positive(limit_price) &&
close <= limit_price)
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);
7305 const bool closing_long = physical.signed_units > 0.0;
7307 && finite_positive(limit_price) && finite_positive(endpoint)) {
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
7326 if (in_flight_remainder
7327 || (later_same_open && endpoint_satisfies && endpoint_ahead)) {
7332 const bool off_grid_endpoint = finite_positive(staged_.syminfo.mintick)
7333 && source_bar_fill_tick(endpoint, staged_.syminfo.mintick) != endpoint;
7335 coof_limit_waypoint_qualified =
true;
7336 coof_limit_waypoint_price = endpoint;
7337 }
else if (later_same_open) {
7340 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
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)) {
7350 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
7352 endpoint, threshold ? threshold->price : limit_price};
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)) {
7362 coof_stop_waypoint_price = endpoint;
7364 defer_marketable_coof_stop = marketable;
7369 if (pooc_short_tick_scope) {
7370 const double tick = staged_.syminfo.mintick;
7381 limit_price, tick,
true,
true)};
7383 && finite_positive(stop_price)) {
7385 stop_price, tick,
true,
false)};
7390 bool defer_new_instance,
7393 request.
intent = host_sized
7395 native_order::HostSizedKind::Close, std::nullopt}}
7399 request.
owner = std::move(owner);
7400 request.
group = std::move(group);
7402 snapshot.
family = family;
7410 binds_pending_reversal_entry;
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;
7420 snapshot.
sizing = exit_sizing;
7427 source_point->decision.coordinate.interval_index;
7430 : (physical.signed_units < 0.0
7434 source_point->decision.script_bar_open_ms;
7437 initialize_l4c_policy(snapshot, {});
7438 if (finite_positive(coof_limit_waypoint_price))
7440 else if (finite_positive(coof_stop_waypoint_price))
7442 if (coof_recalc_active_ && !coof_first_open_ && historical_cascade
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) {
7454 waypoint, staged_.syminfo.mintick)
7455 + (long_position ? -1.0 : 1.0) * config_.slippage
7456 * staged_.syminfo.mintick;
7459 const double source_position = std::abs(require_host().physical_position().signed_units);
7460 if (std::isfinite(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;
7469 pending_entries_.begin(), pending_entries_.end(),
7470 [&](
const PendingEntry& candidate) {
7471 return candidate.snapshot.opening
7472 && candidate.snapshot.source_id != from_entry;
7474 const double percent = std::isfinite(requested_qty_percent)
7475 ? requested_qty_percent : 100.0;
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
7486 && source_position > 0.0) {
7487 const double percent = std::isfinite(snapshot.
qty_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;
7499 if (!
exit || prior.from_entry != from_entry
7500 || prior.source_id == exit_id
7501 || !std::isfinite(prior.projection_remaining_qty)) {
7504 auto& held = reserved_by_exit[prior.source_id];
7505 held = std::max(held, prior.projection_remaining_qty);
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);
7513 || !reserved_by_exit.empty();
7516 if (config_.process_orders_on_close && from_entry.empty() && source_position > 0.0) {
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()
7530 if (!global_exit || prior.source_id == exit_id
7531 || !std::isfinite(prior.projection_remaining_qty)) {
7534 reserved += std::max(0.0, prior.projection_remaining_qty);
7536 const double available = std::max(0.0, source_position - reserved);
7537 requested = std::min(requested, available);
7538 if (!(requested > 0.0))
return;
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()) {
7546 point->decision.coordinate.interval_index;
7548 point->decision.script_bar_open_ms;
7554 delayed_market_orders_.push_back({
7555 std::move(request), std::move(snapshot), replacement_key,
7556 broker_open_epoch_ + 1U,
true});
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;
7564 && finite_positive(stop_price)
7565 && (long_position ? stop_price > birth : stop_price < birth);
7567 && finite_positive(limit_price)
7568 && (long_position ? limit_price < birth : limit_price > birth);
7571 const auto qualified_recross = [&] {
7572 if (coof_limit_waypoint_qualified)
return true;
7573 if (!coof_remaining_recrosses(limit_price, long_position))
return false;
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;
7582 const bool plain_market_parent = parent
7588 if (!plain_market_parent)
return false;
7589 const double next_waypoint = coof_next_waypoint();
7590 const bool reachable_stop = finite_positive(stop_price)
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;
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;
7612 return !direct_partial;
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});
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())
7630 if (stage_chart_tick_scope) {
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;
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));
7644 *queued = std::move(staged);
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;
7660 if (
const auto point = require_host().current_execution_point()) {
7662 point->decision.coordinate.interval_index;
7666 *queued = PendingBracketLeg{std::move(request), std::move(snapshot),
7667 replacement_key, family_key};
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});
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;
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));
7687 *queued = std::move(staged);
7690 bool defer_for_live_parent =
false;
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) {
7708 bool has_pending_parent =
false;
7709 for (
const auto&
entry : pending_entries_) {
7710 if (matches_parent(
entry.snapshot)) {
7711 has_pending_parent =
true;
7715 for (
const auto&
entry : pending_same_bar_commands_) {
7716 if (matches_parent(
entry.snapshot)) {
7717 has_pending_parent =
true;
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;
7728 if (has_pending_parent
7729 && (existing_leg != live_by_source_key_.end()
7731 defer_for_live_parent =
true;
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()) {
7743 (void)require_host().cancel(predecessor);
7744 retire(predecessor);
7746 if (
const auto point = require_host().current_execution_point()) {
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;
7755 PendingBracketLeg staged{std::move(request), std::move(snapshot), replacement_key,
7757 if (queued == pending_bracket_legs_.end())
7758 pending_bracket_legs_.push_back(std::move(staged));
7760 *queued = std::move(staged);
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);
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);
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,
7789 auto& family = bracket_families_[family_key];
7794 if (accepted->incarnation > placement_high_water
7795 || std::find(family.begin(), family.end(), *accepted) == family.end()) {
7796 family.push_back(*accepted);
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;
7818 if (defer_until_parent) {
7819 const auto cohort = cohorts_by_id_.find(from_entry);
7820 if (cohort != cohorts_by_id_.end())
7822 defer_until_parent =
false;
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,
7835 if (from_entry.empty()) {
7836 const auto group_name = oca_name.empty() ? exit_id +
"\x1f" + from_entry : oca_name;
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);
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()) {
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]);
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();
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;
7906 if (origin.incarnation != 0 && !has_live_leg && !origin_is_pending(origin)
7907 && (!origin_opened || consumed_origin_leg())) {
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);
7919 const bool exit_is_buy = !parent_long;
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) {
7942 placed_absolute_leg =
true;
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);
7957 placed_absolute_leg =
true;
7959 bool trail_one_shot =
false;
7960 if (has_trail_request && finite_positive(trail_price)) {
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)) {
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;
7985 trail_one_shot = no_trailing_distance && !already_reached;
7986 if (zero_distance && point) {
7991 if (already_reached) {
7992 native_trail_price = nearest_tick(point->price, tick);
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);
8008 const bool slipped_touch = zero_distance && config_.slippage > 0
8009 && finite_positive(tick);
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;
8015 if (trail_already_reached)
8016 native_arm_price.reset();
8018 0.0, native_arm_price,
8019 native_order::TrailTicks{*native_trail_offset_ticks}});
8020 }
else if (trail_already_reached) {
8030 const double slipped = trail_price + (exit_is_buy ? 1.0 : -1.0)
8031 * config_.slippage * tick;
8033 directional_tick(slipped, tick, exit_is_buy)});
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) {
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;
8065 if (!competing_entry) {
8066 const double close = nearest_tick(
8067 source_point->price, staged_.syminfo.mintick);
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) {
8079 cancel_bracket_siblings(live->second);
8080 native_order::Request request;
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);
8092 (void)require_host().execute_current(
8093 {*accepted, NativeCurrentPriceRule::NearestTick});
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;
8112 native_order::RequestHandle handle;
8113 PlacementSnapshot snapshot;
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
8124 const auto& row = found->second;
8126 && finite_positive(row.exit_levels.limit)
8127 && (closing_long ? quote >= row.exit_levels.limit
8128 : quote <= row.exit_levels.limit);
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});
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;
8141 const auto selected = candidates.front();
8142 cancel_bracket_siblings(selected.handle);
8143 native_order::Request request;
8145 request.label = exit_id;
8146 request.comment = comment;
8147 request.trigger = native_order::Market{};
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)));
8161 (void)require_host().execute_current(
8162 {*accepted, NativeCurrentPriceRule::NearestTick});
8166 if (!placed_absolute_leg
8167 && !(has_trail_request && finite_positive(trail_price))) {
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;
8178 source_shadow_pending_.end());
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
8196 : (physical.signed_units < 0.0
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});
8210 bool pre_script_drain) {
8211 auto queued = std::move(pending_bracket_legs_);
8212 pending_bracket_legs_.clear();
8220 const auto point = require_host().current_execution_point();
8225 const bool parent_walked_after_leg
8227 && position_open_bar_index_
8228 == point->decision.coordinate.interval_index
8229 && position_open_phase_ != NativePathPhase::Open;
8230 const auto resolve_parent_handle
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();
8241 if (placement_.find(origin.incarnation) == placement_.end())
return std::nullopt;
8246 const auto handle = resolve_parent_handle(row);
8247 if (!handle)
return nullptr;
8248 return &placement_.find(handle->incarnation)->second;
8258 const auto parent_precedes_leg
8280 const bool retained_parent_first
8283 std::uint64_t surviving) {
8284 const double percent = std::isfinite(row.
qty_percent)
8294 == point->decision.coordinate.interval_index - 1
8299 && percent >= 100.0 - internal::kFullPercentEps
8310 std::uint64_t parent_incarnation,
8311 std::uint64_t child_predecessor) {
8312 const auto cancelled
8314 const auto surviving
8321 && cancelled != 0 && cancelled < parent_incarnation
8322 && cancelled != child_predecessor
8323 && surviving > cancelled && surviving < parent_incarnation
8325 == point->decision.coordinate.interval_index - 1
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;
8349 if (std::find(book_keys.begin(), book_keys.end(), key)
8351 book_keys.push_back(key);
8353 for (
const auto& live : live_handles_) {
8354 const auto row = placement_.find(live.incarnation);
8355 if (row != placement_.end()) carry_key(row->second);
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,
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()
8377 if (!exact_fresh_parent(parent->second, handle->incarnation,
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,
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);
8399 const auto parent_walks_first
8401 if (!pre_script_drain)
return false;
8402 const auto* parent = resolve_parent(leg);
8403 if (parent ==
nullptr)
return false;
8405 return std::find(ranked_retained_brackets.begin(),
8406 ranked_retained_brackets.end(),
8408 != ranked_retained_brackets.end();
8410 return !parent_walked_after_leg
8411 || parent_precedes_leg(leg, *parent);
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));
8421 resting.push_back(std::move(leg));
8424 queued = std::move(resting);
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));
8433 for (
const auto& leg : queued)
8434 source_pending_orders.insert(key_for(
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();
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;
8463 for (
auto& leg : queued) {
8464 const bool competing_chart_tick = source_pending_population != 1U
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;
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;
8492 && !(cohort_exposure_for(leg.snapshot.
from_entry) > 0.0)) {
8493 pending_bracket_legs_.push_back(std::move(leg));
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)
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));
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()
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));
8536 bool retained_parent_pending =
false;
8540 const auto cohort = cohorts_by_id_.find(leg.snapshot.
from_entry);
8541 if (cohort != cohorts_by_id_.end()) {
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]);
8563 || retained_parent_pending) {
8564 pending_bracket_legs_.push_back(std::move(leg));
8568 && !post_calculation && !parent_walks_first(leg.snapshot)) {
8569 pending_bracket_legs_.push_back(std::move(leg));
8572 bool execute_after_calculation =
false;
8581 const bool priced_exit_leg
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
8593 const double level = is_stop_leg
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;
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);
8640 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
false,
8641 leg.replacement_key);
8643 bracket_families_[leg.family_key].push_back(*accepted);
8644 if (execute_after_calculation) {
8645 (void)require_host().execute_current(
8646 {*accepted, NativeCurrentPriceRule::NearestTick});
8652void PineExecutionAdapter::materialize_pending_bracket_legs(
8656 ? nullptr : &parent_it->second;
8657 const bool retained_parent = parent && parent->
opening
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());
8667 std::uint64_t retained_family = 0;
8668 bool materialize_retained =
false;
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 = ⋚
8678 retained_family = leg.family_key;
8679 }
else if (leg.family_key != retained_family) {
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
8695 const bool exact_child = representative
8696 && representative->snapshot.projection_predecessor
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();
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;
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()
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)));
8726 it->snapshot.bracket_origin =
event.handle();
8727 ready.push_back(std::move(*it));
8728 it = pending_bracket_legs_.erase(it);
8733 for (
auto& leg : ready) {
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);
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);
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))
8760 const bool coof_delayed_price =
order.snapshot.projection_created_during_coof
8763 if (
order.release_open_epoch <= broker_open_epoch_
8764 && (!explicit_brackets_only || explicit_bracket || coof_delayed_price)) {
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) {
8778 const bool execute_coof_open =
order.execute_at_open
8779 && finite_positive(current_open);
8780 if (execute_coof_open) {
8782 order.snapshot.forced_execution_price = current_open;
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),
8789 order.replacement_key);
8793 bracket_families_[family_key].push_back(*accepted);
8795 if (accepted && execute_coof_open) {
8796 (void)require_host().execute_current(
8797 {*accepted, NativeCurrentPriceRule::NearestTick});
8800 delayed_market_orders_.push_back(std::move(
order));
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)) {
8819 pending_entries_.push_back(std::move(
entry));
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;
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);
8834 if (!queued.empty() && !pending_bracket_legs_.empty()) {
8835 auto brackets = std::move(pending_bracket_legs_);
8836 pending_bracket_legs_.clear();
8839 std::size_t index = 0;
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});
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;
8852 rank = queued_position < 0.0 ? 1 : 2;
8856 ordered.push_back({rank, index,
false});
8858 std::stable_sort(ordered.begin(), ordered.end(), [](
const Candidate& left,
8859 const Candidate& right) {
8860 return left.rank < right.rank;
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);
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);
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;
8884 for (
auto& entry : queued) {
8885 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot),
true,
8886 entry.replacement_key);
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;
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
8901 || config_.default_qty_value > 100.0) {
8904 const auto placed = placement_.find(accepted->incarnation);
8905 if (placed == placement_.end()
8906 || ((batch_start > 0.0) == placed->second.is_long)) {
8909 const auto point = require_host().current_execution_point();
8911 if (!point || !pine_host)
return;
8912 const auto next = pine_host->scheduler_.next_source_bar(
8913 point->decision.coordinate.interval_index);
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,
true);
8923 const bool variable_short_context = batch_start < 0.0
8925 const bool full_short_seed = queued.size() == 3U
8927 && queued[0].snapshot.is_long
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
8936 && queued[0].snapshot.is_long
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;
8949 const bool potential_short_seed = full_short_seed || partial_short_seed;
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
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);
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
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) {
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;
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;
9012 (void)submit_or_replace(std::move(request), std::move(snapshot),
9013 command.opening, command.replacement_key);
9017 if (variable_short_context && !potential_short_seed) {
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;
9033 snapshot.frozen_market_instruction =
false;
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);
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;
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;
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;
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;
9077 && std::count_if(queued.begin(), queued.end(), [](
const auto& command) {
9078 return command.snapshot.frozen_market_targeted_close;
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
9093 const bool final_short_candidate = batch_start < 0.0 && opening
9095 const bool materialize_candidate = batch_start < 0.0
9096 && snapshot.frozen_market_targeted_close
9097 && !snapshot.frozen_market_target_was_long;
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;
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
9113 if (!std::isfinite(required) || !std::isfinite(snapshot.sizing.equity)
9114 || required > snapshot.sizing.equity) {
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;
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};
9137 snapshot.is_long ? units : -units};
9139 simulated += snapshot.is_long ? units : -units;
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
9152 native_order::ExplicitUnits{units}}};
9153 simulated += simulated > 0.0 ? -units : units;
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) {
9169 if (!artifact)
continue;
9170 const double signed_units = simulated > 0.0 ? units : -units;
9172 simulated += signed_units;
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;
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};
9210 short_seed_ = pending_short_seed_.plan;
9211 maybe_activate_short_seed_plan();
9216void PineExecutionAdapter::materialize_relative_exits(
9218 if (pending_relative_exits_.empty() || !finite_positive(staged_.syminfo.mintick)) {
9221 withdraw_anchored_relative_legs(
nullptr, &opening.source_id);
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);
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;
9242 double offset = value.trail_offset;
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);
9251 if (finite_positive(value.loss_ticks)) {
9252 stop = directional_tick(event.resolved_price - side * value.loss_ticks * tick,
9253 tick, !opening.is_long);
9259 materializing_relative_ =
true;
9260 materializing_parent_ =
event.handle();
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,
9266 materializing_relative_ =
false;
9267 materializing_parent_ = {};
9270 materializing_relative_ =
false;
9271 materializing_parent_ = {};
9278 withdraw_anchored_relative_legs(
nullptr, &opening.source_id);
9281void PineExecutionAdapter::withdraw_anchored_relative_legs(
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)) {
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;
9300bool PineExecutionAdapter::anchorable_relative_exit(
9301 const PendingRelativeExit& value, native_order::RequestHandle& parent,
9302 bool& parent_long)
const {
9309 if (value.from_entry.empty() || config_.close_entries_rule_any || !std::isnan(value.qty))
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;
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) {
9334 only_long = found->second.is_long;
9337 if (parents != 1)
return false;
9339 parent_long = only_long;
9343std::vector<PineExecutionAdapter::RelativeLegShape>
9344PineExecutionAdapter::relative_leg_shapes(
const PendingRelativeExit& value,
9345 bool parent_long)
const {
9350 std::vector<RelativeLegShape> shapes;
9351 const double side = parent_long ? 1.0 : -1.0;
9352 if (finite_positive(value.profit_ticks)) {
9354 side * value.profit_ticks, value.profit_ticks});
9356 if (finite_positive(value.loss_ticks)) {
9358 -side * value.loss_ticks, value.loss_ticks});
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) {
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))) {
9373 native_order::Limit{0.0, has_offset && config_.slippage > 0},
9374 side * trail_ticks, trail_ticks});
9381 if (anchored_relative_legs_.empty() && pending_relative_exits_.empty())
return;
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);
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;
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);
9407 if (pending_relative_exits_.empty())
return;
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);
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);
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; });
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;
9441 return legs == shapes.size();
9443 if (complete)
continue;
9444 withdraw_anchored_relative_legs(
nullptr, &from_entry);
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;
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);
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)) {
9479 if (anchored_cohort_sequence_
9480 == std::numeric_limits<std::int64_t>::max()) {
9481 throw std::overflow_error(
"Pine anchored OCA member sequence exhausted");
9483 member->cohort = -(++anchored_cohort_sequence_);
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)
9490 leg.handle = *result.handle;
9491 anchored_relative_legs_.push_back(std::move(leg));
9492 ++anchored_relative_stats_.anchored;
9498bool PineExecutionAdapter::armed_relative_legs_adoptable(
9500 const std::vector<PendingRelativeExit>& pending)
const {
9503 if (!pending_entries_.empty() || !pending_same_bar_commands_.empty()
9504 || !delayed_market_orders_.empty() || !pending_coof_requests_.empty()
9505 || coof_recalc_active_) {
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();
9520 if (children != 1)
return false;
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)) {
9533 return armed == expected && armed != 0;
9536double PineExecutionAdapter::exit_limit_trigger(
double limit_price,
double tick,
9537 bool exit_is_buy)
const noexcept {
9544 if (!finite_positive(tick)
9545 || (config_.calc_on_order_fills && nearest_tick(limit_price, tick) == limit_price)) {
9548 return source_trigger_threshold(limit_price, tick, exit_is_buy,
true);
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);
9577 if (!exit_is_buy) one_shot_level = source_level_on_price_grid(one_shot_level, tick);
9590 if (quantized_activation && finite_positive(tick)) {
9591 one_shot_level = source_trigger_threshold(one_shot_level, tick, exit_is_buy,
true);
9593 return one_shot_level;
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;
9603 const double tick = staged_.syminfo.mintick;
9605 leg.installed_level = kNaN;
9608 if (!finite_positive(tick))
return std::nullopt;
9614 const bool exit_is_buy = !leg.parent_long;
9615 double installed = kNaN;
9617 installed = exit_limit_trigger(
9618 source_level_on_price_grid(view.
kernel_level, tick), tick, exit_is_buy);
9620 installed = source_trigger_threshold(
9621 source_level_on_price_grid(view.
kernel_level, tick), tick, exit_is_buy,
false);
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)
9626 : one_shot_trail_trigger(trail_price, leg.trail_offset, tick, exit_is_buy,
true);
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;
9640 leg.installed_level = installed;
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());
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;
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);
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;
9678 token.entry_incarnation = handle.incarnation;
9681 && snapshot->second.from_entry ==
id
9682 && token.surviving_exit_incarnation == 0) {
9683 token.surviving_exit_incarnation = handle.incarnation;
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_) {
9698 pending_same_bar_close_qty_ += command.snapshot.
requested_qty;
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);
9715 for (
const auto& handle : matches) {
9716 const auto result = require_host().cancel(handle);
9717 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
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);
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);
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)) {
9748 if (!pending_same_bar_commands_.empty()) {
9749 source_batch_mutated_ =
true;
9750 flush_pending_same_bar_commands();
9753 if (!pending_entries_.empty()) {
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()
9763 && (!point || found->second.placement_script_open_ms
9764 == point->decision.script_bar_open_ms)) {
9765 replaced_close_all.push_back(handle);
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;
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;
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);
9802 request.
intent = (default_sized || oca_type == 1)
9804 is_long ? native_order::Side::Long : native_order::Side::Short}}
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);
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);
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;
9850 if (coof_recalc_active_ && !coof_first_open_
9851 && risk_.max_intraday_loss > 0.0) {
9852 double target = kNaN;
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;
9860 case NativePathPhase::High: target = coof_script_bar_.high;
break;
9861 case NativePathPhase::Low: target = coof_script_bar_.low;
break;
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;
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;
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});
9887 if (same_bar_defaults.size() >= 2) {
9888 delay_after_default_pair =
true;
9891 request.
group = group_for(oca_name, oca_type);
9892 if (oca_type == 1) {
9899 if (default_sized && oca_type == 2) {
9900 if (
auto* member = std::get_if<native_order::Member>(&request.
group)) {
9904 member->effect = native_order::GroupEffect::Cancel;
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");
9924 snapshot.
sizing = sizing_snapshot();
9925 if (default_sized && finite_positive(snapshot.
sizing.
price)
9927 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH))) {
9929 snapshot.
sizing.
at_fill = config_.calc_on_order_fills && coof_recalc_active_;
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()) {
9940 delayed_market_orders_.push_back({std::move(request), std::move(snapshot), id,
9941 broker_open_epoch_ + 1U});
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) {
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);
9967 native_order::OpeningShape::Transact};
9969 if (snapshot == placement_.end()) {
9976 && facts.
definition->origin == native_order::RequestOrigin::KernelLiquidation) {
9978 if (facts.
is_buy) fire = nearest_tick(fire, staged_.syminfo.mintick);
9979 if (finite_positive(fire))
9984 const auto&
source = snapshot->second;
9990 && !price_present(
source.exit_levels.stop)
9991 && finite_positive(
source.trail_activation_level);
9992 const bool explicit_zero_trail =
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()
10000 && facts.
price_kind == native_order::NativeCandidatePriceKind::PointPrice;
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> {
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)
10012 return std::nullopt;
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);
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);
10026 const auto observed_tick_trail_price = [&]() -> std::optional<double> {
10029 return std::nullopt;
10033 const bool placement_reached_trail_activation =
10034 std::isfinite(
source.sizing.price) && std::isfinite(
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)
10041 && trail_active->best_at_trigger <=
source.trail_activation_level
10042 + staged_.syminfo.mintick * 1e-6)
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;
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;
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;
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;
10084 const auto level = [&](
double value) {
10085 return directional_tick(value, tick, facts.
is_buy);
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);
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;
10101 return directional_tick(open, tick, facts.
is_buy);
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;
10119 const bool zero_offset_open_arms = carried_armed
10120 ? (long_side ? open > carried_best : open < carried_best)
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);
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) {
10137 armed_from_open =
true;
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) {
10145 if (print(open) == level(open))
return print(open);
10151 path[1] = policy_script_bar_.high;
10152 path[2] = policy_script_bar_.low;
10154 path[1] = policy_script_bar_.low;
10155 path[2] = policy_script_bar_.high;
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];
10162 const bool reached = long_side
10163 ? (to >= activation && to > from)
10164 : (to <= activation && to < from);
10165 if (reached)
return level(activation);
10168 const bool favorable = long_side ? to > best : to < best;
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;
10179 if (trail_active && std::isfinite(trail_active->best_at_trigger))
10180 return level(trail_active->best_at_trigger);
10181 return std::nullopt;
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) {
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;
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);
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);
10210 const bool source_gap_point = !non_open
10212 const auto source_stop_fill = [&]() {
10214 ?
source.exit_levels.trail_price :
source.exit_levels.stop;
10215 const double level = finite_positive(source_level) ? source_level
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);
10221 const auto source_bar_fill = [&]() {
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);
10231 const auto source_stop_resolved = [&]() {
10237 return source_bar_fill();
10239 ?
source.exit_levels.trail_price :
source.exit_levels.stop;
10240 const double level = finite_positive(source_level) ? source_level
10249 if (policy_script_bar_valid_
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;
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()
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();
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);
10286 const auto source_limit_fill = [&]() {
10293 const double ticked = directional_tick(*facts.
trigger_level, staged_.syminfo.mintick,
10299 const bool deferred_open_gap =
source.defer_until_post_parent_calculation
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()
10313 && facts.
raw_price == policy_script_bar_.open;
10314 if (!non_open || deferred_open_gap || limit_armed_after_open_fill) {
10316 &&
source.oca_type == 2;
10317 if (raw_oca_reduce) {
10318 return nearest_tick(facts.
raw_price, staged_.syminfo.mintick);
10323 return owner_tick_fill(facts.
raw_price);
10325 return source_bar_fill_tick(facts.
raw_price, staged_.syminfo.mintick);
10328 if (std::holds_alternative<native_order::StopLimit>(trigger)) {
10329 return directional_tick(facts.
raw_price, staged_.syminfo.mintick,
10332 const double level = finite_positive(
source.exit_levels.limit)
10333 ?
source.exit_levels.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;
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;
10362 const auto source_trail_one_shot_fill = [&]() {
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)) {
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
10383 return directional_tick(
10386 const double ticked = directional_tick(level, tick, !facts.
is_buy);
10387 return facts.
is_buy ? std::min(ticked, level) : std::max(ticked, level);
10389 if (!std::isfinite(
source.exit_levels.trail_offset)
10391 return directional_tick(
10395 return source_limit_fill();
10405 const bool core_sized = std::holds_alternative<native_order::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();
10416 && std::holds_alternative<native_order::Stop>(trigger)
10421 result.resolved_price = source_stop_resolved();
10423 if (finite_positive(
source.forced_execution_price)) {
10428 ?
source.forced_execution_price
10429 : source_forced_fill(
source.forced_execution_price);
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)
10437 result.resolved_price = facts.
is_buy
10438 ? std::min(result.resolved_price, constraint)
10439 : std::max(result.resolved_price, constraint);
10444 result.resolved_price = directional_tick(
10445 result.resolved_price, staged_.syminfo.mintick, facts.
is_buy);
10450 && ((risk_.direction > 0 && !
source.is_long)
10451 || (risk_.direction < 0 &&
source.is_long));
10452 if (live_direction_gate) {
10457 result.shape = native_order::OpeningShape::CloseOpposite;
10462 result.units = 0.0;
10466 const bool market_like = std::holds_alternative<native_order::Market>(trigger);
10472 result.resolved_price = source_bar_fill();
10473 }
else if (limit_fill) {
10474 result.resolved_price = source_limit_fill();
10476 if (finite_positive(
source.forced_execution_price)) {
10477 result.resolved_price = source_forced_fill(
source.forced_execution_price);
10487 && facts.
price_kind == native_order::NativeCandidatePriceKind::TriggerLevel) {
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);
10506 const double best = trail_active
10508 result.resolved_price = directional_tick(
10509 best, staged_.syminfo.mintick, facts.
is_buy);
10513 result.resolved_price = source_bar_fill();
10516 result.resolved_price = source_limit_fill();
10518 result.resolved_price = directional_tick(
10521 && std::isfinite(
source.exit_levels.stop)) {
10522 result.resolved_price = directional_tick(
10523 source.exit_levels.stop, staged_.syminfo.mintick, facts.
is_buy);
10525 result.resolved_price = directional_tick(
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) {
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
10546 staged_.syminfo.mintick, facts.
is_buy);
10548 && facts.
price_kind == native_order::NativeCandidatePriceKind::PointPrice
10550 || (
source.defer_until_post_parent_calculation
10552 == NativePriceProvenance::Confirmed))) {
10556 result.resolved_price = nearest_tick(
10566 if (finite_positive(
source.exit_levels.limit)
10567 && !finite_positive(
source.forced_execution_price)) {
10568 result.resolved_price = source_limit_fill();
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)
10574 result.resolved_price = facts.
is_buy
10575 ? std::min(result.resolved_price, constraint)
10576 : std::max(result.resolved_price, constraint);
10580 && std::isfinite(
source.exit_levels.trail_offset)
10581 && std::floor(
source.exit_levels.trail_offset) == 0.0
10582 && policy_script_bar_valid_
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);
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) {
10603 result.resolved_price = directional_tick(
10604 activation, staged_.syminfo.mintick, facts.
is_buy);
10607 if ((std::holds_alternative<native_order::Stop>(trigger)
10608 || std::holds_alternative<native_order::Trail>(trigger)
10610 && facts.
trigger_level && !explicit_zero_trail && !trail_limit_one_shot
10611 && !(
source.defer_until_post_parent_calculation
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());
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)) {
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,
10635 const bool explicit_source_exit = finite_positive(
source.requested_qty)
10639 if (explicit_source_exit
10641 &&
source.close_batch_calls != 0)) {
10642 result.grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
10648 result.units = 0.0;
10659 const auto cover_full_scope = [&](
double units) {
10669 if (
const auto* host_close = std::get_if<native_order::HostSized>(
10672 && host_close->kind == native_order::HostSizedKind::Close) {
10673 result.grid_policy =
10674 native_order::ExecutionGridPolicy::ExplicitUnits;
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) {
10688 if (
const auto* host_close = std::get_if<native_order::HostSized>(
10690 host_close && host_close->kind == native_order::HostSizedKind::Close) {
10691 result.grid_policy =
10692 native_order::ExecutionGridPolicy::ExplicitUnits;
10694 result.units = cover_full_scope(
10695 std::max(0.0,
source.projection_remaining_qty));
10699 &&
source.qty_percent >= 100.0 - 1e-9
10701 && (!
source.from_entry.empty()
10702 ||
source.pooc_global_full_exit_dynamic_qty)) {
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)
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)) {
10735 if (finite_positive(
source.requested_qty)) {
10736 result.units =
source.requested_qty;
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
10748 : cover_full_scope(
10753 result.units = std::max(0.0,
source.requested_qty);
10758 result.shape = native_order::OpeningShape::CloseOpposite;
10765 if (
source.affordability_close_only) {
10767 result.shape = opposite_now ? native_order::OpeningShape::CloseOpposite
10768 : native_order::OpeningShape::Transact;
10771 double own_units =
source.requested_qty;
10773 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10775 own_units = finite_positive(denominator)
10776 ? floor_quantity_grid(
source.requested_qty / denominator,
10777 staged_.quantity_grid) : 0.0;
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,
10788 double cash = projection.realized_balance *
source.requested_qty / 100.0;
10790 && config_.commission_value > 0.0) {
10791 cash /= 1.0 + config_.commission_value / 100.0;
10793 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10795 own_units = std::isfinite(cash) && finite_positive(denominator)
10796 ? floor_quantity_grid(cash / denominator, staged_.quantity_grid) : 0.0;
10798 result.units = own_units;
10800 source.projection_position_side);
10802 && finite_positive(
source.projection_tv_carry_qty)
10805 result.units = std::abs(own_units) +
source.projection_tv_carry_qty;
10806 result.shape = native_order::OpeningShape::Transact;
10809 if (finite_positive(
source.frozen_reversal_transaction)
10810 &&
source.placement_cycle == current_position_cycle_
10812 ? -
source.frozen_reversal_transaction :
source.frozen_reversal_transaction))
10814 result.units =
source.frozen_reversal_transaction;
10815 result.shape = native_order::OpeningShape::CloseOpposite;
10821 result.shape = opposite_now ? native_order::OpeningShape::ReverseTo
10822 : native_order::OpeningShape::Transact;
10828 result.units = finite_positive(result.resolved_price)
10829 ?
source.requested_qty / (result.resolved_price * staged_.syminfo.pointvalue
10832 const double equity = percent_commission_live_equity(result.resolved_price);
10833 const double denominator = result.resolved_price * staged_.syminfo.pointvalue
10835 double cash = equity *
source.requested_qty / 100.0;
10837 && config_.commission_value > 0.0) {
10838 cash /= 1.0 + config_.commission_value / 100.0;
10840 result.units = finite_positive(equity) && finite_positive(denominator)
10841 ? floor_quantity_grid(cash / denominator, staged_.quantity_grid) : 0.0;
10843 result.units =
source.requested_qty;
10845 }
else if (core_sized) {
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
10856 && finite_positive(
source.exit_levels.stop)
10857 && !finite_positive(
source.exit_levels.limit))
10858 || (config_.default_qty_type
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;
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;
10875 result.units = default_sizing_units(sizing);
10877 const auto created_side =
static_cast<PositionSide>(
source.projection_position_side);
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;
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
10901 return source.signal_close_mc_bar ==
source.projection_created_bar
10902 &&
source.projection_created_bar
10904 &&
source.signal_close_mc_entry_incarnation != 0
10905 && last_margin_call_event_ordinal_ == last_applied_ordinal_
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
10917 &&
source.placement_cycle == current_position_cycle_
10921 ==
source.signal_close_mc_remaining_qty
10922 && std::isfinite(close_surplus)
10923 && std::abs(close_surplus - 1.0) < 1e-6;
10929 const bool default_money_candidate = std::holds_alternative<native_order::Market>(trigger)
10930 && !std::isfinite(
source.requested_qty)
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)
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;
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;
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
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
10982 && nearest_tick(result.resolved_price, staged_.syminfo.mintick)
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
10988 && !
source.projection_after_close &&
source.projection_predecessor == 0
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;
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(
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
11024 result.shape = keep_mc_close_surplus
11025 ? native_order::OpeningShape::ReverseTo
11026 : (opposite ? native_order::OpeningShape::CloseOpposite
11027 : native_order::OpeningShape::Transact);
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;
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
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);
11059 if (affordability_close_only) {
11061 result.units = 0.0;
11064 result.units = keep_mc_close_surplus ? 1.0
11066 result.shape = keep_mc_close_surplus
11067 ? native_order::OpeningShape::ReverseTo
11068 : native_order::OpeningShape::CloseOpposite;
11077 result.shape = native_order::OpeningShape::CloseOpposite;
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;
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)) {
11105 if (
source.sequential_rank == 1 && opposite)
11107 result.shape = native_order::OpeningShape::Transact;
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));
11126 &&
source.projection_position_side
11128 && ((
source.projection_position_side
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) {
11138 result.units = *result.units +
source.projection_tv_carry_qty;
11139 result.shape = native_order::OpeningShape::Transact;
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
11149 const bool flat_dual_stop =
source.projection_position_side
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);
11165 result.shape = source_close_precedes || replacement_transaction
11166 ? native_order::OpeningShape::Transact
11167 : prior_cycle_close_only
11168 ? native_order::OpeningShape::CloseOpposite
11170 ? native_order::OpeningShape::Transact
11171 : native_order::OpeningShape::ReverseTo;
11177 std::uint64_t incarnation)
const noexcept {
11181 const auto snapshot = placement_.find(incarnation);
11182 return snapshot != placement_.end()
11183 && snapshot->second.post_parent_calc_level_fill;
11188 const auto snapshot = placement_.find(incarnation);
11189 if (snapshot == placement_.end())
return false;
11190 const auto&
source = snapshot->second;
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));
11202 const auto snapshot = placement_.find(incarnation);
11203 if (snapshot == placement_.end())
return std::nullopt;
11205 if (std::isnan(snapshot->second.exit_levels.trail_offset))
return 0.0;
11210 const auto snapshot = placement_.find(incarnation);
11211 if (snapshot == placement_.end())
return false;
11220 return static_cast<bool>(definition)
11221 && definition->origin == native_order::RequestOrigin::KernelLiquidation;
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) {
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)) {
11247 if (sn.projection_position_side == target_side
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()
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)
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;
11289 if (
exit && (leg.source_id !=
exit->source_id
11290 || leg.from_entry !=
exit->from_entry)) {
11295 if (!
exit ||
exit->from_entry.empty() ||
exit->legs.dormant()
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)) {
11304 const auto cohort = cohorts_by_id_.find(
exit->from_entry);
11305 if (cohort == cohorts_by_id_.end() || cohort->second.opened.empty())
return false;
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) {
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;
11318 const double open = nearest_tick(view.
raw_price, tick);
11319 return !((std::isfinite(limit) && open >= limit)
11320 || (std::isfinite(stop) && open <= stop));
11330 if (snapshot != placement_.end() || kernel_liquidation) {
11336 const auto&
source = snapshot != placement_.end() ? snapshot->second
11337 : kKernelLiquidation;
11338 const auto physical = require_host().physical_position();
11349 const Bar& sample_bar = pine->current_bar_;
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;
11366 const bool pre_exit_chronology = view.
current
11367 ? (one_x_long_opening && crosses)
11369 pine->excursion_margin_prefix_ = config_.process_orders_on_close
11370 || pre_exit_chronology;
11379 bool carried_open_slice =
11381 && position_open_bar_index_ >= 0
11393 if (carried_open_slice && one_x_long_opening) {
11395 view, physical.signed_units);
11397 pine->excursion_margin_fill_only_ = !config_.process_orders_on_close
11398 && (carried_open_slice || (!view.
current && crosses));
11409 const bool stale_close_for_new_position =
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;
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);
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;
11445 || prior.is_long !=
source.is_long
11446 || !finite_positive(prior.exit_levels.stop)
11447 || finite_positive(prior.exit_levels.limit)
11449 || prior.source_sequence >=
source.source_sequence) {
11453 prior.projection_position_side);
11466 if (prior.projection_created_bar ==
source.projection_created_bar) {
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;
11487 && physical.signed_units == 0.0
11488 && entry_openings_this_interval_ > 0
11490 && leftover_flat_stop
11491 && !config_.calc_on_order_fills
11492 && !stream_mode_) {
11496 if (!throttled_rearm_already_queued(throttled_reopen_rearm_,
source))
11497 throttled_reopen_rearm_.push_back(
source);
11498 return NativePrecommitVerdict::Refuse;
11501 && physical.signed_units != 0.0
11502 && ((physical.signed_units > 0.0) !=
source.is_long);
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;
11516 && std::abs(physical.signed_units) >= risk_.max_position_size) {
11517 return NativePrecommitVerdict::Refuse;
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;
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());
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;
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
11553 && transfer->close_fill == cap_latest_fill_;
11554 if (!inherited)
return NativePrecommitVerdict::Refuse;
11556 const auto physical_now = require_host().physical_position();
11559 && ((physical_now.signed_units > 0.0) ==
source.is_long)) {
11560 return NativePrecommitVerdict::Refuse;
11568 return NativePrecommitVerdict::AdmitWithHostMargin;
11576 const auto native_state = require_host().native_state();
11577 const bool pooc_default_all_in = !std::isfinite(
source.requested_qty)
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)
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
11588 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
11592 && !
source.projection_after_close && !
source.birth.from_fill()
11593 &&
source.projection_predecessor == 0 && !
source.replaced_opening
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
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
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;
11629 return NativePrecommitVerdict::AdmitWithHostMargin;
11632 const double margin_pct =
source.is_long ? config_.margin_long : config_.margin_short;
11634 const auto physical = require_host().physical_position();
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
11646 && physical.signed_units == 0.0
11647 &&
source.projection_position_side
11649 && !
source.projection_after_close &&
source.projection_predecessor == 0
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
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;
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
11688 const double required = units * view.
resolved_price * staged_.syminfo.pointvalue * fx
11689 * margin_pct / 100.0;
11693 double equity = reversal
11695 : (finite_positive(
source.sizing.equity)
11697 const bool pooc_slipped_signal = config_.process_orders_on_close
11699 &&
source.projection_position_side
11701 && physical.signed_units == 0.0
11702 && std::holds_alternative<native_order::Market>(
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);
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;
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)
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;
11744 return NativePrecommitVerdict::AdmitWithHostMargin;
11746 if (!std::isfinite(required) || !std::isfinite(equity) || required > equity + epsilon) {
11747 return NativePrecommitVerdict::Refuse;
11749 return NativePrecommitVerdict::AdmitWithHostMargin;
11759 return NativePrecommitVerdict::Refuse;
11760 const auto& bounds =
source.leg_activation.bounds();
11763 if (bounds && current_position_cycle_ > 0) {
11764 const bool ready = stop_leg
11766 : (limit_leg ?
source.leg_activation.limit_ready(
11768 if (!ready)
return NativePrecommitVerdict::Refuse;
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;
11777 && !
source.projection_after_close
11779 && config_.default_qty_value >= 100.0
11780 && finite_positive(
source.sizing.frozen_units)) {
11785 const double margin =
source.is_long ? config_.margin_long : config_.margin_short;
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()
11797 && config_.commission_value == 0.0 && config_.slippage == 0
11798 && !config_.process_orders_on_close && !config_.calc_on_order_fills
11800 && source_money_round(source_money_round(equity)
11802 if (!std::isfinite(required) || !std::isfinite(equity)
11803 || (required > equity + guard && !nested_price_gap_affordable)) {
11804 return NativePrecommitVerdict::Refuse;
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
11810 && require_host().physical_position().lot_count
11811 >=
static_cast<std::size_t
>(config_.pyramiding)) {
11812 return NativePrecommitVerdict::Refuse;
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;
11826 if (!std::isfinite(frozen_required) || !std::isfinite(
source.sizing.equity)) {
11827 return NativePrecommitVerdict::Refuse;
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);
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) {
11840 return NativePrecommitVerdict::AdmitWithHostMargin;
11846 if (active_fx ==
source.sizing.fx) {
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)
11854 && config_.default_qty_value < 100.0));
11859 const bool all_in_reversal = reversal
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>(
11877 const bool all_in_true_flat_opening = !reversal
11879 && !std::isfinite(
source.requested_qty)
11880 && physical.signed_units == 0.0
11881 &&
source.projection_position_side
11883 && !
source.projection_after_close
11884 && config_.default_qty_type
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)
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;
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;
11908 const auto native_state = require_host().native_state();
11909 const bool magnified = native_state.spec
11910 && !native_state.spec->intrabar.is_none();
11912 && !std::isfinite(
source.requested_qty)
11914 && config_.default_qty_value == 100.0
11915 && std::abs((
source.is_long ? config_.margin_long : config_.margin_short)
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()
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
11927 && ((physical.signed_units == 0.0
11928 &&
source.projection_position_side
11930 && !paired_market_opening)
11932 const bool price_gap_affordable = price_gap_scope
11933 && source_money_round(source_money_round(
source.sizing.equity)
11936 &&
source.projection_position_side
11938 && !
source.projection_after_close
11939 && physical.signed_units == 0.0
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;
11949 &&
source.projection_after_close && physical.signed_units == 0.0) {
11958 return NativePrecommitVerdict::AdmitWithHostMargin;
11960 double admission_guard = float_guard;
11961 if (!reversal && staged_.quantity_grid) {
11962 admission_guard = std::max(admission_guard,
11964 * staged_.syminfo.pointvalue * active_fx * fraction);
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;
11973 return NativePrecommitVerdict::AdmitWithHostMargin;
11982 const bool opening_margin_checkpoint =
11984 && finite_positive(
source.requested_qty)
11987 if (opening_margin_checkpoint)
return NativePrecommitVerdict::AdmitWithHostMargin;
11990 return NativePrecommitVerdict::Refuse;
11992 return NativePrecommitVerdict::AdmitWithHostMargin;
11996 const std::time_t seconds =
static_cast<std::time_t
>(timestamp_ms / 1000);
11998 const auto utc = [&]() {
11999 return ::gmtime_r(&seconds, &fields) !=
nullptr;
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();
12006 tz_util::ScopedTimezone guard(timezone);
12007 if (::localtime_r(&seconds, &fields) ==
nullptr) {
12008 if (!utc())
return std::numeric_limits<std::int64_t>::min();
12014 if (!utc())
return std::numeric_limits<std::int64_t>::min();
12017 return static_cast<std::int64_t
>(fields.tm_mday) * 100
12018 +
static_cast<std::int64_t
>(fields.tm_mon + 1);
12027 static_cast<int>(key / 100),
static_cast<int>(key % 100)};
12030compat::pine::Calculation PineExecutionAdapter::cap_calculation(
12031 const NativeDecisionContext& context)
const {
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;
12038 coof_recalc_active_, magnifier,
12039 state.phase == NativeRunPhase::Warmup,
12040 state.phase != NativeRunPhase::Realtime,
12042 context.coordinate.interval_index};
12045compat::pine::MatchedAttempt PineExecutionAdapter::cap_attempt(
12047 const native_order::ExecutionAppliedEvent* applied)
const {
12050 kind = (finite_positive(snapshot.exit_levels.limit)
12051 || finite_positive(snapshot.exit_levels.stop))
12054 const auto position = require_host().physical_position();
12059 std::size_t prefill_entries = position.lot_count;
12061 if (applied->closed_units > 0.0) {
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) {
12078 const auto projected =
static_cast<PositionSide>(snapshot.projection_position_side);
12081 prefill_entries = 0;
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};
12092bool PineExecutionAdapter::cap_placement_denied(
const NativeDecisionContext& context) {
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;
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;
12109 const double drawdown = risk_.observed_peak_equity - equity;
12110 if (drawdown > risk_.observed_max_drawdown)
12111 risk_.observed_max_drawdown = drawdown;
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;
12124 if (risk_.max_cons_loss_days > 0
12125 && day_ledger_.consecutive_loss_days >= risk_.max_cons_loss_days) {
12126 risk_.halted =
true;
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)) {
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;
12159 double mark_price, std::int64_t sub_bar_open_ms)
const {
12161 const auto position = require_host().physical_position();
12162 if (position.signed_units < 0.0) {
12165 mark_price = nearest_tick(mark_price, staged_.syminfo.mintick);
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)) {
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;
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) {
12192 money.
equity = percent_commission_live_equity(mark_price);
12206 if (!(raw_minimum > 0.0) || !std::isfinite(raw_minimum))
return 0.0;
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;
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;
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)
12231 units = std::min(money.
held, units);
12234 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units))
return 0.0;
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);
12259 if (!staged_.quantity_grid || *staged_.quantity_grid != 1.0
12261 || config_.default_qty_value != 100.0 || config_.commission_value != 0.0) {
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;
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;
12281 if (rounded_tie_opening)
break;
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))) {
12310 if (point.
kind == NativeMarginCheckKind::FxRoll)
return false;
12316 if (kernel_margin_resize_point_ == ordinal) {
12325 }
else if (kernel_margin_path_point_ != ordinal) {
12339 if (!money.valid)
return std::nullopt;
12341 decision.
required = money.required;
12342 decision.
equity = money.equity;
12364 && std::abs(config_.margin_long - 100.0) < 1e-12) {
12378bool PineExecutionAdapter::submit_margin_call_slice(
12380 bool opening_checkpoint) {
12383 mark_price = money.mark;
12384 if (!money.valid)
return false;
12387 if (!(units > 0.0))
return false;
12396 double close_base = mark_price;
12397 if (opening_checkpoint && std::isfinite(config_.
slippage)
12399 close_base = source_bar_fill_tick(
12400 mark_price - (position.signed_units > 0.0 ? 1.0 : -1.0)
12404 return submit_margin_call_units(close_base, context, units);
12407bool PineExecutionAdapter::submit_margin_call_units(
12408 double mark_price,
const NativeDecisionContext& context,
double units,
12409 bool force_execution_price) {
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)) {
12416 units = std::min(units, held);
12417 native_order::Request request;
12418 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
12420 request.comment =
"Margin call";
12421 PlacementSnapshot snapshot;
12423 snapshot.source_id = request.label;
12424 snapshot.requested_qty = units;
12425 if (force_execution_price) {
12435 snapshot.forced_execution_price =
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});
12446bool PineExecutionAdapter::submit_tv_money_long_margin_call(
12447 const Bar& bar,
const NativeDecisionContext& context) {
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) {
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) {
12479 if (position_open_priced_
12480 && (!std::isfinite(lot_value) || lot_value >= 1.0)) {
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()
12494 if (position_open_script_bar_ == context.script_bar_open_ms) {
12499 if (position_open_phase_ != NativePathPhase::Open)
return false;
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;
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);
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);
12541 if (!std::isfinite(exact_value) || !std::isfinite(equity)
12542 || equity + arithmetic_guard < exact_value
12543 || !(equity + arithmetic_guard < rounded_value)) {
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)
12555 return submit_margin_call_units(
price, context, units,
true);
12560bool PineExecutionAdapter::slipped_pooc_opening_money_scope(
12561 const Bar& bar,
const NativeDecisionContext& context)
const {
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()) {
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;
12597 if (sole_cohort ==
nullptr || sole_cohort->opened.size() != 1
12598 || sole_cohort->live_units_by_origin.size() != 1) {
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()
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_) {
12617 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue;
12618 return std::isfinite(lot_value) && lot_value < 1.0;
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) {
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)) {
12638 return submit_margin_call_units(
12639 bar.open, context, std::min(1.0, position.signed_units),
false);
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) {
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)) {
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;
12677 if (
exit && !candidate.from_entry.empty()
12678 && cohort_exposure_for(candidate.from_entry) == 0.0) {
12682 || owned_trail !=
nullptr) {
12685 owned_trail = &candidate;
12686 owned_trail_incarnation = handle.incarnation;
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;
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;
12720 if (!finite_positive(fire_price))
return false;
12721 if (config_.process_orders_on_close && owned_trail) {
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;
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;
12746 if (
price >= activation) {
12752 if (offset > 0.0 ?
price <= best - offset :
price < best) {
12753 fill_point = point;
12755 best = std::max(best,
price);
12758 if (fill_point < 0 || !(fire_point < fill_point))
return false;
12761 native_order::Request request;
12762 request.intent = native_order::Reduce{native_order::ExplicitUnits{
12763 std::min(1.0, position.signed_units)}};
12765 request.comment =
"Margin call";
12766 request.trigger = fire_price <= bar.open
12769 PlacementSnapshot snapshot;
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__"));
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;
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;
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) {
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;
12826 trail = &candidate;
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) {
12843 const double cohort = cohort_exposure_for(trail->from_entry);
12844 return std::isfinite(cohort)
12845 && cohort == std::abs(position.signed_units);
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)) {
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;
12862 || priced !=
nullptr || candidate.from_entry.empty()
12863 || cohort_exposure_for(candidate.from_entry) <= 0.0) {
12866 priced = &candidate;
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()) {
12876 return cohort_exposure_for(priced->from_entry)
12877 == std::abs(position.signed_units);
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)) {
12898 const double adverse = nearest_tick(bar.high, staged_.syminfo.mintick);
12899 if (!finite_positive(adverse)
12900 || !(*grid * adverse * staged_.syminfo.pointvalue < 1.0)) {
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;
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())
12924 if (only)
return true;
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))) {
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);
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);
12967 if (competing_entry && position.signed_units < 0.0)
return false;
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;
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()) {
12995 const bool high_first = source_path_uses_high_first(bar);
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},
13006 for (
int index = 0; index < 4; ++index) {
13007 if (path[index].phase == context.coordinate.path_phase) {
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;
13021 if (!finite_positive(adverse))
return false;
13029 kernel_margin_path_point_ = context.coordinate.ordinal;
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;
13044 || candidate.is_long == (position.signed_units > 0.0)
13045 || !candidate.reverse_to || candidate.projection_after_close) {
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) {
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;
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;
13077 || candidate.is_long == (position.signed_units > 0.0)
13078 || !candidate.reverse_to || candidate.projection_after_close) {
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;
13095 if (!declined_reversal)
return;
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);
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);
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);
13127 struct DeferredStop {
13128 native_order::RequestHandle handle;
13129 PlacementSnapshot snapshot;
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;
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)) {
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});
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);
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
13170 const auto accepted = submit_or_replace(
13171 std::move(request), std::move(deferred.snapshot),
false,
13174 bracket_families_[key_for(
13175 placement_.at(accepted->incarnation).source_id,
13176 placement_.at(accepted->incarnation).from_entry)].push_back(*accepted);
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) {
13187 native_order::Request request;
13192 if (!execute_current) request.trigger = native_order::Stop{mark_price};
13193 PlacementSnapshot snapshot;
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;
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))
13221 (void)submit_intraday_loss_close(adverse, context,
false);
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) {
13230 native_order::Request request;
13232 request.comment =
"Close Position (Max number of filled orders in one day)";
13233 PlacementSnapshot snapshot;
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__");
13240 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
13241 cap.after_immediate_close_attempt();
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();
13250 native_order::Request request;
13252 request.comment =
close.request.comment;
13253 PlacementSnapshot snapshot;
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__");
13264 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
13266 cap.after_immediate_close_attempt();
13269void PineExecutionAdapter::observe_intraday_cap(
13270 const native_order::ExecutionAppliedEvent& event,
13272 if (!
cap.active())
return;
13274 || snapshot.source_id ==
"__intraday_cap_close__")
13276 const auto clock = cap_clock(context);
13277 const auto calculation = cap_calculation(context);
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
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;
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({
13317 candidate.projection_created_bar, candidate.is_long,
13318 static_cast<std::int64_t
>(candidate.command_sequence),
13319 handle.incarnation});
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;
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;
13337 continuation_units = continuation_snapshot->sizing.frozen_units;
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;
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;
13359 continuation_snapshot->market_admission = {};
13360 const auto accepted = submit_or_replace(
13361 std::move(request), *continuation_snapshot,
true,
13362 continuation_snapshot->source_id);
13364 continuation = *accepted;
13365 candidates[continuation_index].incarnation = accepted->incarnation;
13367 continuation.reset();
13368 candidates.erase(candidates.begin()
13369 +
static_cast<std::ptrdiff_t
>(continuation_index));
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});
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_);
13389 cap.decline(event.handle().incarnation);
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;
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;
13404 const auto decision =
cap.post_dispatch(admission, calculation, attempt,
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);
13416 cap_latest_fill_ =
event.ordinal;
13419void PineExecutionAdapter::observe_intraday_cap_noop(
13420 bool is_long,
const NativeDecisionContext& context) {
13421 if (!
cap.active())
return;
13422 PlacementSnapshot snapshot;
13424 snapshot.is_long = is_long;
13425 snapshot.projection_position_side = is_long
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_);
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;
13445 const auto decision =
cap.post_dispatch(admission, calculation, attempt,
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);
13459 cap.source_batch_end();
13462void PineExecutionAdapter::record_market_review(
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);
13479 if (selected.empty())
return;
13483 review.
receipt = {allocation.sequence(), checkpoint, bar, 0};
13484 review.
open_price = policy_script_bar_valid_ ? policy_script_bar_.
open : kNaN;
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;
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);
13515 snapshot.market_admission.reviewed(
13516 {allocation.sequence(), checkpoint, bar, origin->command});
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 =
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()) {
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
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);
13557 && std::isfinite(snapshot.projection_affordability_equity)) {
13558 snapshot.projection_affordability_equity =
13559 require_host().native_marked_equity(mark);
13562 if (!revised || cause_fill == 0)
continue;
13564 const auto& origin = snapshot.market_admission.observation();
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);
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;
13587 native_order::RequestHandle handle;
13588 const PlacementSnapshot* snapshot =
nullptr;
13590 std::vector<Candidate> market;
13591 bool foreign_live_order =
false;
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
13605 for (
const auto& delayed : delayed_market_orders_) {
13606 if (delayed.snapshot.projection_created_bar == source_bar)
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
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()
13624 if (unpriced_entry) {
13625 market.push_back({handle, &snapshot});
13628 const bool same_bar_unpriced_close = snapshot.projection_created_bar == source_bar
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()
13638 if (!same_bar_unpriced_close) foreign_live_order =
true;
13640 const bool family_s_command_order =
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;
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);
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 =
13671 && first.source_id != second.source_id
13672 && first.is_long != second.is_long;
13673 if (default_pair) {
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);
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);
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;
13725 if (!earlier_opposite) cancel_later(market[index].handle);
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);
13740void PineExecutionAdapter::defer_open_marketable_sells(
const Bar& bar) {
13741 if (config_.calc_on_order_fills || stream_mode_)
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);
13751 const double fill_point_price = config_.process_orders_on_close ? bar.close : bar.open;
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;
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;
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)) {
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);
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;
13789 min_open_buy_incarnation = std::min(min_open_buy_incarnation, buy.handle.incarnation);
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;
13800 if (!deferred_open_sell)
return;
13801 for (
const auto& sell : sells) {
13802 if (sell.open_marketable)
continue;
13805 if (!sell.touched)
continue;
13806 deferred.push_back(sell);
13808 std::stable_sort(deferred.begin(), deferred.end(),
13809 [](
const Candidate& left,
const Candidate& right) {
13810 return left.handle.incarnation < right.handle.incarnation;
13812 deferred.erase(std::unique(deferred.begin(), deferred.end(),
13813 [](
const Candidate& left,
const Candidate& right) {
13814 return left.handle.incarnation == right.handle.incarnation;
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));
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;
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};
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;
13858 row.snapshot.projection_after_close =
true;
13859 row.snapshot.cancellation = {};
13860 row.snapshot.market_admission = {};
13861 const SourceId key = row.replacement_key;
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);
13876 const auto accepted = submit_or_replace(
13877 std::move(request), std::move(row.snapshot),
true, key);
13879 (void)require_host().execute_current(
13880 {*accepted, NativeCurrentPriceRule::NearestTick});
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;
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
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)) {
13911 const double margin =
entry.is_long ? config_.margin_long : config_.margin_short;
13912 if (!finite_positive(margin) || !finite_positive(staged_.syminfo.pointvalue))
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;
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
13929 double affordable_price = kNaN;
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)) {
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;
13956 if (!opposite_market)
return;
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;
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;
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;
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);
13994 && !std::isfinite(leg.exit_levels.trail_offset)
13995 && finite_positive(trail_activation);
13996 const bool exit_is_buy = physical.signed_units < 0.0;
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
14002 ? bar.open <= priced_level : bar.open >= priced_level)
14004 ? bar.open >= priced_level : bar.open <= priced_level));
14005 const bool reorder_priced_leg = gapped_priced_leg
14006 && (!gap_decline || opening_margin_slice);
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))) {
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;
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,
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"
14069 delayed.emplace(PendingBracketLeg{
14070 std::move(request), leg, replacement_key,
14071 key_for(leg.source_id, leg.from_entry)});
14075 && !reorder_priced_leg) {
14076 margin_revival = leg;
14078 retired_legs.push_back({handle, std::move(delayed),
14079 std::move(margin_revival), release_at_open});
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};
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});
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);
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});
14113 pending_bracket_legs_.push_back(std::move(*retired.delayed_leg));
14119void PineExecutionAdapter::reaccept_gapped_bracket_behind_same_id_add(
14120 const Bar& bar,
const NativeDecisionContext& context) {
14133 if (config_.calc_on_order_fills || config_.process_orders_on_close
14134 || stream_mode_ || coof_recalc_active_ || context.sub_index != 0) {
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,
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();
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;
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)) {
14169 int exit_priority = 0;
14171 && (long_position ? bar.open <= leg.exit_levels.stop
14172 : bar.open >= leg.exit_levels.stop)) {
14173 exit_priority = long_position ? 2 : 1;
14175 && finite_positive(leg.exit_levels.limit)
14176 && (long_position ? bar.open >= leg.exit_levels.limit
14177 : bar.open <= leg.exit_levels.limit)) {
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);
14189 if (add_waits) gapped.push_back(handle);
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 = {
14201 static_cast<std::int64_t
>(leg.source_sequence), handle.incarnation,
14202 leg.placement_cycle, leg.legs.revision(), leg.requested_qty, kNaN};
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;
14210 request.trigger = native_order::Stop{source_trigger_threshold(
14211 leg.exit_levels.stop, tick, exit_is_buy,
false)};
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)};
14221 leg.forced_execution_price = source_bar_fill_tick(bar.open, tick);
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);
14237void PineExecutionAdapter::apply_terminal_explicit_market_policy(
14238 const NativeDecisionContext& context) {
14239 if (!config_.process_orders_on_close
14240 || config_.pyramiding != 0 || stream_mode_) {
14244 native_order::RequestHandle handle;
14245 PlacementSnapshot snapshot;
14246 std::uint64_t priority = 0;
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;
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()
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;
14271 candidates.push_back({handle, row, origin->source_sequence});
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;
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);
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
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);
14311 record_market_review(admission::Checkpoint::TerminalGross,
14312 context.coordinate.interval_index,
14313 terminal_review_handles);
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);
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;
14334 candidate.snapshot.reverse_to =
false;
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,
14344 simulated_sign = requested_sign;
14345 (void)require_host().execute_current(
14346 {*accepted, NativeCurrentPriceRule::NearestTick});
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()
14363 if (
const auto state = require_host().trail_state(handle))
14364 trail_state_at_open_.emplace(handle.incarnation, *state);
14373 entry_openings_this_interval_ = 0;
14377 ++broker_open_epoch_;
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;
14397 last_bar_dual_entry_path_ = 0;
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;
14406 || !finite_positive(snapshot.exit_levels.stop)
14407 || std::isfinite(snapshot.exit_levels.limit)
14408 || finite_positive(snapshot.exit_levels.trail_offset)) {
14411 stops.push_back(&snapshot);
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
14422 <= std::abs(bar.
open - bar.
low));
14423 last_bar_dual_entry_path_ = high_first ? 1 : 2;
14427 flush_coof_tail(
false,
true);
14428 suspend_coof_declined_reversal_at_open(bar, context);
14430 close_all_pending_script_bar_ = std::numeric_limits<std::int64_t>::min();
14432 pooc_open_basis_ = std::abs(require_host().physical_position().signed_units);
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;
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;
14454 if (
exit && std::isfinite(candidate.exit_levels.limit)
14455 && bar.
open >= candidate.exit_levels.limit) {
14456 marketable_limit_at_open =
true;
14461 if (long_full_margin && !marketable_limit_at_open
14465 (void)submit_tv_money_long_margin_call(open_only, context);
14467 (void)schedule_tv_money_long_margin_before_trail(bar, context);
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
14481 && pending.is_long != (opening_position.signed_units > 0.0)) {
14482 opposite_entry_waits =
true;
14488 || finite_positive(pending.exit_levels.limit)
14489 || finite_positive(pending.exit_levels.stop)
14490 || finite_positive(pending.exit_levels.trail_offset)) {
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);
14503 || (owned >= held_at_open - 1e-10
14504 && closing >= held_at_open - 1e-10)) {
14505 whole_market_close_waits =
true;
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);
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);
14530 if (declined_reversal && !opening_margin_applied) {
14531 defer_declined_reversal_exits_at_adverse(
14532 bar, context, margin_scheduled);
14535 (void)submit_intraday_loss_close(bar.
open, context,
true);
14536 schedule_intraday_loss_path(bar, context);
14537 schedule_preopen_margin_slice(bar, context);
14547 (void)submit_margin_call_slice(tick.
close, context.
decision);
14550void PineExecutionAdapter::rearm_throttled_reopens() {
14551 auto queued = std::move(throttled_reopen_rearm_);
14552 throttled_reopen_rearm_.clear();
14554 for (
auto& snapshot : queued) {
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};
14561 const double units = std::abs(snapshot.requested_qty);
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);
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)
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});
14596void PineExecutionAdapter::flush_pooc_marketable_limit_entry_fills(
14597 const Bar& bar,
const NativeDecisionContext& context) {
14607 if (config_.calc_on_order_fills || !config_.process_orders_on_close
14608 || stream_mode_ || coof_recalc_active_) {
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;
14620 if (row.birth.from_fill())
continue;
14621 if (row.projection_created_bar != context.coordinate.interval_index)
continue;
14622 if (row.projection_position_side
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);
14632 for (
auto& candidate : marketable) {
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
14640 if (!host_sized && !finite_positive(row.requested_qty))
continue;
14641 native_order::Request request;
14642 request.
intent = host_sized
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 = {};
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);
14662 (void)require_host().execute_current(
14663 {*accepted, NativeCurrentPriceRule::NearestTick});
14668void PineExecutionAdapter::flush_pooc_marketable_exit_fills(
14669 const Bar& bar,
const NativeDecisionContext& context) {
14675 if (config_.calc_on_order_fills || !config_.process_orders_on_close
14676 || stream_mode_ || coof_recalc_active_) {
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;
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;
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
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
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;
14732 native_order::RequestHandle handle;
14733 PlacementSnapshot snapshot;
14736 bool has_stop =
false;
14738 bool has_limit =
false;
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;
14749 if (row.birth.from_fill())
continue;
14750 if (row.projection_created_bar != context.coordinate.interval_index)
continue;
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{}});
14760 Group& group = groups[found_group->second].second;
14762 if (!group.has_stop) {
14763 group.has_stop =
true;
14764 group.stop =
Leg{handle, row};
14766 }
else if (!group.has_limit) {
14767 group.has_limit =
true;
14768 group.limit =
Leg{handle, row};
14771 for (
auto&
entry : groups) {
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;
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;
14832 immediate.forced_execution_price = nearest_tick(
14833 raw_close + (stop_close ? (closing_long ? -1.0 : 1.0) : 0.0)
14834 * config_.slippage * 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)));
14843 (void)require_host().execute_current(
14844 {*accepted, NativeCurrentPriceRule::NearestTick});
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();
14860 last_bar_dual_entry_path_ = 0;
14863 apply_terminal_explicit_market_policy(context);
14864 update_risk_state(bar.
close);
14865 if (stream_mode_)
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);
14877 const auto position = require_host().physical_position();
14882 const bool non_pooc_commissioned_short = !config_.process_orders_on_close
14883 && position.signed_units < 0.0
14884 && config_.margin_short == 100.0
14886 && config_.commission_value > 0.0;
14887 if (non_pooc_commissioned_short && finite_positive(bar.
high)) {
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_) {
14906 for (
const auto& origin : cohort->second.opened) {
14907 const auto opening = placement_.find(origin.incarnation);
14908 if (opening == placement_.end())
continue;
14910 const auto& row = opening->second;
14911 explicit_market_opening = row.
opening && !row.is_long
14913 && finite_positive(row.requested_qty)
14914 && !price_present(row.exit_levels.limit)
14915 && !price_present(row.exit_levels.stop);
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);
14922 const double adverse = nearest_tick(bar.
high, staged_.syminfo.mintick);
14923 (void)submit_margin_call_slice(adverse, context);
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()
14929 if (carried_pooc_short && finite_positive(bar.
high)) {
14936 if (market_orders_pending_at_close(context)) {
14940 (void)submit_margin_call_slice(bar.
high, context);
14954 std::optional<PlacementSnapshot> placement_snapshot;
14956 placement != placement_.end()) {
14957 placement_snapshot = placement->second;
14960 == native_order::RequestOrigin::KernelLiquidation) {
14972 adopted.
sizing = sizing_snapshot();
14974 placement_snapshot = adopted;
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));
14984 const bool from_bracket =
14992 pine_host->adapter_label_bracket_trades(event, from_bracket);
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);
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};
15022 && require_host().physical_position().signed_units == 0.0
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;
15036 || pending.projection_position_side
15037 !=
static_cast<std::int32_t
>(closed_side)) {
15040 const bool resting_limit =
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 =
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);
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};
15078 if (placement_snapshot
15091 if (require_host().physical_position().signed_units == 0.0)
15092 retire_in_position_exits_at_flat(
true,
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));
15103 remaining.push_back(std::move(
entry));
15106 pending_entries_ = std::move(remaining);
15107 for (
auto&
entry : after_close) {
15108 entry.snapshot.paired_reversal_parent = {};
15109 entry.snapshot.market_admission = {};
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(
15123 if (marketable_now) {
15124 entry.snapshot.forced_execution_price =
event.resolved_price;
15132 placement_snapshot->projection_position_side);
15133 if (!priced_stop && config_.slippage != 0
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;
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;
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) {
15161 prior.projection_position_side);
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;
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));
15181 entry.snapshot.is_long ? units : -units};
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});
15194 bool preclose_intraday_loss =
false;
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;
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;
15215 last_applied_ordinal_ =
event.ordinal;
15218 entry_openings_this_interval_ += 1;
15219 if (!throttled_reopen_rearm_.empty())
15220 rearm_throttled_reopens();
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;
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(),
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);
15243 if ((!placement_snapshot
15244 || cohort.first != placement_snapshot->source_id)
15245 && !nested_new_side_opening) {
15246 closed_cohorts.push_back(cohort.first);
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();
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();
15267 if (next_sign != 0 && (current_position_sign_ == 0 || current_position_sign_ != next_sign)) {
15268 ++current_position_cycle_;
15270 position_open_epoch_ = broker_open_epoch_;
15273 position_open_priced_ = placement_snapshot
15274 && (finite_positive(placement_snapshot->exit_levels.limit)
15275 || finite_positive(placement_snapshot->exit_levels.stop)
15278 current_position_sign_ = next_sign;
15279 if (placement_snapshot
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;
15298 if (sibling->second.command_sequence == placement_snapshot->command_sequence
15299 && sibling->second.bracket_origin == placement_snapshot->bracket_origin)
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);
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_;
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()
15325 && peer->second.oca_type == 1
15326 && peer->second.oca_name == placement_snapshot->oca_name) {
15327 siblings.push_back(handle);
15330 for (
const auto& sibling : siblings) {
15331 const auto result = require_host().cancel(sibling);
15332 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
15336 if (placement_snapshot && placement_snapshot->opening
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;
15351 if (!
exit || snapshot.from_entry != placement_snapshot->source_id
15352 || snapshot.bracket_origin.incarnation != 0
15353 || !std::isfinite(snapshot.requested_qty)) {
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);
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;
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]
15376 close_logical_units_[placement_snapshot->source_id]
15378 record_opening_fee(*placement_snapshot, event);
15379 materialize_pending_bracket_legs(event);
15381 placement_snapshot->projection_position_side);
15382 const bool consumed_deferred_carry =
15383 finite_positive(placement_snapshot->projection_tv_carry_qty)
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) {
15400 candidate.projection_tv_carry_qty = 0.0;
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));
15420 return row.bracket_origin.incarnation != 0
15421 && row.bracket_origin !=
event.handle();
15423 const bool partial_prearmed_parent = std::isfinite(
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;
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;
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;
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)) {
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;
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;
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;
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);
15507 for (
const auto& handle : delayed_legs) {
15508 const auto pending = placement_.find(handle.incarnation);
15509 if (pending == placement_.end())
continue;
15513 native_order::HostSizedKind::Close, std::nullopt};
15517 const bool exit_is_buy =
event.opened_units < 0.0;
15521 exit_is_buy,
false)};
15525 exit_is_buy,
true)};
15527 const bool explicit_origin = std::isfinite(row.
requested_qty)
15529 const std::string origin_suffix = explicit_origin
15531 const std::string group_name = row.
oca_name.empty()
15534 request.
group = group_for(group_name, 1);
15535 const std::string replacement_key = row.
source_id +
"\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)
15542 delayed_market_orders_.push_back({
15543 std::move(request), row, replacement_key,
15544 broker_open_epoch_ + 1U});
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;
15558 || row.projection_created_bar
15559 != placement_snapshot->projection_created_bar
15560 || carried_origin_leg(row)) {
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) {
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)
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);
15608 && config_.default_qty_type
15610 && config_.default_qty_value < 100.0) {
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()
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);
15626 for (
const auto& handle : prearmed_trails) {
15627 const auto result = require_host().cancel(handle);
15628 if (result.status == native_order::CancelStatus::Cancelled)
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;
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_) {
15649 context.
sub_count > 1 ? exit_legs::Domain::MagnifierFillRecalc
15650 : exit_legs::Domain::FillRecalc,
15651 exit_legs::Phase::Observation};
15653 candidate.legs.revision(), cause,
15655 (void)candidate.legs.apply(candidate.legs.target(),
bind);
15657 initialize_l4c_policy(candidate, handle);
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);
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;
15676 candidate.pooc_global_full_exit_bound_add = bound_preexit_add;
15681 if (placement_snapshot && placement_snapshot->opening
15682 && placement_snapshot->reverse_to && event.
closed_units > 0.0
15684 purge_brackets_after_applied_reversal(*placement_snapshot);
15688 refresh_pending_sizing_after_margin(event, context);
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)) {
15708 sole_live_entry_incarnation = opening.incarnation;
15709 ++live_entry_count;
15712 const std::uint64_t source_fill_sequence =
15714 if (live_entry_count == 1U && sole_live_entry_incarnation != 0
15715 && physical.signed_units > 0.0 && physical.lot_count == 1U
15717 && config_.default_qty_type
15719 && std::abs(config_.default_qty_value - 100.0) < 1e-12
15721 && config_.commission_value == 0.0 && config_.slippage == 0
15722 && source_fill_sequence != 0) {
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_
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)) {
15744 const auto opening = placement_.find(origin.incarnation);
15745 if (opening == placement_.end()
15747 || opening->second.
is_long != (physical.signed_units > 0.0)) {
15750 sole_entry_incarnation = origin.incarnation;
15751 ++live_entry_origins;
15755 if (live_entry_origins != 1) sole_entry_incarnation = 0;
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_
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);
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
15791 && candidate.projection_position_side
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
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);
15816 revive_brackets_after_margin(event, context);
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);
15824 const bool current_debit_observed =
15825 current_debited_applied_ordinals_.erase(event.
ordinal) != 0;
15827 consume_closed_trade_rows(event,
15828 placement_snapshot ? &*placement_snapshot :
nullptr);
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);
15859 for (
auto& cohort : cohorts_by_id_) cohort.second.live_units_by_origin.clear();
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;
15876 if (revival != pending_margin_revivals_.end()) {
15878 pending_margin_revivals_.erase(revival);
15879 const bool reached = physical.signed_units > 0.0
15888 const bool exit_is_buy = physical.signed_units < 0.0;
15891 exit_is_buy,
false)};
15900 ?
event.resolved_price : kNaN;
15905 + snapshot.
from_entry +
"\x1fmargin-revival";
15906 const auto accepted = submit_or_replace(
15907 std::move(request), std::move(snapshot),
false, replacement_key);
15909 bracket_families_[family_key].push_back(*accepted);
15911 (void)require_host().execute_current(
15912 {*accepted, NativeCurrentPriceRule::NearestTick});
15917 if (require_host().physical_position().signed_units == 0.0) {
15918 std::vector<SourceId> exit_owners;
15923 if (
exit) exit_owners.push_back(candidate.from_entry);
15925 for (
const auto& handle : live_handles_) {
15926 const auto found = placement_.find(handle.incarnation);
15927 if (found != placement_.end()) collect_owner(found->second);
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) {
15940 for (
const auto& handle : live_handles_) {
15942 const auto found = placement_.find(handle.incarnation);
15943 if (found != placement_.end() && found->second.opening
15945 && found->second.source_id == owner) {
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;
15955 for (
const auto& owner : exit_owners) {
15959 if (!pending_parent(owner)) cancel_exit_orders_for_full_close(owner);
15967 retire_in_position_exits_at_flat(
true,
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();
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;
16004 if (!found->second.from_entry.empty()
16005 && std::find(ended_sources.begin(), ended_sources.end(),
16006 found->second.from_entry) == ended_sources.end()) {
16009 (void)require_host().cancel(handle);
16013 bracket_shadowed_openings_.clear();
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;
16019 update_l4c_lifecycle(event, context);
16021 if (event.
ordinal != day_ledger_.observed_applied_ordinal) {
16022 day_ledger_.observed_applied_ordinal =
event.ordinal;
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");
16037 ++day_ledger_.consecutive_loss_days;
16038 }
else if (pnl > 0.0) {
16039 day_ledger_.consecutive_loss_days = 0;
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);
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;
16055 && policy_script_bar_valid_
16057 && after_margin.signed_units != 0.0 && !one_x_long) {
16063 (void)schedule_margin_call_path(policy_script_bar_, context);
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()
16078 && policy_script_bar_valid_
16080 && finite_positive(policy_script_bar_.high)) {
16081 (void)submit_margin_call_slice(policy_script_bar_.high, context);
16087 risk_.intraday_cancel_pending =
true;
16090 if (preclose_intraday_loss) {
16092 risk_.intraday_cancel_pending =
true;
16093 if (require_host().physical_position().signed_units != 0.0) {
16099 snapshot.
source_id =
"__intraday_loss__";
16101 snapshot.
sizing = sizing_snapshot();
16102 const auto accepted = submit_or_replace(
16103 std::move(request), std::move(snapshot),
false,
16104 "__intraday_loss_close__");
16106 (void)require_host().execute_current(
16107 {*accepted, NativeCurrentPriceRule::NearestTick});
16111 if (risk_.intraday_cancel_pending) {
16112 risk_.intraday_cancel_pending =
false;
16115 if (placement_snapshot && placement_snapshot->opening
16116 && std::abs(event.
opened_units) > 0.0 && policy_script_bar_valid_) {
16122 const bool commissioned_short_opening =
16123 require_host().physical_position().signed_units < 0.0
16124 && config_.margin_short == 100.0
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;
16141 const bool flat_dual_stop_member = last_bar_dual_entry_path_ != 0
16142 && placement_snapshot->projection_position_side
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);
16163 const bool stable_opening_fx = staged_.account_fx_effective_from_ms.empty()
16164 || placement_snapshot->sizing.fx
16166 const bool zero_fee_true_flat_default =
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
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;
16190 const bool short_preempted_by_priced_exit = opened_position.signed_units < 0.0
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;
16200 if (row.projection_created_bar < 0
16209 if (!row.from_entry.empty()
16210 && !from_entry_filled_this_cycle(row.from_entry)) {
16213 if (std::isfinite(row.exit_levels.stop)
16214 && policy_script_bar_.high >= row.exit_levels.stop) {
16217 if (std::isfinite(row.exit_levels.limit)
16218 && policy_script_bar_.low <= row.exit_levels.limit) {
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
16236 const double opening_equity = require_host().native_marked_equity(
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);
16247 if (!defer_slipped_pooc_rounding) {
16248 (void)submit_margin_call_slice(
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);
16260 schedule_margin_call_path(policy_script_bar_, context);
16264 schedule_intraday_loss_path(policy_script_bar_, context);
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()) {
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()
16280 stale_margin_requests.push_back(handle);
16283 for (
const auto& handle : stale_margin_requests) {
16284 const auto cancelled = require_host().cancel(handle);
16285 if (cancelled.status == native_order::CancelStatus::Cancelled)
16288 (void)schedule_margin_call_path(policy_script_bar_, context);
16294 && policy_script_bar_valid_
16296 && !config_.process_orders_on_close && !config_.calc_on_order_fills
16297 && staged_.account_fx_effective_from_ms.empty()) {
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()
16314 stale_margin_requests.push_back(handle);
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)
16323 if (after_exit.signed_units != 0.0)
16324 (void)schedule_margin_call_path(policy_script_bar_, context);
16325 }
else if (!one_x_long) {
16335 if (after_exit.signed_units != 0.0)
16336 (void)schedule_margin_call_path(policy_script_bar_, context);
16339 apply_fx_opening_margin_slice(event, context);
16340 refresh_pending_view();
16350 && !placement_snapshot->from_entry.empty()
16351 && require_host().physical_position().signed_units != 0.0
16352 && current_position_cycle_ > 0) {
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)
16359 std::vector<native_order::RequestHandle> adds;
16360 double units = 0.0;
16361 for (
const auto& lot : pine->pyramid_entries_) {
16363 || lot.entry_bar_index != bar
16364 || lot.entry_id != placement_snapshot->from_entry
16365 || !(lot.qty > internal::kQtyEpsilon)) {
16368 const auto opened = std::find_if(
16369 cohort->second.opened.begin(), cohort->second.opened.end(),
16371 return handle.incarnation == lot.entry_incarnation;
16373 if (opened == cohort->second.opened.end())
continue;
16374 adds.push_back(*opened);
16377 if (!adds.empty() && units > 0.0) {
16380 request.
label = placement_snapshot->source_id;
16381 request.
comment = placement_snapshot->comment;
16384 std::move(adds), current_position_cycle_};
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");
16396 (void)require_host().execute_current(
16397 {*accepted, NativeCurrentPriceRule::NearestTick});
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;
16416 return intraday_loss_relabel_ordinals_.erase(ordinal) != 0;
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 {
16432 std::uint64_t command_sequence = 0;
16433 std::uint64_t incarnation = 0;
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});
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,
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()) {
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;
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;
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);
16470 incarnation = next_incarnation++;
16473 rows.push_back({incarnation, pending.snapshot,
true});
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});
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;
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;
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;
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;
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;
16519 return close_reserved_units_.size();
16523 return close_first_units_.size();
16527 std::size_t count = 0;
16528 for (
const auto& owner : close_callsite_reserved_units_) count += owner.second.size();
16533 std::size_t count = 0;
16534 for (
const auto& owner : close_callsite_first_units_) count += owner.second.size();
16539 double total = 0.0;
16540 for (
const auto& owner : close_callsite_reserved_units_)
16541 for (
const auto& claim : owner.second) total += claim.second;
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});
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;
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()
16578 && found->second.source_id ==
id) {
16579 matches.push_back(handle);
16582 for (
const auto& handle : matches) {
16583 const auto result = require_host().cancel(handle);
16584 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
16586 named_entry_cancel_tokens_.erase(
id);
16592 risk_.max_drawdown = value;
16593 if (percent) risk_.max_drawdown_percent =
true;
16596 risk_.max_intraday_loss = value;
16597 if (percent) risk_.max_intraday_loss_percent =
true;
16601 source_margin_call_enabled_ = enabled;
16604 market_pyramid_adds_.insert(incarnation);
16610 auto result = std::move(first_open_newborns_);
16611 first_open_newborns_.clear();
16615void PineExecutionAdapter::refresh_pending_view() noexcept {
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);
16631bool PineExecutionAdapter::projected_raw_pending_at(
16632 int index,
const PlacementSnapshot*& snapshot,
16633 native_order::RequestHandle& handle)
const noexcept {
16634 snapshot =
nullptr;
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;
16645 offset -= live_handles_.size();
16646 const auto locate = [&](
const auto& rows,
auto read) {
16647 if (offset >= rows.size()) {
16648 offset -= rows.size();
16651 snapshot = &read(rows[offset]);
16654 if (locate(pending_same_bar_commands_,
16656 return row.snapshot;
16658 if (locate(pending_entries_,
16660 return row.snapshot;
16662 if (locate(pending_bracket_legs_,
16664 return row.snapshot;
16666 if (locate(pending_coof_requests_,
16668 return row.snapshot;
16670 return locate(source_shadow_pending_,
16672 return row.snapshot;
16676bool PineExecutionAdapter::same_projected_order(
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;
16690int PineExecutionAdapter::projected_pending_size() const noexcept {
16691 const int raw_count = projected_raw_pending_size();
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)) {
16707 if (!duplicate && count != std::numeric_limits<int>::max()) ++count;
16712bool PineExecutionAdapter::projected_pending_at(
16714 native_order::RequestHandle& handle)
const noexcept {
16715 snapshot =
nullptr;
16717 if (index < 0)
return false;
16718 const int raw_count = projected_raw_pending_size();
16720 for (
int raw = 0; raw < raw_count; ++raw) {
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) {
16727 native_order::RequestHandle earlier_handle;
16728 if (projected_raw_pending_at(prior, earlier, earlier_handle) && earlier
16729 && same_projected_order(*candidate, *earlier)) {
16734 if (duplicate)
continue;
16735 if (projected++ == index) {
16736 snapshot = candidate;
16737 handle = candidate_handle;
16745 return owner_ ? owner_->projected_pending_size() : 0;
16749 int* close_only,
int* partition)
const noexcept {
16750 if (!owner_ || !qty || !close_only || !partition)
return -1;
16753 if (!owner_->projected_pending_at(index, row, handle) || !row)
return -1;
16754 const auto& snapshot = *row;
16758 if (!snapshot.opening)
return 1;
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)) {
16768 *qty = std::max(0.0, snapshot.frozen_market_transaction_units
16769 - std::abs(physical.signed_units));
16771 kernel_close_only = !(*qty > 1e-10);
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;
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;
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
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
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;
16808 }
else if (!sized && default_stop) {
16809 *qty = snapshot.sizing.frozen_units;
16812 }
else if (!sized && !default_stop_shape && unpriced_market
16813 && finite_positive(snapshot.sizing.frozen_units)) {
16814 *qty = snapshot.sizing.frozen_units;
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;
16827 const int type = owner_->config_.default_qty_type;
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)
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)
16846 *qty = floor_quantity_grid(owner_->config_.default_qty_value,
16847 owner_->staged_.quantity_grid);
16854 && snapshot.projection_position_side
16855 != (physical.signed_units > 0.0
16858 && !snapshot.projection_predecessor_market;
16859 *close_only = (snapshot.affordability_close_only || prior_cycle_close_only
16860 || kernel_close_only) ? 1 : 0;
16865 if (!owner_)
return -1;
16868 if (!owner_->projected_pending_at(index, snapshot, handle) || !snapshot)
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;
16875 double* trail_activation)
const noexcept {
16876 if (!owner_ || !stop || !limit || !trail_activation)
return -1;
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();
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) {
16894 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
16895 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
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;
16919 if (std::isnan(*limit) && !std::isnan(profit_ticks)) {
16920 *limit = source_level_on_price_grid(
16921 entry + direction * profit_ticks * tick, tick);
16923 if (std::isnan(*stop) && !std::isnan(loss_ticks)) {
16924 *stop = source_level_on_price_grid(
16925 entry - direction * loss_ticks * tick, tick);
16928 *trail_activation = kNaN;
16929 if (!std::isnan(trail_points)) {
16933 entry + direction * ticks * tick, tick);
16936 *trail_activation = trail_price;
16942 if (!owner_ || !out)
return -1;
16945 if (!owner_->projected_pending_at(index, row, handle) || !row)
return -1;
16948 std::memset(out, 0,
sizeof(*out));
16949 out->struct_version = 1;
16950 out->size =
static_cast<std::uint32_t
>(
sizeof(*out));
16952 copy_pending_prefixed_string(
"__close__", snapshot.
source_id,
16953 out->id, &out->id_truncated,
16956 copy_pending_string(snapshot.
source_id, out->id,
16957 &out->id_truncated, &out->id_hash64);
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;
16974 for (
int raw = 0; raw < owner_->projected_raw_pending_size(); ++raw) {
16977 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
16978 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
17000 out->qty_type = snapshot.
qty_type;
17002 out->oca_type = snapshot.
oca_type;
17004 out->created_seq =
static_cast<std::int64_t
>(snapshot.
source_sequence);
17014 out->recreated_after_named_cancelled_entry_incarnation =
17020 out->created_during_coof_recalc = snapshot.
birth.
from_fill() ? 1U : 0U;
17033 out->reverses_same_bar_market_from_flat =
17051 out->default_stop_sizing_price = snapshot.
sizing.
price;
17054 out->sizing_fx = snapshot.
sizing.
fx;
17056 out->opening_affordability_exemption_candidate = snapshot.
opening
17059 out->explicit_flat_admission_candidate = snapshot.
opening
17074 out->market_admission_observation_present = snapshot.
opening ? 1U : 0U;
17075 out->market_admission_observation_original_sizing_present =
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 =
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;
17110 out->created_while_in_position = !snapshot.
opening
17127 out->dormant_bracket = snapshot.
legs.
dormant() ? 1U : 0U;
17134 out->dormant_trail_leg_dead = snapshot.
legs.
retired(exit_legs::Leg::Trail) ? 1U : 0U;
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());
17150 out->pine_birth_reach =
static_cast<std::int32_t
>(snapshot.
birth_reach);
17153 out->pine_frozen_market_instruction_transaction_units =
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;
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;
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;
17209 out->legs_target_incarnation = target.
incarnation;
17210 out->legs_target_owner = target.owner;
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;
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) {
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;
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);
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;
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;
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;
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;
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;
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;
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;
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;
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;
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])
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;
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;
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;
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;
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;
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;
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);
17508 const auto& observation =
admission.observation();
17509 if (observation) out->market_admission_observation_present = 1U;
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;
17608 out->market_admission_observation_explicit_equity = observation->explicit_equity;
17609 out->market_admission_observation_explicit_price = observation->explicit_price;
17611 out->market_admission_review_present =
admission.review() ? 1U : 0U;
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;
17619 out->market_admission_sizing_revision_present =
admission.sizing_revision() ? 1U : 0U;
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;
17628 out->cancellation_cause =
static_cast<std::int32_t
>(snapshot.
cancellation.
cause);
17642 if (!owner_)
return -1;
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);
17651 return owner_ && owner_->host_ ? owner_->host_->trail_best_price() : kNaN;
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
bool holds_limit() const noexcept
bool holds_stop() const noexcept
const std::optional< ExitPlacementEvidence > & evidence() const noexcept
bool retained_parent_first() const
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 Definition & current_definition() const
bool pending_replacement() const
double trail_best() const
int64_t excluded_bar() const
const std::optional< Action > & last_action() const
uint64_t revision() const
double trail_prefix() const
double original_stop() const
uint64_t generation(Leg leg) const
const std::optional< Suspension > & suspension() const
bool retired(Leg leg) 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 size() const noexcept
int last_bar_dual_entry_path() const noexcept
void observe_terminal_receipts()
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 &)
bool source_margin_rounded_tie_veto() const
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 cancel(const SourceId &id)
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)
void end_coof_recalc() noexcept
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
compat::pine::IntradayCap cap
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()
void flush_pending_entries()
int source_entry_slot_count() const noexcept
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)
void enable_intraday_cap() noexcept
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 flush_pending_closes()
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
friend class PineStrategyHost
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
void anchor_relative_exits()
bool calc_on_order_fills() 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
void attach_execution_adapter() 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
MarketAdmissionJournal admission_journal
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
execution::Flatten Flatten
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)
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...
std::vector< BookObservation > book
Configuration configuration
std::vector< BookObservation > reviewed
Ephemeral read-only facts of one anchored-leg materialization (L7b), offered to the host exactly once...
Borrowed begin-call facts.
MagnifierDistribution magnifier_distribution
int magnifier_volume_weighted_min_samples
bool magnifier_volume_weighted
Read-only owning-value facts for one candidate.
native_order::ExecutionScope scope
native_order::DefinitionRef definition
native_order::MatchCursor cursor
native_order::Remaining remaining
native_order::NativeCandidatePriceKind price_kind
native_order::TriggerState trigger_state
double opposite_book_units
native_order::RequestHandle target
double default_resolved_price
std::optional< double > trigger_level
NativePhysicalPosition position
double scope_exposure_units
Ephemeral factual view of one kernel-issued liquidation before its units are fixed.
native_order::MatchCursor cursor
NativePhysicalPosition position
Ephemeral factual view of one kernel check point, offered to the host before the check runs.
NativeMarginCheckKind kind
bool liquidation_resting
Whether the model already holds a liquidation resting from an earlier admitted point.
native_order::MatchCursor cursor
The host's answer to one requirement view.
Ephemeral factual view of the numbers the kernel is about to compare, at one check point,...
native_order::MatchCursor cursor
Ephemeral factual view of one prepared execution before any physical effect.
native_order::MatchCursor cursor
execution::AccountEffectProjection account
native_order::DefinitionRef definition
double inspected_opened_units
double inspected_closed_units
native_order::RequestHandle target
One accepted realtime print before native matching at its current decision point.
NativeDecisionContext decision
double resulting_abs_notional
uint64_t incarnation() const
NativePathPhase path_phase
int64_t effective_time_ms
NativePriceProvenance provenance
int64_t script_bar_open_ms
NativeCoordinate coordinate
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,...
const RequestHandle & handle() const noexcept
std::size_t closed_trade_count
const Request & request() const noexcept
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).
NativeLiquidationLevelBase level_base
NativeLiquidationCheck check
std::optional< double > maintenance_long
NativeMarginEquityBasis basis
std::string liquidation_comment
std::string liquidation_label
std::optional< double > maintenance_short
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.
std::uint32_t max_recalculations_per_point
NativeFeedTolerance legacy_tolerance
NativePathOrder path_order
NativeCalculationTrigger calculation
Calculation timing.
NativeCloseExecution close_execution
NativeOpenDirections allowed_open_directions
std::string chart_timezone
std::optional< double > quantity_grid
std::optional< NativeMarginModel > margin
Opt-in generic margin model.
native_order::RunIdentity identity
bool timeframe_undetected
A public begin with fewer than two bars may not establish a timeframe.
std::uint32_t slippage_ticks
NativeAbortReporting abort_reporting
NativeReportPolicy report_policy
PineCancellationCause cause
std::int32_t close_claim_release
double close_claim_retired
std::uint64_t target_incarnation
std::int64_t target_owner
std::uint64_t target_revision
std::int64_t source_sequence
double close_claim_consumed
std::uint64_t source_incarnation
bool process_orders_on_close
bool close_entries_rule_any
double paired_flat_market_transaction_qty
bool retained_parent_topology
double signal_close_mc_remaining_qty
bool close_first_carry_valid
MarketAdmissionDraft market_admission
bool terms_priced_reverse
std::uint64_t recreated_after_named_cancelled_entry_incarnation
bool frozen_market_target_was_long
std::int32_t signal_close_mc_bar
double projection_affordability_signal_price
std::uint64_t named_cancel_surviving_exit_incarnation
std::uint64_t close_callsite_token
bool replacement_predecessor_market
double projection_default_stop_signal_close
bool projection_coof_mid_bar
bool projection_opposite_market_predecessor
double projection_affordability_held_qty
bool reservation_deferred_to_pending_entry
bool projection_over_pyramiding
std::uint64_t projection_predecessor
std::int32_t coof_cascade_seg_i
bool projection_coof_at_terminal
std::uint64_t command_sequence
bool affordability_policy_active
PineSizingSnapshot sizing
std::uint8_t sequential_rank
std::int64_t placement_cycle
double close_first_carry_qty
bool stop_limit_activated
double paired_flat_market_signal_equity
PineExitLevels exit_levels
std::int32_t projection_created_bar
native_order::RequestHandle paired_reversal_parent
ReservationGrowthSource reservation_growth_source
native_order::RequestHandle bracket_origin
std::uint64_t sequential_group
compat::pine::HistoricalBirthReach birth_reach
bool defer_until_post_parent_calculation
std::int32_t projection_position_side
double forced_execution_price
exit_legs::Lifecycle legs
bool pooc_global_full_exit_dynamic_qty
std::int64_t paired_flat_market_peer_seq
PineCancellationReceipt cancellation
bool pooc_global_full_exit_tracks_bound_adds
bool rounded_signal_cost_close_only
std::uint64_t signal_close_mc_entry_incarnation
bool projection_created_bar_pinned
bool paired_flat_market_candidate
double projection_default_stop_equity
bool pooc_global_full_exit_bound_add
std::uint64_t source_sequence
bool fixed_exit_reservation
bool frozen_market_targeted_close
bool projection_created_during_coof
double paired_flat_market_own_qty
bool close_retire_ledger_whole
std::uint32_t close_batch_calls
double projection_tv_carry_qty
double close_first_target
bool projection_predecessor_market
bool frozen_market_instruction
bool post_parent_calc_level_fill
double paired_flat_market_signal_pointvalue
std::int64_t placement_script_open_ms
bool projection_after_close
double paired_flat_market_signal_margin_pct
double projection_explicit_equity
double projection_affordability_equity
double close_pending_later_qty
double retained_trail_best
double paired_flat_market_signal_close
bool projection_predecessor_exit
std::uint64_t command_ordinal
double frozen_market_transaction_units
ExitLegActivation leg_activation
bool affordability_close_only
bool restored_after_margin
std::uint64_t signal_close_mc_fill_seq
double paired_flat_market_signal_fx
double projection_remaining_qty
std::int64_t placement_sub_open_ms
double trail_activation_level
bool close_first_ledger_consumed
bool coof_cascade_inflight_fires
double projection_explicit_signal_close
ReservationExpansion reservation_expansion
double frozen_market_own_units
compat::pine::ExitActivationPolicy exit_activation
double frozen_reversal_transaction
std::string chart_timezone
std::optional< double > quantity_grid