11#include "../engine_internal.hpp"
12#include "../engine_value_invalid.hpp"
13#include "../native_execution_consumer.hpp"
15#include "../timezone.hpp"
23#include "../command_scratch_cache.hpp"
34#ifndef PINEFORGE_POSITION_TAIL_DIAGNOSTICS
35#ifdef PINEFORGE_POSITION_TAIL_OBSERVE
36#define PINEFORGE_POSITION_TAIL_DIAGNOSTICS \
37 ::pineforge::source::detail::PositionTailDiagnostics
39#define PINEFORGE_POSITION_TAIL_DIAGNOSTICS \
40 ::pineforge::source::detail::PositionTailNoDiagnostics
46#ifndef PINEFORGE_TAIL_WORK
47#define PINEFORGE_TAIL_WORK(site, count) ((void)0)
51#ifndef PINEFORGE_LOOKUP_INDEX_WORK
52#define PINEFORGE_LOOKUP_INDEX_WORK(site, owner, run, count) ((void)0)
54#ifndef PINEFORGE_RETIREMENT_OBSERVE
55#define PINEFORGE_RETIREMENT_OBSERVE(rows, cycle, askable, roots, candidates, first) ((void)0)
57#ifndef PINEFORGE_MARGIN_REVIVAL_OBSERVE
58#define PINEFORGE_MARGIN_REVIVAL_OBSERVE(before, rows, event, context, units, cycle, selected) ((void)0)
87#define PF_RARE(condition) __builtin_expect(static_cast<bool>(condition), 0)
90#ifdef PINEFORGE_CR277_DIAGNOSTIC
95std::atomic<std::uint64_t> cr277_pending_close_pushes[2]{};
97extern "C" std::uint64_t pineforge_cr277_diagnostic_hits(
int site)
noexcept {
98 return site >= 0 && site < 2
99 ? cr277_pending_close_pushes[site].load(std::memory_order_relaxed) : 0;
102#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
104thread_local double (*adapter_execution_value_fixture)(
const char*, double) =
nullptr;
106void fixture_adapter_execution_values(
double (*fixture)(
const char*,
double))
noexcept {
107 adapter_execution_value_fixture = fixture;
110bool PineExecutionAdapter::modeled_input()
const {
116 ? point->decision.coordinate.provenance : NativePriceProvenance::Calculation);
121constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
123void check_slippage_fill_price(
double value,
bool has_basis) {
124 if (has_basis && !std::isfinite(value)) {
125 engine_command_value_invalid_or_count(
"slippage.fill_price", EngineValueStage::slippage,
126 EngineValueReason::numeric_overflow);
130#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
131double fixture_adapter_execution_value(
const char* site,
double value) {
132 return adapter_execution_value_fixture ? adapter_execution_value_fixture(site, value) : value;
136bool same_double_bits(
double left,
double right)
noexcept {
137 std::uint64_t left_bits = 0;
138 std::uint64_t right_bits = 0;
139 std::memcpy(&left_bits, &left,
sizeof(left_bits));
140 std::memcpy(&right_bits, &right,
sizeof(right_bits));
141 return left_bits == right_bits;
144bool same_exit_levels(
const PineExitLevels& left,
const PineExitLevels& right)
noexcept {
145 return same_double_bits(left.limit, right.limit)
146 && same_double_bits(left.stop, right.stop)
147 && same_double_bits(left.trail_points, right.trail_points)
148 && same_double_bits(left.trail_offset, right.trail_offset)
149 && same_double_bits(left.trail_price, right.trail_price)
150 && same_double_bits(left.profit_ticks, right.profit_ticks)
151 && same_double_bits(left.loss_ticks, right.loss_ticks);
158bool withdrawn(
const exit_legs::Lifecycle& legs)
noexcept {
159 return legs.retired(exit_legs::Leg::Stop) && legs.retired(exit_legs::Leg::Limit)
160 && legs.retired(exit_legs::Leg::Trail);
170bool unbound_limit_without_revival(
const PlacementSnapshot& row)
noexcept {
171 const auto& levels = row.exit_levels;
172 const auto no_protection = [](
const exit_legs::Prices& prices) {
173 return std::isnan(prices.stop_price) && std::isnan(prices.loss_ticks)
174 && std::isnan(prices.trail_points) && std::isnan(prices.trail_price)
175 && std::isnan(prices.trail_offset);
177 const auto& suspension = row.legs.suspension();
179 && row.bracket_origin.incarnation == 0 && row.per_entry_origin == 0
180 && !row.pending_bound_exit && !row.legs.pending_replacement()
181 && std::isnan(levels.stop) && std::isnan(levels.loss_ticks)
182 && std::isnan(levels.trail_points) && std::isnan(levels.trail_price)
183 && std::isnan(levels.trail_offset)
184 && no_protection(row.legs.prices())
185 && (!suspension || !suspension->revival_definition
186 || no_protection(suspension->revival_definition->prices()));
189bool filled_limit_without_revival(
const PlacementSnapshot& row)
noexcept {
190 return row.filled_exit && unbound_limit_without_revival(row);
195std::uint64_t settled_bracket_hash(std::uint64_t key,
const BracketRoster::Settled& row) {
196 BrokerStateHashSink f;
197 f.u(key); f.u(row.last); f.u(row.count); f.u(row.digest);
201bool finite_positive(
double value)
noexcept {
202 return std::isfinite(value) && value > 0.0;
205bool finite_non_negative(
double value)
noexcept {
206 return std::isfinite(value) && value >= 0.0;
211bool price_present(
double value)
noexcept {
return !std::isnan(value); }
213bool pure_stop_entry_marketable_at(
const PlacementSnapshot& snapshot,
double open)
noexcept {
215 if (!finite_positive(snapshot.exit_levels.stop))
return false;
216 if (finite_positive(snapshot.exit_levels.limit)
217 || finite_positive(snapshot.exit_levels.trail_points)
218 || finite_positive(snapshot.exit_levels.trail_price)
219 || finite_positive(snapshot.exit_levels.trail_offset)) {
222 return snapshot.is_long ? open >= snapshot.exit_levels.stop
223 : open <= snapshot.exit_levels.stop;
227bool pure_limit_entry(
const PlacementSnapshot& snapshot)
noexcept {
228 return finite_positive(snapshot.exit_levels.limit)
229 && !finite_positive(snapshot.exit_levels.stop)
230 && !finite_positive(snapshot.exit_levels.trail_points)
231 && !finite_positive(snapshot.exit_levels.trail_price)
232 && !finite_positive(snapshot.exit_levels.trail_offset);
236 double current,
bool high_first,
237 double tick = 0.0,
int slippage = 0)
noexcept {
238 const double tol = (slippage > 0 && tick > 0.0) ? (slippage + 0.5) * tick : 1e-6;
239 const auto at = [tol](
double left,
double right) {
240 return std::abs(left - right) <= tol;
243 case NativePathPhase::Open:
244 return high_first ? bar.high : bar.low;
245 case NativePathPhase::High:
246 if (!at(current, bar.high) && current < bar.high - tol)
return bar.high;
247 return high_first ? bar.low : kNaN;
248 case NativePathPhase::Low:
249 if (!at(current, bar.low) && current > bar.low + tol)
return bar.low;
250 return high_first ? kNaN : bar.high;
251 case NativePathPhase::Close:
253 case NativePathPhase::None:
259std::uint64_t fnv_append(std::uint64_t value,
const void* bytes, std::size_t size)
noexcept {
260 const auto* p =
static_cast<const unsigned char*
>(bytes);
261 for (std::size_t i = 0; i < size; ++i) {
263 value *= 1099511628211ULL;
268std::uint64_t fnv_string(std::string_view value)
noexcept {
269 return fnv_append(1469598103934665603ULL, value.data(), value.size());
276bool java_string_hash(std::string_view text, std::uint32_t& hash)
noexcept {
278 for (std::size_t i = 0; i < text.size();) {
279 const auto lead =
static_cast<unsigned char>(text[i]);
280 std::uint32_t point = 0;
281 std::size_t width = 0;
282 if (lead < 0x80) { point = lead; width = 1; }
283 else if ((lead & 0xE0) == 0xC0) { point = lead & 0x1FU; width = 2; }
284 else if ((lead & 0xF0) == 0xE0) { point = lead & 0x0FU; width = 3; }
285 else if ((lead & 0xF8) == 0xF0) { point = lead & 0x07U; width = 4; }
287 if (i + width > text.size())
return false;
288 for (std::size_t k = 1; k < width; ++k) {
289 const auto next =
static_cast<unsigned char>(text[i + k]);
290 if ((next & 0xC0) != 0x80)
return false;
291 point = (point << 6) | (next & 0x3FU);
293 if ((width == 2 && point < 0x80U) || (width == 3 && point < 0x800U)
294 || (width == 4 && (point < 0x10000U || point > 0x10FFFFU))
295 || (point >= 0xD800U && point <= 0xDFFFU)) {
299 if (point >= 0x10000U) {
301 h = 31U * h + (0xD800U + (point >> 10));
302 h = 31U * h + (0xDC00U + (point & 0x3FFU));
311void copy_pending_string(std::string_view value,
char* out, std::uint8_t* truncated,
312 std::uint64_t* hash)
noexcept {
313 *hash = fnv_string(value);
314 const std::size_t size = std::min<std::size_t>(value.size(), 63U);
315 if (size != 0) std::memcpy(out, value.data(), size);
317 *truncated = value.size() > size ? 1U : 0U;
320void copy_pending_prefixed_string(std::string_view prefix, std::string_view value,
321 char* out, std::uint8_t* truncated,
322 std::uint64_t* hash)
noexcept {
325 std::uint64_t digest = fnv_append(
326 1469598103934665603ULL, prefix.data(), prefix.size());
327 *hash = fnv_append(digest, value.data(), value.size());
328 const std::size_t total = prefix.size() + value.size();
329 const std::size_t prefix_size = std::min<std::size_t>(prefix.size(), 63U);
330 if (prefix_size != 0) std::memcpy(out, prefix.data(), prefix_size);
331 const std::size_t value_size = std::min<std::size_t>(
332 value.size(), 63U - prefix_size);
334 std::memcpy(out + prefix_size, value.data(), value_size);
335 out[prefix_size + value_size] =
'\0';
336 *truncated = total > prefix_size + value_size ? 1U : 0U;
354std::uint64_t source_key(
const SourceId& left,
const SourceId& right)
noexcept {
355 std::uint64_t value = fnv_string(left);
356 const char separator =
'\0';
357 value = fnv_append(value, &separator,
sizeof(separator));
361double nearest_tick(
double value,
double tick)
noexcept {
362 if (!std::isfinite(value) || !finite_positive(tick))
return value;
363 return std::floor(value / tick + 0.5) * tick;
370double source_decimal_tick(
double value,
double tick)
noexcept {
371 if (!std::isfinite(value) || !finite_positive(tick))
return value;
372 const double k = std::floor(value / tick + 0.5);
373 const double inverse = 1.0 / tick;
374 const double integral_inverse = std::floor(inverse + 0.5);
375 if (integral_inverse > 0.0
376 && std::abs(inverse - integral_inverse) <= 1e-6 * integral_inverse) {
377 return k / integral_inverse;
382double source_bar_fill_tick(
double value,
double tick)
noexcept {
383 if (!std::isfinite(value) || !finite_positive(tick))
return value;
384 const double k = std::floor(value / tick + 0.5);
393 if (k * tick == value)
return value;
394 return source_decimal_tick(value, tick);
400bool source_same_point(
double booked,
double raw,
double tick)
noexcept {
402 || (finite_positive(tick) && source_bar_fill_tick(raw, tick) == raw
403 && nearest_tick(raw, tick) == booked);
408double source_arith_value(
const std::optional<double>& value,
const char* field) {
414double directional_tick(
double value,
double tick,
bool upward,
415 const char* field =
nullptr) {
416 if (!std::isfinite(value) || !finite_positive(tick))
return value;
418 const double scaled = source_arith_value(
checked_div(value, tick), field);
420 upward ? std::ceil(scaled - 1e-9) : std::floor(scaled + 1e-9), tick), field);
422 const double scaled = value / tick;
423 return (upward ? std::ceil(scaled - 1e-9) : std::floor(scaled + 1e-9)) * tick;
433bool add_margin_exceeds_equity(
double held_units,
const PineSizingSnapshot& sizing,
434 double margin_pct,
double mintick,
double pointvalue)
noexcept {
435 const double signal = nearest_tick(sizing.mark, mintick);
436 const double unit_margin = signal * pointvalue * sizing.fx * margin_pct / 100.0;
437 const double required = (held_units + sizing.frozen_units) * unit_margin;
438 const double slack = std::max(1e-9, std::abs(sizing.equity) * 1e-12);
439 return margin_pct > 0.0 && std::isfinite(required) && std::isfinite(sizing.equity)
440 && required > sizing.equity + slack;
443int price_grid_decimals(
double tick)
noexcept {
444 if (!finite_positive(tick))
return -1;
445 double scaled = tick;
446 for (
int digits = 0; digits <= 10; ++digits) {
447 const double k = std::floor(scaled + 0.5);
448 if (k >= 1.0 && std::abs(scaled - k) <= 1e-6 * k)
return digits;
454double source_level_on_price_grid(
double level,
double tick,
455 const char* field =
nullptr) {
456 if (!std::isfinite(level))
return level;
457 const int digits = price_grid_decimals(tick);
458 if (digits < 0)
return level;
459 double price_scale = 1.0;
460 for (
int i = 0; i < digits; ++i) price_scale *= 10.0;
461 const double point_size = 1.0 / price_scale;
462 const double points = field
463 ? source_arith_value(
checked_div(level, point_size), field)
464 : level / point_size;
465 const double grid = std::floor(points + 0.5);
466 if (std::abs(points - grid) <= 0.01 / price_scale)
467 return grid / price_scale;
471double source_trigger_threshold(
double level,
double tick,
472 bool is_buy,
bool is_limit,
473 const char* field =
nullptr) {
474 if (!std::isfinite(level) || !finite_positive(tick))
return level;
478 if (level <= 0.0)
return 0.0;
479 const bool upward = is_limit ? !is_buy : is_buy;
480 double scaled = field ? source_arith_value(
checked_div(level, tick), field) : level / tick;
481 const double inverse = 1.0 / tick;
482 const double integral_inverse = std::floor(inverse + 0.5);
483 if (integral_inverse > 0.0
484 && std::abs(inverse - integral_inverse) <= 1e-6 * integral_inverse) {
485 scaled = field ? source_arith_value(
checked_mul(level, integral_inverse), field)
486 : level * integral_inverse;
487 const double nearest_index = std::floor(scaled + 0.5);
488 if (source_bar_fill_tick(level, tick) == level)
489 scaled = nearest_index;
491 const double target_index = upward
492 ? std::ceil(scaled - 1e-12)
493 : std::floor(scaled + 1e-12);
494 const double grid = field ? source_arith_value(
checked_mul(target_index, tick), field)
495 : target_index * tick;
496 double threshold = field
497 ? source_arith_value(
checked_add(grid, (upward ? -0.5 : 0.5) * tick), field)
498 : grid + (upward ? -0.5 : 0.5) * tick;
503 const auto reaches_target = [&](
double price) {
504 const double rounded_index = field
506 : std::floor(
price / tick + 0.5);
507 return upward ? rounded_index >= target_index
508 : rounded_index <= target_index;
510 for (
int i = 0; i < 16 && !reaches_target(threshold); ++i) {
511 threshold = std::nextafter(threshold, upward
512 ? std::numeric_limits<double>::infinity()
513 : -std::numeric_limits<double>::infinity());
518 for (
int i = 0; i < 16; ++i) {
519 const double candidate = std::nextafter(threshold, upward
520 ? -std::numeric_limits<double>::infinity()
521 : std::numeric_limits<double>::infinity());
522 if (!reaches_target(candidate))
break;
523 threshold = candidate;
527 if (field && !std::isfinite(threshold))
542double source_trail_arm_level(
double activation,
double tick,
bool exit_is_buy)
noexcept {
543 return source_trigger_threshold(activation, tick, exit_is_buy,
true);
546int legacy_volume_weighted_max_samples(
int samples)
noexcept {
547 constexpr int kMaxSamples = 1 << 20;
548 const int nonnegative = std::max(samples, 0);
549 const int scaled = nonnegative > kMaxSamples / 4
550 ? kMaxSamples : nonnegative * 4;
551 return std::max(scaled, 8);
565double source_one_shot_reach_tick(
double resting_level,
double tick,
bool exit_is_buy)
noexcept {
566 return directional_tick(resting_level, tick, !exit_is_buy);
578bool source_trail_reached_at(
double price,
double activation,
double tick,
579 bool exit_is_buy)
noexcept {
580 const double arm = source_trail_arm_level(activation, tick, exit_is_buy);
584double floor_quantity_grid(
double units,
const std::optional<double>& grid)
noexcept {
585 if (!std::isfinite(units) || units <= 0.0)
return 0.0;
586 if (!grid || !std::isfinite(*grid) || *grid <= 0.0)
return units;
587 const double floored = std::floor(units / *grid + 1e-6) * *grid;
591 return floored < units ? floored : units;
594double source_fifo_close_units(
double units,
const std::vector<NativeOpenLot>& lots)
noexcept {
595 if (!std::isfinite(units) || units <= 0.0)
return units;
596 const double lower = units - internal::kQtyEpsilon;
597 const double upper = units + internal::kQtyEpsilon;
599 for (
const auto& lot : lots) {
600 prefix += std::abs(lot.signed_units);
601 if (prefix >= lower && prefix <= upper)
return prefix;
602 if (prefix > upper)
break;
607bool source_fifo_close_needs_resolution(
double units,
608 const std::vector<PyramidEntry>& lots,
609 const std::vector<double>& preceding_closes) {
610 if (!std::isfinite(units) || units <= 0.0)
return false;
611 std::vector<double> remaining;
612 remaining.reserve(lots.size());
613 for (
const auto& lot : lots) remaining.push_back(lot.qty);
614 const auto ambiguous_prefix = [&] {
616 for (
const double quantity : remaining) {
618 if (prefix == units)
return false;
619 if (std::abs(prefix - units) <= internal::kQtyEpsilon)
621 if (prefix > units + internal::kQtyEpsilon)
break;
625 if (ambiguous_prefix())
return true;
626 for (
const double quantity : preceding_closes) {
627 if (!std::isfinite(quantity) || quantity <= 0.0)
continue;
629 double left = quantity;
630 for (
auto& lot : remaining) {
631 if (!(left > internal::kQtyEpsilon))
break;
632 const double amount = std::min(lot, left);
635 left = quantity - closed;
637 remaining.erase(std::remove_if(remaining.begin(), remaining.end(), [](
double lot) {
638 return lot <= internal::kQtyEpsilon;
639 }), remaining.end());
640 if (ambiguous_prefix())
return true;
645double source_money_round(
double value)
noexcept {
646 if (!std::isfinite(value) || value == 0.0)
return value;
647 const double magnitude = std::floor(std::log10(std::abs(value)));
648 const double scale = std::pow(10.0, 9.0 - magnitude);
649 const double rounded = std::floor(std::abs(value) * scale + 0.5) / scale;
650 return value < 0.0 ? -rounded : rounded;
658bool source_price_band_affordable(
double equity,
double units,
659 double fill_price,
double tick)
noexcept {
660 if (!finite_positive(units) || !finite_positive(tick))
return false;
661 const double affordable = source_money_round(source_money_round(equity) / units);
662 return affordable >= nearest_tick(fill_price, tick);
668std::optional<int> decimal_grid_exponent(
double grid)
noexcept {
669 static const std::array<double, 19> powers = [] {
670 std::array<double, 19> out{};
671 for (
int exponent = -12; exponent <= 6; ++exponent) {
672 out[
static_cast<std::size_t
>(exponent + 12)] = exponent >= 0
673 ? std::pow(10.0, exponent) : 1.0 / std::pow(10.0, -exponent);
677 for (
int exponent = -12; exponent <= 6; ++exponent) {
678 if (powers[
static_cast<std::size_t
>(exponent + 12)] == grid)
return exponent;
690std::optional<double> decimal_floor_lot(
double units,
double grid)
noexcept {
691 const auto exponent = decimal_grid_exponent(grid);
692 if (!exponent || !std::isfinite(units) || units <= 0.0)
return std::nullopt;
695 for (; digits <= 17; ++digits) {
696 std::snprintf(text,
sizeof text,
"%.*e", digits - 1, units);
697 if (std::strtod(text,
nullptr) == units)
break;
699 if (digits > 17)
return std::nullopt;
701 std::uint64_t mantissa = 0;
702 const char* cursor = text;
703 for (; *cursor !=
'e' && *cursor !=
'\0'; ++cursor) {
704 if (*cursor >=
'0' && *cursor <=
'9')
705 mantissa = mantissa * 10 +
static_cast<std::uint64_t
>(*cursor -
'0');
707 if (*cursor !=
'e')
return std::nullopt;
708 const int shift = std::atoi(cursor + 1) - (digits - 1) - *exponent;
709 std::uint64_t lots = mantissa;
711 for (
int i = 0; i < -shift && lots > 0; ++i) lots /= 10;
713 for (
int i = 0; i < shift; ++i) {
714 if (lots > std::numeric_limits<std::uint64_t>::max() / 10)
return std::nullopt;
718 std::snprintf(text,
sizeof text,
"%llue%d",
static_cast<unsigned long long>(lots), *exponent);
719 return std::strtod(text,
nullptr);
733double explicit_entry_quantity_floor(
double units,
const std::optional<double>& grid)
noexcept {
734 const double floored = floor_quantity_grid(units, grid);
736 || !grid || !std::isfinite(*grid) || *grid <= 0.0 || !std::isfinite(units)
740 if (!(std::floor(units / *grid + 1e-6) > std::floor(units / *grid)))
return floored;
741 const auto decimal = decimal_floor_lot(units, *grid);
742 return decimal && *decimal < units ? *decimal : floored;
745double source_money_floor_lot(
double units,
const std::optional<double>& grid)
noexcept {
746 if (!grid || !std::isfinite(*grid) || *grid <= 0.0)
return units;
747 if (!std::isfinite(units) || units <= 0.0)
return units;
748 double floored = std::floor(units / *grid) * *grid;
750 const double cent_candidate = std::floor(units * 100.0) * *grid;
751 if (cent_candidate > floored && cent_candidate <= units) floored = cent_candidate;
753 return floored < units ? floored : units;
761 default:
return NativeFeeKind::Percent;
766 if (direction > 0)
return NativeOpenDirections::Long;
767 if (direction < 0)
return NativeOpenDirections::Short;
768 return NativeOpenDirections::Both;
775bool throttled_rearm_already_queued(
776 const std::vector<PlacementSnapshot>& queue,
777 const PlacementSnapshot& source)
noexcept {
778 for (
const auto& queued : queue) {
779 if (queued.source_id == source.source_id
780 && queued.source_sequence == source.source_sequence) {
798std::uint64_t exit_queue_rank(
const PlacementTable& table,
799 const PlacementSnapshot& row)
noexcept {
800 if (row.chain_origin_sequence != 0)
return row.chain_origin_sequence;
801 if (row.projection_predecessor != 0 && table.contains(row.projection_predecessor)) {
802 const auto& predecessor = table.at(row.projection_predecessor);
803 const std::uint64_t origin = predecessor.chain_origin_sequence;
804 if (origin != 0 && (!predecessor.void_issue || row.void_issue))
return origin;
806 if (row.source_sequence != 0)
return row.source_sequence;
807 return std::numeric_limits<std::uint64_t>::max();
815bool any_live_row(
const std::vector<native_order::RequestHandle>& live,
816 const PlacementTable& placement, Row&& row) {
817 for (
const auto& handle : live) {
818 const auto found = placement.find(handle.incarnation);
819 if (found != placement.end() && row(found->second))
return true;
832 const int index =
static_cast<int>(rule);
834 callback_lifecycle_rules[index].store(on, std::memory_order_relaxed);
837 const int index =
static_cast<int>(rule);
839 || callback_lifecycle_rules[index].load(std::memory_order_relaxed);
861double source_trail_points_ticks(
double trail_points,
double mintick,
862 const char* field =
nullptr) {
863 if (field && finite_positive(mintick)
865 const double tolerance = source_arith_value(
checked_mul(
867 return std::ceil(source_arith_value(
checked_add(trail_points, -tolerance), field));
871 : compat::pine::trail_points_to_ticks(trail_points);
875#if PINEFORGE_PINE_QUIET_BAR_PROBE
877QuietBarCounts& quiet_bar_counts() noexcept {
878 static QuietBarCounts counts;
900void PlacementTable::place(std::uint64_t incarnation, std::unique_ptr<PlacementSnapshot> node) {
if (old_only_)
return place_old(incarnation, std::move(node));
901 if (window_rows_ == 0 && (old_.empty() || incarnation > old_.back().first)) {
907 if (incarnation < base_) {
910 const auto at = std::lower_bound(old_.begin(), old_.end(), incarnation,
911 [](
const auto& entry, std::uint64_t key) { return entry.first < key; });
912 placement_work_ +=
static_cast<std::uint64_t
>(old_.end() -
at) + 1U; old_.emplace(
at, incarnation, std::move(node));
915 const auto index = head_ +
static_cast<std::size_t
>(incarnation - base_);
916 if (index >= window_.size()) grow_window(index);
917 ++placement_work_; window_[index] = std::move(node);
921std::unique_ptr<PlacementSnapshot> PlacementTable::take(std::uint64_t incarnation)
noexcept {
922 if (!old_only_ && incarnation >= base_) {
923 const std::uint64_t offset = incarnation - base_;
924 if (offset >= window_.size() - head_)
return nullptr;
925 auto& slot = window_[head_ +
static_cast<std::size_t
>(offset)];
926 if (slot) { --window_rows_; ++removal_work_; }
927 return std::move(slot);
929 const auto found = std::lower_bound(old_.begin(), old_.end(), incarnation,
930 [
this](
const auto& entry, std::uint64_t key) { ++lookup_work_; return entry.first < key; });
931 if (found == old_.end() || found->first != incarnation)
return nullptr;
932 auto node = std::move(found->second);
933 removal_work_ +=
static_cast<std::uint64_t
>(old_.end() - found); old_.erase(found);
939 && std::find(erased_foreign_ids_.begin(), erased_foreign_ids_.end(), row.source_id)
940 == erased_foreign_ids_.end()) {
941 erased_foreign_ids_.push_back(row.source_id);
943 const auto family =
static_cast<std::uint64_t
>(row.family);
944 erased_digest_ = fnv_append(erased_digest_, &family,
sizeof family);
945 const std::uint64_t id_size = row.source_id.size();
946 erased_digest_ = fnv_append(erased_digest_, &id_size,
sizeof id_size);
947 erased_digest_ = fnv_append(erased_digest_, row.source_id.data(), row.source_id.size());
948 const std::uint64_t from_size = row.from_entry.size();
949 erased_digest_ = fnv_append(erased_digest_, &from_size,
sizeof from_size);
950 erased_digest_ = fnv_append(erased_digest_, row.from_entry.data(), row.from_entry.size());
951 erased_digest_ = fnv_append(erased_digest_, &row.command_sequence,
952 sizeof row.command_sequence);
956 constexpr std::size_t kDenseFloor = 64;
958 while (head_ < window_.size() && !window_[head_]) {
962 if (head_ == window_.size()) {
965 base_ = high_water_ + 1U;
968 if (head_ >= kDenseFloor && head_ >= window_.size() / 2) {
969 window_.erase(window_.begin(), window_.begin() +
static_cast<std::ptrdiff_t
>(head_));
972 const std::size_t span = window_.size() - head_;
973 if (span <= kDenseFloor || span <= 4U * window_rows_)
return;
976 const std::size_t half = head_ + span / 2U;
977 for (std::size_t index = head_; index < half; ++index) {
978 if (!window_[index])
continue;
979 old_.emplace_back(base_ +
static_cast<std::uint64_t
>(index - head_),
980 std::move(window_[index]));
987 const std::string& from_entry)
const {
988 constexpr auto absent = std::numeric_limits<std::uint64_t>::max();
990 std::uint64_t any = absent;
991 bool paired_known =
false;
992 std::uint64_t paired = absent;
993 const auto retained = retained_sequences(source_id, from_entry);
996 paired = retained[1];
997 known = any != absent;
998 paired_known = paired != absent;
1003 if (
const auto minima = erased_minima_.lookup(source_id)) {
1005 any = std::min(any, *minima);
1006 if (
const auto pair = erased_minima_.lookup(source_id, from_entry)) {
1007 paired_known =
true;
1008 paired = std::min(paired, *pair);
1011 if (!known)
return absent;
1012 if (paired_known && paired != absent)
return paired;
1021struct PlacementAudit {
1023 std::map<std::uint64_t, std::unique_ptr<PlacementSnapshot>>> tombstones;
1024 std::map<std::pair<int, unsigned>, std::uint64_t> hits;
1025 std::vector<std::pair<std::uint64_t, const PlacementSnapshot*>> view;
1027 const char* path = std::getenv(
"PINEFORGE_PLACEMENT_AUDIT_LOG");
1028 std::FILE* out = path ? std::fopen(path,
"a") : nullptr;
1029 std::uint64_t rows = 0;
1030 for (
const auto& table : tombstones) rows += table.second.size();
1031 std::fprintf(out ? out : stderr,
1032 "placement-audit: %zu sites hit, %llu tombstones retained at exit\n",
1033 hits.size(),
static_cast<unsigned long long>(rows));
1034 for (
const auto& hit : hits) {
1035 std::fprintf(out ? out : stderr,
"placement-audit: %s pine_adapter line %u: %llu\n",
1036 hit.first.first == 0 ?
"lookup" :
"scan", hit.first.second,
1037 static_cast<unsigned long long>(hit.second));
1039 if (out) std::fclose(out);
1042PlacementAudit& placement_audit() {
1043 static PlacementAudit audit;
1050 placement_audit().tombstones[table][incarnation] =
1051 std::make_unique<PlacementSnapshot>(std::move(row));
1055 placement_audit().tombstones.erase(table);
1059 unsigned line)
noexcept {
1060 auto& audit = placement_audit();
1061 const auto stones = audit.tombstones.find(table);
1062 if (stones == audit.tombstones.end() || stones->second.count(incarnation) == 0)
return;
1063 ++audit.hits[{0,
line}];
1067 ++placement_audit().hits[{1,
line}];
1070const std::vector<std::pair<std::uint64_t, const PlacementSnapshot*>>*
1072 auto& audit = placement_audit();
1073 const auto stones = audit.tombstones.find(table);
1074 if (stones == audit.tombstones.end())
return nullptr;
1076 for (
const auto& stone : stones->second) audit.view.emplace_back(stone.first, stone.second.get());
1085#if PINEFORGE_PLACEMENT_AUDIT
1086template <
class Predicate>
1087void audit_placement_scan(
const PlacementTable& table,
unsigned line, Predicate&& predicate) {
1089 if (!stones)
return;
1090 const auto copy = *stones;
1091 for (
const auto& stone : copy) {
1092 if (predicate(stone.first, *stone.second))
1096#define PINEFORGE_AUDIT_PLACEMENT_SCAN(table, ...) \
1097 audit_placement_scan((table), __LINE__, __VA_ARGS__)
1099#define PINEFORGE_AUDIT_PLACEMENT_SCAN(table, ...) ((void)0)
1105 pending_view_.owner_ =
this;
1118struct CommandScratchRun {
1120 static constexpr std::size_t page_size = 512;
1121 std::unique_ptr<pineforge::detail::ScratchBacking> backing;
1122 CommandScratchStore store;
1124 CommandScratchRun(ScratchKeyOrder order,
CacheCapacity capacity)
1125 : backing(make_command_scratch_backing(page_size, capacity)),
1126 store{{2, page_size, 1, 1, order,
1133struct ExitScratchRun {
1134 CommandScratchRun scratch;
1135 ExitScratchContext context;
1136 ExitScratchVersionsProduction versions{scratch.store};
1138 ExitScratchRun(
const native_order::RunIdentity& run, CacheCapacity capacity)
1140 context.versions = &versions;
1144std::shared_ptr<ExitScratchContext> make_exit_scratch(
1145 const native_order::RunIdentity& identity, CacheCapacity capacity) {
1146 auto run = std::make_shared<ExitScratchRun>(identity, capacity);
1147 return {run, &run->context};
1153 return *adapter.exit_scratch_;
1160 "exit scratch operation failed"),
1172 if (exit_scratch_.get() == &context)
return;
1173 exit_scratch_ = std::shared_ptr<detail::ExitScratchContext>(
1178 if (position_tail_summary_)
1182 exit_scratch_ =
nullptr;
1183 pine_view_host_ =
nullptr;
1184 const auto* pine_host = pine_view_of(host_);
1185 input_scheduler_ = pine_host ? &pine_host->scheduler_ :
nullptr;
1196 if (host != pine_view_host_) {
1197 pine_view_host_ = host;
1209 return context.coordinate.input_interval_index;
1221[[noreturn]]
void refuse_unbound_adapter() {
1223 "Pine execution adapter is not bound to a native host");
1229NativeStrategyHost& PineExecutionAdapter::require_host()
const {
1230 if (!host_) refuse_unbound_adapter();
1234OrderBirth PineExecutionAdapter::capture_order_birth()
const {
1237 const int bar = point->decision.coordinate.interval_index;
1238 const std::int64_t timestamp = point->decision.sub_bar_open_ms;
1241 const bool magnified = point->decision.sub_count > 1;
1244 const int count = magnified ? std::max(1, point->decision.sub_count) : 4;
1245 int index = magnified ? point->decision.sub_index : 0;
1247 const bool high_first = source_path_uses_high_first(coof_script_bar_);
1248 switch (point->decision.coordinate.path_phase) {
1249 case NativePathPhase::Open: index = 0;
break;
1250 case NativePathPhase::High: index = high_first ? 1 : 2;
break;
1251 case NativePathPhase::Low: index = high_first ? 2 : 1;
break;
1252 case NativePathPhase::Close: index = 3;
break;
1253 case NativePathPhase::None: index = 0;
break;
1256 index = std::max(0, std::min(index, count - 1));
1258 const std::uint64_t ordinal = std::max<std::uint64_t>(1, last_applied_ordinal_);
1260 ordinal, ordinal, ordinal);
1263bool PineExecutionAdapter::opening_market_short_scope(
PineOrderFamily family)
const {
1265 const auto opening_parent = placement_.find(coof_market_entry_recalc_incarnation_);
1266 const bool carried_opening_parent = point && opening_parent != placement_.end()
1267 && opening_parent->second.placement_script_open_ms < point->decision.script_bar_open_ms;
1268 const bool opening_high = coof_script_bar_valid_
1269 && source_decimal_tick(coof_script_bar_.open, staged_.syminfo.mintick)
1270 == source_decimal_tick(coof_script_bar_.high, staged_.syminfo.mintick);
1273 && coof_market_entry_recalc_incarnation_ != 0 && config_.slippage == 0
1274 && (opening_high || (config_.margin_short == 0.0
1275 && (coof_first_open_ || carried_opening_parent)))
1277 && (!native.spec || native.spec->intrabar.is_none())
1278 && point && point->decision.coordinate.path_phase == NativePathPhase::Open;
1282 native_order::RequestHandle handle) {
1284 snapshot.birth = capture_order_birth();
1285 const bool trailing = finite_positive(
snapshot.exit_levels.trail_points)
1286 || finite_positive(
snapshot.exit_levels.trail_price)
1287 || finite_positive(
snapshot.exit_levels.trail_offset);
1289 if (coof_recalc_active_) {
1290 snapshot.coof_cascade_seg_i = coof_context_.coordinate.interval_index;
1291 snapshot.coof_cascade_inflight_fires =
true;
1293 if (handle.incarnation && placement_.contains(handle.incarnation)) invalidate_retirement();
1299 const exit_legs::Prices prices{
snapshot.exit_levels.limit,
snapshot.exit_levels.stop,
1303 if (!
snapshot.legs.target().incarnation && handle.incarnation != 0) {
1305 snapshot.legs.attach(handle.incarnation, std::max<std::int64_t>(0,
snapshot.placement_cycle));
1306 }
else if (handle.incarnation != 0
1307 &&
snapshot.legs.target().incarnation != handle.incarnation) {
1308 snapshot.legs.fork(handle.incarnation, std::max<std::int64_t>(0,
snapshot.placement_cycle));
1313 if (physical.signed_units == 0.0 ||
snapshot.projection_created_bar < 0)
return;
1314 const int direction = physical.signed_units > 0.0 ? 1 : -1;
1316 const Bar activation_bar = coof_script_bar_valid_ ? coof_script_bar_
1317 : policy_script_bar_;
1318 const bool path_high_first = source_path_uses_high_first(activation_bar);
1319 int historical_point = 0;
1320 double waypoint = activation_bar.open;
1322 switch (point->decision.coordinate.path_phase) {
1323 case NativePathPhase::Open:
1324 historical_point = 0; waypoint = activation_bar.open;
break;
1325 case NativePathPhase::High:
1326 historical_point = path_high_first ? 1 : 2; waypoint = activation_bar.high;
break;
1327 case NativePathPhase::Low:
1328 historical_point = path_high_first ? 2 : 1; waypoint = activation_bar.low;
break;
1329 case NativePathPhase::Close:
1330 historical_point = 3; waypoint = activation_bar.close;
break;
1331 case NativePathPhase::None:
1332 historical_point = 0; waypoint = point->price;
break;
1335 const bool at_waypoint = point
1336 && source_same_point(point->price, waypoint, staged_.syminfo.mintick);
1337 const bool historical_segment = point && historical_point > 0 && !at_waypoint;
1338 const int recalc_leg = historical_segment
1339 ? std::max(0, historical_point - 1) : historical_point;
1341 std::string_view first_lot_id;
1342 std::uint64_t first_lot_incarnation = 0;
1343 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
1344 for (
const auto& opening :
cohort.second.opened) {
1345 const auto units =
cohort.second.live_units_by_origin.find(opening.incarnation);
1346 if (units ==
cohort.second.live_units_by_origin.end()
1347 || !(units->second > 0.0)) {
1350 first_lot_id =
cohort.first;
1351 first_lot_incarnation = opening.incarnation;
1354 if (first_lot_incarnation != 0)
break;
1357 const double activation_quantity = std::isfinite(
snapshot.projection_remaining_qty)
1359 const bool full_quantity = (!std::isfinite(
snapshot.qty_percent)
1360 ||
snapshot.qty_percent >= 100.0 - 1e-9)
1361 && (!std::isfinite(activation_quantity)
1362 || activation_quantity >= std::abs(physical.signed_units) - 1e-9);
1363 compat::pine::ExitActivationContext context;
1364 context.cycle = current_position_cycle_;
1365 context.bar_index =
snapshot.projection_created_bar;
1366 context.position_open_bar = position_open_bar_index_;
1367 context.direction = direction;
1368 context.cursor_price = point ? point->price :
snapshot.sizing.price;
1369 context.fill_recalc = coof_recalc_active_;
1370 context.scheduler = config_.calc_on_order_fills;
1371 context.magnifier = point && point->decision.sub_count > 1;
1372 context.process_on_close = config_.process_orders_on_close;
1374 context.stream_idle = modeled_input();
1375 context.first_open_fill = opening_market_short_scope(
snapshot.family);
1376 context.after_first_open_fill = coof_recalc_active_ && !coof_first_open_
1377 && point && point->decision.coordinate.path_phase == NativePathPhase::Open;
1378 context.recalc_leg = recalc_leg;
1379 context.historical_segment = historical_segment;
1380 context.at_extreme = at_waypoint
1381 && (historical_point == 1 || historical_point == 2);
1382 context.historical_point = historical_point;
1383 context.current_fill = coof_current_fill_seq_;
1384 context.bar = activation_bar;
1385 context.position_entry_count =
static_cast<int>(physical.lot_count);
1386 context.position_quantity = std::abs(physical.signed_units);
1387 context.pyramiding = config_.pyramiding;
1388 context.lot_count = physical.lot_count;
1389 context.first_lot_id = first_lot_id;
1390 context.first_lot_incarnation = first_lot_incarnation;
1391 context.market_recalc_incarnation = coof_market_entry_recalc_incarnation_;
1392 context.market_recalc_fill = coof_market_entry_recalc_fill_seq_;
1393 context.pending_empty = live_handles_.empty() && pending_entries_.empty()
1394 && pending_bracket_legs_.empty() && pending_same_bar_commands_.empty()
1395 && pending_coof_requests_.empty();
1396 context.slippage = config_.slippage;
1397 context.pointvalue = staged_.syminfo.pointvalue;
1398 context.account_fx = point ? active_staged_fx(point->decision.sub_bar_open_ms)
1399 : staged_.account_fx;
1400 context.fx_series_empty = staged_.account_fx_effective_from_ms.empty();
1401 context.bar_path_high_first = path_high_first;
1402 context.tick_high = source_bar_fill_tick(activation_bar.high, staged_.syminfo.mintick);
1403 compat::pine::ExitActivationRequest
request;
1404 request.requested_trailing = trailing;
1405 request.full_quantity = full_quantity;
1411 request.quantity = activation_quantity;
1414 if (
snapshot.exit_activation.evidence()) {
1416 current_position_cycle_, position_open_bar_index_));
1424 const bool full_global = config_.process_orders_on_close &&
snapshot.from_entry.empty()
1425 && !std::isfinite(
snapshot.requested_qty)
1427 && physical.signed_units != 0.0;
1428 const bool side = physical.signed_units > 0.0;
1429 const auto is_unpriced_market_add = [](
const PlacementSnapshot& candidate) {
1430 return !finite_positive(candidate.exit_levels.limit)
1431 && !finite_positive(candidate.exit_levels.stop)
1432 && !finite_positive(candidate.exit_levels.trail_offset)
1433 && !finite_positive(candidate.exit_levels.trail_price);
1435 const auto is_entry_like = [](
const PlacementSnapshot& candidate) {
1439 struct CandidateRef {
1440 PlacementSnapshot*
snapshot =
nullptr;
1441 std::uint64_t incarnation = 0;
1443 std::vector<compat::pine::ReservationGrowthCandidate> candidates;
1444 std::vector<CandidateRef> candidate_refs;
1445 const auto collect = [&](PlacementSnapshot& candidate,
1446 std::uint64_t incarnation) {
1447 if (!is_entry_like(candidate))
return;
1448 compat::pine::ReservationGrowthCandidate fact;
1449 fact.incarnation = incarnation;
1450 fact.source_id = candidate.source_id;
1452 && is_unpriced_market_add(candidate);
1453 fact.from_fill = candidate.birth.from_fill();
1454 fact.at_entry_capacity = candidate.projection_over_pyramiding;
1455 fact.is_long = candidate.is_long;
1456 fact.created_position_side =
1457 static_cast<PositionSide>(candidate.projection_position_side);
1458 fact.created_bar = candidate.projection_created_bar;
1459 candidates.push_back(std::move(fact));
1460 candidate_refs.push_back({&candidate, incarnation});
1466 for (
auto& pending : pending_entries_) collect(
pending.snapshot, 0);
1467 for (
auto& pending : pending_same_bar_commands_) collect(
pending.snapshot, 0);
1468 for (
const auto& live : live_handles_) {
1469 const auto existing = placement_.find(live.incarnation);
1470 if (existing != placement_.end()) collect(existing->second, live.incarnation);
1473 const double percent = std::isfinite(
snapshot.qty_percent)
1475 const auto selected = full_global
1477 candidates,
snapshot.from_entry, config_.process_orders_on_close,
1478 physical.signed_units == 0.0, percent,
1481 : std::vector<std::uint64_t>{};
1482 const bool partial = percent < 100.0 - 1e-9
1483 || (std::isfinite(
snapshot.projection_remaining_qty)
1484 &&
snapshot.projection_remaining_qty
1485 < std::abs(physical.signed_units) - 1e-9);
1487 selected, partial,
snapshot.projection_remaining_qty,
1488 std::abs(physical.signed_units));
1489 snapshot.pooc_global_full_exit_dynamic_qty = qualified_adds;
1490 snapshot.pooc_global_full_exit_tracks_bound_adds = qualified_adds;
1491 if (qualified_adds && handle.incarnation != 0) {
1493 snapshot.reservation_expansion.capture(handle.incarnation, current_position_cycle_,
1496 std::abs(physical.signed_units));
1497 }
catch (
const std::invalid_argument&) {
1500 for (
const auto& selected_source : candidate_refs) {
1501 auto* candidate = selected_source.snapshot;
1502 if (!candidate)
continue;
1503 candidate->reservation_growth_owner_incarnation = handle.incarnation; invalidate_retirement();
1504 if (selected_source.incarnation != 0
1505 && selected_source.incarnation != handle.incarnation) {
1507 candidate->reservation_growth_source.assign_capture(
1508 selected_source.incarnation, handle.incarnation);
1509 candidate->pooc_global_full_exit_bound_add =
true;
1510 }
catch (
const std::invalid_argument&) {
1518void PineExecutionAdapter::update_l4c_priority() {
1519 auto&
host = require_host();
1530 || !config_.process_orders_on_close || config_.calc_on_order_fills
1531 || coof_recalc_active_) {
1534 std::size_t placed = 0;
1535 for (
const auto& handle : live_handles_) {
1536 if (placement_.find(handle.incarnation) != placement_.end()) ++placed;
1538 if (placed != 2)
return;
1539 std::vector<compat::pine::OrderPriorityCandidate> candidates;
1540 candidates.reserve(live_handles_.size());
1541 for (
const auto& handle : live_handles_) {
1542 const auto found = placement_.find(handle.incarnation);
1543 if (found == placement_.end())
continue;
1544 const auto&
snapshot = found->second;
1545 compat::pine::OrderPriorityCandidate candidate;
1546 candidate.handle = handle;
1555 candidate.from_entry =
snapshot.from_entry;
1556 candidate.created_bar =
snapshot.projection_created_bar;
1557 candidate.source_sequence =
snapshot.source_sequence;
1558 candidate.predecessor =
snapshot.projection_predecessor;
1559 candidate.recreated_after_named_cancelled =
1560 snapshot.recreated_after_named_cancelled_entry_incarnation;
1561 candidate.named_cancel_surviving_exit =
snapshot.named_cancel_surviving_exit_incarnation;
1562 candidate.created_flat =
snapshot.projection_position_side
1564 candidate.birth_from_fill =
snapshot.birth.from_fill();
1565 candidate.prior_close =
snapshot.projection_after_close;
1566 candidate.at_entry_capacity =
snapshot.projection_over_pyramiding;
1567 candidate.stop_limit_activated =
snapshot.stop_limit_activated;
1568 candidate.default_quantity = !std::isfinite(
snapshot.requested_qty);
1569 candidate.requested_qty =
snapshot.requested_qty;
1570 candidate.qty_percent =
snapshot.qty_percent;
1571 candidate.stop =
snapshot.exit_levels.stop;
1572 candidate.limit =
snapshot.exit_levels.limit;
1573 candidate.trail_points =
snapshot.exit_levels.trail_points;
1574 candidate.trail_price =
snapshot.exit_levels.trail_price;
1575 candidate.trail_offset =
snapshot.exit_levels.trail_offset;
1576 candidate.profit_ticks =
snapshot.exit_levels.profit_ticks;
1577 candidate.loss_ticks =
snapshot.exit_levels.loss_ticks;
1578 candidate.oca_name =
snapshot.oca_name;
1579 candidate.oca_type =
snapshot.oca_type;
1580 candidates.push_back(std::move(candidate));
1583 const compat::pine::OrderPriorityContext context{
1585 config_.process_orders_on_close, config_.calc_on_order_fills,
1586 coof_recalc_active_, point && point->decision.sub_count > 1,
false,
true,
1587 point ? point->decision.coordinate.interval_index : -1};
1588 const auto decision =
priority.select(context, candidates);
1589 if (!decision)
return;
1590 const auto rank = [&](
const native_order::RequestHandle& handle) {
1591 if (handle == decision->parent)
return 0;
1592 if (handle == decision->child)
return 1;
1595 std::stable_sort(pending_write(live_handles_).begin(), live_handles_.end(),
1596 [&](
const auto& left,
const auto& right) { return rank(left) < rank(right); });
1599void PineExecutionAdapter::update_l4c_lifecycle(
1600 const native_order::ExecutionAppliedEvent& event,
1601 const NativeDecisionContext& context) {
1602 const auto found = placement_.find(event.handle().incarnation);
1603 if (found == placement_.end())
return;
1608 if (!
exit || !
snapshot.legs.target().incarnation)
return;
1609 const auto domain = context.sub_count > 1
1610 ? (config_.calc_on_order_fills ? exit_legs::Domain::MagnifierFillRecalc
1611 : exit_legs::Domain::Magnifier)
1614 const exit_legs::Frame cause{
event.ordinal, context.coordinate.interval_index,
1615 domain, exit_legs::Phase::AfterMargin};
1617 const exit_legs::Action action{snapshot.legs.target(), snapshot.legs.revision(),
1618 cause, *completion};
1619 (
void)
snapshot.legs.apply(
snapshot.legs.target(), action); invalidate_retirement();
1624bool PineExecutionAdapter::is_declined_market_reversal(
1625 const native_order::MatchRejectedEvent& event)
const noexcept {
1626 if (event.reason != native_order::MatchRejectReason::HostPrecommit
1627 && event.reason != native_order::MatchRejectReason::InitialMargin) {
1630 const auto found = placement_.find(event.handle().incarnation);
1631 if (found == placement_.end())
return false;
1632 const auto& source = found->second;
1633 const auto prior =
static_cast<PositionSide>(source.projection_position_side);
1635 && source.reverse_to && std::holds_alternative<native_order::Market>(event.request().trigger)
1640bool PineExecutionAdapter::follows_same_bar_declined_reversal(
1642 if (!
exit.legs.target().incarnation)
return false;
1644 const auto* rejected = std::get_if<native_order::MatchRejectedEvent>(&command);
1645 if (!rejected || rejected->cursor.point.interval_index != view.cursor.point.interval_index
1646 || !is_declined_market_reversal(*rejected)) {
1649 const auto reversal = placement_.find(rejected->handle().incarnation);
1656 return reversal != placement_.end()
1657 && (
exit.from_entry.empty()
1658 ?
exit.projection_position_side
1659 !=
static_cast<std::int32_t
>(reversal->second.is_long
1661 : bracket_belongs_to_reversal(
exit, reversal->second));
1663 auto&
host = require_host();
1665 if (
auto* pine = pine_view_of(&host)) {
1666 return as_native_consumer(pine->execution_consumer())
1667 .visit_commands_after(receipt_cursor_, follows);
1669 for (
const auto& row :
host.native_events(receipt_cursor_)) {
1670 if (row.command && follows(*row.command))
return true;
1699 struct CohortSides {
1700 std::size_t folded = 0;
1701 bool opened_long =
false;
1702 bool opened_short =
false;
1705 std::uint64_t answers = 0;
1710 std::unordered_map<std::uint64_t, CohortSides> cohort_sides;
1726 std::uint64_t erased_seen = 0;
1727 std::uint64_t folded_high = 0;
1728 std::size_t rows_folded = 0;
1729 std::vector<std::uint64_t> unbound;
1730 std::array<std::unordered_map<std::uint64_t, std::vector<std::uint64_t>>, 3> legs_by_origin;
1731 std::unordered_map<std::int32_t, std::vector<std::uint64_t>> immediate_closes_by_bar;
1738 std::int64_t day_lo = 1;
1739 std::int64_t day_hi = 0;
1740 std::int64_t day_key = 0;
1742 std::uint64_t answered()
const noexcept override {
return answers; }
1744 void clear()
noexcept {
1745 cohort_sides.clear();
1752 void clear_rows()
noexcept {
1757 for (
auto& legs : legs_by_origin) legs.clear();
1758 immediate_closes_by_bar.clear();
1766 static bool working(
const std::vector<native_order::RequestHandle>& live,
1767 std::uint64_t incarnation)
noexcept {
1768 return std::any_of(live.begin(), live.end(),
1770 return handle.incarnation == incarnation;
1776 std::uint64_t origin)
const noexcept {
1777 const auto& legs = legs_by_origin[
static_cast<std::size_t
>(family)
1779 const auto found = legs.find(origin);
1780 return found == legs.end() ? nullptr : &found->second;
1784 const std::vector<native_order::RequestHandle>& live) {
1785 if (exit_leg(row.
family)) {
1787 legs_by_origin[
static_cast<std::size_t
>(row.
family)
1790 }
else if (working(live, incarnation)) {
1791 unbound.push_back(incarnation);
1801 const std::vector<native_order::RequestHandle>& live) {
1802 if (table.erased_rows() != erased_seen || table.size() < rows_folded
1803 || table.high_water() < folded_high) {
1805 erased_seen = table.erased_rows();
1807 for (std::size_t i = 0; i < unbound.size();) {
1808 const auto found = table.find(unbound[i]);
1809 if (found != table.end() && found->second.bracket_origin.incarnation != 0) {
1810 fold(unbound[i], found->second, live);
1811 }
else if (found != table.end() && working(live, unbound[i])) {
1815 unbound.erase(unbound.begin() +
static_cast<std::ptrdiff_t
>(i));
1817 for (
auto row = table.upper_bound(folded_high); row != table.end(); ++row) {
1818 fold(row->first, row->second, live);
1820 folded_high = row->first;
1822 if (rows_folded < table.size()) {
1825 erased_seen = table.erased_rows();
1826 for (
const auto& row : table) {
1827 fold(row.first, row.second, live);
1829 folded_high = row.first;
1839constexpr std::size_t kIndexedFrom = 16;
1845AdapterLookupIndex* adapter_lookup_index(
IExecutionConsumer* consumer,
const void* owner,
1846 std::uint64_t run,
bool create =
true)
noexcept {
1847 if (!consumer)
return nullptr;
1848 auto& native = as_native_consumer(*consumer);
1849 NativeHostCache*
const cache = native.host_cache();
1851 ?
static_cast<AdapterLookupIndex*
>(cache) :
nullptr;
1852 if (index && index->owner == owner && index->run == run)
return index;
1853 if (!create)
return nullptr;
1855 auto fresh = std::make_unique<AdapterLookupIndex>();
1856 fresh->owner = owner;
1858 return static_cast<AdapterLookupIndex*
>(native.adopt_host_cache(std::move(fresh)));
1859 }
catch (
const std::bad_alloc&) {
1867[[gnu::noinline]] AdapterLookupIndex* adopt_lookup_index(NativeExecutionConsumer& native,
1869 std::uint64_t run)
noexcept {
1871 auto fresh = std::make_unique<AdapterLookupIndex>();
1872 fresh->owner = owner;
1874 return static_cast<AdapterLookupIndex*
>(native.adopt_host_cache(std::move(fresh)));
1888 std::uint64_t run)
noexcept {
1889 if (host ==
nullptr)
return nullptr;
1890 NativeExecutionConsumer* native =
nullptr;
1892 native = &NativeExecutionConsumer::bound(*host);
1896 NativeHostCache* cache = native->host_cache();
1897 if (cache !=
nullptr &&
typeid(*cache) ==
typeid(AdapterLookupIndex)) {
1898 auto* index =
static_cast<AdapterLookupIndex*
>(cache);
1899 if (index->owner == owner && index->run == run)
return index;
1901 return adopt_lookup_index(*native, owner, run);
1912 if (!consumer.running())
return;
1913 if (
auto* index = adapter_lookup_index(&consumer, &adapter, run))
1914 index->aggregates_input_bars = consumer.aggregates_input();
1918 auto* pine = pine_view_of(host_);
1919 if (!pine)
return nullptr;
1927bool PineExecutionAdapter::cohort_opened_on_side(
const CohortFacts& cohort,
1928 bool is_long)
const noexcept {
1931 const bool erased = (
cohort.erased_opening_sides & (is_long ? 1U : 2U)) != 0;
1932 const auto walk = [&] {
1933 if (erased)
return true;
1934 for (
const auto& origin :
cohort.origins) {
1935 if (!placement_.contains(origin.incarnation))
continue;
1936 const auto opening = placement_.find(origin.incarnation);
1937 if (opening != placement_.end() && opening->second.opening
1938 && opening->second.is_long == is_long) {
1944 if (
cohort.origins.size() <= kIndexedFrom ||
cohort.origins.members().empty())
return walk();
1945 AdapterLookupIndex* index = adapter_lookup_index(bound_consumer(),
this, run_counter_);
1946 if (!index)
return walk();
1948 auto& sides = index->cohort_sides[
cohort.handle.value];
1949 const auto& roster =
cohort.origins;
1950 if (roster.size() < sides.folded) sides = {};
1951 for (sides.folded = std::max(sides.folded, roster.base());
1952 sides.folded < roster.size(); ++sides.folded) {
1953 if (!placement_.contains(roster[sides.folded].incarnation))
continue;
1954 const auto opening = placement_.find(roster[sides.folded].incarnation);
1955 if (opening == placement_.end() || !opening->second.opening)
continue;
1956 (opening->second.is_long ? sides.opened_long : sides.opened_short) =
true;
1960 const bool opened = erased || (is_long ? sides.opened_long : sides.opened_short);
1961 assert(opened == walk());
1963 }
catch (
const std::bad_alloc&) {
1968bool PineExecutionAdapter::origin_leg_consumed(
1970 const native_order::RequestHandle& origin)
const noexcept {
1972 const auto members = bracket_families_.find(family_key);
1973 if (members == bracket_families_.end())
return false;
1976 const bool erased = members->second.consumed(
static_cast<std::uint8_t
>(family),
1977 origin.incarnation);
1978 const auto consumed = [&](std::uint64_t incarnation) {
1980 if (!placement_.contains(incarnation))
return false;
1981 const auto found = placement_.find(incarnation);
1982 if (found == placement_.end())
return false;
1983 const auto& prior = found->second;
1984 return prior.family == family && prior.bracket_origin == origin
1985 && !prior.legs.dormant()
1986 && std::none_of(live_handles_.begin(), live_handles_.end(),
1987 [&](
const native_order::RequestHandle& live) {
1988 return live.incarnation == incarnation;
1994 const auto walk = [&] {
1995 return erased || std::any_of(members->second.working_tail().begin(),
1996 members->second.working_tail().end(),
1997 [&](
const native_order::RequestHandle& handle) {
1998 return consumed(handle.incarnation);
2001 if (members->second.size() <= kIndexedFrom)
return walk();
2002 AdapterLookupIndex* index = AdapterLookupIndex::exit_leg(family)
2003 ? adapter_lookup_index(bound_consumer(),
this, run_counter_) : nullptr;
2004 if (!index)
return walk();
2006 index->sync_rows(placement_, live_handles_);
2010 bool answer = erased;
2011 if (
const auto* legs = answer ?
nullptr : index->legs_of(family, origin.incarnation)) {
2012 for (
const auto incarnation : *legs) {
2013 if (members->second.count(incarnation) != 0 && consumed(incarnation)) {
2021 assert(answer == walk());
2023 }
catch (
const std::bad_alloc&) {
2028bool PineExecutionAdapter::immediate_close_placed_on(std::int32_t bar)
const noexcept {
2031 && row.projection_created_bar == bar;
2035 const auto walk = [&] {
2036 return std::any_of(placement_.begin(), placement_.end(),
2037 [&](
const auto& row) { return placed(row.second); });
2041 if (placement_.size() <= kIndexedFrom)
return walk();
2042 AdapterLookupIndex* index = adapter_lookup_index(bound_consumer(),
this, run_counter_);
2043 if (!index)
return walk();
2045 index->sync_rows(placement_, live_handles_);
2046 bool answer =
false;
2047 if (
const auto closes = index->immediate_closes_by_bar.find(bar);
2048 closes != index->immediate_closes_by_bar.end()) {
2049 for (
const auto incarnation : closes->second) {
2050 const auto found = placement_.find(incarnation);
2051 if (found != placement_.end() && placed(found->second)) {
2059 assert(answer == walk());
2061 }
catch (
const std::bad_alloc&) {
2066bool PineExecutionAdapter::bracket_belongs_to_reversal(
2069 if (bracket.projection_position_side == reversal.projection_position_side)
2071 const auto cohort = cohorts_by_id_.find(bracket.from_entry);
2072 if (cohort == cohorts_by_id_.end())
return false;
2073 const bool prior_long = reversal.projection_position_side
2075 return cohort_opened_on_side(
cohort->second, prior_long);
2078void PineExecutionAdapter::suspend_declined_reversal_brackets(
2079 const native_order::MatchRejectedEvent& event) {
2080 if (!is_declined_market_reversal(event))
return;
2081 const auto reversal = placement_.find(event.handle().incarnation);
2082 if (reversal == placement_.end())
return;
2083 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
2084 : exit_legs::Domain::Ordinary;
2085 const exit_legs::Frame cause{
event.ordinal,
event.cursor.point.interval_index,
2086 domain, exit_legs::Phase::Observation};
2087 suspend_brackets_for_reversal(reversal->second, cause,
2088 policy_script_bar_valid_ ? policy_script_bar_.open : reversal->second.sizing.mark);
2091bool PineExecutionAdapter::reissue_revives_declined_exit(
2118 || config_.calc_on_order_fills
2123 const auto& suspension = previous.legs.suspension();
2124 return suspension && !suspension->hold && suspension->window
2125 && !suspension->replacement;
2128void PineExecutionAdapter::suspend_brackets_for_reversal(
2130 double open_price) {
2131 const int direction = reversal.projection_position_side
2137 std::unordered_map<SourceId, bool> cross_side_by_entry;
2138 const auto belongs = [&](
const PlacementSnapshot& bracket) {
2141 if (bracket.from_entry.empty()) {
2142 return bracket.projection_position_side
2146 if (bracket.projection_position_side == reversal.projection_position_side)
2148 const auto cached = cross_side_by_entry.find(bracket.from_entry);
2149 if (cached != cross_side_by_entry.end())
return cached->second;
2150 const bool value = bracket_belongs_to_reversal(bracket, reversal);
2151 cross_side_by_entry.emplace(bracket.from_entry, value);
2158 [&](std::uint64_t,
const PlacementSnapshot& candidate) {
2159 return lifecycle_readable(candidate);
2161 for (
auto row : placement_) {
2162 auto& candidate = row.second;
2166 if (!
exit || candidate.legs.dormant()
2167 || !candidate.legs.target().incarnation
2168 || !belongs(candidate)) {
2171 const compat::pine::ExitSuspensionContext context{
2172 cause, direction, require_host().position_avg_price(), staged_.syminfo.mintick,
2174 candidate.legs.trail_best(),
false,
true};
2176 if (!operation)
continue;
2177 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
2179 (void)candidate.legs.apply(candidate.legs.target(), action); invalidate_retirement();
2183void PineExecutionAdapter::suspend_coof_declined_reversal_at_open(
2184 const Bar& bar,
const NativeDecisionContext& context) {
2186 if (physical.signed_units == 0.0 || !finite_positive(bar.open))
return;
2187 const auto handles = live_handles_;
2188 for (
const auto& handle : handles) {
2189 const auto found = placement_.find(handle.incarnation);
2190 if (found == placement_.end())
continue;
2191 const auto& reversal = found->second;
2192 const bool opposite = (physical.signed_units > 0.0) != reversal.is_long;
2193 const bool market_entry = !finite_positive(reversal.exit_levels.limit)
2194 && !finite_positive(reversal.exit_levels.stop)
2195 && !finite_positive(reversal.exit_levels.trail_offset)
2196 && !finite_positive(reversal.exit_levels.trail_price);
2198 || !reversal.reverse_to || !opposite || !market_entry) {
2201 const double units = finite_positive(reversal.sizing.frozen_units)
2202 ? reversal.sizing.frozen_units : reversal.requested_qty;
2203 const double margin = reversal.is_long ? config_.margin_long : config_.margin_short;
2204 const double fx = active_staged_fx(context.sub_bar_open_ms);
2205 const double required = units * bar.open * staged_.syminfo.pointvalue
2206 * fx * margin / 100.0;
2207 const double equity = std::isfinite(reversal.sizing.equity)
2208 ? reversal.sizing.equity : require_host().native_marked_equity(bar.open);
2209 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
2210 if (finite_positive(units) && finite_positive(margin) && std::isfinite(required)
2211 && std::isfinite(equity) && required > equity + guard) {
2212 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
2213 : exit_legs::Domain::Ordinary;
2214 const exit_legs::Frame cause{context.coordinate.ordinal,
2215 context.coordinate.interval_index, domain,
2216 exit_legs::Phase::Observation};
2217 suspend_brackets_for_reversal(reversal, cause, bar.open);
2222void PineExecutionAdapter::hold_reversal_pair_brackets(
const SourceId& from_entry) {
2227 if (!point || !live)
return;
2228 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
2229 : exit_legs::Domain::Ordinary;
2230 const exit_legs::Frame cause{live->decision.coordinate.ordinal,
2231 point->decision.coordinate.interval_index, domain, exit_legs::Phase::Observation};
2234 [&](std::uint64_t,
const PlacementSnapshot& candidate) {
2235 return lifecycle_readable(candidate);
2237 for (
auto row : placement_) {
2238 auto& candidate = row.second;
2242 if (!
exit || candidate.from_entry != from_entry || candidate.legs.dormant()
2243 || !candidate.legs.target().incarnation) {
2247 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
2249 (void)candidate.legs.apply(candidate.legs.target(), action); invalidate_retirement();
2286void PineExecutionAdapter::retire_cancelled_exits(
2288 const std::vector<std::pair<std::uint64_t, std::uint64_t>>& cancelled) {
2293 if (!point || !live)
return;
2294 const auto domain = config_.calc_on_order_fills ? exit_legs::Domain::FillRecalc
2295 : exit_legs::Domain::Ordinary;
2296 const auto retire_legs = [&](PlacementSnapshot& candidate, std::uint64_t event) {
2300 if (!
exit || !candidate.legs.target().incarnation || withdrawn(candidate.legs))
return;
2305 if (
const auto& last = candidate.legs.last_action(); last && last->cause.event >= event)
2306 event = last->cause.event + 1;
2307 const exit_legs::Frame cause{event, point->decision.coordinate.interval_index, domain,
2308 exit_legs::Phase::Observation};
2309 const exit_legs::Action action{candidate.legs.target(), candidate.legs.revision(),
2310 cause, exit_legs::Cancel{{exit_legs::Leg::Stop, exit_legs::Leg::Limit,
2311 exit_legs::Leg::Trail}}};
2312 (void)candidate.legs.apply(candidate.legs.target(), action); invalidate_retirement();
2314 for (
const auto& [incarnation, event] : cancelled) {
2315 const auto found = placement_.find(incarnation);
2316 if (found != placement_.end()) retire_legs(found->second, event);
2320 [&](std::uint64_t,
const PlacementSnapshot& candidate) {
2321 return lifecycle_readable(candidate);
2323 for (
auto row : placement_) {
2324 auto& candidate = row.second;
2325 if (
id && candidate.source_id != *
id)
continue;
2326 retire_legs(candidate, live->decision.coordinate.ordinal);
2330void PineExecutionAdapter::purge_brackets_after_applied_reversal(
2332 const bool prior_long = reversal.projection_position_side
2334 std::vector<native_order::RequestHandle>
stale;
2335 for (
const auto& handle : live_handles_) {
2336 const auto found = placement_.find(handle.incarnation);
2337 if (found == placement_.end())
continue;
2338 const auto& candidate = found->second;
2343 && candidate.projection_position_side == reversal.projection_position_side;
2344 bool targets_prior =
false;
2345 if (
const auto cohort = cohorts_by_id_.find(candidate.from_entry);
2346 cohort != cohorts_by_id_.end()) {
2347 targets_prior = cohort_opened_on_side(
cohort->second, prior_long);
2349 if ((
exit && targets_prior) || stale_margin)
stale.push_back(handle);
2351 for (
const auto& handle :
stale) {
2352 (void)require_host().cancel(handle);
2355 const auto held = cohorts_by_id_.held_rows();
2356 for (
auto at = held.begin(); at != held.end();) {
2357 const auto current = at++;
2361 if (
cohort.first == reversal.source_id)
continue;
2362 if (cohort_opened_on_side(
cohort.second, prior_long)) {
2363 if (position_tail_summary_) {
2364 for (
const auto& origin :
cohort.second.live_units_by_origin)
2365 position_tail_summary_->retire_origin(origin.first);
2366 position_tail_summary_->live_cohort(&cohort,
false);
2368 assert(cohort_opened_slot_count_ >=
cohort.second.opened.size());
2369 cohort_opened_slot_count_ -=
cohort.second.opened.size();
2372 cohorts_by_id_.refresh_held(current);
2377void PineExecutionAdapter::revive_brackets_after_margin(
2378 const native_order::ExecutionAppliedEvent& event,
2379 const NativeDecisionContext& context) {
2381 if (physical.signed_units == 0.0)
return;
2387 const auto* rejected = std::get_if<native_order::MatchRejectedEvent>(&command);
2389 && rejected->cursor.point.interval_index == context.coordinate.interval_index
2390 && is_declined_market_reversal(*rejected)) {
2391 suspend_declined_reversal_brackets(*rejected);
2396 if (
auto* pine = pine_view_of(&require_host())) {
2397 as_native_consumer(pine->execution_consumer())
2398 .visit_commands_after(receipt_cursor_, suspend);
2400 for (
const auto& row : require_host().native_events(receipt_cursor_)) {
2401 if (row.command) suspend(*row.command);
2404 const auto domain = context.sub_count > 1
2405 ? (config_.calc_on_order_fills ? exit_legs::Domain::MagnifierFillRecalc
2406 : exit_legs::Domain::Magnifier)
2409 const exit_legs::Frame cause{
event.ordinal, context.coordinate.interval_index,
2410 domain, exit_legs::Phase::AfterMargin};
2411 std::optional<PlacementSnapshot> marketable;
2412 native_order::RequestHandle marketable_handle{};
2419 [&](std::uint64_t,
const PlacementSnapshot& candidate) {
2420 return candidate.placement_cycle == current_position_cycle_
2421 && candidate.legs.dormant() && candidate.legs.target().incarnation != 0
2422 && !withdrawn(candidate.legs) && !filled_limit_without_revival(candidate);
2424#if PINEFORGE_PLACEMENT_AUDIT
2425 for (
const auto& retained : placement_) {
2426 const auto& candidate = retained.second;
2427 if (retained.second.placement_cycle != current_position_cycle_
2428 || !candidate.legs.dormant() || !candidate.legs.target().incarnation
2429 || withdrawn(candidate.legs) || filled_limit_without_revival(candidate)) {
2432 const auto inc = retained.first;
2433 const auto target_inc = candidate.legs.target().incarnation;
2434 const auto supersedes = [&](std::uint64_t incarnation,
const PlacementSnapshot& peer) {
2435 return peer.placement_cycle == current_position_cycle_
2436 && ((peer.projection_predecessor != 0
2437 && (peer.projection_predecessor == inc
2438 || peer.projection_predecessor == target_inc))
2439 || (incarnation > inc && peer.family == candidate.family
2440 && peer.source_id == candidate.source_id
2441 && peer.from_entry == candidate.from_entry
2442 && peer.placement_script_open_ms > candidate.placement_script_open_ms
2443 && !same_double_bits(peer.exit_levels.stop,
2444 candidate.exit_levels.stop)));
2448 const bool answered = std::any_of(placement_.begin(), placement_.end(),
2449 [&](
const auto& peer) { return supersedes(peer.first, peer.second); });
2454 physical.signed_units, current_position_cycle_, 0);
2455 for (
auto row : placement_) {
2460 if (row.second.placement_cycle != current_position_cycle_)
continue;
2461 auto& candidate = row.second;
2469 const bool global = candidate.from_entry.empty();
2470 if (!
exit || !candidate.legs.dormant()
2471 || (!global && !(cohort_exposure_for(candidate.from_entry) > 0.0))
2472 || !candidate.legs.target().incarnation) {
2478 if (withdrawn(candidate.legs))
continue;
2483 const auto inc = row.first;
2484 const auto target_inc = candidate.legs.target().incarnation;
2485 bool superseded =
false;
2486 for (
const auto other : placement_) {
2487 if (other.second.placement_cycle == current_position_cycle_
2488 && other.second.projection_predecessor != 0
2489 && (other.second.projection_predecessor == inc
2490 || other.second.projection_predecessor == target_inc)) {
2504 const auto& peer = other.second;
2505 if (other.first > inc && peer.placement_cycle == current_position_cycle_
2506 && peer.family == candidate.family
2507 && peer.source_id == candidate.source_id
2508 && peer.from_entry == candidate.from_entry
2509 && peer.placement_script_open_ms > candidate.placement_script_open_ms
2510 && !same_double_bits(peer.exit_levels.stop, candidate.exit_levels.stop)) {
2515 if (superseded)
continue;
2517 const exit_legs::Action restore{candidate.legs.target(), candidate.legs.revision(),
2518 cause, exit_legs::Restore{{exit_legs::Leg::Stop, exit_legs::Leg::Limit,
2519 exit_legs::Leg::Trail}}};
2520 const auto restored = candidate.legs.apply(candidate.legs.target(), restore); invalidate_retirement();
2521 if (restored != exit_legs::Result::Applied
2522 && restored != exit_legs::Result::Replay) {
2525 candidate.restored_after_margin =
true;
2526 const double held = std::abs(physical.signed_units);
2531 const bool full = !std::isfinite(candidate.requested_qty)
2532 ? (!std::isfinite(candidate.qty_percent) || candidate.qty_percent >= 100.0 - 1e-5)
2533 : candidate.requested_qty >= held - 1e-9;
2534 const bool ready = !candidate.leg_activation.bounds()
2535 || candidate.leg_activation.stop_ready(
2536 current_position_cycle_, context.coordinate.interval_index);
2537 const double executable_stop = finite_positive(candidate.exit_levels.stop)
2538 ? candidate.exit_levels.stop : revive_stop;
2539 const bool reaches = std::isfinite(executable_stop)
2540 && (physical.signed_units < 0.0
2541 ? executable_stop <=
event.resolved_price
2542 : executable_stop >=
event.resolved_price);
2543 if (!marketable && full && ready && reaches) {
2544 marketable = candidate;
2545 marketable_handle = native_order::RequestHandle{
2546 event.handle().run, candidate.legs.target().incarnation};
2550 physical.signed_units, current_position_cycle_, marketable_handle.incarnation);
2551 if (!marketable)
return;
2552 if (marketable_handle.incarnation) {
2553 (void)require_host().cancel(marketable_handle);
2554 retire(marketable_handle);
2556 native_order::Request
request;
2558 request.label = marketable->source_id;
2559 request.comment = marketable->comment;
2560 PlacementSnapshot
snapshot = *marketable;
2561 snapshot.forced_execution_price =
event.resolved_price;
2562 snapshot.requested_qty = std::numeric_limits<double>::quiet_NaN();
2568 const auto accepted = submit_or_replace(
2569 std::move(request), std::move(snapshot),
false,
2570 "__margin_revival__" + std::to_string(marketable->legs.target().incarnation));
2572 (void)require_host().execute_current(
2573 {*accepted, NativeCurrentPriceRule::NearestTick});
2578 position_tail_spill_.reset();
2579 position_tail_summary_ = std::make_shared<detail::AdapterPositionTailSummary>();
2580 exit_scratch_ =
nullptr;
2581 pine_view_host_ =
nullptr;
2583 retired_row_scratch_.unsuperseded.clear();
2584 retired_row_scratch_.reissues.clear();
2585 retired_row_scratch_.adopted_reissues.clear();
2586 retired_row_scratch_.reissue_high_water = 0;
2587 retired_row_scratch_.reissue_cycle = -1;
2588 retired_row_scratch_.roster_pass_due =
true; retirement_gate_ = {};
2589 if (
auto* index = adapter_lookup_index(bound_consumer(),
this, run_counter_,
false))
2592 cohort_opened_slot_count_ = 0;
2593 cohorts_by_id_.clear();
2594 pending_scan_work_ = {}; retirement_work_ = {};
2596 live_by_source_key_.clear();
2597 clear_bracket_families();
2598 pending_write(pending_bracket_legs_).clear();
2599 pending_write(pending_entries_).clear();
2600 delayed_market_orders_.clear();
2601 deferred_open_marketable_sells_.clear();
2602 throttled_reopen_rearm_.clear();
2603 entry_openings_interval_index_ = -1;
2604 entry_openings_this_interval_ = 0;
2605 pending_write(pending_same_bar_commands_).clear();
2606 pending_write(source_shadow_pending_).clear();
2607 pending_same_bar_close_qty_ = 0.0;
2608 pending_relative_exits_.clear();
2609 anchored_relative_legs_.clear();
2610 anchored_relative_stats_ = {};
2611 anchored_cohort_sequence_ = 0;
2612 per_entry_exits_.clear();
2613 pending_write(pending_coof_requests_).clear();
2614 pending_margin_revivals_.clear();
2616 first_open_newborns_.clear();
2617 dropped_close_count_ = 0;
2618 dropped_close_digest_ = 1469598103934665603ULL;
2619 open_entry_fees_.clear();
2620 trade_exit_phase_.clear(); exit_group_certificate_ = {}; exit_group_work_ = {};
2621 exit_phase_final_ = 0;
2622 exit_phase_digest_ = 1469598103934665603ULL;
2623 exit_rank_keys_.clear();
2624 exit_rank_keys_digest_ = 1469598103934665603ULL;
2625 exit_rank_peak_ = 0;
2626 exit_rank_alt_peak_ = 0;
2627 admission_events_folded_ = 0;
2628 admission_events_last_ = 0;
2629 admission_events_digest_ = 1469598103934665603ULL;
2630 current_debited_applied_ordinals_.clear();
2631 intraday_loss_relabel_ordinals_.clear();
2632 consumed_partial_exit_cycles_.clear();
2633 bracket_shadowed_openings_.clear();
2634 named_entry_cancel_tokens_.clear();
2635 id_bound_exits_.clear();
2636 close_logical_units_.clear();
2637 close_ledger_records_.clear();
2638 pyramiding_records_.clear();
2639 exit_tombstones_.clear();
2640 close_batch_callsites_.clear();
2641 close_batch_bar_ = -1;
2642 close_batch_queue_sequence_ = 0;
2643 close_batch_pending_debt_ = 0.0;
2644 close_batch_admitted_total_ = 0.0;
2645 receipt_cursor_ = 0;
2646 last_applied_ordinal_ = 0;
2647 terminal_receipt_cursor_ = 0;
2648 materializing_relative_ =
false;
2649 current_position_cycle_ = 0;
2650 current_position_sign_ = 0;
2651 next_sequential_group_ = 0;
2652 source_batch_mutated_ =
false;
2653 coof_recalc_active_ =
false;
2654 coof_first_open_ =
false;
2655 coof_open_stop_next_waypoint_ =
false;
2656 coof_fill_forced_ =
false;
2657 coof_market_entry_recalc_incarnation_ = 0;
2658 coof_market_entry_recalc_fill_seq_ = 0;
2659 coof_current_fill_seq_ = 0;
2660 coof_fill_cursor_t_ = kNaN;
2661 coof_fill_target_print_ = kNaN;
2663 coof_script_bar_ = {};
2664 coof_script_bar_valid_ =
false;
2665 pooc_close_basis_last_bar_ = std::numeric_limits<std::int64_t>::min();
2666 pooc_close_basis_count_ = 0;
2667 pooc_close_basis_digest_ = 1469598103934665603ULL;
2668 pooc_open_basis_ = 0.0;
2669 pooc_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
2670 close_all_pending_script_bar_ = std::numeric_limits<std::int64_t>::min();
2671 close_first_script_bar_ = std::numeric_limits<std::int64_t>::min();
2672 close_first_side_ = 0;
2673 last_fx_rate_ = kNaN;
2674 position_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
2675 position_open_epoch_ = 0;
2676 position_open_bar_index_ = -1;
2677 position_open_phase_ = NativePathPhase::None;
2678 position_open_priced_ =
false;
2679 last_margin_call_script_bar_ = std::numeric_limits<std::int64_t>::min();
2680 kernel_margin_path_point_ = std::numeric_limits<std::uint64_t>::max();
2681 kernel_margin_resize_point_ = std::numeric_limits<std::uint64_t>::max();
2682 close_margin_point_ = std::numeric_limits<std::uint64_t>::max();
2683 close_margin_cancelled_bar_ = std::numeric_limits<std::int64_t>::min();
2684 close_margin_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
2685 close_margin_follow_up_units_ = 0.0;
2686 close_margin_open_bar_ = std::numeric_limits<std::int64_t>::min();
2687 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
2688 owed_follow_up_point_ = -1;
2689 owed_follow_up_units_ = 0.0;
2690 open_exit_first_bar_ = std::numeric_limits<std::int64_t>::min();
2691 open_exit_first_origin_ = 0;
2692 open_exit_first_id_.clear();
2693 open_exit_first_fill_ = 0.0;
2694 open_call_sequence_ =
false;
2695 unrepeated_margin_calls_.clear();
2696 close_call_after_script_bar_ = std::numeric_limits<std::int64_t>::min();
2697 close_call_after_script_book_ = 0.0;
2698 close_call_after_script_units_ = 0.0;
2699 waypoint_chain_bar_ = std::numeric_limits<std::int64_t>::min();
2700 pooc_close_checkpoint_deferred_ms_ = std::numeric_limits<std::int64_t>::min();
2701 signal_close_mc_event_bar_ = -1;
2702 signal_close_mc_position_cycle_ = 0;
2703 signal_close_mc_entry_incarnation_ = 0;
2704 signal_close_mc_fill_seq_ = 0;
2705 signal_close_mc_before_qty_ = kNaN;
2706 signal_close_mc_remaining_qty_ = kNaN;
2707 last_margin_call_event_ordinal_ = 0;
2708 last_margin_call_entry_incarnation_ = 0;
2709 last_margin_call_position_cycle_ = 0;
2710 last_margin_call_at_script_close_ =
false;
2711 last_margin_call_closed_units_ = 0.0;
2712 last_margin_call_remaining_units_ = 0.0;
2713 source_gains_ = 0.0;
2714 source_losses_ = 0.0;
2715 source_money_folded_ = 0;
2716 risk_coof_direct_script_bar_ = std::numeric_limits<std::int64_t>::min();
2717 cap_latest_fill_ = 0;
2719 risk_.observed_peak_equity = kNaN;
2720 risk_.observed_max_drawdown = 0.0;
2721 risk_.intraday_block_day = std::numeric_limits<std::int64_t>::min();
2722 risk_.intraday_cancel_pending =
false;
2723 policy_script_bar_ = {};
2724 policy_script_bar_valid_ =
false;
2725 market_pyramid_adds_.clear();
2726 trail_state_at_open_.clear();
2727 carried_trail_retunes_.clear();
2728 stream_mode_ =
false;
2729 bar_magnifier_ =
false;
2731 pending_short_seed_ = {};
2732 short_seed_long_candidate_ = {};
2733 last_bar_dual_entry_path_ = 0;
2734 last_bar_dual_entry_script_open_ms_ =
2735 std::numeric_limits<std::int64_t>::min();
2736 source_sequence_ = 0;
2737 command_ordinal_ = 0;
2738 broker_open_epoch_ = 0;
2739 last_broker_open_ms_ = std::numeric_limits<std::int64_t>::min();
2740 source_command_sequence_ = 0;
2741 entry_attempt_bar_ = -1;
2742 entry_attempts_on_bar_ = 0;
2744 refresh_pending_view();
2755[[gnu::always_inline]]
inline void
2756PineExecutionAdapter::refresh_configuration_scopes(
const PineStrategyConfig& config)
noexcept {
2757 const bool commission_free = config.commission_value == 0.0;
2758 const bool one_lot_cap = config.pyramiding >= 0 && config.pyramiding <= 1;
2759 gain_loss_configured_ = commission_free && staged_scopes_.unit_money && lot_grid_pinned_;
2760 slipped_long_configured_ = config.margin_long == 100.0 && commission_free
2761 && config.slippage > 0 && staged_scopes_.unit_grid && one_lot_cap
2762 && staged_scopes_.unit_money;
2763 unified_configured_ = lot_grid_pinned_ && !config.calc_on_order_fills && commission_free
2764 && one_lot_cap && staged_scopes_.unit_money && staged_scopes_.mintick;
2765 ledger_records_configured_ = config.pyramiding > 1 && !config.close_entries_rule_any
2766 && lot_grid_pinned_;
2767 pooc_fee_configured_ = config.process_orders_on_close
2769 && config.commission_value > 0.0
2771 && staged_scopes_.positive_grid && staged_scopes_.unit_point_money;
2775 refresh_configuration_scopes(config);
2781 lot_grid_pinned_ = grid && *grid > 0.0 && decimal_grid_exponent(*grid).has_value();
2782 staged_scopes_.positive_grid = grid && *grid > 0.0;
2783 staged_scopes_.unit_grid = grid && *grid > 0.0 && *grid <= 1.0;
2784 staged_scopes_.unit_point_money = staged_.syminfo.pointvalue == 1.0
2785 && staged_.account_fx_effective_from_ms.empty();
2786 staged_scopes_.unit_money = staged_scopes_.unit_point_money && staged_.account_fx == 1.0;
2787 staged_scopes_.mintick = finite_positive(staged_.syminfo.mintick);
2788 refresh_configuration_scopes(config_);
2791 stream_mode_ = is_stream;
2792 bar_magnifier_ = bar_magnifier;
2797 case NativeRunSpecField::InstrumentFeedInstrument:
return "instrument_feed_instrument";
2798 case NativeRunSpecField::InstrumentFeedTimeframe:
return "instrument_feed_timeframe";
2799 case NativeRunSpecField::InstrumentFeedBars:
return "instrument_feed_bars";
2800 case NativeRunSpecField::InstrumentFeedClose:
return "instrument_feed_close";
2801 case NativeRunSpecField::InstrumentFeedColumns:
return "instrument_feed_columns";
2802 case NativeRunSpecField::SubscriptionInstrument:
return "subscription_instrument";
2803 default:
return nullptr;
2809 const char*
symbol =
nullptr;
2810 const char* setting =
nullptr;
2811 const char* option =
nullptr;
2814 case F::Ticker:
symbol =
"ticker";
break;
2815 case F::TickerId:
symbol =
"ticker_id";
break;
2816 case F::Type:
symbol =
"type";
break;
2817 case F::Currency:
symbol =
"currency";
break;
2818 case F::BaseCurrency:
symbol =
"base_currency";
break;
2819 case F::Description:
symbol =
"description";
break;
2820 case F::VolumeType:
symbol =
"volume_type";
break;
2821 case F::Timezone:
symbol =
"timezone";
break;
2822 case F::Session:
symbol =
"session";
break;
2823 case F::ChartTimezone:
symbol =
"chart_timezone";
break;
2824 case F::PointValue:
symbol =
"point_value";
break;
2825 case F::AccountFx:
symbol =
"account_fx";
break;
2826 case F::PriceTick:
symbol =
"price_tick";
break;
2827 case F::QuantityGrid:
2829 case F::InitialCapital: setting =
"initial_capital";
break;
2830 case F::SlippageTicks: setting =
"slippage_ticks";
break;
2831 case F::FeeKind: setting =
"fee_kind";
break;
2832 case F::FeeValue: setting =
"fee_value";
break;
2833 case F::CloseExecution: setting =
"close_execution";
break;
2834 case F::MaxAbsUnits: setting =
"max_abs_units";
break;
2835 case F::MaxOpenLots: setting =
"max_open_lots";
break;
2836 case F::AllowedOpenDirections: setting =
"allowed_open_directions";
break;
2837 case F::InitialMarginFraction: setting =
"initial_margin_fraction";
break;
2838 case F::PriceGrid: setting =
"price_grid";
break;
2839 case F::GridRounding: setting =
"grid_rounding";
break;
2840 case F::MarginModel: setting =
"margin_model";
break;
2841 case F::MarginInitial: setting =
"margin_initial";
break;
2842 case F::MarginMaintenance: setting =
"margin_maintenance";
break;
2843 case F::MarginSizing: setting =
"margin_sizing";
break;
2844 case F::MarginShortfallMultiple: setting =
"margin_shortfall_multiple";
break;
2845 case F::MarginMinUnits: setting =
"margin_min_units";
break;
2846 case F::MarginCheck: setting =
"margin_check";
break;
2847 case F::MarginEquityBasis: setting =
"margin_equity_basis";
break;
2848 case F::MarginLevelBase: setting =
"margin_level_base";
break;
2849 case F::Calculation: setting =
"calculation";
break;
2850 case F::OpenBarView: setting =
"open_bar_view";
break;
2851 case F::RiskLimits: setting =
"risk_limits";
break;
2852 case F::RiskDrawdown: setting =
"risk_drawdown";
break;
2853 case F::RiskIntradayLoss: setting =
"risk_intraday_loss";
break;
2854 case F::RiskLossDays: setting =
"risk_loss_days";
break;
2855 case F::RiskFillsPerDay: setting =
"risk_fills_per_day";
break;
2856 case F::RiskDayBasis: setting =
"risk_day_basis";
break;
2857 case F::RiskAction: setting =
"risk_action";
break;
2858 case F::QuantityTolerance: setting =
"quantity_tolerance";
break;
2859 case F::InputTimeframe: option =
"input_timeframe";
break;
2860 case F::ScriptTimeframe: option =
"script_timeframe";
break;
2861 case F::IntrabarTimeframe: option =
"intrabar_timeframe";
break;
2862 case F::IntrabarSamples: option =
"intrabar_samples";
break;
2863 case F::IntrabarDistribution: option =
"intrabar_distribution";
break;
2864 case F::IntrabarVolumeSamples: option =
"intrabar_volume_samples";
break;
2865 case F::IntrabarSampleEligibility: option =
"intrabar_sample_eligibility";
break;
2866 case F::PathOrder: option =
"path_order";
break;
2869 case F::InstrumentFeedInstrument:
2870 case F::InstrumentFeedTimeframe:
2871 case F::InstrumentFeedBars:
2872 case F::InstrumentFeedClose:
2873 case F::InstrumentFeedColumns:
2874 case F::SubscriptionInstrument:
2880 case F::AbortReporting:
2881 case F::TimeframeUndetected:
2882 case F::SlotLabelPolicy:
2883 case F::LegacyTolerance:
2884 case F::ReportPolicy:
2885 case F::SubscriptionTimeframe:
2886 case F::SubscriptionBars:
2887 case F::AuxiliaryFeedTimeframe:
2888 case F::AuxiliaryFeedBars:
2889 case F::SubscriptionSource:
2890 case F::EventRetention:
2900 {{
"field", setting}});
2904 {{
"option", option}});
2908 {{
"reason",
"kernel_refused_series"}, {
"field", feed}});
2910 }
catch (
const std::bad_alloc&) {
2920 std::vector<NativeInstrumentFeed>* instrument_feeds)
const {
2922 if (run_counter_ == std::numeric_limits<std::uint64_t>::max()) {
2924 "Pine native run counter exhausted");
2930 spec.
identity = {session +
"@" + timezone, ++run_counter_};
2944 std::string effective_input = args.
input_tf;
2945 if (effective_input.empty() && args.
n >= 2 && args.
bars !=
nullptr) {
2948 spec.
input_tf = std::move(effective_input);
2984 std::vector<NativeInstrumentFeed>* home;
2986 int pending = std::uncaught_exceptions();
2988 if (home !=
nullptr && std::uncaught_exceptions() > pending)
2991 } feeds_back{instrument_feeds, spec};
2992 if (instrument_feeds !=
nullptr) spec.
instrument_feeds = std::exchange(*instrument_feeds, {});
3002 |
static_cast<std::uint32_t
>(NativeFeedTolerance::WarmupNonNegativeOHLC));
3006 ? NativeCloseExecution::AfterCalculation : NativeCloseExecution::NextEligiblePoint;
3015 spec.
calculation = NativeCalculationTrigger::BarCloseAndFills;
3044 spec.
report_policy = NativeReportPolicy::KernelRecordedAtHostMarks;
3069 const double margin_long_fraction = config.
margin_long / 100.0;
3070 const double margin_short_fraction = config.
margin_short / 100.0;
3071 if (source_margin_call_enabled_
3072 && std::isfinite(margin_long_fraction) && margin_long_fraction > 0.0
3073 && std::isfinite(margin_short_fraction) && margin_short_fraction > 0.0) {
3099 margin.
check = NativeLiquidationCheck::PathAdverseExtremeMark;
3106 ? NativeMarginEquityBasis::MarkedEquity
3107 : NativeMarginEquityBasis::MarkedEquityBeforeOpenCommission;
3109 margin.
level_base = NativeLiquidationLevelBase::RealizedOnly;
3119 if (host_ !=
nullptr)
3120 NativeExecutionConsumer::bound(*host_).stage_lower_path_source(
nullptr);
3125 const int volume_weighted_cap =
3129 IntrabarPath::synthesized path;
3134 path.volume_weighted_max_samples = volume_weighted_cap;
3146 IntrabarPath::lower_tf path;
3147 if (host_ !=
nullptr) {
3148 NativeExecutionConsumer::bound(*host_).stage_lower_path_source(
3151 }
else if (args.
bars && args.
n > 0) {
3162 path.volume_weighted_max_samples = volume_weighted_cap;
3178 IntrabarPath::lower_tf inert;
3182 inert.volume_weighted =
false;
3183 inert.volume_weighted_min_samples = 2;
3184 inert.volume_weighted_max_samples = 64;
3193 const auto validation = host_ !=
nullptr
3194 ? NativeExecutionConsumer::bound(*host_).normalize_projected_spec(spec)
3198 "Pine adapter produced invalid native run spec field "
3199 + std::to_string(
static_cast<unsigned>(validation.field)));
3204 const auto& cache_policy = host_ ? host_->cache_policy_for_run() : defaults;
3205 auto exit_scratch = detail::make_exit_scratch(spec.
identity, cache_policy.projection_exit_pages);
3210std::uint64_t PineExecutionAdapter::key_for(
const SourceId& left,
const SourceId& right)
const noexcept {
3211 return source_key(left, right);
3217 auto found = cohorts_by_id_.
find(
id);
3218 if (found != cohorts_by_id_.
end())
return found->second.handle;
3220 cohorts_by_id_.
admit(
id, facts.spillable());
3222 if (facts.handle.value == 0) {
3224 "native cohort allocation refused");
3226 const auto result =
facts.handle;
3227 cohorts_by_id_.place(
id, std::move(facts));
3232 const auto&
host = require_host();
3237 snapshot.mark = nearest_tick(point->price, staged_.syminfo.mintick);
3239 mark_sizing_equity(snapshot,
snapshot.mark);
3243 snapshot.fx = active_staged_fx(point->decision.sub_bar_open_ms);
3273double PineExecutionAdapter::default_sizing_cash(
3275 if (config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)) {
3276 if (!std::isfinite(config_.default_qty_value)) {
3277 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3278 EngineValueReason::nonfinite);
3280 return config_.default_qty_value;
3283 if (default_sizing_reserves_cash_fee()) {
3284 if (!finite_positive(sizing.strategy_equity)) {
3285 if (!std::isfinite(sizing.strategy_equity)) {
3286 engine_command_value_invalid_or_count(
"sizing.basis", EngineValueStage::sizing,
3287 EngineValueReason::nonfinite);
3291 const double equity = staged_.quantity_grid ? source_money_round(sizing.strategy_equity)
3292 : sizing.strategy_equity;
3293 const double money = config_.default_qty_value / 100.0 * equity;
3294 if (!std::isfinite(money)) {
3295 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3296 EngineValueReason::nonfinite);
3299 const double cash = money - config_.commission_value;
3300 if (!std::isfinite(cash)) {
3301 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3302 EngineValueReason::nonfinite);
3308 const double unit_cost = sizing.price * staged_.syminfo.pointvalue * sizing.fx;
3309 if (!finite_positive(unit_cost)) {
3310 if (!std::isfinite(unit_cost)) {
3311 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3312 EngineValueReason::nonfinite);
3316 const double divisor = unit_cost + config_.commission_value;
3317 if (!std::isfinite(divisor)) {
3318 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3319 EngineValueReason::nonfinite);
3321 const double cash = money * unit_cost / divisor;
3322 if (!std::isfinite(cash)) {
3323 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3324 EngineValueReason::nonfinite);
3328 if (!finite_positive(sizing.equity)) {
3329 if (!std::isfinite(sizing.equity)) {
3330 engine_command_value_invalid_or_count(
"sizing.basis", EngineValueStage::sizing,
3331 EngineValueReason::nonfinite);
3335 const double money = config_.default_qty_value / 100.0 * sizing.equity;
3336 const double cash = staged_.quantity_grid ? source_money_round(money) : money;
3337 if (!std::isfinite(money) || !std::isfinite(cash)) {
3338 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3339 EngineValueReason::nonfinite);
3348bool PineExecutionAdapter::default_sizing_reserves_percent_fee() const noexcept {
3351 && config_.commission_value > 0.0;
3354bool PineExecutionAdapter::default_sizing_reserves_cash_fee() const noexcept {
3358 && config_.commission_value > 0.0;
3367double PineExecutionAdapter::default_sizing_lot_floor(
double units)
const noexcept {
3369 && staged_.quantity_grid) {
3373 && !default_sizing_reserves_percent_fee() && !default_sizing_reserves_cash_fee()
3374 && staged_.syminfo.pointvalue == 1.0 && staged_.account_fx == 1.0
3375 && staged_.account_fx_effective_from_ms.empty()) {
3376 if (
const auto floored = decimal_floor_lot(units, *staged_.quantity_grid))
3379 return source_money_floor_lot(units, staged_.quantity_grid);
3381 return floor_quantity_grid(units, staged_.quantity_grid);
3387std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_shape(
3389 const double cash = default_sizing_cash(sizing);
3390 if (!finite_positive(cash))
return std::nullopt;
3391 native_order::Sized sized;
3392 sized.basis = native_order::CashValue{cash};
3393 sized.time = native_order::SizeTime::AtAcceptance;
3394 sized.price = native_order::SizePrice::SignalOnTick;
3395 sized.grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
3396 sized.reserve_percent_fee = default_sizing_reserves_percent_fee();
3404double PineExecutionAdapter::default_sizing_units(
const PineSizingSnapshot& sizing)
const {
3405 if (!finite_positive(sizing.price) || !finite_positive(sizing.fx)) {
3406 if (!std::isfinite(sizing.price) || !std::isfinite(sizing.fx)) {
3407 engine_command_value_invalid_or_count(
"sizing.quantity", EngineValueStage::sizing,
3408 EngineValueReason::nonfinite);
3412 const auto sized = default_sizing_shape(sizing);
3413 if (!sized)
return 0.0;
3414 const auto quotient = require_host().native_sized_units(
3415 *sized, sizing.price, sizing.equity, sizing.fx);
3416 return quotient ? default_sizing_lot_floor(*quotient) : 0.0;
3423double PineExecutionAdapter::typed_quantity_units(
double money,
bool percent,
double price,
3425 native_order::Sized sized;
3426 sized.basis = native_order::CashValue{money};
3427 sized.grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
3428 sized.reserve_percent_fee = percent
3430 && config_.commission_value > 0.0;
3431 return floor_quantity_grid(
3432 require_host().native_sized_units(sized,
price, 0.0, fx).value_or(0.0),
3433 staged_.quantity_grid);
3441std::optional<double> PineExecutionAdapter::typed_entry_units(
3443 native_order::RequestHandle target)
const {
3444 const double qty = source.requested_qty;
3446 return typed_quantity_units(qty,
false,
price, fx);
3448 if (source.terms_priced_reverse) {
3449 const auto* pine_host = pine_view_of(&require_host());
3450 if (!pine_host)
return std::nullopt;
3451 const auto projection = pine_host->project_native_settlement_at(
3452 execution::Flatten{},
3453 execution::Fill{
price, source.source_id, source.comment, target.incarnation}, fx);
3454 return typed_quantity_units(projection.realized_balance * qty / 100.0,
true,
price, fx);
3456 return typed_quantity_units(percent_commission_live_equity(
price, fx) * qty / 100.0,
true,
3460std::optional<native_order::Sized> PineExecutionAdapter::default_sizing_intent(
3465 if (!finite_positive(staged_.account_fx)
3466 || !finite_positive(sizing.price) || !finite_positive(staged_.syminfo.pointvalue)
3467 || !finite_positive(staged_.syminfo.mintick)) {
3468 return std::nullopt;
3470 auto sized = default_sizing_shape(sizing);
3471 if (sized) sized->side = is_long ? native_order::Side::Long : native_order::Side::Short;
3479double PineExecutionAdapter::default_market_sizing_price(
3480 double mark,
bool is_long)
const noexcept {
3481 const double tick = staged_.syminfo.mintick;
3482 return nearest_tick(mark + (is_long ? 1.0 : -1.0) * config_.slippage * tick, tick);
3497std::optional<double> PineExecutionAdapter::pooc_fee_units(
3500 if (!rules.pooc_fee_sizing || !config_.process_orders_on_close
3502 || !(config_.commission_value > 0.0)
3504 || !staged_.quantity_grid || !(*staged_.quantity_grid > 0.0)
3505 || staged_.syminfo.pointvalue != 1.0 || sizing.fx != 1.0
3506 || !staged_.account_fx_effective_from_ms.empty() || !finite_positive(sizing.price)
3507 || !std::isfinite(sizing.equity)) {
3508 return std::nullopt;
3510 const double money = config_.default_qty_value / 100.0 * sizing.equity;
3511 const double cash = source_money_round(money);
3512 const double unit = tv_pooc_fee_grossed_unit(sizing.price);
3513 if (!finite_positive(cash) || !finite_positive(unit))
return std::nullopt;
3514 const double quotient = cash / unit;
3515 if (rules.decimal_sizing) {
3516 if (
const auto floored = decimal_floor_lot(quotient, *staged_.quantity_grid))
3519 return source_money_floor_lot(quotient, staged_.quantity_grid);
3533double PineExecutionAdapter::tv_pooc_fee_grossed_unit(
double price)
const {
3534 double unit = source_money_round(
price * (1.0 + config_.commission_value / 100.0));
3535#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
3536 unit = fixture_adapter_execution_value(
"fee.adapter.grossed_unit", unit);
3538 if (std::isfinite(
price) && !std::isfinite(unit)) {
3539 engine_command_value_invalid_or_count(
"fee.commission", EngineValueStage::fee,
3540 EngineValueReason::numeric_overflow);
3547 if (!finite_positive(sizing.
mark))
return false;
3548 sizing.
price = default_market_sizing_price(sizing.
mark, is_long);
3549#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
3550 sizing.
price = fixture_adapter_execution_value(
"slippage.default.core", sizing.
price);
3552 check_slippage_fill_price(sizing.
price, std::isfinite(sizing.
mark));
3553 mark_sizing_equity(sizing, sizing.
mark);
3554 return finite_positive(default_sizing_units(sizing))
3555 && default_sizing_intent(sizing, is_long).has_value()
3556 && core_sizing_price_matches(sizing, is_long);
3559bool PineExecutionAdapter::core_sizing_price_matches(
3562 if (!finite_positive(tick) || !finite_positive(sizing.
price))
return false;
3566 if (!point || !finite_positive(point->price))
return false;
3570 if (active_staged_fx(point->decision.coordinate.effective_time_ms) != sizing.
fx)
return false;
3577 const auto on_tick = [tick](
double value) {
return std::round(value / tick) * tick; };
3578 const double ticks = config_.
slippage < 0 ? 0.0 :
static_cast<double>(config_.
slippage);
3579 const double slipped = is_long ?
on_tick(point->price) + ticks * tick
3580 :
on_tick(point->price) - ticks * tick;
3584bool PineExecutionAdapter::same_bar_market_tx_scope()
const {
3589bool PineExecutionAdapter::fixed_unmargined_market_batch_scope()
const {
3590 return config_.pyramiding >= 2
3592 && config_.margin_long == 0.0 && config_.margin_short == 0.0
3593 && config_.commission_value == 0.0 && !config_.calc_on_order_fills
3594 && same_point_pair_scope();
3606bool PineExecutionAdapter::same_point_pair_scope()
const {
3607 const bool all_in_percent = config_.default_qty_type
3609 && config_.default_qty_value >= 100.0;
3610 const bool fixed_default = config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED);
3611 const bool variable_default = config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
3613 && config_.default_qty_value < 100.0);
3614 const bool variable_short_seed = variable_default && short_seed_context_is_live();
3615 if (!host_ || config_.process_orders_on_close
3616 || coof_recalc_active_ || config_.close_entries_rule_any || all_in_percent
3617 || (!fixed_default && !variable_short_seed)
3618 || config_.slippage != 0
3619 || risk_.direction != 0 || risk_.max_cons_loss_days != 0
3620 || risk_.max_drawdown > 0.0 || risk_.max_intraday_loss > 0.0
3621 || risk_.max_position_size > 0.0 || risk_.halted ||
cap.active()) {
3625 const auto* inert = state.spec ? state.spec->intrabar.lower() :
nullptr;
3627 const bool inactive_sampler_path = inert !=
nullptr && inert->bars.empty()
3628 && (views.lower_path ==
nullptr || views.lower_path->count() == 0)
3629 && inert->tf == state.spec->input_tf && inert->samples == 4
3631 && !inert->volume_weighted && inert->volume_weighted_min_samples == 2
3632 && inert->volume_weighted_max_samples == 64
3634 return state.spec !=
nullptr
3635 && (state.spec->intrabar.is_none() || inactive_sampler_path);
3642 double stop_price)
const {
3643 if (price_present(limit_price) && price_present(stop_price))
3644 return native_order::StopLimit{stop_price, limit_price};
3645 if (price_present(limit_price))
return native_order::Limit{limit_price};
3646 if (price_present(stop_price))
return native_order::Stop{stop_price};
3647 return native_order::Market{};
3651 std::int64_t cohort)
const {
3652 if (name.empty() || type == 0)
return native_order::NoGroup{};
3653 const auto group = fnv_string(name);
3654 return native_order::Member{group == 0 ? 1 : group,
cohort,
3655 type == 1 ? native_order::GroupEffect::Cancel : native_order::GroupEffect::Reduce};
3658void PineExecutionAdapter::remember(
const native_order::RequestHandle& handle,
3665 if (placement_.contains(handle.incarnation)) {
3666 retired_row_scratch_.reissues.clear();
3667 retired_row_scratch_.reissue_high_water = 0;
3668 }
else if (handle.incarnation <= retired_row_scratch_.reissue_high_water) {
3669 retired_row_scratch_.adopted_reissues.push_back(handle.incarnation);
3671 if (
snapshot.reservation_growth_owner_incarnation != 0
3672 &&
snapshot.reservation_growth_owner_incarnation != handle.incarnation) {
3673 snapshot.reservation_growth_source.assign_capture(
3674 handle.incarnation,
snapshot.reservation_growth_owner_incarnation);
3675 snapshot.pooc_global_full_exit_bound_add =
true;
3677 initialize_l4c_policy(snapshot, handle);
3683 if (
snapshot.projection_predecessor == 0) {
3685 }
else if (placement_.contains(
snapshot.projection_predecessor)) {
3686 const auto& predecessor = placement_.at(
snapshot.projection_predecessor);
3687 snapshot.chain_origin_sequence = predecessor.void_issue && !
snapshot.void_issue
3688 ?
snapshot.source_sequence : predecessor.chain_origin_sequence;
3689 }
else if (
snapshot.chain_origin_sequence == 0) {
3701 remember_live(handle, std::move(snapshot));
3704 const auto family = placement_.at(handle.incarnation).family;
3707 note_exit_rank_transients();
3709 update_l4c_priority();
3710 refresh_pending_view();
3713void PineExecutionAdapter::retire(native_order::RequestHandle handle)
noexcept {
3714 retired_row_scratch_.roster_pass_due =
true;
3715 forget_live(handle);
3716 first_open_newborns_.erase(std::remove(first_open_newborns_.begin(),
3717 first_open_newborns_.end(), handle), first_open_newborns_.end());
3718 for (
auto it = live_by_source_key_.begin(); it != live_by_source_key_.end();) {
3719 if (it->second == handle) it = live_by_source_key_.erase(it);
else ++it;
3721 if (pending_short_seed_.ready && (handle == pending_short_seed_.plan.long_entry
3722 || handle == pending_short_seed_.plan.materialize_long
3723 || handle == pending_short_seed_.plan.final_short)) {
3724 pending_short_seed_ = {};
3726 if (handle == short_seed_long_candidate_) short_seed_long_candidate_ = {};
3727 if (short_seed_.active && (handle == short_seed_.long_entry
3728 || handle == short_seed_.materialize_long || handle == short_seed_.final_short)) {
3729 short_seed_.active =
false;
3731 refresh_pending_view();
3738std::atomic<bool> retain_rows{
false};
3739std::atomic<std::uint64_t> rows_erased{0};
3742 retain_rows.store(retain, std::memory_order_relaxed);
3747std::atomic<bool> carry_bindings{
true};
3750 carry_bindings.store(carry, std::memory_order_relaxed);
3753 return carry_bindings.load(std::memory_order_relaxed);
3760bool PineExecutionAdapter::lifecycle_readable(
const PlacementSnapshot& row)
const noexcept {
3761 if (row.legs.target().incarnation == 0 || filled_limit_without_revival(row))
return false;
3762 const std::uint64_t origin = row.bracket_origin.incarnation;
3763 bool askable =
false;
3764 for (
const auto& handle : live_handles_)
if (handle.incarnation == origin) askable =
true;
3765 for (
const auto& cohort : cohorts_by_id_.held_rows())
3766 for (
const auto& opening : cohort.second.opened)
if (opening.incarnation == origin) askable =
true;
3767 if ((row.placement_cycle >= current_position_cycle_ && !withdrawn(row.legs))
3768 || (askable && !row.legs.dormant()))
return true;
3771 for (
auto peer : placement_) {
3772 const auto& pin = peer.second;
3773 if (!pin.legs.dormant() || pin.legs.target().incarnation == 0 || pin.family != row.family
3774 || pin.bracket_origin != row.bracket_origin || pin.source_id != row.source_id
3775 || pin.from_entry != row.from_entry)
continue;
3776 return &pin == &row;
3861void PineExecutionAdapter::sweep_retired_rows(
const NativeDecisionContext& context, RetirementWork& work) {
3863 const std::size_t phase_base =
detail::run_consumer(require_host()).closed_trade_prefix().base;
3864 if (exit_phase_final_ < phase_base)
3866 "exit-phase fold precedes the sealed prefix");
3867 const auto finish_phases = [&] {
3868 for (; exit_phase_final_ < phase_base + trade_exit_phase_.size(); ++exit_phase_final_) {
3870 trade_exit_phase_.at(exit_phase_final_ - phase_base));
3876 if (placement_.size() <= live_handles_.size()) { finish_phases();
return; }
3877 const int interval = context.coordinate.interval_index;
3878 const std::int64_t cycle = current_position_cycle_;
3879 ++work.placement_sweeps;
3880 auto& sweep = retired_row_scratch_;
3882 const auto contains = [](
const std::vector<std::uint64_t>& sorted, std::uint64_t
value) {
3883 return std::binary_search(sorted.begin(), sorted.end(), value);
3891 auto& askable_origins = sweep.askable_origins;
3892 for (
const auto& handle : live_handles_) askable_origins.push_back(handle.incarnation);
3893 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
3894 for (
const auto& opening :
cohort.second.opened)
3895 askable_origins.push_back(opening.incarnation);
3897 std::sort(askable_origins.begin(), askable_origins.end());
3898 askable_origins.erase(std::unique(askable_origins.begin(), askable_origins.end()),
3899 askable_origins.end());
3903 auto& origin_pins = sweep.origin_pins;
3904 placement_.visit_rows([&](std::uint64_t,
const PlacementSnapshot& value) { ++work.row_visits; ++work.pin_rows; observe_retirement_expiry(value, interval);
3905 if (!exit_family(
value.family) || !
value.legs.dormant()
3906 ||
value.legs.target().incarnation == 0
3907 || !contains(askable_origins,
value.bracket_origin.incarnation))
return false;
3908 const bool represented = std::any_of(origin_pins.begin(), origin_pins.end(),
3909 [&](
const PlacementSnapshot* pin) {
3910 ++work.pin_comparisons; return pin->bracket_origin == value.bracket_origin && pin->family == value.family
3911 && pin->source_id == value.source_id && pin->from_entry == value.from_entry;
3913 if (!represented) origin_pins.push_back(&value);
3916 const auto pinned_by_itself = [&](
const PlacementSnapshot&
value) {
3921 const bool askable = contains(askable_origins,
value.bracket_origin.incarnation);
3922 if (
value.legs.target().incarnation != 0 && !filled_limit_without_revival(value)
3923 && ((
value.placement_cycle >= cycle && !withdrawn(
value.legs))
3924 || (askable && !
value.legs.dormant())
3925 || std::find_if(origin_pins.begin(), origin_pins.end(), [&](
const auto* pin) { ++work.pin_comparisons; return pin == &value; }) != origin_pins.end()))
return true;
3928 &&
value.projection_created_bar >= interval) {
3933 &&
value.placement_script_open_ms >= last_broker_open_ms_;
3939 const auto held_by_cycle = [&](
const PlacementSnapshot&
value) {
3940 return pinned_by_itself(value) &&
value.placement_cycle == cycle
3941 && exit_family(
value.family) && !
value.from_entry.empty()
3942 && !withdrawn(
value.legs)
3943 && !contains(askable_origins,
value.bracket_origin.incarnation);
3945 auto& held = sweep.held;
3946 bool unpinned =
false;
3949 auto askable_cursor = askable_origins.begin();
3950 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& value) { ++work.row_visits; ++work.gate_rows;
3951 const bool cycle_held = held_by_cycle(value);
3952 if (!cycle_held && pinned_by_itself(value))
return false;
3953 while (askable_cursor != askable_origins.end() && *askable_cursor < incarnation)
3955 if (askable_cursor != askable_origins.end() && *askable_cursor == incarnation)
3958 held.push_back({incarnation, &
value});
3967 auto& released = sweep.released;
3968 auto& memo = sweep.unsuperseded;
3969 const auto release_superseded = [&] {
3970 if (held.empty())
return;
3971 const std::uint64_t top = placement_.high_water();
3972 std::uint64_t scan_from = std::numeric_limits<std::uint64_t>::max();
3973 std::size_t cursor = 0;
3974 for (
auto& leg : held) {
3975 while (cursor < memo.size() && memo[cursor].first < leg.incarnation) ++cursor;
3976 if (cursor < memo.size() && memo[cursor].first == leg.incarnation) {
3977 leg.read_through = memo[cursor].second;
3979 const std::uint64_t target = leg.row->legs.target().incarnation;
3980 leg.read_through = std::min(leg.incarnation, target) - 1;
3982 scan_from = std::min(scan_from, leg.read_through);
3986 if (scan_from >= top)
return;
3987 const auto stop_bits = [](
double value) {
3988 std::uint64_t out = 0;
3989 std::memcpy(&out, &value,
sizeof(out));
3992 const PlacementTable& rows = placement_;
3993 for (
auto it = rows.upper_bound(scan_from); it != rows.end(); ++it) {
3994 const PlacementSnapshot& peer = it->second; ++work.row_visits; ++work.peer_visits;
3995 if (peer.placement_cycle != cycle)
continue;
3996 const bool reissue = exit_family(peer.family);
3997 const std::uint64_t named = peer.projection_predecessor;
3998 if (named == 0 && !reissue)
continue;
3999 for (
auto& leg : held) { ++work.held_comparisons;
4000 if (leg.released || it->first <= leg.read_through)
continue;
4001 const PlacementSnapshot& own = *leg.row;
4003 && (named == leg.incarnation || named == own.legs.target().incarnation)) {
4004 leg.released =
true;
4005 }
else if (reissue && it->first > leg.incarnation && peer.family == own.family
4006 && peer.placement_script_open_ms > own.placement_script_open_ms
4007 && stop_bits(peer.exit_levels.stop) != stop_bits(own.exit_levels.stop)
4008 && peer.source_id == own.source_id
4009 && peer.from_entry == own.from_entry) {
4010 leg.released =
true;
4014 std::size_t kept = 0;
4015 for (
const auto& leg : held) kept += leg.released ? 0 : 1;
4018 for (
const auto& leg : held) {
4019 if (leg.released) released.push_back(leg.incarnation);
4020 else memo[kept++] = {leg.incarnation, top};
4024 if (!unpinned) release_superseded();
4027 if (market_pyramid_adds_.empty() && !unpinned && released.empty()) { finish_phases();
return; }
4029 IExecutionConsumer* consumer = bound_consumer();
4030 if (!consumer) { finish_phases();
return; }
4031 const auto& core = as_native_consumer(*consumer).request_core();
4033 auto& roots = sweep.roots;
4034 const auto root = [&](std::uint64_t incarnation) {
4035 ++work.root_visits;
if (incarnation != 0) roots.push_back(incarnation);
4037 for (
const auto& handle : live_handles_) root(handle.incarnation);
4042 for (
const auto& working : core.live()) root(working.definition->handle.incarnation);
4047 assert(as_native_consumer(*consumer).event_window_start() <= receipt_cursor_ + 1);
4048 as_native_consumer(*consumer).visit_commands_after(receipt_cursor_,
4050 std::visit([&](const auto& event) {
4051 using Event = std::decay_t<decltype(event)>;
4052 if constexpr (std::is_same_v<Event, native_order::RejectedEvent>) {
4054 } else if constexpr (std::is_same_v<Event, native_order::ReplacedEvent>) {
4055 root(event.predecessor().incarnation);
4056 root(event.successor().incarnation);
4057 } else if constexpr (std::is_same_v<Event, native_order::ReplaceRejectedEvent>) {
4058 root(event.target().incarnation);
4059 } else if constexpr (std::is_same_v<Event, native_order::NotWorkingEvent>
4060 || std::is_same_v<Event, native_order::InvalidHandleEvent>) {
4061 root(event.target.incarnation);
4062 } else if constexpr (std::is_same_v<Event, native_order::NativeRiskEvent>) {
4065 if (event.definition) root(event.definition->handle.incarnation);
4066 if constexpr (std::is_same_v<Event, native_order::ReservationReducedEvent>
4067 || std::is_same_v<Event,
4068 native_order::DeferredGroupAdjustmentEvent>) {
4069 root(event.recipient.incarnation);
4075 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
4076 for (
const auto& opening :
cohort.second.opened) root(opening.incarnation);
4078 if (
auto* pine = pine_view_of(host_)) {
4079 for (
const auto& lot : pine->pyramid_entries_) root(lot.entry_incarnation);
4081 for (
const auto& handle : first_open_newborns_) root(handle.incarnation);
4082 for (
const auto& row : live_by_source_key_) root(row.second.incarnation);
4083 auto& live_groups = sweep.live_groups;
4084 auto& source_bars = sweep.source_bars;
4086 root(
snapshot.bracket_origin.incarnation);
4087 root(
snapshot.paired_reversal_parent.incarnation);
4088 root(
snapshot.preserved_by_close_all.incarnation);
4089 root(
snapshot.projection_predecessor);
4090 root(
snapshot.legs.target().incarnation);
4091 root(
snapshot.reservation_growth_owner_incarnation);
4092 if (
snapshot.sequential_group != 0) live_groups.push_back(
snapshot.sequential_group);
4093 if (
snapshot.projection_created_bar_pinned)
4094 source_bars.push_back(
snapshot.projection_created_bar);
4096 const auto queued_request = [&](
const native_order::Request&
request) {
4097 if (
const auto* wait = std::get_if<native_order::WaitForApplied>(&
request.owner)) {
4098 root(wait->parent.incarnation);
4099 }
else if (
const auto* opening = std::get_if<native_order::BindOpening>(&
request.owner)) {
4100 root(opening->opening.incarnation);
4101 }
else if (
const auto* openings
4102 = std::get_if<native_order::BindOpenings>(&
request.owner)) {
4103 for (
const auto& handle : openings->openings) root(handle.incarnation);
4106 for (
const auto& leg : pending_bracket_legs_) {
4107 queued_snapshot(leg.snapshot);
4108 queued_request(leg.request);
4110 for (
const auto& entry : pending_entries_) {
4111 queued_snapshot(entry.snapshot);
4112 queued_request(entry.request);
4114 for (
const auto& order : delayed_market_orders_) {
4115 queued_snapshot(order.snapshot);
4116 queued_request(order.request);
4118 for (
const auto& sell : deferred_open_marketable_sells_) queued_snapshot(sell.snapshot);
4119 for (
const auto& snapshot : throttled_reopen_rearm_) queued_snapshot(snapshot);
4120 for (
const auto& command : pending_same_bar_commands_) {
4121 queued_snapshot(
command.snapshot);
4122 queued_request(
command.request);
4124 for (
const auto& shadow : source_shadow_pending_) queued_snapshot(shadow.snapshot);
4125 for (
const auto& pending : pending_coof_requests_) {
4126 queued_snapshot(
pending.snapshot);
4127 queued_request(
pending.request);
4129 for (
const auto& revival : pending_margin_revivals_) queued_snapshot(revival.snapshot);
4130 for (
const auto& leg : anchored_relative_legs_) {
4131 root(leg.handle.incarnation);
4132 root(leg.parent.incarnation);
4133 queued_request(leg.request);
4135 root(materializing_parent_.incarnation);
4136 for (
const ShortSeedPlan* plan : {&short_seed_, &pending_short_seed_.plan}) {
4137 root(
plan->long_entry.incarnation);
4138 root(
plan->materialize_long.incarnation);
4139 root(
plan->final_short.incarnation);
4141 root(short_seed_long_candidate_.incarnation);
4142 std::sort(roots.begin(), roots.end());
4143 roots.erase(std::unique(roots.begin(), roots.end()), roots.end());
4146 for (
const auto& handle : live_handles_) {
4147 const auto found = placement_.find(handle.incarnation);
4148 if (found == placement_.end())
continue;
4149 if (found->second.sequential_group != 0)
4150 live_groups.push_back(found->second.sequential_group);
4152 source_bars.push_back(found->second.projection_created_bar);
4154 std::sort(live_groups.begin(), live_groups.end());
4155 std::sort(source_bars.begin(), source_bars.end());
4156 auto& candidates = sweep.candidates;
4157 auto& candidate_names = sweep.candidate_names;
4159 if (unpinned) held.clear();
4160 auto candidate_origin_cursor = askable_origins.begin();
4161 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& value) { ++work.row_visits; ++work.candidate_rows;
4162 if (
value.legs.target().incarnation == 0 ||
value.placement_cycle != cycle
4163 || !exit_family(
value.family) ||
value.from_entry.empty()
4164 || withdrawn(
value.legs) || filled_limit_without_revival(value)) {
4168 while (candidate_origin_cursor != askable_origins.end()
4169 && *candidate_origin_cursor < incarnation) ++candidate_origin_cursor;
4170 const bool askable = candidate_origin_cursor != askable_origins.end()
4171 && *candidate_origin_cursor == incarnation;
4172 if (!askable && !contains(askable_origins,
value.bracket_origin.incarnation))
4173 held.push_back({incarnation, &
value});
4175 candidates.emplace_back(incarnation, &value);
4178 if (unpinned) release_superseded();
4180 candidates.erase(std::remove_if(candidates.begin(), candidates.end(), [&](
const auto& c) {
4181 return !contains(roots, c.first) && contains(released, c.first)
4182 && contains(askable_origins, c.second->bracket_origin.incarnation);
4183 }), candidates.end());
4184 for (
const auto& c : candidates) {
4185 candidate_names.push_back(c.first);
4186 candidate_names.push_back(c.second->legs.target().incarnation);
4188 std::sort(candidate_names.begin(), candidate_names.end());
4192 auto& first_reissues = sweep.first_reissues;
4193 auto& next_reissues = sweep.next_reissues;
4194 if (sweep.reissue_cycle != cycle) {
4195 sweep.reissues.clear();
4196 sweep.reissue_high_water = 0;
4197 sweep.reissue_cycle = cycle;
4199 std::size_t prior = 0;
4200 for (std::size_t index = 0; index < candidates.size(); ++index) {
4201 const auto incarnation = candidates[index].first;
4202 while (prior < sweep.reissues.size() && sweep.reissues[prior].first < incarnation) ++prior;
4203 const bool known = prior < sweep.reissues.size()
4204 && sweep.reissues[prior].first == incarnation
4205 && (sweep.reissues[prior].second == 0
4206 || placement_.contains(sweep.reissues[prior].second));
4207 const auto witness = known ? sweep.reissues[prior].second : 0;
4208 next_reissues.emplace_back(incarnation, witness);
4209 if (!known) sweep.new_candidates.push_back(index);
4211 sweep.known_candidates.push_back(index);
4212 if (witness == 0) sweep.unresolved_candidates.push_back(index);
4216 std::size_t index) {
4218 auto& witness = next_reissues[index].second;
4219 const auto& c = candidates[index];
4220 if (incarnation <= c.first || (witness != 0 && incarnation >= witness))
return;
4221 const auto& q = *c.second;
4222 if (q.family ==
value.family
4223 && q.placement_script_open_ms <
value.placement_script_open_ms
4224 && !same_double_bits(q.exit_levels.stop,
value.exit_levels.stop)
4225 && q.source_id ==
value.source_id && q.from_entry ==
value.from_entry)
4226 witness = incarnation;
4228 if (!sweep.new_candidates.empty()) {
4229 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& value) { ++work.row_visits; ++work.peer_visits;
4230 if (
value.placement_cycle == cycle && exit_family(
value.family))
4231 for (
const auto index : sweep.new_candidates) compare_reissue(incarnation, value, index);
4235 if (!sweep.unresolved_candidates.empty()) {
4237 for (
auto it = rows.upper_bound(sweep.reissue_high_water); it != rows.end(); ++it) {
4238 const auto&
value = it->second; ++work.row_visits; ++work.peer_visits;
4239 if (
value.placement_cycle == cycle && exit_family(
value.family))
4240 for (
const auto index : sweep.unresolved_candidates)
4241 compare_reissue(it->first, value, index);
4244 for (
const auto incarnation : sweep.adopted_reissues) {
4245 const auto row = placement_.find(incarnation);
4246 if (row == placement_.end() || row->second.placement_cycle != cycle
4247 || !exit_family(row->second.family))
continue;
4248 ++work.row_visits; ++work.peer_visits;
for (
const auto index : sweep.known_candidates)
4249 compare_reissue(incarnation, row->second, index);
4251 sweep.adopted_reissues.clear();
4252 for (
const auto& answer : next_reissues)
4253 if (answer.second != 0) first_reissues.push_back(answer.second);
4254 std::sort(first_reissues.begin(), first_reissues.end());
4255 sweep.reissues.swap(next_reissues);
4256 sweep.reissue_high_water = placement_.high_water();
4258 candidates, first_reissues);
4260 auto& doomed = sweep.doomed;
4261 auto root_cursor = roots.begin();
4262 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& value) { ++work.row_visits; ++work.final_rows;
4265 if (pinned_by_itself(value) && (released.empty() || !contains(released, incarnation)))
4267 while (root_cursor != roots.end() && *root_cursor < incarnation) ++root_cursor;
4268 if (root_cursor != roots.end() && *root_cursor == incarnation)
return false;
4269 if (
value.placement_cycle == cycle) {
4271 if (
value.projection_predecessor != 0
4272 && contains(candidate_names,
value.projection_predecessor)) {
4276 if (exit_family(
value.family) && contains(first_reissues, incarnation))
return false;
4280 &&
value.sequential_rank != 0
4281 && std::binary_search(live_groups.begin(), live_groups.end(),
4282 value.sequential_group)) {
4288 && finite_positive(
value.exit_levels.stop) && !finite_positive(
value.exit_levels.limit)
4289 && (
value.projection_created_bar >= interval
4290 || std::binary_search(source_bars.begin(), source_bars.end(),
4291 value.projection_created_bar))) {
4294 doomed.push_back(incarnation);
4301 placement_.preflight_erased_rows([&](
const auto& emit) {
4302 for (
const auto incarnation : doomed) emit(placement_.at(incarnation));
4307 for (
auto mark = market_pyramid_adds_.begin(); mark != market_pyramid_adds_.end();) {
4308 if (std::binary_search(roots.begin(), roots.end(), *mark)) ++mark;
4309 else mark = market_pyramid_adds_.erase(mark);
4311 if (doomed.empty())
return;
4313 for (
const auto incarnation : doomed) {
4314 const auto&
value = placement_.at(incarnation);
4318 if (exit_family(
value.family) && !
value.legs.dormant()) {
4320 if (family.count(incarnation) == 0)
return false;
4321 family.record_consumed(
static_cast<std::uint8_t
>(
value.family),
4322 value.bracket_origin.incarnation);
4325 const auto keyed = bracket_families_.find(key_for(
value.source_id,
value.from_entry));
4326 if (keyed == bracket_families_.end() || !record(keyed->second)) {
4327 for (
auto& family : bracket_families_) record(family.second);
4331 if (
value.opening) {
4332 const auto cohort = cohorts_by_id_.find(
value.source_id);
4333 if (cohort != cohorts_by_id_.end()
4334 &&
cohort->second.origins.positions_of(incarnation) !=
nullptr) {
4335 cohort->second.erased_opening_sides |=
value.is_long ? 1U : 2U;
4338 if (!retain_rows) erase_placement(incarnation);
4339 if (!retain_rows) detail::rows_erased.fetch_add(1, std::memory_order_relaxed);
4343 for (
auto& family : bracket_families_) {
4345 family.second.settle([&](std::uint64_t incarnation) {
4346 return retain_rows ? contains(doomed, incarnation)
4347 : !placement_.contains(incarnation);
4349 family.second.prune_consumed([&](std::uint64_t origin) {
4350 return contains(askable_origins, origin);
4355 if (!retain_rows) archive_settled_bracket_families();
4356 if (!retain_rows) placement_.compact();
4359BracketRoster& PineExecutionAdapter::bracket_family(std::uint64_t key) {
4360 const auto archived = settled_bracket_families_.find(key);
4361 if (archived != settled_bracket_families_.end()) {
4364 const auto placed = bracket_families_.try_emplace(key, archived->second);
4365 assert(placed.second);
4366 settled_bracket_digest_ ^= settled_bracket_hash(key, archived->second);
4367 settled_bracket_families_.erase(archived);
4368 return placed.first->second;
4370 return bracket_families_[
key];
4373void PineExecutionAdapter::archive_settled_bracket_families() {
4374 for (
auto at = bracket_families_.begin(); at != bracket_families_.end();) {
4375 if (!at->second.wholly_settled()) {
4379 const auto value = at->second.settled_record();
4380 [[maybe_unused]]
const auto inserted = settled_bracket_families_.emplace(at->first, value);
4381 assert(inserted.second);
4382 settled_bracket_digest_ ^= settled_bracket_hash(at->first, value);
4383 at = bracket_families_.erase(at);
4387void PineExecutionAdapter::clear_bracket_families() noexcept {
4388 bracket_families_.clear();
4389 settled_bracket_families_.clear();
4390 settled_bracket_digest_ = 0;
4417void PineExecutionAdapter::release_closed_cohort_origins() {
4418 auto& scratch = retired_row_scratch_;
4419 if (!scratch.roster_pass_due)
return;
4421 IExecutionConsumer* consumer = bound_consumer();
4422 if (!consumer)
return;
4423 auto* pine = pine_view_of(host_);
4425 const auto& core = as_native_consumer(*consumer).request_core();
4426 std::size_t members = 0;
4427 for (
const auto& [key, roster] : core.nonempty_cohorts()) {
PF_ROSTER_PASS_WORK(roster_cohort_visits); members += roster.origins.size(); }
4428 if (members == 0)
return;
4432 const auto root_of = [](
const native_order::LiveRequest& row) {
4433 return row.definition->root ? row.definition->root->incarnation
4434 : row.handle().incarnation;
4436 constexpr std::size_t kDirectScan = 64;
4437 if (members * (pine->pyramid_entries_.size() + core.live().size()) <= kDirectScan) {
4438 bool every_member_bound =
true;
4439 for (
const auto& [key, roster] : core.nonempty_cohorts()) {
PF_ROSTER_PASS_WORK(roster_cohort_visits);
4440 for (
const auto& origin : roster.origins) {
4441 const std::uint64_t incarnation = origin.incarnation;
4442 const bool bound = std::any_of(pine->pyramid_entries_.begin(),
4443 pine->pyramid_entries_.end(), [&](
const PyramidEntry& lot) {
4444 return lot.entry_incarnation == incarnation;
4446 || std::any_of(core.live().begin(), core.live().end(),
4447 [&](
const native_order::LiveRequest& row) {
4448 return root_of(row) == incarnation;
4451 every_member_bound =
false;
4455 if (!every_member_bound)
break;
4457 if (every_member_bound)
return;
4460 auto& working = scratch.working_roots;
4461 auto& lots = scratch.lot_openings;
4462 if (!position_tail_summary_) position_tail_summary_ = std::make_shared<detail::AdapterPositionTailSummary>();
4463 auto& roster_roots =
static_cast<detail::AdapterPositionTailSummary&
>(
4464 *position_tail_summary_).roster_roots;
4465 auto& successor_lots = scratch.successor_lots;
4466 auto& released = scratch.released_origins;
4469 roster_roots.clear();
4470 successor_lots.clear();
4472 for (
const auto& live : core.live()) working.push_back(root_of(live));
4473 std::sort(working.begin(), working.end());
4474 for (
const auto& lot : pine->pyramid_entries_) lots.push_back(lot.entry_incarnation);
4475 std::sort(lots.begin(), lots.end());
4476 lots.erase(std::unique(lots.begin(), lots.end()), lots.end());
4477 for (
const auto& [key, roster] : core.nonempty_cohorts()) {
PF_ROSTER_PASS_WORK(roster_cohort_visits);
4479 for (
const auto& origin : roster.origins)
4480 roster_roots.push_back(origin.incarnation);
4482 std::sort(roster_roots.begin(), roster_roots.end());
4483 roster_roots.erase(std::unique(roster_roots.begin(), roster_roots.end()),
4484 roster_roots.end());
4487 for (
const auto opening : lots) {
4488 if (opening == 0)
continue;
4489 if (!std::binary_search(roster_roots.begin(), roster_roots.end(), opening)) {
4490 successor_lots.push_back(opening);
4493 auto& probe = scratch.lot_probe;
4494 for (
const auto& [key, roster] : core.nonempty_cohorts()) {
PF_ROSTER_PASS_WORK(roster_cohort_visits);
4495 const auto& origins = roster.origins;
4496 if (origins.empty())
continue;
4497 if (probe.run != origins.front().run) probe.run = origins.front().run;
4500 std::uint64_t kept_below = 0;
4502 probe.incarnation = opening;
4503 if (core.cohort_contains(roster.handle, probe)) kept_below = opening;
4505 for (
const auto& origin : origins) {
4506 const std::uint64_t incarnation = origin.incarnation;
4507 if (incarnation < kept_below
4508 || std::binary_search(working.begin(), working.end(), incarnation)
4509 || std::binary_search(lots.begin(), lots.end(), incarnation)) {
4512 released.emplace_back(roster.handle, incarnation);
4515 if (released.empty())
return;
4516 auto&
host = require_host();
4517 for (
const auto& [cohort, incarnation] : released) {
4518 native_order::RequestHandle origin = probe;
4519 origin.incarnation = incarnation;
4520 host.cohort_remove(cohort, std::move(origin));
4524void PineExecutionAdapter::maybe_activate_short_seed_plan() {
4530bool PineExecutionAdapter::short_seed_context_is_live() const noexcept {
4531 if (!host_)
return false;
4533 if (!(physical.signed_units < 0.0) || physical.lot_count != 1U)
return false;
4534 const double held = std::abs(physical.signed_units);
4535 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
4536 if (cohort_exposure_for(
cohort.second) == held)
return true;
4541bool PineExecutionAdapter::qualify_short_seed_plan(
const ShortSeedPlan& plan)
const {
4542 if (!host_ ||
plan.long_entry.incarnation == 0 ||
plan.materialize_long.incarnation == 0
4543 ||
plan.final_short.incarnation == 0 ||
plan.seed_id.empty()) {
4546 const auto long_it = placement_.find(
plan.long_entry.incarnation);
4547 const auto materialize_it = placement_.find(
plan.materialize_long.incarnation);
4548 const auto final_it = placement_.find(
plan.final_short.incarnation);
4549 if (long_it == placement_.end() || materialize_it == placement_.end()
4550 || final_it == placement_.end()) {
4553 const PlacementSnapshot& long_entry = long_it->second;
4554 const PlacementSnapshot& materialize = materialize_it->second;
4555 const PlacementSnapshot& final_short = final_it->second;
4556 const bool bar_magnifier = bar_magnifier_;
4557 const auto is_live = [&](
const native_order::RequestHandle& handle) {
4558 return std::find(live_handles_.begin(), live_handles_.end(), handle) != live_handles_.end();
4560 const auto no_level = [](
const PineExitLevels& levels) {
4561 return std::isnan(levels.limit) && std::isnan(levels.stop)
4562 && std::isnan(levels.trail_points) && std::isnan(levels.trail_offset)
4563 && std::isnan(levels.trail_price) && std::isnan(levels.profit_ticks)
4564 && std::isnan(levels.loss_ticks);
4566 const auto fresh_plain = [&](
const PlacementSnapshot& row) {
4567 return row.placement_open_epoch + 1U == broker_open_epoch_
4569 && !row.replaced_opening && row.projection_predecessor == 0
4570 && row.recreated_after_named_cancelled_entry_incarnation == 0
4571 && row.named_cancel_surviving_exit_incarnation == 0
4572 && !row.birth.from_fill() && !row.birth.at_terminal_fill()
4574 && !row.projection_created_during_coof
4575 && !row.reservation_expansion.capture()
4576 && row.oca_name.empty() && row.oca_type == 0;
4578 const bool fixed_default = config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED);
4579 const auto pure_default_market_entry = [&](
const PlacementSnapshot& row) {
4580 const bool sizing_shape = fixed_default ? std::isnan(row.sizing.frozen_units)
4581 : finite_positive(row.sizing.frozen_units) && finite_positive(row.sizing.equity)
4582 && finite_positive(row.sizing.
price) && finite_positive(row.sizing.mark)
4583 && finite_positive(row.sizing.fx);
4585 && std::isnan(row.requested_qty) && row.qty_type == -1 && sizing_shape
4586 && no_level(row.exit_levels) && !row.projection_after_close;
4588 const auto exact_full_fifo_close_short = [&]() {
4590 && materialize.source_id ==
plan.seed_id && materialize.from_entry ==
plan.seed_id
4591 && !materialize.is_long && finite_positive(materialize.requested_qty)
4592 && materialize.qty_percent == 100.0 && no_level(materialize.exit_levels)
4593 && materialize.frozen_market_instruction && materialize.frozen_market_targeted_close
4594 && finite_positive(materialize.frozen_market_transaction_units)
4595 && !materialize.reservation_expansion.capture()
4596 && std::abs(materialize.frozen_market_transaction_units -
plan.seed_qty) <= 1e-12
4597 && std::abs(materialize.requested_qty -
plan.seed_qty) <= 1e-12;
4599 std::array<std::uint64_t, 3> incarnations{
4600 plan.long_entry.incarnation,
4601 plan.materialize_long.incarnation,
4602 plan.final_short.incarnation,
4604 std::sort(incarnations.begin(), incarnations.end());
4605 const bool consecutive_incarnations = incarnations[0] != 0
4606 && incarnations[0] != std::numeric_limits<std::uint64_t>::max()
4607 && incarnations[0] + 1U == incarnations[1]
4608 && incarnations[1] != std::numeric_limits<std::uint64_t>::max()
4609 && incarnations[1] + 1U == incarnations[2];
4612 const double open = point ? point->price : kNaN;
4613 const double tick = staged_.syminfo.mintick;
4614 const double admit = finite_positive(tick) ? nearest_tick(open, tick) : open;
4615 const double entry_qty = fixed_default ? config_.default_qty_value : long_entry.sizing.frozen_units;
4616 const double final_qty = fixed_default ? config_.default_qty_value : final_short.sizing.frozen_units;
4617 const double fx = point ? active_staged_fx(point->decision.sub_bar_open_ms) : staged_.account_fx;
4618 const double notional = staged_.syminfo.pointvalue * fx;
4619 const double marked = percent_commission_live_equity(open);
4620 const double projected_long =
entry_qty + std::min(
plan.seed_qty, entry_qty);
4621 const double projected_free = marked - projected_long * open * notional;
4622 const double projected_required = (projected_long + final_qty) * admit * notional;
4623 const double epsilon = std::max(1e-9, std::abs(marked) * 1e-12);
4624 const bool projected_final_short_admission_is_safe = finite_positive(admit)
4625 && finite_positive(entry_qty) && finite_positive(final_qty) && finite_positive(notional)
4626 && std::isfinite(projected_free) && std::isfinite(projected_required)
4627 && projected_required <= projected_free + epsilon;
4628 bool percent_rechecks_safe =
true;
4630 && config_.default_qty_value <= 100.0) {
4631 for (
const PlacementSnapshot* row : {&long_entry, &final_short}) {
4632 const double required = row->sizing.frozen_units * admit * staged_.syminfo.pointvalue
4634 if (!(finite_positive(required) && required <= row->sizing.
equity)) {
4635 percent_rechecks_safe =
false;
4640 const int source_bar = long_entry.projection_created_bar;
4641 return !config_.close_entries_rule_any && !config_.process_orders_on_close
4642 && !config_.calc_on_order_fills && !coof_recalc_active_
4643 && !bar_magnifier && !risk_.halted
4644 && risk_.direction == 0 && risk_.max_cons_loss_days == 0 && risk_.max_drawdown <= 0.0
4645 && risk_.max_intraday_loss <= 0.0 && risk_.max_position_size <= 0.0 && !
cap.active()
4646 && config_.pyramiding == 1 && config_.slippage == 0 && config_.commission_value == 0.0
4647 && std::abs(config_.margin_long - 100.0) < 1e-12
4648 && std::abs(config_.margin_short - 100.0) < 1e-12
4649 && physical.signed_units < 0.0 && physical.lot_count == 1U && current_position_cycle_ > 0
4650 && is_live(
plan.long_entry) && is_live(
plan.materialize_long) && is_live(
plan.final_short)
4653 && long_entry.command_ordinal + 1U == final_short.command_ordinal
4654 && final_short.command_ordinal + 1U == materialize.command_ordinal
4655 && consecutive_incarnations
4656 && fresh_plain(long_entry) && fresh_plain(final_short) && fresh_plain(materialize)
4657 && pure_default_market_entry(long_entry) && pure_default_market_entry(final_short)
4658 && !long_entry.source_id.empty() && long_entry.is_long
4659 && !long_entry.projection_over_pyramiding && !final_short.source_id.empty()
4660 && long_entry.source_id != final_short.source_id
4661 && long_entry.source_id != materialize.source_id && !final_short.is_long
4662 && final_short.projection_over_pyramiding
4663 && long_entry.placement_cycle ==
plan.seed_cycle
4664 && final_short.placement_cycle ==
plan.seed_cycle
4665 && std::abs(long_entry.projection_tv_carry_qty -
plan.seed_qty) <= 1e-12
4666 && std::abs(final_short.projection_tv_carry_qty -
plan.seed_qty) <= 1e-12
4667 && exact_full_fifo_close_short() &&
plan.seed_id == final_short.source_id
4668 && position_open_epoch_ < broker_open_epoch_
4669 && finite_positive(
plan.seed_qty) && std::abs(std::abs(physical.signed_units) -
plan.seed_qty) <= 1e-12
4670 && (fixed_default ? std::abs(config_.default_qty_value -
plan.seed_qty) <= 1e-12
4671 : std::abs(long_entry.sizing.frozen_units - final_short.sizing.frozen_units) <= 1e-12)
4672 && std::abs(materialize.projection_tv_carry_qty -
plan.seed_qty) <= 1e-12
4673 && projected_final_short_admission_is_safe && percent_rechecks_safe;
4676void PineExecutionAdapter::activate_short_seed_plan_at_open(
const NativeDecisionContext&) {
4677 if (!pending_short_seed_.ready)
return;
4678 if (broker_open_epoch_ < pending_short_seed_.expected_open_epoch)
return;
4679 if (broker_open_epoch_ == pending_short_seed_.expected_open_epoch
4680 && qualify_short_seed_plan(pending_short_seed_.plan)) {
4681 short_seed_ = pending_short_seed_.plan;
4682 short_seed_.active =
true;
4683 }
else if (short_seed_.long_entry == pending_short_seed_.plan.long_entry
4684 && short_seed_.materialize_long == pending_short_seed_.plan.materialize_long
4685 && short_seed_.final_short == pending_short_seed_.plan.final_short) {
4688 pending_short_seed_ = {};
4691std::optional<native_order::RequestHandle> PineExecutionAdapter::submit_or_replace(
4696 if (opening) cohorts_by_id_.require_insert(
snapshot.source_id);
4697 auto&
host = require_host();
4704 if (std::holds_alternative<native_order::Sized>(
request.intent)
4706 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
4707 snapshot.is_long ? native_order::Side::Long : native_order::Side::Short};
4710 snapshot.projection_position_side = physical.signed_units > 0.0
4715 || pending_same_bar_close_qty_ > 0.0;
4716 const auto ledger_records = opening ? open_ledger_records() : std::nullopt;
4717 snapshot.projection_over_pyramiding = opening
4718 && !(config_.process_orders_on_close &&
snapshot.projection_after_close)
4719 && config_.pyramiding > 0
4720 && ((physical.signed_units > 0.0) ==
snapshot.is_long)
4721 && physical.signed_units != 0.0
4722 && (ledger_records ? *ledger_records : physical.lot_count)
4723 >=
static_cast<std::size_t
>(config_.pyramiding);
4724 snapshot.projection_created_during_coof =
4725 snapshot.projection_created_during_coof || coof_recalc_active_;
4726 snapshot.projection_coof_at_terminal =
snapshot.projection_coof_at_terminal
4727 || (coof_recalc_active_ && coof_context_.is_terminal_sub_bar);
4729 || (coof_recalc_active_ && !coof_context_.is_terminal_sub_bar);
4730 snapshot.projection_tv_carry_qty = opening
4731 ? std::max(0.0, std::abs(physical.signed_units)
4732 - pending_same_bar_close_qty_)
4733 : std::abs(physical.signed_units);
4736 snapshot.projection_explicit_equity = std::isfinite(
snapshot.requested_qty)
4738 snapshot.projection_explicit_signal_close = std::isfinite(
snapshot.requested_qty)
4740 if (
snapshot.affordability_policy_active) {
4743 snapshot.projection_affordability_held_qty = std::abs(physical.signed_units);
4745 snapshot.projection_affordability_equity = kNaN;
4746 snapshot.projection_affordability_signal_price = kNaN;
4747 snapshot.projection_affordability_held_qty = kNaN;
4750 && config_.close_entries_rule_any && !std::isfinite(
snapshot.requested_qty)
4751 && std::isfinite(
snapshot.qty_percent);
4752 if (!opening && !
snapshot.reservation_deferred_to_pending_entry
4753 && !fill_time_any_percentage
4754 && !std::isfinite(
snapshot.projection_remaining_qty)
4756 && physical.signed_units != 0.0) {
4757 const double percent = std::isfinite(
snapshot.qty_percent)
4759 snapshot.projection_remaining_qty = quantize_close_units(
4760 std::abs(physical.signed_units), percent);
4762 if (opening && !
snapshot.source_id.empty()) {
4763 if (
const auto token = named_entry_cancel_tokens_.find(
snapshot.source_id);
4764 token != named_entry_cancel_tokens_.end()) {
4765 snapshot.recreated_after_named_cancelled_entry_incarnation =
4766 token->second.entry_incarnation;
4767 snapshot.named_cancel_surviving_exit_incarnation =
4768 token->second.surviving_exit_incarnation;
4769 named_entry_cancel_tokens_.erase(token);
4774 snapshot.placement_cycle = current_position_cycle_;
4776 snapshot.birth = capture_order_birth();
4777 snapshot.coof_cascade_seg_i = coof_recalc_active_
4778 ? coof_context_.coordinate.interval_index : -1;
4779 snapshot.coof_cascade_inflight_fires = coof_recalc_active_;
4780 if (!
snapshot.paired_flat_market_candidate) {
4782 && physical.signed_units == 0.0;
4784 snapshot.paired_flat_market_transaction_qty =
4785 snapshot.frozen_market_transaction_units;
4790 ? config_.margin_long : config_.margin_short;
4791 snapshot.paired_flat_market_signal_pointvalue = staged_.syminfo.pointvalue;
4793 snapshot.pooc_global_full_exit_dynamic_qty = config_.process_orders_on_close
4794 && !opening && !std::isfinite(
snapshot.requested_qty)
4795 && (!std::isfinite(
snapshot.qty_percent) ||
snapshot.qty_percent >= 100.0);
4796 snapshot.pooc_global_full_exit_tracks_bound_adds =
snapshot.pooc_global_full_exit_dynamic_qty;
4806 if (!
snapshot.projection_created_bar_pinned) {
4809 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
4810 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
4814 &&
snapshot.affordability_close_only
4815 && !std::isfinite(
snapshot.requested_qty)
4816 && !finite_positive(
snapshot.exit_levels.limit)
4817 && !finite_positive(
snapshot.exit_levels.stop)
4818 && !
snapshot.projection_after_close
4819 && !config_.process_orders_on_close && !config_.calc_on_order_fills
4820 && !coof_recalc_active_
4821 && physical.signed_units > 0.0 && physical.lot_count == 1U
4822 && last_margin_call_script_bar_ ==
snapshot.placement_script_open_ms
4823 && last_margin_call_event_ordinal_ != 0
4824 && last_margin_call_event_ordinal_ == last_applied_ordinal_
4825 && last_margin_call_entry_incarnation_ != 0
4826 && last_margin_call_position_cycle_ == current_position_cycle_
4827 && last_margin_call_at_script_close_
4828 && last_margin_call_closed_units_ == 1.0
4829 && last_margin_call_remaining_units_ == std::abs(physical.signed_units)) {
4830 const auto* pine_host = pine_view_of(&host);
4832 const bool magnifier = pine_host
4833 && pine_host->scheduler_.bar_magnifier_enabled();
4834 const double carried = last_margin_call_remaining_units_
4835 + last_margin_call_closed_units_;
4836 if (pine_host && !magnifier
4837 && std::isfinite(carried)
4838 && std::abs(carried - std::abs(physical.signed_units) - 1.0) < 1e-6) {
4843 snapshot.projection_tv_carry_qty = carried;
4845 snapshot.signal_close_mc_entry_incarnation =
4846 last_margin_call_entry_incarnation_;
4847 snapshot.signal_close_mc_fill_seq =
4848 pine_host->adapter_broker_fill_event_sequence();
4849 snapshot.signal_close_mc_remaining_qty =
4850 last_margin_call_remaining_units_;
4851 snapshot.affordability_keep_mc_close_surplus =
true;
4856 &&
snapshot.affordability_close_only
4857 && signal_close_mc_event_bar_ ==
snapshot.projection_created_bar
4858 && signal_close_mc_position_cycle_ ==
snapshot.placement_cycle
4859 && signal_close_mc_entry_incarnation_ != 0
4860 && signal_close_mc_fill_seq_ != 0) {
4861 snapshot.signal_close_mc_bar = signal_close_mc_event_bar_;
4862 snapshot.signal_close_mc_entry_incarnation =
4863 signal_close_mc_entry_incarnation_;
4864 snapshot.signal_close_mc_fill_seq = signal_close_mc_fill_seq_;
4865 snapshot.signal_close_mc_remaining_qty = signal_close_mc_remaining_qty_;
4866 snapshot.projection_tv_carry_qty = signal_close_mc_before_qty_;
4868 snapshot.placement_open_epoch = broker_open_epoch_;
4869 if (
snapshot.command_ordinal == 0)
snapshot.command_ordinal = ++command_ordinal_;
4870 if (
snapshot.command_sequence == 0) {
4871 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
4873 "Pine source command sequence exhausted");
4875 snapshot.command_sequence = ++source_command_sequence_;
4877 std::optional<admission::Allocation> admission_allocation;
4878 std::shared_ptr<const admission::CommandObservation> admission_observation;
4882 auto observed = std::make_shared<admission::CommandObservation>();
4883 observed->command = admission_allocation->sequence();
4885 ? admission::CommandKind::Entry : admission::CommandKind::Raw;
4888 observed->requested_quantity =
snapshot.requested_qty;
4889 observed->quantity_type =
snapshot.qty_type;
4891 observed->prices = {
snapshot.exit_levels.limit,
snapshot.exit_levels.stop};
4892 observed->oca_name =
snapshot.oca_name;
4893 observed->oca_type =
snapshot.oca_type;
4895 auto& configuration = observed->configuration;
4896 configuration.process_on_close = config_.process_orders_on_close;
4897 configuration.calc_on_fills = config_.calc_on_order_fills;
4898 configuration.magnifier = native.spec && !native.spec->intrabar.is_none();
4899 configuration.fill_recalculation = coof_recalc_active_;
4900 configuration.scheduler = config_.calc_on_order_fills;
4901 configuration.slippage = config_.slippage;
4902 configuration.pyramiding = config_.pyramiding;
4903 configuration.default_quantity_type = config_.default_qty_type;
4904 configuration.default_quantity_value = config_.default_qty_value;
4905 configuration.long_margin = config_.margin_long;
4906 configuration.short_margin = config_.margin_short;
4907 configuration.commission_value = config_.commission_value;
4908 configuration.commission_type = config_.commission_type;
4909 configuration.pointvalue = staged_.syminfo.pointvalue;
4910 configuration.fx =
snapshot.sizing.fx;
4911 configuration.quantity_step = staged_.quantity_grid.value_or(0.0);
4912 configuration.mintick = staged_.syminfo.mintick;
4913 configuration.risk_direction = risk_.direction;
4914 configuration.loss_days_limit = risk_.max_cons_loss_days;
4915 configuration.drawdown_limit = risk_.max_drawdown;
4916 configuration.intraday_loss_limit = risk_.max_intraday_loss;
4917 configuration.position_limit = risk_.max_position_size;
4918 configuration.fill_cap_active =
cap.active();
4919 configuration.risk_halted = risk_.halted;
4920 observed->bar =
snapshot.projection_created_bar;
4921 observed->placement_side =
snapshot.projection_position_side;
4922 observed->placement_cycle =
snapshot.placement_cycle;
4923 observed->prior_close_quantity =
snapshot.projection_after_close
4924 ?
snapshot.projection_tv_carry_qty : 0.0;
4925 observed->held_quantity =
snapshot.projection_tv_carry_qty;
4926 observed->held_entries =
static_cast<int>(physical.lot_count);
4927 observed->realized_equity =
snapshot.sizing.equity;
4928 observed->placement_equity =
snapshot.sizing.equity;
4929 observed->signal_close =
snapshot.sizing.mark;
4930 observed->quantized_fixed_quantity = std::isfinite(
snapshot.requested_qty)
4932 if (!std::isfinite(
snapshot.requested_qty)
4933 && std::holds_alternative<native_order::Market>(
request.trigger)
4935 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH))) {
4936 observed->original_sizing = admission::SizingObservation{
4940 observed->explicit_equity =
snapshot.projection_explicit_equity;
4941 observed->explicit_price =
snapshot.projection_explicit_signal_close;
4942 admission_observation = std::move(observed);
4943 snapshot.market_admission.bind(admission_observation);
4945 if (
auto* member = std::get_if<native_order::Member>(&
request.group)) {
4946 if (source_sequence_ >=
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())) {
4948 "Pine OCA member sequence exhausted");
4953 member->cohort =
static_cast<std::int64_t
>(source_sequence_ + 1U);
4955 const auto key = replacement_key.empty() ? 0 : checked_live_key(replacement_key);
4956 std::optional<native_order::RequestHandle> accepted;
4962 const PlacementSnapshot* predecessor_snapshot =
nullptr;
4963 exit_legs::Lifecycle predecessor_legs;
4966 decltype(live_by_source_key_)::node_type reused_key_node;
4967 bool predecessor_exit =
false;
4968 bool predecessor_market =
false;
4969 const auto unchanged_dynamic_exit = [&](
const PlacementSnapshot& prior) {
4970 const bool preserve_coof_birth = prior.projection_created_during_coof
4971 && !coof_recalc_active_;
4972 if (opening || materializing_relative_
4973 || ((coof_recalc_active_ || config_.calc_on_order_fills)
4974 && !preserve_coof_birth)
4976 || !prior.deferred_cohort ||
snapshot.family != prior.family
4980 || !std::isnan(
snapshot.requested_qty) || !std::isnan(prior.requested_qty)
4981 ||
snapshot.source_id != prior.source_id ||
snapshot.from_entry != prior.from_entry
4982 ||
snapshot.comment != prior.comment ||
snapshot.oca_name != prior.oca_name
4983 ||
snapshot.oca_type != prior.oca_type
4984 || !same_double_bits(
snapshot.qty_percent, prior.qty_percent)
4985 ||
snapshot.bracket_origin != prior.bracket_origin
4986 || !same_exit_levels(
snapshot.exit_levels, prior.exit_levels)) {
4989 const auto* sized = std::get_if<native_order::HostSized>(&
request.intent);
4990 const auto* owner = std::get_if<native_order::BindCohort>(&
request.owner);
4992 const bool fifo_live_owner = !config_.close_entries_rule_any
4993 && std::holds_alternative<native_order::Independent>(
request.owner)
4994 &&
cohort != cohorts_by_id_.end()
4995 && cohort_exposure_for(
snapshot.from_entry) > 0.0;
4996 if (!sized || sized->kind != native_order::HostSizedKind::Close || sized->side
4997 || ((!owner || cohort == cohorts_by_id_.end()
4998 || owner->cohort !=
cohort->second.handle)
4999 && !fifo_live_owner)) {
5002 if (
const auto* limit = std::get_if<native_order::Limit>(&
request.trigger)) {
5003 if (preserve_coof_birth)
5006 && same_double_bits(
limit->price, prior.exit_levels.limit);
5008 if (
const auto* stop = std::get_if<native_order::Stop>(&
request.trigger)) {
5009 if (preserve_coof_birth)
5012 && same_double_bits(stop->price, prior.exit_levels.stop);
5014 if (
const auto* trail = std::get_if<native_order::Trail>(&
request.trigger)) {
5018 const double trail_offset = trail->ticks
5019 ? trail->ticks->ticks * staged_.syminfo.mintick : trail->offset;
5022 const auto arm_of_prior = [&](
bool exit_is_buy) {
5023 return source_trail_arm_level(prior.exit_levels.trail_price,
5024 staged_.syminfo.mintick, exit_is_buy);
5027 && same_double_bits(trail_offset, prior.exit_levels.trail_offset)
5028 && trail->arm_price.has_value() == std::isfinite(prior.exit_levels.trail_price)
5029 && (!trail->arm_price
5030 || same_double_bits(*trail->arm_price, arm_of_prior(
true))
5031 || same_double_bits(*trail->arm_price, arm_of_prior(
false)));
5035 std::optional<std::uint64_t> retained_source_sequence;
5037 std::optional<native_order::RequestHandle> existing_handle;
5038 if (
const auto existing = live_by_source_key_.find(key);
5039 existing != live_by_source_key_.end()) {
5042 existing_handle = existing->second;
5043 if (
const auto previous = placement_.find(existing_handle->incarnation);
5044 previous != placement_.end()) {
5045 predecessor_snapshot = &previous->second;
5050 if (unchanged_dynamic_exit(previous->second)
5051 && !reissue_revives_declined_exit(previous->second)) {
5052 if (previous->second.void_issue && !
snapshot.void_issue)
5053 unvoid_exit(previous->second);
5054 return existing_handle;
5056 const auto family = previous->second.family;
5060 && !std::isfinite(previous->second.exit_levels.limit)
5061 && !std::isfinite(previous->second.exit_levels.stop)
5062 && !std::isfinite(previous->second.exit_levels.trail_offset);
5065 if (existing_handle) {
5071 if (predecessor_snapshot
5074 const auto same = [](
double left,
double right) {
5075 return (std::isnan(left) && std::isnan(right)) || left == right;
5077 const bool same_activation = same(predecessor_snapshot->exit_levels.trail_points,
5079 && same(predecessor_snapshot->exit_levels.trail_price,
5081 const bool offset_changed = !same(predecessor_snapshot->exit_levels.trail_offset,
5082 snapshot.exit_levels.trail_offset);
5083 if (same_activation) {
5084 const auto live = placement_.find(existing_handle->incarnation);
5085 if (live != placement_.end()) {
5086 if (offset_changed) {
5087 if (
const auto trail =
host.trail_state(*existing_handle);
5088 trail && trail->activated
5089 && std::isfinite(trail->best_price)) {
5090 live->second.retained_trail_best = trail->best_price;
5093 if (live->second.void_issue && !
snapshot.void_issue)
5094 unvoid_exit(live->second);
5095 live->second.exit_levels.trail_offset =
snapshot.exit_levels.trail_offset; invalidate_retirement();
5096 live->second.trail_activation_level =
snapshot.trail_activation_level;
5097 live->second.requested_qty =
snapshot.requested_qty;
5098 live->second.projection_remaining_qty =
snapshot.projection_remaining_qty;
5099 live->second.qty_percent =
snapshot.qty_percent;
5100 live->second.comment =
snapshot.comment;
5101 live->second.sizing =
snapshot.sizing;
5103 refresh_pending_view();
5104 return existing_handle;
5106 if (std::isfinite(
snapshot.sizing.price))
5117 native_order::ReplaceOptions reissue;
5119 const auto result =
host.replace(*existing_handle, request, reissue);
5120 if (result.status == native_order::ReplaceStatus::Replaced && result.successor) {
5127 if (
const auto found_p = placement_.find(existing_handle->incarnation);
5128 found_p != placement_.end()) {
5129 predecessor_legs = std::move(found_p->second.legs);
5130 found_p->second.legs = {}; invalidate_retirement();
5132 snapshot.projection_predecessor = existing_handle->incarnation;
5133 if (predecessor_snapshot) {
5134 const auto family = predecessor_snapshot->family;
5138 && !std::isfinite(predecessor_snapshot->exit_levels.limit)
5139 && !std::isfinite(predecessor_snapshot->exit_levels.stop)
5140 && !std::isfinite(predecessor_snapshot->exit_levels.trail_offset);
5141 const bool fresh_after_dormant = predecessor_legs.dormant()
5147 const bool fresh_after_void =
5148 predecessor_snapshot->void_issue && !
snapshot.void_issue;
5149 const auto predecessor_revision = predecessor_legs.revision();
5154 if (!fresh_after_dormant && !fresh_after_void) {
5155 snapshot.legs = std::move(predecessor_legs);
5156 snapshot.leg_activation = predecessor_snapshot->leg_activation;
5157 snapshot.exit_activation = predecessor_snapshot->exit_activation;
5159 predecessor_snapshot->restored_after_margin;
5161 if (!fresh_after_void) {
5163 predecessor_snapshot->reservation_expansion;
5164 snapshot.reservation_growth_source =
5165 predecessor_snapshot->reservation_growth_source;
5168 existing_handle->incarnation,
5169 static_cast<std::int64_t
>(predecessor_snapshot->source_sequence),
5170 existing_handle->incarnation, predecessor_snapshot->placement_cycle,
5171 predecessor_revision,
5172 predecessor_snapshot->requested_qty, kNaN};
5177 const auto token = named_entry_cancel_tokens_.find(
snapshot.from_entry);
5178 bool retained_child = token != named_entry_cancel_tokens_.end()
5179 && token->second.surviving_exit_incarnation
5180 == existing_handle->incarnation;
5181 for (
const auto& live : live_handles_) {
5182 const auto parent = placement_.find(live.incarnation);
5183 if (parent != placement_.end() && parent->second.opening
5185 && parent->second.source_id ==
snapshot.from_entry
5186 && (parent->second.named_cancel_surviving_exit_incarnation
5187 == existing_handle->incarnation
5188 || predecessor_snapshot->source_sequence
5189 < parent->second.source_sequence)) {
5190 retained_child =
true;
5195 && (!predecessor_snapshot->void_issue ||
snapshot.void_issue)) {
5196 retained_source_sequence = predecessor_snapshot->source_sequence;
5202 retained_source_sequence = predecessor_snapshot->source_sequence;
5205 snapshot.projection_predecessor_exit = predecessor_exit;
5206 snapshot.projection_predecessor_market = predecessor_market;
5207 reused_key_node = live_by_source_key_.extract(key);
5208 retire(*existing_handle);
5209 accepted = *result.successor;
5213 if (
auto* member = std::get_if<native_order::Member>(&
request.group)) {
5214 if (source_sequence_ >=
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())) {
5216 "Pine OCA member sequence exhausted");
5221 member->cohort =
static_cast<std::int64_t
>(source_sequence_ + 1U);
5223 if (!accepted && materializing_relative_ && !opening) {
5229 const auto armed = std::find_if(
5230 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
5231 [&](
const AnchoredRelativeLeg& leg) {
5232 return leg.armed && leg.parent == materializing_parent_
5233 && leg.family == snapshot.family
5234 && leg.exit_id == snapshot.source_id
5235 && leg.from_entry == snapshot.from_entry;
5237 if (armed != anchored_relative_legs_.end()) {
5238 const auto same_trigger = [&] {
5239 const auto& mine = armed->request.trigger;
5240 if (mine.index() !=
request.trigger.index())
return false;
5241 if (
const auto* limit = std::get_if<native_order::Limit>(&
request.trigger)) {
5242 return same_double_bits(
limit->price, armed->installed_level)
5243 &&
limit->fill_through
5244 == std::get<native_order::Limit>(mine).fill_through;
5246 if (
const auto* stop = std::get_if<native_order::Stop>(&
request.trigger))
5247 return same_double_bits(stop->price, armed->installed_level);
5248 if (
const auto* trail = std::get_if<native_order::Trail>(&
request.trigger)) {
5249 const auto& anchored = std::get<native_order::Trail>(mine);
5250 return trail->arm_price && trail->ticks && anchored.ticks
5251 && same_double_bits(*trail->arm_price, armed->installed_level)
5252 && same_double_bits(trail->ticks->ticks, anchored.ticks->ticks)
5253 && trail->offset == 0.0;
5257 const auto* mine_group = std::get_if<native_order::Member>(&armed->request.group);
5258 const auto* group = std::get_if<native_order::Member>(&
request.group);
5259 const bool same_group = mine_group && group && mine_group->group == group->group
5260 && mine_group->effect == group->effect;
5263 const auto* sized = std::get_if<native_order::HostSized>(&
request.intent);
5264 const bool same_close = sized && sized->kind == native_order::HostSizedKind::Close
5266 && std::holds_alternative<native_order::Independent>(
request.owner)
5267 && std::holds_alternative<native_order::ImmediateRemaining>(
request.capacity);
5268 if (same_trigger && same_group && same_close) {
5269 accepted = armed->handle;
5270 anchored_relative_legs_.erase(armed);
5271 ++anchored_relative_stats_.adopted;
5276 const auto result =
host.submit(request);
5277 if (result.status != native_order::SubmitStatus::Accepted || !result.handle)
return std::nullopt;
5278 accepted = *result.handle;
5281 if (!std::isfinite(
snapshot.projection_remaining_qty)
5282 && std::isfinite(
snapshot.requested_qty)) {
5285 const auto next_source_sequence = ++source_sequence_;
5286 const bool staged_replacement_sequence =
snapshot.projection_predecessor != 0
5288 snapshot.source_sequence = retained_source_sequence
5289 ? *retained_source_sequence
5290 : (staged_replacement_sequence ?
snapshot.source_sequence : next_source_sequence);
5291 if (admission_observation) {
5292 admission::CommandEvent event;
5293 event.observation = admission_observation;
5294 event.outcome = admission::Outcome::Admitted;
5295 event.admitted_incarnation = accepted->incarnation;
5301 const std::uint64_t accepted_command_sequence =
snapshot.command_sequence;
5302 remember(*accepted, std::move(snapshot));
5306 if (!replacement_key.empty()) {
5307 pending_write(pending_coof_requests_).erase(
5308 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
5309 [&](
const PendingCoofRequest& row) { return row.replacement_key == replacement_key; }),
5310 pending_coof_requests_.end());
5311 pending_write(pending_bracket_legs_).erase(
5312 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
5313 [&](
const PendingBracketLeg& row) { return row.replacement_key == replacement_key; }),
5314 pending_bracket_legs_.end());
5315 delayed_market_orders_.erase(
5316 std::remove_if(delayed_market_orders_.begin(), delayed_market_orders_.end(),
5317 [&](
const DelayedMarketOrder& row) { return row.replacement_key == replacement_key; }),
5318 delayed_market_orders_.end());
5329 && config_.calc_on_order_fills && !coof_recalc_active_) {
5330 pending_write(pending_entries_).erase(
5331 std::remove_if(pending_entries_.begin(), pending_entries_.end(),
5332 [&](
const PendingEntry& row) {
5333 return row.replacement_key == replacement_key
5334 && row.snapshot.family == PineOrderFamily::Entry
5335 && row.snapshot.opening
5336 && row.snapshot.paired_reversal_parent.incarnation == 0
5337 && !row.snapshot.retained_parent_topology
5338 && row.snapshot.command_sequence < accepted_command_sequence;
5340 pending_entries_.end());
5344 if (reused_key_node) {
5345 reused_key_node.mapped() = live_source_value(*accepted, replacement_key);
5346 auto placed = live_by_source_key_.insert(std::move(reused_key_node));
5347 if (!placed.inserted) { (void)checked_live_key(replacement_key); placed.position->second = live_source_value(*accepted, replacement_key); }
5349 live_by_source_key_[checked_live_key(replacement_key)] = live_source_value(*accepted, replacement_key);
5353 const auto source = placement_.at(accepted->incarnation).source_id;
5354 const auto cohort = cohort_for(source);
5355 host.cohort_add(cohort, *accepted);
5356 require_cohort(source)->second.origins.push_back(*accepted);
5358 const auto& accepted_snapshot = placement_.at(accepted->incarnation);
5359 if (coof_recalc_active_
5360 && ((std::holds_alternative<native_order::Market>(
request.trigger)
5361 && (coof_first_open_
5362 || finite_positive(accepted_snapshot.forced_execution_price)))
5363 || (coof_first_open_
5364 && finite_positive(accepted_snapshot.forced_execution_price)))
5365 && std::holds_alternative<native_order::ImmediateRemaining>(
request.capacity)
5367 first_open_newborns_.push_back(*accepted);
5372std::vector<native_order::RequestHandle> PineExecutionAdapter::openings_for(
const SourceId&
id)
const {
5373 const auto found = cohorts_by_id_.find(
id);
5374 return found == cohorts_by_id_.end() ? std::vector<native_order::RequestHandle>{}
5375 : found->second.opened;
5378bool PineExecutionAdapter::from_entry_filled_this_cycle(
const SourceId&
id)
const {
5381 const auto found = cohorts_by_id_.find(
id);
5382 return found != cohorts_by_id_.end() && current_position_cycle_ > 0
5383 && found->second.cycle == current_position_cycle_;
5386double PineExecutionAdapter::cohort_exposure_for(
const SourceId&
id)
const noexcept {
5387 const auto found = cohorts_by_id_.find_resident(
id);
5388 return found == cohorts_by_id_.end() ? 0.0 : cohort_exposure_for(found->second);
5391double PineExecutionAdapter::cohort_exposure_for(
const CohortFacts& facts)
const noexcept {
5404 facts.live_units_by_origin);
5407 facts.origins.members().size());
5409 facts.live_units_by_origin.size());
5410 if (folded.path == 2)
5412 if (folded.path == 3)
5414#ifdef PINEFORGE_COHORT_EXPOSURE_FIXTURES
5415 detail::verify_cohort_exposure(
facts.origins,
facts.live_units_by_origin,
5422 return folded.total;
5432std::optional<double> PineExecutionAdapter::from_entry_units(
5433 const SourceId& from_entry)
const noexcept {
5434 if (from_entry.empty())
return std::nullopt;
5435 return cohort_exposure_for(from_entry);
5439 assert(cohorts_by_id_.resident_opened_slot_count() == cohort_opened_slot_count_);
5440 if (fixture_verify_source_entry_slots_) {
5441 std::size_t direct = 0;
5442 for (
const auto& row : cohorts_by_id_.resident_rows()) direct += row.second.opened.size();
5443 if (direct != cohort_opened_slot_count_) std::abort();
5445 return cohort_opened_slot_count_ >
static_cast<std::size_t
>(std::numeric_limits<int>::max())
5446 ? std::numeric_limits<int>::max()
5447 :
static_cast<int>(cohort_opened_slot_count_);
5457 result.
directory_bytes = cohorts_by_id_.resident_directory_heap_estimate();
5458 for (
const auto& row : cohorts_by_id_.resident_rows()) {
5459 result.
key_bytes += row.first.capacity() + 1;
5460 const auto& facts = row.second;
5461 result.
origin_bytes += facts.origins.heap_bytes_estimate();
5464 result.
live_unit_bytes += facts.live_units_by_origin.bucket_count() *
sizeof(
void*)
5465 + facts.live_units_by_origin.size()
5466 * (
sizeof(
decltype(facts.live_units_by_origin)::value_type) + 2 *
sizeof(
void*));
5474double PineExecutionAdapter::percent_commission_live_equity(
double mark)
const noexcept {
5475 return percent_commission_live_equity(mark, std::nullopt);
5478double PineExecutionAdapter::percent_commission_live_equity(
5479 double mark, std::optional<double> fx)
const noexcept {
5480 if (!host_)
return std::numeric_limits<double>::quiet_NaN();
5484 if (
auto* pine = pine_view_of(host_)) {
5486 && config_.commission_value > 0.0
5488 double paid_open_commission = 0.0;
5489 for (
const auto& pe : pine->pyramid_entries_) {
5490 if (pe.qty <= internal::kQtyEpsilon)
continue;
5491 const double fee = pine->open_entry_commission(pe);
5492 if (!std::isfinite(fee))
return std::numeric_limits<double>::quiet_NaN();
5493 paid_open_commission += fee;
5495 const double open = fx ? pine->open_profit_at(mark, *fx)
5496 : pine->BacktestEngine::open_profit(mark);
5497 return (pine->closed_trade_equity() + open) - paid_open_commission;
5500 const double marked = strategy_equity_at(mark, fx);
5501 if (!std::isfinite(marked))
return marked;
5510 double restored = 0.0;
5511 for (
const auto& fact : open_entry_fees_) {
5512 if (!std::isfinite(fact.nonpercent_fee))
5513 return std::numeric_limits<double>::quiet_NaN();
5514 restored += fact.nonpercent_fee;
5516 return std::isfinite(restored) ? marked + restored
5517 : std::numeric_limits<double>::quiet_NaN();
5520double PineExecutionAdapter::strategy_equity_at(
5521 double mark, std::optional<double> fx)
const noexcept {
5522 if (!host_)
return std::numeric_limits<double>::quiet_NaN();
5523 const auto* pine = pine_view_of(host_);
5524 return fx && pine ? pine->marked_equity_at(mark, *fx) : host_->native_marked_equity(mark);
5527void PineExecutionAdapter::mark_sizing_equity(
5529 sizing.
equity = percent_commission_live_equity(mark, fx);
5531 const double mirrored = gain_loss_signal_equity(mark);
5532 if (std::isfinite(mirrored)) sizing.
equity = mirrored;
5535 ? strategy_equity_at(mark, fx) : kNaN;
5538void PineExecutionAdapter::record_opening_fee(
5539 const PlacementSnapshot& source,
const native_order::ExecutionAppliedEvent& event) {
5540 const double opened = std::abs(event.opened_units);
5541 if (!(opened > 0.0) || !std::isfinite(opened)
5547 fee = config_.commission_value * opened;
5549 const double total = opened + std::abs(event.closed_units);
5550 fee = total > 0.0 ? config_.commission_value * opened / total : 0.0;
5552#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
5553 fee = fixture_adapter_execution_value(
"fee.adapter.opening", fee);
5555 if (!std::isfinite(fee)) {
5556 engine_command_value_invalid_or_count(
"fee.commission", EngineValueStage::fee,
5557 EngineValueReason::numeric_overflow);
5560 open_entry_fees_.push_back({
event.handle(), source.source_id, opened, fee});
5563void PineExecutionAdapter::consume_opening_fees(
5564 const native_order::ExecutionAppliedEvent& event,
const SourceId* source_id) {
5565 if (!(event.closed_units > 0.0) || !std::isfinite(event.closed_units))
return;
5566 std::vector<std::uint64_t> selected;
5567 if (
const auto* opening = std::get_if<execution::OpeningExposure>(&event.scope)) {
5568 selected.push_back(opening->incarnation);
5569 }
else if (
const auto* openings = std::get_if<native_order::SelectedExposure>(&event.scope)) {
5570 selected = openings->incarnations;
5572 double remaining =
event.closed_units;
5573 for (
auto it = open_entry_fees_.begin(); it != open_entry_fees_.end() && remaining > 0.0;) {
5574 const bool selected_origin = selected.empty()
5575 || std::find(selected.begin(), selected.end(), it->opening.incarnation) != selected.end();
5576 if (!selected_origin || (source_id && it->source_id != *source_id)
5577 || !(it->units > 0.0) || !std::isfinite(it->units)) {
5581 const double consumed = std::min(it->units, remaining);
5582 const double fraction = consumed / it->units;
5583 it->units -= consumed;
5584 it->nonpercent_fee -= it->nonpercent_fee * fraction;
5585 remaining -= consumed;
5586 if (!(it->units > 0.0)) it = open_entry_fees_.erase(it);
else ++it;
5590void PineExecutionAdapter::record_dropped_close(
5591 const SourceId&
id,
const std::string& comment,
double qty,
double qty_percent,
5592 bool immediately, std::uint64_t callsite_token) {
5594 {id, comment, qty, qty_percent, immediately, callsite_token, command_ordinal_});
5595 ++dropped_close_count_;
5598double PineExecutionAdapter::quantize_close_units(
double basis,
double percent)
const noexcept {
5599 if (!std::isfinite(basis) || basis <= 0.0 || !std::isfinite(percent) || percent <= 0.0)
5608 if (percent >= 100.0 - internal::kFullPercentEps) {
5609 if (staged_.quantity_grid && *staged_.quantity_grid > 0.0) {
5610 const double nearest = std::round(basis / *staged_.quantity_grid) * *staged_.quantity_grid;
5611 if (std::abs(basis - nearest) <= internal::kQtyEpsilon)
return nearest;
5615 double units = basis * percent / 100.0;
5616 if (!std::isfinite(units) || units <= 0.0)
return 0.0;
5617 return quantize_percent_exit_units(units, basis);
5620double PineExecutionAdapter::quantize_percent_exit_units(
5621 double requested,
double available)
const noexcept {
5622 if (!std::isfinite(requested) || requested <= 0.0)
return 0.0;
5623 if (!staged_.quantity_grid || !std::isfinite(*staged_.quantity_grid)
5624 || *staged_.quantity_grid <= 0.0) {
5627 const double step = *staged_.quantity_grid;
5628 const double floored = std::floor(requested / step + 1e-6) * step;
5634 if (requested < step && available + internal::kQtyEpsilon >= step) result = step;
5638bool PineExecutionAdapter::compute_exit_reservation(
5640 double requested_qty,
double& qty_percent,
double live_basis,
5641 double percentage_basis,
double& reserved_qty)
const {
5642 qty_percent = std::isfinite(qty_percent)
5643 ? std::clamp(qty_percent, 0.0, 100.0) : 100.0;
5644 reserved_qty = kNaN;
5645 if (!(live_basis > internal::kQtyEpsilon)) {
5646 if (std::isfinite(requested_qty)) {
5647 reserved_qty = std::abs(requested_qty);
5648 return reserved_qty > internal::kQtyEpsilon;
5653 struct Reservation {
5654 std::uint64_t family = 0;
5655 double units = kNaN;
5656 double percent = 100.0;
5657 bool explicit_units =
false;
5658 std::vector<std::uint64_t> origins;
5661 std::uint64_t rank = std::numeric_limits<std::uint64_t>::max();
5662 bool queued_exit =
false;
5664 std::vector<Reservation> reservations;
5665 auto observe = [&](
const PlacementSnapshot&
snapshot) {
5675 &&
snapshot.placement_cycle < current_position_cycle_) {
5679 auto row = std::find_if(reservations.begin(), reservations.end(),
5680 [&](
const Reservation& value) { return value.family == family; });
5681 const double percent = std::isfinite(
snapshot.qty_percent)
5682 ? std::clamp(
snapshot.qty_percent, 0.0, 100.0) : 100.0;
5683 const double units = std::isfinite(
snapshot.projection_remaining_qty)
5684 ? std::max(0.0,
snapshot.projection_remaining_qty)
5685 : (std::isfinite(
snapshot.requested_qty)
5686 ? std::max(0.0, std::abs(
snapshot.requested_qty))
5690 : live_basis * (percent / 100.0));
5691 const bool explicit_units = std::isfinite(
snapshot.requested_qty);
5692 const auto origin =
snapshot.bracket_origin.incarnation;
5693 const auto rank = queued_exit ? exit_queue_rank(placement_, snapshot)
5694 : std::numeric_limits<std::uint64_t>::max();
5695 if (row == reservations.end()) {
5697 next.family = family;
5699 next.percent = percent;
5700 next.explicit_units = explicit_units;
5701 if (explicit_units) next.origins.push_back(origin);
5703 next.queued_exit = queued_exit;
5704 reservations.push_back(std::move(next));
5706 if (explicit_units && row->explicit_units
5707 && std::find(row->origins.begin(), row->origins.end(), origin)
5708 == row->origins.end()) {
5709 row->units += units;
5710 row->origins.push_back(origin);
5712 row->units = std::max(row->units, units);
5714 row->explicit_units = row->explicit_units || explicit_units;
5715 row->percent = std::max(row->percent, percent);
5716 row->rank = std::min(row->rank, rank);
5717 row->queued_exit = row->queued_exit && queued_exit;
5720 for (
const auto& handle : live_handles_) {
5721 const auto found = placement_.find(handle.incarnation);
5722 if (found != placement_.end()) observe(found->second);
5724 for (
const auto& pending : pending_bracket_legs_) observe(
pending.snapshot);
5725 for (
const auto& pending : pending_coof_requests_) observe(
pending.snapshot);
5726 for (
const auto& delayed : delayed_market_orders_) observe(delayed.snapshot);
5728 const auto this_family = key_for(exit_id, from_entry);
5732 std::uint64_t this_rank = std::numeric_limits<std::uint64_t>::max();
5733 for (
const auto& reservation : reservations) {
5734 if (reservation.family == this_family) this_rank = reservation.rank;
5736 double already_reserved = 0.0;
5737 double preserved_reserved = kNaN;
5738 bool other_full_exit =
false;
5739 for (
const auto& reservation : reservations) {
5740 if (reservation.family == this_family) {
5741 if (std::isfinite(reservation.units)) {
5742 preserved_reserved = std::isfinite(preserved_reserved)
5743 ? std::max(preserved_reserved, reservation.units)
5744 : reservation.units;
5748 if (reservation.queued_exit && reservation.rank > this_rank)
continue;
5749 if (std::isfinite(reservation.units)) already_reserved += reservation.units;
5750 if (reservation.percent >= 100.0 - internal::kFullPercentEps) other_full_exit =
true;
5752 const double available = std::max(0.0, live_basis - already_reserved);
5753 if (std::isfinite(requested_qty)) {
5754 reserved_qty = std::min(std::abs(requested_qty), available);
5755 }
else if (qty_percent < 100.0 - internal::kFullPercentEps
5756 && std::isfinite(preserved_reserved)) {
5757 reserved_qty = std::min(preserved_reserved, live_basis);
5763 double requested = qty_percent >= 100.0 - internal::kFullPercentEps
5764 ? live_basis : percentage_basis * qty_percent / 100.0;
5765 if (qty_percent < 100.0 - internal::kFullPercentEps) {
5766 requested = quantize_percent_exit_units(requested, available);
5768 reserved_qty = std::min(requested, available);
5770 if (!(reserved_qty > internal::kQtyEpsilon))
return false;
5771 qty_percent = reserved_qty / live_basis * 100.0;
5772 const bool partial = reserved_qty < live_basis - 1e-9;
5773 if (partial && other_full_exit)
return false;
5777void PineExecutionAdapter::reconcile_deferred_exit_reservations(
5778 const SourceId& from_entry,
double live_basis) {
5779 if (!(live_basis > internal::kQtyEpsilon))
return;
5782 std::uint64_t
key = 0;
5783 std::uint64_t command_sequence = 0;
5784 std::uint64_t rank = std::numeric_limits<std::uint64_t>::max();
5785 double percent = 100.0;
5786 double existing = kNaN;
5787 double explicit_requested = kNaN;
5788 std::vector<native_order::RequestHandle> handles;
5789 std::vector<std::size_t> queued;
5790 std::vector<std::size_t> delayed;
5791 std::vector<std::uint64_t> origins;
5793 std::vector<Family> families;
5794 for (
const auto& handle : live_handles_) {
5795 const auto found = placement_.find(handle.incarnation);
5796 if (found == placement_.end())
continue;
5797 const auto&
snapshot = found->second;
5803 auto family = std::find_if(families.begin(), families.end(),
5804 [&](
const Family& value) { return value.key == key; });
5805 if (family == families.end()) {
5808 next.command_sequence =
snapshot.command_sequence;
5809 next.rank = exit_queue_rank(placement_, snapshot);
5810 next.percent = std::isfinite(
snapshot.qty_percent)
5811 ? std::clamp(
snapshot.qty_percent, 0.0, 100.0) : 100.0;
5812 next.existing =
snapshot.projection_remaining_qty;
5813 next.explicit_requested =
snapshot.requested_qty;
5814 next.handles.push_back(handle);
5815 next.origins.push_back(
snapshot.bracket_origin.incarnation);
5816 families.push_back(std::move(next));
5818 family->command_sequence = std::min(family->command_sequence,
5820 family->rank = std::min(family->rank, exit_queue_rank(placement_, snapshot));
5821 family->handles.push_back(handle);
5822 if (std::isfinite(
snapshot.projection_remaining_qty)) {
5823 family->existing = std::isfinite(family->existing)
5824 ? std::max(family->existing,
snapshot.projection_remaining_qty)
5825 :
snapshot.projection_remaining_qty;
5827 if (std::isfinite(
snapshot.requested_qty))
5828 family->explicit_requested =
snapshot.requested_qty;
5829 if (std::find(family->origins.begin(), family->origins.end(),
5830 snapshot.bracket_origin.incarnation) == family->origins.end()) {
5831 family->origins.push_back(
snapshot.bracket_origin.incarnation);
5835 for (std::size_t index = 0; index < pending_bracket_legs_.size(); ++index) {
5836 const auto&
snapshot = pending_bracket_legs_[index].snapshot;
5842 auto family = std::find_if(families.begin(), families.end(),
5843 [&](
const Family& value) { return value.key == key; });
5844 if (family == families.end()) {
5847 next.command_sequence =
snapshot.command_sequence;
5848 next.rank = exit_queue_rank(placement_, snapshot);
5849 next.percent = std::isfinite(
snapshot.qty_percent)
5850 ? std::clamp(
snapshot.qty_percent, 0.0, 100.0) : 100.0;
5851 next.existing =
snapshot.projection_remaining_qty;
5852 next.explicit_requested =
snapshot.requested_qty;
5853 next.queued.push_back(index);
5854 next.origins.push_back(
snapshot.bracket_origin.incarnation);
5855 families.push_back(std::move(next));
5857 family->command_sequence = std::min(family->command_sequence,
5859 family->rank = std::min(family->rank, exit_queue_rank(placement_, snapshot));
5860 family->queued.push_back(index);
5861 if (std::isfinite(
snapshot.projection_remaining_qty)) {
5862 family->existing = std::isfinite(family->existing)
5863 ? std::max(family->existing,
snapshot.projection_remaining_qty)
5864 :
snapshot.projection_remaining_qty;
5866 if (std::isfinite(
snapshot.requested_qty))
5867 family->explicit_requested =
snapshot.requested_qty;
5868 if (std::find(family->origins.begin(), family->origins.end(),
5869 snapshot.bracket_origin.incarnation) == family->origins.end()) {
5870 family->origins.push_back(
snapshot.bracket_origin.incarnation);
5874 for (std::size_t index = 0; index < delayed_market_orders_.size(); ++index) {
5875 const auto&
snapshot = delayed_market_orders_[index].snapshot;
5881 auto family = std::find_if(families.begin(), families.end(),
5882 [&](
const Family& value) { return value.key == key; });
5883 if (family == families.end()) {
5886 next.command_sequence =
snapshot.command_sequence;
5887 next.rank = exit_queue_rank(placement_, snapshot);
5888 next.percent = std::isfinite(
snapshot.qty_percent)
5889 ? std::clamp(
snapshot.qty_percent, 0.0, 100.0) : 100.0;
5890 next.existing =
snapshot.projection_remaining_qty;
5891 next.explicit_requested =
snapshot.requested_qty;
5892 next.delayed.push_back(index);
5893 next.origins.push_back(
snapshot.bracket_origin.incarnation);
5894 families.push_back(std::move(next));
5896 family->command_sequence = std::min(family->command_sequence,
5898 family->rank = std::min(family->rank, exit_queue_rank(placement_, snapshot));
5899 family->delayed.push_back(index);
5900 if (std::isfinite(
snapshot.projection_remaining_qty)) {
5901 family->existing = std::isfinite(family->existing)
5902 ? std::max(family->existing,
snapshot.projection_remaining_qty)
5903 :
snapshot.projection_remaining_qty;
5905 if (std::isfinite(
snapshot.requested_qty))
5906 family->explicit_requested =
snapshot.requested_qty;
5907 if (std::find(family->origins.begin(), family->origins.end(),
5908 snapshot.bracket_origin.incarnation) == family->origins.end()) {
5909 family->origins.push_back(
snapshot.bracket_origin.incarnation);
5915 std::stable_sort(families.begin(), families.end(),
5916 [](
const Family& left,
const Family& right) {
5917 if (left.rank != right.rank) return left.rank < right.rank;
5918 return left.command_sequence < right.command_sequence;
5922 std::vector<native_order::RequestHandle>
cancel;
5923 for (
const auto& family : families) {
5924 const double available = std::max(0.0, live_basis - reserved);
5926 if (std::isfinite(family.explicit_requested)) {
5932 units = std::min(std::abs(family.explicit_requested), available);
5933 }
else if (family.percent < 100.0 - internal::kFullPercentEps
5934 && std::isfinite(family.existing)) {
5935 units = std::min(family.existing, available);
5943 double requested = family.percent >= 100.0 - internal::kFullPercentEps
5944 ? live_basis : live_basis * family.percent / 100.0;
5945 if (family.percent < 100.0 - internal::kFullPercentEps) {
5946 requested = quantize_percent_exit_units(requested, available);
5948 units = std::min(requested, available);
5950 if (!(units > internal::kQtyEpsilon)) {
5951 cancel.insert(
cancel.end(), family.handles.begin(), family.handles.end());
5952 for (
const auto index : family.queued) {
5953 if (index < pending_bracket_legs_.size())
5954 pending_bracket_legs_[index].snapshot.qty_percent = 0.0;
5956 for (
const auto index : family.delayed) {
5957 if (index < delayed_market_orders_.size())
5958 delayed_market_orders_[index].snapshot.qty_percent = 0.0;
5962 const double normalized_percent = units / live_basis * 100.0;
5963 for (
const auto& handle : family.handles) {
5964 const auto found = placement_.find(handle.incarnation);
5965 if (found == placement_.end())
continue;
5966 found->second.projection_remaining_qty = units;
5967 found->second.qty_percent = normalized_percent;
5968 found->second.fixed_exit_reservation =
5969 std::isfinite(family.explicit_requested)
5970 || family.percent < 100.0 - internal::kFullPercentEps
5971 || std::isfinite(family.existing);
5972 found->second.reservation_deferred_to_pending_entry =
false;
5974 for (
const auto index : family.queued) {
5975 if (index >= pending_bracket_legs_.size())
continue;
5976 auto&
snapshot = pending_bracket_legs_[index].snapshot;
5977 snapshot.projection_remaining_qty = units;
5978 snapshot.qty_percent = normalized_percent;
5980 std::isfinite(family.explicit_requested)
5981 || family.percent < 100.0 - internal::kFullPercentEps
5982 || std::isfinite(family.existing);
5983 snapshot.reservation_deferred_to_pending_entry =
false;
5985 for (
const auto index : family.delayed) {
5986 if (index >= delayed_market_orders_.size())
continue;
5987 auto&
snapshot = delayed_market_orders_[index].snapshot;
5988 snapshot.projection_remaining_qty = units;
5989 snapshot.qty_percent = normalized_percent;
5991 std::isfinite(family.explicit_requested)
5992 || family.percent < 100.0 - internal::kFullPercentEps
5993 || std::isfinite(family.existing);
5994 snapshot.reservation_deferred_to_pending_entry =
false;
5999 const std::size_t multiplicity = std::max<std::size_t>(1, family.origins.size());
6000 reserved += std::isfinite(family.explicit_requested)
6001 ? std::min(available, units *
static_cast<double>(multiplicity))
6004 for (
const auto& handle :
cancel) {
6005 const auto result = require_host().cancel(handle);
6006 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
6008 pending_write(pending_bracket_legs_).erase(
6009 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
6010 [](
const PendingBracketLeg& leg) {
6011 return leg.snapshot.qty_percent == 0.0;
6013 pending_bracket_legs_.end());
6022void PineExecutionAdapter::reconcile_exit_reservations_after_margin() {
6023 std::vector<SourceId> entries;
6024 const auto note = [&](
const PlacementSnapshot&
snapshot) {
6029 if (std::find(entries.begin(), entries.end(),
snapshot.from_entry) == entries.end())
6030 entries.push_back(
snapshot.from_entry);
6032 for (
const auto& handle : live_handles_) {
6033 const auto found = placement_.find(handle.incarnation);
6034 if (found != placement_.end()) note(found->second);
6036 for (
const auto& pending : pending_bracket_legs_) note(
pending.snapshot);
6037 for (
const auto& delayed : delayed_market_orders_) note(delayed.snapshot);
6038 if (entries.empty())
return;
6040 for (
const auto&
entry : entries) {
6041 const double exposure =
entry.empty() ? physical : cohort_exposure_for(
entry);
6042 if (exposure > internal::kQtyEpsilon)
6043 reconcile_deferred_exit_reservations(
entry, exposure);
6050double PineExecutionAdapter::active_staged_fx(std::int64_t timestamp_ms)
const noexcept {
6051 const auto* pine = pine_view_of(host_);
6052 return pine ? pine->account_currency_fx_at(timestamp_ms) : staged_.account_fx;
6065void PineExecutionAdapter::apply_fx_open_margin_slice(
6066 const Bar& bar,
const NativeDecisionContext& context) {
6067 const double rate = active_staged_fx(context.sub_bar_open_ms);
6068 const double prior = last_fx_rate_;
6069 last_fx_rate_ = rate;
6070 if (!std::isfinite(prior) || prior == rate)
return;
6072 if (position.signed_units == 0.0
6073 || position_open_script_bar_ >= context.script_bar_open_ms)
return;
6074 (void)submit_margin_call_slice(bar.open, context);
6077void PineExecutionAdapter::apply_fx_opening_margin_slice(
6078 const native_order::ExecutionAppliedEvent& event,
6079 const NativeDecisionContext& context) {
6084 || !(config_.commission_value > 0.0)) {
6087 std::optional<PlacementSnapshot> opening_snapshot;
6088 if (
const auto found = placement_.find(event.handle().incarnation);
6089 found != placement_.end()) {
6090 opening_snapshot = found->second;
6092 if (!opening_snapshot || !opening_snapshot->opening
6094 || !finite_positive(opening_snapshot->sizing.frozen_units)
6097 || !(config_.commission_value > 0.0)) {
6100 const double rate = active_staged_fx(context.sub_bar_open_ms);
6101 if (!std::isfinite(opening_snapshot->sizing.fx) || opening_snapshot->sizing.fx == rate)
return;
6107 if ((position.signed_units > 0.0 ? config_.margin_long : config_.margin_short) != 100.0) {
6110 (void)submit_margin_call_slice(event.resolved_price, context,
true);
6121bool PineExecutionAdapter::leveraged_entry_bar_checked() const noexcept {
6122 return !bar_magnifier_ && staged_.account_fx_effective_from_ms.empty();
6129bool PineExecutionAdapter::preopen_slice_class(
6130 const PlacementSnapshot& opening,
const NativeDecisionContext& context)
const noexcept {
6133 && finite_positive(opening.sizing.frozen_units)
6134 && finite_positive(opening.exit_levels.stop)
6135 && context.coordinate.path_phase == NativePathPhase::Open;
6138void PineExecutionAdapter::schedule_preopen_margin_slice(
6139 const Bar& bar,
const NativeDecisionContext& context) {
6146 if (!source_margin_call_enabled_
6149 || !finite_positive(staged_.syminfo.pointvalue)
6150 || !finite_positive(staged_.syminfo.mintick)
6151 || !finite_positive(bar.open)) {
6158 const auto live_handles = live_handles_;
6159 for (
const auto& handle : live_handles) {
6160 std::optional<PlacementSnapshot> opening_copy;
6161 if (
const auto found = placement_.find(handle.incarnation);
6162 found != placement_.end()) {
6163 opening_copy = found->second;
6165 if (!opening_copy)
continue;
6166 const PlacementSnapshot& opening = *opening_copy;
6168 || !finite_positive(opening.sizing.frozen_units)
6169 || !finite_positive(opening.exit_levels.stop)) {
6172 const bool marketable = opening.is_long
6173 ? opening.exit_levels.stop <= bar.open
6174 : opening.exit_levels.stop >= bar.open;
6175 if (!marketable)
continue;
6177 const double entry = nearest_tick(bar.open, staged_.syminfo.mintick);
6178 const double adverse_raw = opening.is_long ? bar.low : bar.high;
6179 const double adverse = nearest_tick(adverse_raw, staged_.syminfo.mintick);
6180 if (!finite_positive(
entry) || !finite_positive(adverse)
6181 || (opening.is_long ? !(adverse <
entry) : !(adverse >
entry))) {
6184 const double fx = active_staged_fx(context.sub_bar_open_ms);
6185 const double margin = opening.is_long ? config_.margin_long : config_.margin_short;
6186 if (!finite_positive(fx) || !finite_positive(margin))
continue;
6187 const double units = opening.sizing.frozen_units;
6205 const double admission_cost = units *
entry * staged_.syminfo.pointvalue
6206 * fx * margin / 100.0;
6207 const double admission_guard = std::max(
6208 1e-9, std::abs(opening.sizing.equity) * 1e-12);
6209 if (!std::isfinite(admission_cost)
6210 || admission_cost > opening.sizing.equity + admission_guard) {
6213 const double unrealized = (opening.is_long ? adverse -
entry :
entry - adverse)
6214 * units * staged_.syminfo.pointvalue * fx;
6215 const double marked_equity = opening.sizing.equity + unrealized;
6216 const double required = units * adverse * staged_.syminfo.pointvalue * fx
6218 const double unit_margin = adverse * staged_.syminfo.pointvalue * fx
6220 if (!std::isfinite(marked_equity) || !std::isfinite(required)
6221 || !finite_positive(unit_margin) || !(required > marked_equity)) {
6230 money.mark = adverse;
6232 money.unit_margin = unit_margin;
6233 money.exact_required = required;
6234 money.required = required;
6235 money.equity = marked_equity;
6237 if (!finite_positive(slice))
continue;
6239 native_order::Request
request;
6240 request.intent = native_order::HostSized{native_order::HostSizedKind::Close, std::nullopt};
6241 request.label =
"__margin_preopen__" + opening.source_id;
6242 request.comment =
"Margin call";
6245 request.trigger = native_order::Stop{adverse_raw};
6246 request.owner = native_order::BindCohort{cohort_for(opening.source_id)};
6250 snapshot.from_entry = opening.source_id;
6254 (void)submit_or_replace(std::move(request), std::move(snapshot),
false,
6255 "__margin_preopen__" + opening.source_id);
6262void PineExecutionAdapter::consume_cohort_units(
6263 const SourceId&
id,
const native_order::ExecutionAppliedEvent& event) {
6264 if (!(event.closed_units > 0.0) || !std::isfinite(event.closed_units))
return;
6265 observe_position_tail_rows();
6266 const auto found = cohorts_by_id_.find(
id);
6267 if (found == cohorts_by_id_.end())
return;
6268 auto&
facts = found->second;
6269 std::vector<std::uint64_t> selected;
6270 if (
const auto* opening = std::get_if<execution::OpeningExposure>(&event.scope)) {
6271 selected.push_back(opening->incarnation);
6272 }
else if (
const auto* openings = std::get_if<native_order::SelectedExposure>(&event.scope)) {
6273 selected = openings->incarnations;
6275 for (
const auto& handle :
facts.opened) selected.push_back(handle.incarnation);
6277 double remaining =
event.closed_units;
6278 for (
const auto incarnation : selected) {
6279 auto unit =
facts.live_units_by_origin.find(incarnation);
6280 if (unit ==
facts.live_units_by_origin.end() || !(unit->second > 0.0))
continue;
6281 const double deduction = std::min(unit->second, remaining);
6282 unit->second -= deduction;
6283 remaining -= deduction;
6284 if (unit->second == 0.0) {
6285 facts.live_units_by_origin.erase(unit);
6286 if (position_tail_summary_) position_tail_summary_->retire_origin(incarnation);
6288 if (!(remaining > 0.0))
break;
6290 if (position_tail_summary_)
6291 position_tail_summary_->live_cohort(&*found, !
facts.live_units_by_origin.empty());
6292 cohorts_by_id_.refresh_held(found);
6296void PineExecutionAdapter::consume_closed_trade_rows(
6297 const native_order::ExecutionAppliedEvent& event,
6299 observe_position_tail_rows();
6300 auto&
host = require_host();
6301 const auto settle_slot = [&](CohortFacts&
cohort,
const Trade& trade,
6314 const bool exact_bracket_owner = cause->bracket_origin.incarnation != 0
6315 ? cause->bracket_origin.incarnation == trade.entry_incarnation
6316 : cause->from_entry == trade.entry_id;
6317 if (bracket && !exact_bracket_owner)
6318 bracket_shadowed_openings_.insert(trade.entry_incarnation);
6319 if (!drained)
return;
6320 const bool owned_bracket = bracket && exact_bracket_owner
6321 && bracket_shadowed_openings_.find(trade.entry_incarnation)
6322 == bracket_shadowed_openings_.end();
6323 if (!close_path && !owned_bracket)
return;
6324 const auto removed_at = std::remove_if(
cohort.opened.begin(),
cohort.opened.end(),
6325 [&](
const auto& opening) {
6326 return opening.incarnation == trade.entry_incarnation;
6328 const auto removed =
static_cast<std::size_t
>(
6329 std::distance(removed_at,
cohort.opened.end()));
6331 assert(cohort_opened_slot_count_ >= removed);
6332 cohort_opened_slot_count_ -= removed;
6334 bracket_shadowed_openings_.erase(trade.entry_incarnation);
6336 for (std::size_t row = 0; row <
event.closed_trade_count; ++row) {
6337 const std::size_t index =
event.first_trade_index + row;
6338 if (index >=
static_cast<std::size_t
>(
host.trade_count()))
continue;
6339 const Trade& trade =
host.get_trade(
static_cast<int>(index));
6340 double remaining = trade.qty;
6341 bool matched =
false;
6342 const auto held = cohorts_by_id_.held_rows();
6343 for (
auto at = held.begin(); at != held.end();) {
6344 const auto current = at++;
6346 auto units =
cohort.second.live_units_by_origin.find(
6347 trade.entry_incarnation);
6348 if (units ==
cohort.second.live_units_by_origin.end())
continue;
6349 const double consumed = std::min(units->second, remaining);
6350 units->second -= consumed;
6351 remaining -= consumed;
6352 const bool drained = !(units->second > 1e-10);
6354 cohort.second.live_units_by_origin.erase(units);
6355 if (position_tail_summary_)
6356 position_tail_summary_->retire_origin(trade.entry_incarnation);
6358 if (position_tail_summary_)
6359 position_tail_summary_->live_cohort(
6360 &cohort, !
cohort.second.live_units_by_origin.empty());
6361 settle_slot(
cohort.second, trade, drained);
6362 cohorts_by_id_.refresh_held(current);
6366 if (matched || !(remaining > 0.0))
continue;
6367 const auto cohort = cohorts_by_id_.find(trade.entry_id);
6368 if (cohort == cohorts_by_id_.end())
continue;
6370 const auto consume_origin = [&](std::uint64_t incarnation) {
6371 auto units =
cohort->second.live_units_by_origin.find(incarnation);
6372 if (units ==
cohort->second.live_units_by_origin.end())
return true;
6373 const double consumed = std::min(units->second, remaining);
6374 units->second -= consumed;
6375 remaining -= consumed;
6376 const bool drained = !(units->second > 1e-10);
6378 cohort->second.live_units_by_origin.erase(units);
6379 if (position_tail_summary_) position_tail_summary_->retire_origin(incarnation);
6381 if (position_tail_summary_)
6382 position_tail_summary_->live_cohort(
6383 &*cohort, !
cohort->second.live_units_by_origin.empty());
6384 settle_slot(
cohort->second, trade, drained);
6385 cohorts_by_id_.refresh_held(cohort);
6386 return remaining > 0.0;
6388 auto* lookup =
cohort->second.origins.size() <= kIndexedFrom ? nullptr
6389 : adapter_lookup_index(bound_consumer(),
this, run_counter_);
6391 for (
const auto& origin :
cohort->second.origins)
6392 if (!consume_origin(origin.incarnation))
break;
6401 std::vector<std::pair<std::size_t, std::uint64_t>> carrying;
6402 carrying.reserve(
cohort->second.live_units_by_origin.size());
6403 for (
const auto& unit :
cohort->second.live_units_by_origin) {
6406 const auto* positions =
cohort->second.origins.positions_of(unit.first);
6407 if (positions && !positions->empty())
6408 carrying.emplace_back(positions->front(), unit.first);
6410 std::sort(carrying.begin(), carrying.end());
6413 for (
const auto& origin : carrying)
6414 if (!consume_origin(origin.second))
break;
6418bool PineExecutionAdapter::origin_is_pending(
6419 const native_order::RequestHandle& origin)
const noexcept {
6420 if (origin.incarnation == 0)
return false;
6422 if (std::find(live_handles_.begin(), live_handles_.end(), origin) == live_handles_.end())
6424 const auto placement = placement_.find(origin.incarnation);
6428std::vector<std::size_t> PineExecutionAdapter::live_origin_positions(
6429 const CohortFacts& cohort)
const {
6430 std::vector<std::size_t> positions;
6431 for (
const auto& handle : live_handles_)
cohort.origins.append_positions_of(handle, positions);
6432 std::sort(positions.rbegin(), positions.rend());
6433 positions.erase(std::unique(positions.begin(), positions.end()), positions.end());
6437void PineExecutionAdapter::cancel_bracket_origin(native_order::RequestHandle origin) {
6438 pending_write(pending_bracket_legs_).erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
6439 [&](
const PendingBracketLeg& leg) { return leg.snapshot.bracket_origin == origin; }),
6440 pending_bracket_legs_.end());
6441 std::vector<native_order::RequestHandle> matches;
6442 for (
const auto& handle : live_handles_) {
6443 const auto placement = placement_.find(handle.incarnation);
6444 if (placement != placement_.end() &&
placement->second.bracket_origin == origin)
6445 matches.push_back(handle);
6447 for (
const auto& handle : matches) {
6448 const auto result = require_host().cancel(handle);
6449 if (result.status == native_order::CancelStatus::Cancelled) {
6450 if (
const auto placement = placement_.find(handle.incarnation);
6451 placement != placement_.end()) {
6453 handle.incarnation,
static_cast<std::int64_t
>(
placement->second.source_sequence),
6454 handle.incarnation,
placement->second.placement_cycle,
6462void PineExecutionAdapter::cancel_bracket_siblings(native_order::RequestHandle handle,
6463 std::uint64_t executed_at) {
6466 std::optional<PlacementSnapshot> snapshot_copy;
6467 if (
const auto source = placement_.find(handle.incarnation);
6468 source != placement_.end()) {
6469 const auto family = source->second.family;
6472 snapshot_copy = source->second;
6474 if (!snapshot_copy)
return;
6475 const PlacementSnapshot&
snapshot = *snapshot_copy;
6476 std::vector<native_order::RequestHandle> matches;
6477 for (
const auto& candidate : live_handles_) {
6478 if (candidate == handle)
continue;
6479 const auto placement = placement_.find(candidate.incarnation);
6480 if (placement == placement_.end())
continue;
6481 const auto& sibling =
placement->second;
6482 if (sibling.source_id ==
snapshot.source_id && sibling.from_entry ==
snapshot.from_entry
6483 && sibling.bracket_origin ==
snapshot.bracket_origin
6486 matches.push_back(candidate);
6500 const auto later_call = [&](
const native_order::RequestHandle& sibling) {
6501 const auto placement = placement_.find(sibling.incarnation);
6505 if (executed_at != 0 && std::any_of(matches.begin(), matches.end(), later_call)) {
6506 const auto working = require_host().native_working_requests();
6507 matches.erase(std::remove_if(matches.begin(), matches.end(),
6508 [&](
const native_order::RequestHandle& sibling) {
6509 if (!later_call(sibling)) return false;
6510 const auto row = std::find_if(working.begin(), working.end(),
6511 [&](const NativeWorkingRequest& request) {
6512 return request.definition->handle == sibling;
6514 return row != working.end()
6515 && row->definition->birth.acceptance_ordinal > executed_at;
6518 for (
const auto& sibling : matches) {
6519 const auto result = require_host().cancel(sibling);
6520 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
6524void PineExecutionAdapter::cancel_exit_orders_for_full_close(
6527 cancel_exit_orders_for_full_close(text);
6530void PineExecutionAdapter::cancel_exit_orders_for_full_close(
6535 const bool keep_pending_bound =
6537 const auto matches = [&](
const PlacementSnapshot&
snapshot) {
6541 return exit && !(keep_pending_bound &&
snapshot.pending_bound_exit)
6545 pending_write(pending_bracket_legs_).erase(
6546 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
6547 [&](
const PendingBracketLeg& row) { return matches(row.snapshot); }),
6548 pending_bracket_legs_.end());
6549 pending_write(pending_coof_requests_).erase(
6550 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
6551 [&](
const PendingCoofRequest& row) { return matches(row.snapshot); }),
6552 pending_coof_requests_.end());
6553 pending_write(source_shadow_pending_).erase(
6554 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
6555 [&](
const SourceShadowPending& row) { return matches(row.snapshot); }),
6556 source_shadow_pending_.end());
6557 pending_relative_exits_.erase(
6558 std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
6559 [&](
const PendingRelativeExit& row) {
6560 return detail::exit_scratch_equal(detail::exit_scratch_text(row.from_entry),
6563 pending_relative_exits_.end());
6564 if (from_entry.size() != 0) withdraw_anchored_relative_readers(
nullptr, &from_entry);
6566 detail::ExitScratchRows<native_order::RequestHandle> handles(*
this);
6567 for (
const auto& handle : live_handles_) {
6568 const auto found = placement_.find(handle.incarnation);
6569 if (found != placement_.end() && matches(found->second)
6570 && !coof_leg_reaches_exit(found->second)) {
6571 handles.push_back(handle);
6574 for (
const auto& handle : handles) {
6575 const auto result = require_host().cancel(handle);
6576 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
6578 detail::ExitScratchIndex removed(handles.context());
6579 for (
const auto& handle : handles) removed.put(handle.incarnation, 0);
6580 const BracketRoster::RemovalSelection selection(handles);
6581 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
6582 if (!handles.empty()) family->second.remove_selected(
6583 removed, handles.context().run, selection,
6584 fixture_bracket_remove_precheck_);
6585 if (family->second.empty()) family = bracket_families_.erase(family);
6588 refresh_pending_view();
6620void PineExecutionAdapter::retire_in_position_exits_at_flat(
6621 bool preserve_pending_parents,
bool dormant_rows_only,
6632 const auto pending_parent = [&](
const PlacementSnapshot& row) {
6633 if (!preserve_pending_parents || row.from_entry.empty())
return false;
6634 if (paired_close !=
nullptr
6635 && row.projection_created_bar != paired_close->projection_created_bar) {
6638 const SourceId& owner = row.from_entry;
6639 const auto admits = [&](
const PlacementSnapshot& parent) {
6640 return paired_close ==
nullptr
6641 || row.command_sequence > paired_close->command_sequence
6642 || (parent.projection_created_bar == row.projection_created_bar
6643 && row.command_sequence > parent.command_sequence
6644 && paired_close->projection_position_side
6648 for (
const auto& handle : live_handles_) {
6649 const auto found = placement_.find(handle.incarnation);
6650 if (found != placement_.end() && found->second.opening
6652 && found->second.source_id == owner && admits(found->second)) {
6656 return std::any_of(pending_entries_.begin(), pending_entries_.end(),
6657 [&](
const PendingEntry&
entry) {
6658 return entry.snapshot.opening && entry.snapshot.source_id == owner
6659 && admits(entry.snapshot);
6666 const bool keep_pending_bound =
6668 const auto matches = [&](
const PlacementSnapshot&
snapshot) {
6675 && !(keep_pending_bound &&
snapshot.pending_bound_exit)
6676 && !pending_parent(snapshot);
6678 if (!dormant_rows_only) {
6679 pending_write(pending_bracket_legs_).erase(
6680 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
6681 [&](
const PendingBracketLeg& row) { return matches(row.snapshot); }),
6682 pending_bracket_legs_.end());
6683 pending_write(pending_coof_requests_).erase(
6684 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
6685 [&](
const PendingCoofRequest& row) { return matches(row.snapshot); }),
6686 pending_coof_requests_.end());
6687 pending_write(source_shadow_pending_).erase(
6688 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
6689 [&](
const SourceShadowPending& row) { return matches(row.snapshot); }),
6690 source_shadow_pending_.end());
6693 std::vector<native_order::RequestHandle> handles;
6694 if (!dormant_rows_only) {
6695 for (
const auto& handle : live_handles_) {
6696 const auto found = placement_.find(handle.incarnation);
6697 if (found != placement_.end() && matches(found->second))
6698 handles.push_back(handle);
6700 for (
const auto& handle : handles) {
6701 const auto result = require_host().cancel(handle);
6702 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
6704 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
6705 family->second.remove_all(handles);
6706 if (family->second.empty()) family = bracket_families_.erase(family);
6710 refresh_pending_view();
6725 return placement_.command_sequence_for(exit_id, from_entry);
6730 if (trade_index < base)
6732 "exit-phase read precedes the sealed prefix");
6733 return trade_index - base < trade_exit_phase_.size()
6734 ? trade_exit_phase_[trade_index - base] :
static_cast<std::uint8_t
>(NativePathPhase::None);
6738 return exit_phase_value(trade_index) ==
static_cast<std::uint8_t
>(NativePathPhase::Open);
6742 const std::vector<std::size_t>& indices) {
6744 [&](std::size_t i) { return indices[i]; });
6748 std::size_t start, std::size_t count,
6749 const std::function<std::size_t(std::size_t)>& source_at) {
6753 if (position_tail_summary_)
6756 const auto none =
static_cast<std::uint8_t
>(NativePathPhase::None);
6760 "exit-phase permutation precedes the sealed prefix");
6764 for (std::size_t i = 0; i < count; ++i) {
6765 const std::size_t index = source_at(i);
6768 "exit-phase read precedes the sealed prefix");
6769 permuted.push_back(index - base < trade_exit_phase_.size()
6770 ? trade_exit_phase_[index - base] :
none);
6772 for (std::size_t i = 0; i < permuted.size(); ++i) {
6773 const std::size_t target = start + i;
6774 if (target - base >= trade_exit_phase_.size() && permuted[i] ==
none)
continue;
6775 set_exit_phase(target, permuted[i]);
6792void PineExecutionAdapter::note_exit_rank_key(
const SourceId&
id) {
6795 if (exit_rank_keys_.
size() > 26
6796 || exit_rank_keys_.
find(
id).has_value()) {
6801 exit_rank_keys_.
insert(
id,
static_cast<std::uint32_t
>(exit_rank_keys_.
size()));
6802 const std::uint64_t size =
id.size();
6803 exit_rank_keys_digest_ = fnv_append(exit_rank_keys_digest_, &size,
sizeof(size));
6804 exit_rank_keys_digest_ = fnv_append(exit_rank_keys_digest_,
id.data(),
id.size());
6805 note_exit_rank_transients();
6808void PineExecutionAdapter::note_exit_rank_transients() {
6809 assert_live_counts();
6810 const auto& counts = retired_row_scratch_.live_families;
6811 const auto closes =
static_cast<std::uint32_t
>(
6814 const auto markets =
static_cast<std::uint32_t
>(
6815 counts[RetiredRowScratch::family_count]);
6823 const auto keys =
static_cast<std::uint32_t
>(exit_rank_keys_.
size());
6824 exit_rank_peak_ = std::max(exit_rank_peak_, keys + closes);
6825 exit_rank_alt_peak_ = std::max(exit_rank_alt_peak_, keys + closes + markets);
6829 const std::vector<SourceId>& ids)
const {
6830 const auto buckets_for = [](std::uint32_t peak) {
6831 std::uint32_t capacity = 16;
6832 while (
static_cast<std::uint64_t
>(peak) * 16U >
static_cast<std::uint64_t
>(capacity) * 13U)
6836 const std::uint32_t capacity = buckets_for(exit_rank_peak_);
6837 if (capacity > 32 || buckets_for(exit_rank_alt_peak_) != capacity)
return std::nullopt;
6838 std::vector<std::uint32_t> bucket(exit_rank_keys_.size());
6839 std::vector<std::uint32_t> load(capacity, 0);
6840 bool supported =
true;
6841 exit_rank_keys_.for_each_inserted([&](
const auto& key,
auto age) {
6842 std::uint32_t hash = 0;
6843 if (!java_string_hash(key, hash)) { supported =
false;
return; }
6844 bucket[age] = (hash ^ (hash >> 16)) & (capacity - 1);
6848 if (++load[bucket[age]] >= 8) supported =
false;
6850 if (!supported)
return std::nullopt;
6851 std::vector<std::size_t> ages;
6852 ages.reserve(ids.size());
6853 for (
const auto&
id : ids) {
6854 const auto found = exit_rank_keys_.find(
id);
6855 if (!found)
return std::nullopt;
6856 const auto age =
static_cast<std::size_t
>(exit_rank_keys_.get(*found));
6857 if (capacity > 16) {
6858 for (
const auto other : ages)
6859 if (bucket[other] == bucket[age])
return std::nullopt;
6861 ages.push_back(age);
6863 std::vector<std::uint32_t> ranks;
6864 ranks.reserve(ids.size());
6865 for (
const auto age : ages) {
6866 std::uint32_t rank = 0;
6867 for (std::size_t other = 0; other < bucket.size(); ++other) {
6868 if (bucket[other] < bucket[age] || (bucket[other] == bucket[age] && other > age))
6871 ranks.push_back(rank);
6879 for (
const auto& handle : live_handles_) {
6880 if (handle == filled)
continue;
6881 const auto row = placement_.find(handle.incarnation);
6882 if (row == placement_.end())
continue;
6883 const auto& leg = row->second;
6885 if (leg.from_entry.empty() && leg.source_id == exit_id)
continue;
6888 && (closing_long ?
price >= leg.exit_levels.limit :
price <= leg.exit_levels.limit)) {
6899void PineExecutionAdapter::set_exit_phase(std::size_t trade_index, std::uint8_t phase) {
6901 : NativeExecutionConsumer::ClosedTradePrefix{};
6902 if (trade_index < prefix.base)
6904 "exit-phase write precedes the sealed prefix");
6905 const std::size_t offset = trade_index - prefix.base;
6906 if (trade_exit_phase_.
empty() && host_)
6908 if (offset >= trade_exit_phase_.
size())
6909 trade_exit_phase_.
resize(offset + 1,
static_cast<std::uint8_t
>(NativePathPhase::None));
6910 if (trade_index < exit_phase_final_ && trade_exit_phase_[offset] != phase) {
6912 exit_phase_final_ = prefix.base;
6913 exit_phase_digest_ = prefix.exit_phase_digest;
6916 if (trade_exit_phase_[offset] != phase) { invalidate_exit_group(trade_index); } trade_exit_phase_.set(offset, phase);
6920 auto& host = require_host();
6923 auto* pine = pine_view_of(&host);
6934 const std::uint64_t high_water = consumer->event_high_water();
6935 const bool quiet = high_water <= receipt_cursor_
6936 || !consumer->has_command_after(receipt_cursor_);
6942 receipt_cursor_ = std::max(receipt_cursor_, high_water);
6950 const bool in_place = consumer !=
nullptr;
6955 std::optional<std::uint64_t> terminal_high_water;
6956 if (in_place && (!state.spec || state.spec->intrabar.is_none())) {
6957 terminal_high_water = consumer->terminal_receipt_high_water();
6960 std::visit([&](
const auto& event) {
6961 using Event = std::decay_t<
decltype(event)>;
6962 if constexpr (!std::is_same_v<Event, native_order::AcceptedEvent>)
6963 retired_row_scratch_.roster_pass_due =
true;
6964 if constexpr (std::is_same_v<Event, native_order::MatchRejectedEvent>
6965 || std::is_same_v<Event, native_order::CancelledEvent>
6966 || std::is_same_v<Event, native_order::NoEffectEvent>) {
6970 const auto ended = std::remove_if(
6971 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
6972 [&](
const AnchoredRelativeLeg& leg) {
6973 return leg.handle == event.handle();
6975 anchored_relative_stats_.withdrawn +=
static_cast<std::uint64_t
>(
6976 std::distance(ended, anchored_relative_legs_.end()));
6977 anchored_relative_legs_.erase(ended, anchored_relative_legs_.end());
6978 const auto placement = placement_.find(event.handle().incarnation);
6979 if (placement != placement_.end()) {
6980 if constexpr (std::is_same_v<Event, native_order::MatchRejectedEvent>) {
6981 suspend_declined_reversal_brackets(event);
6983 const auto handle =
event.handle();
6984 const bool opening = placement->second.opening;
6985 placement->second.cancellation = {
6986 std::is_same_v<Event, native_order::CancelledEvent>
6988 1, 0, handle.incarnation,
6989 static_cast<std::int64_t
>(placement->second.source_sequence),
6990 handle.incarnation, placement->second.placement_cycle,
6991 placement->second.legs.revision(),
6992 placement->second.requested_qty, kNaN};
6994 if (opening) cancel_bracket_origin(handle);
6996 }
else if constexpr (std::is_same_v<Event, native_order::ActivatedEvent>) {
6997 if (std::holds_alternative<native_order::StopLimitLive>(event.after)) {
6998 const auto placement = placement_.find(event.definition->handle.incarnation);
6999 if (placement != placement_.end()) placement->second.stop_limit_activated =
true;
7001 }
else if constexpr (std::is_same_v<Event, native_order::ReservationReducedEvent>) {
7002 const auto placement = placement_.find(event.recipient.incarnation);
7003 if (placement != placement_.end()) {
7004 if (
const auto* remaining = std::get_if<native_order::RemainingProjectionUnits>(
7006 placement->second.projection_remaining_qty = remaining->q;
7009 }
else if constexpr (std::is_same_v<Event, native_order::ExecutionAppliedEvent>) {
7010 if (event.terminal) cancel_bracket_siblings(event.handle(), event.ordinal);
7023 const std::uint64_t high_water = consumer->event_high_water();
7024 if (high_water > receipt_cursor_) {
7025 consumer->visit_commands_after(receipt_cursor_,
7027 receipt_cursor_ = std::max(receipt_cursor_, std::visit(
7028 [](
const auto& payload) {
return payload.ordinal; }, command));
7032 receipt_cursor_ = std::max(receipt_cursor_, high_water);
7035 for (
const auto& row : host.native_events(receipt_cursor_)) {
7036 receipt_cursor_ = std::max(receipt_cursor_, row.ordinal);
7037 if (row.command) observe(*row.command);
7040 if (terminal_high_water) {
7041 terminal_receipt_cursor_ = std::max(
7042 terminal_receipt_cursor_, *terminal_high_water);
7047 host.native_acknowledge_events(receipt_cursor_);
7059 const auto found = cohorts_by_id_.
find(
id);
7064 if (found == cohorts_by_id_.
end() || !(cohort_exposure_for(
id) > 0.0)) {
7066 found == cohorts_by_id_.
end()
7068 : found->second.handle};
7070 return native_order::Independent{};
7076 if (dynamic && coof_recalc_active_ && coof_first_open_
7077 && found != cohorts_by_id_.end() && !found->second.opened.empty()) {
7078 return native_order::BindOpenings{found->second.opened, found->second.cycle};
7080 if (dynamic || found == cohorts_by_id_.end()) {
7081 if (found == cohorts_by_id_.end())
7083 return native_order::BindCohort{found->second.handle};
7085 return native_order::BindOpenings{found->second.opened, found->second.cycle};
7088void PineExecutionAdapter::stage_flat_children_before_parent(
7089 const SourceId& parent_id, std::int32_t created_bar,
7090 std::int64_t script_open_ms) {
7091 std::vector<native_order::RequestHandle> children;
7092 for (
const auto& handle : live_handles_) {
7093 const auto found = placement_.find(handle.incarnation);
7094 if (found == placement_.end())
continue;
7095 const auto& child = found->second;
7098 if (bracket && child.from_entry == parent_id
7099 && child.projection_position_side
7101 && child.projection_created_bar == created_bar
7102 && child.placement_script_open_ms == script_open_ms) {
7103 children.push_back(handle);
7106 for (
const auto& handle : children) {
7107 const auto found = placement_.find(handle.incarnation);
7108 if (found == placement_.end())
continue;
7109 PlacementSnapshot
snapshot = found->second;
7110 native_order::Request
request;
7111 request.intent = native_order::HostSized{
7112 native_order::HostSizedKind::Close, std::nullopt};
7121 const std::string group_name =
snapshot.oca_name.empty()
7124 request.group = group_for(group_name, 1);
7125 snapshot.defer_until_post_parent_calculation =
true;
7128 const auto cancelled = require_host().cancel(handle);
7129 if (
cancelled.status != native_order::CancelStatus::Cancelled)
continue;
7131 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
7132 [&](
const PendingBracketLeg& row) {
7133 return row.replacement_key == replacement_key;
7135 PendingBracketLeg staged{std::move(request), std::move(snapshot),
7137 key_for(found->second.source_id,
7138 found->second.from_entry)};
7139 if (queued == pending_bracket_legs_.end()) {
7140 pending_write(pending_bracket_legs_).push_back(std::move(staged));
7142 pending_write(*queued) = std::move(staged);
7145 refresh_pending_view();
7151 std::uint64_t source_fill_sequence) {
7152 coof_recalc_active_ =
true;
7153 coof_first_open_ = first_open;
7154 coof_current_fill_seq_ = source_fill_sequence;
7155 coof_fill_cursor_t_ =
event.cursor.t;
7156 coof_market_entry_recalc_fill_seq_ = source_fill_sequence;
7157 coof_market_entry_recalc_incarnation_ = 0;
7159 coof_fill_target_print_ = kNaN;
7160 if (!bar_magnifier_ && placement != placement_.end()
7161 && finite_positive(placement->second.forced_execution_price)
7162 && finite_positive(placement->second.forced_waypoint_print)
7164 const double tick = staged_.syminfo.mintick;
7165 const double target = placement->second.forced_waypoint_print;
7166 const double slippage = config_.slippage * tick;
7167 const double forced_price = placement->second.forced_execution_price;
7168 const bool paired = same_double_bits(forced_price, nearest_tick(target, tick) + slippage)
7169 || same_double_bits(forced_price, nearest_tick(target, tick) - slippage)
7170 || same_double_bits(forced_price, source_bar_fill_tick(target, tick) + slippage)
7171 || same_double_bits(forced_price, source_bar_fill_tick(target, tick) - slippage);
7172 if (paired) coof_fill_target_print_ = target;
7174 coof_open_stop_next_waypoint_ = placement != placement_.end()
7175 && placement->second.coof_open_stop_next_waypoint;
7177 && std::holds_alternative<native_order::Market>(event.
request().
trigger)) {
7178 if (placement != placement_.end()
7180 coof_market_entry_recalc_incarnation_ =
event.handle().incarnation;
7190 const auto* armed_limit = placement != placement_.end()
7192 ? std::get_if<native_order::StopLimit>(&event.
request().
trigger) :
nullptr;
7193 const bool armed_limit_gap_fill = armed_limit
7194 && (placement->second.projection_position_side
7196 ? armed_limit->stop >= armed_limit->limit
7197 : armed_limit->stop <= armed_limit->limit)
7198 && coof_fill_at_path_point(armed_limit->stop);
7199 coof_fill_forced_ = placement != placement_.end()
7200 && (finite_positive(placement->second.forced_execution_price)
7201 || armed_limit_gap_fill);
7202 coof_context_ = context;
7206 coof_recalc_active_ =
false;
7207 coof_first_open_ =
false;
7208 coof_open_stop_next_waypoint_ =
false;
7209 coof_fill_forced_ =
false;
7210 coof_market_entry_recalc_incarnation_ = 0;
7211 coof_market_entry_recalc_fill_seq_ = 0;
7212 coof_current_fill_seq_ = 0;
7213 coof_fill_cursor_t_ = kNaN;
7214 coof_fill_target_print_ = kNaN;
7228 if (host_ && config_.calc_on_order_fills && event.closed_units > 0.0) {
7230 const auto filled = placement_.find(event.handle().incarnation);
7231 if (held != 0.0 && filled != placement_.end() && !filled->second.from_entry.empty()
7234 const double price =
event.raw_price;
7235 const bool closing_long = held > 0.0;
7236 for (
const auto& handle : live_handles_) {
7237 if (handle == event.handle())
continue;
7238 const auto sibling = placement_.find(handle.incarnation);
7239 if (sibling == placement_.end())
continue;
7240 const auto& row = sibling->second;
7241 if (row.from_entry != filled->second.from_entry
7242 || row.source_id == filled->second.source_id) {
7245 const double stop = row.exit_levels.stop;
7246 const double limit = row.exit_levels.limit;
7249 const auto at = [tick = staged_.syminfo.mintick,
price](
double level) {
7250 return price == level || (finite_positive(tick)
7251 && nearest_tick(
price, tick) == nearest_tick(level, tick));
7253 const bool triggered =
7257 && ((closing_long ?
price > limit :
price < limit) || at(limit)));
7258 if (triggered)
return true;
7262 if (!host_ || !config_.calc_on_order_fills || config_.process_orders_on_close
7263 || !modeled_input() || config_.pyramiding != 0 || config_.close_entries_rule_any
7264 || config_.slippage != 0 || config_.commission_value != 0.0
7265 || staged_.account_fx != 1.0 || !staged_.account_fx_effective_from_ms.empty()
7266 || risk_.max_intraday_loss != 0.0 || risk_.max_drawdown != 0.0
7267 || risk_.max_cons_loss_days != 0 ||
cap.active()
7268 || context.coordinate.path_phase != NativePathPhase::Low
7269 || !policy_script_bar_valid_ || event.closed_units <= 0.0
7274 if (state.spec && !state.spec->intrabar.is_none())
return false;
7275 const auto filled = placement_.find(event.handle().incarnation);
7276 if (filled == placement_.end())
return false;
7280 && row.from_entry == filled->second.from_entry && !row.from_entry.empty()
7281 && std::isfinite(row.requested_qty) && row.requested_qty > 0.0
7282 && row.oca_name.empty() && std::isnan(row.exit_levels.trail_points)
7283 && std::isnan(row.exit_levels.trail_price)
7284 && std::isfinite(row.exit_levels.stop)
7285 && row.exit_levels.stop <=
event.resolved_price
7286 && row.exit_levels.stop >= policy_script_bar_.low;
7288 if (!eligible(filled->second))
return false;
7289 return std::any_of(live_handles_.begin(), live_handles_.end(),
7291 if (handle == event.handle()) return false;
7292 const auto sibling = placement_.find(handle.incarnation);
7293 return sibling != placement_.end() && eligible(sibling->second);
7300 if (context.coordinate.provenance != NativePriceProvenance::Calculation)
return false;
7301 const auto filled = placement_.find(event.handle().incarnation);
7302 return filled != placement_.end() && filled->second.immediately
7307bool PineExecutionAdapter::defer_coof_tail() const noexcept {
7308 if (!coof_recalc_active_ || coof_first_open_)
return false;
7310 if (state.spec && state.spec->intrabar.lower())
return false;
7312 if (phase == NativePathPhase::Close || phase == NativePathPhase::None)
7317 return coof_fill_forced_ && coof_fill_at_second_extreme();
7332bool PineExecutionAdapter::coof_fill_at_second_extreme() const noexcept {
7333 if (!coof_recalc_active_ || coof_first_open_ || !coof_script_bar_valid_)
return false;
7334 const bool high_first = source_path_uses_high_first(coof_script_bar_);
7336 ? NativePathPhase::Low : NativePathPhase::High;
7337 const double endpoint = high_first ? coof_script_bar_.
low : coof_script_bar_.
high;
7339 && coof_fill_at_path_point(endpoint);
7342bool PineExecutionAdapter::coof_fill_on_path_point() const noexcept {
7345 return coof_recalc_active_
7346 && (coof_fill_cursor_t_ == 0.0 || coof_fill_cursor_t_ == 1.0);
7349bool PineExecutionAdapter::coof_fill_at_path_point(
double waypoint)
const noexcept {
7351 if (!point)
return false;
7356 const double tick = staged_.syminfo.mintick;
7358 && finite_positive(coof_fill_target_print_))
7359 return source_same_point(coof_fill_target_print_, waypoint, tick);
7360 if (source_same_point(point->price, waypoint, tick))
return true;
7361 return finite_positive(tick) && source_bar_fill_tick(waypoint, tick) != waypoint
7362 && source_decimal_tick(point->price, tick) == source_decimal_tick(waypoint, tick)
7363 && coof_fill_on_path_point();
7373bool PineExecutionAdapter::coof_waits_for_next_sub_bar() const noexcept {
7374 const auto* pine_host = pine_view_of(host_);
7375 if (!pine_host)
return false;
7376 const auto next = pine_host->scheduler_.next_input_waypoint(*pine_host, coof_context_);
7377 return next && next->next_open;
7384bool PineExecutionAdapter::source_path_uses_high_first(
const Bar& bar)
const noexcept {
7385 auto* pine = pine_view_of(host_);
7389bool PineExecutionAdapter::coof_current_fill_was_forced_waypoint() const noexcept {
7394 if (!coof_recalc_active_)
return false;
7396 if (!point)
return false;
7397 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
7398 for (
const auto& opening :
cohort.second.opened) {
7399 const auto units =
cohort.second.live_units_by_origin.find(
7400 opening.incarnation);
7401 if (units ==
cohort.second.live_units_by_origin.end()
7402 || !(units->second > 0.0)) {
7405 const auto snapshot = placement_.find(opening.incarnation);
7406 if (snapshot != placement_.end() &&
snapshot->second.opening
7407 &&
snapshot->second.projection_created_during_coof
7408 &&
snapshot->second.placement_script_open_ms
7409 == coof_context_.script_bar_open_ms
7410 && same_double_bits(
snapshot->second.forced_execution_price,
7419double PineExecutionAdapter::coof_next_waypoint(
int* path_index)
const noexcept {
7423 if (path_index) *path_index = -1;
7424 if (!coof_recalc_active_ || !coof_script_bar_valid_)
return kNaN;
7426 if (state.spec && state.spec->intrabar.lower()) {
7427 if (
const auto* pine_host = pine_view_of(&require_host())) {
7428 const auto next = pine_host->scheduler_.next_input_waypoint(
7429 *pine_host, coof_context_);
7430 if (next)
return next->price;
7433 const bool high_first = source_path_uses_high_first(coof_script_bar_);
7435 NativePathPhase::Open,
7436 high_first ? NativePathPhase::High : NativePathPhase::Low,
7437 high_first ? NativePathPhase::Low : NativePathPhase::High,
7438 NativePathPhase::Close,
7440 const double path_price[] = {
7441 coof_script_bar_.open,
7442 high_first ? coof_script_bar_.high : coof_script_bar_.low,
7443 high_first ? coof_script_bar_.low : coof_script_bar_.high,
7444 coof_script_bar_.close,
7446 const bool forced_waypoint = coof_current_fill_was_forced_waypoint();
7447 for (
int index = 0; index < 4; ++index) {
7448 if (path_phase[index] != coof_context_.coordinate.path_phase)
continue;
7459 const double tick = staged_.syminfo.mintick;
7460 const bool on_grid_waypoint = finite_positive(tick)
7461 && source_bar_fill_tick(path_price[index], tick) == path_price[index];
7463 && finite_positive(coof_fill_target_print_)
7464 ? source_same_point(coof_fill_target_print_, path_price[index], tick)
7465 : point && finite_positive(tick)
7466 ? source_decimal_tick(point->
price, tick)
7467 == source_decimal_tick(path_price[index], tick)
7468 && (on_grid_waypoint || coof_fill_on_path_point())
7469 : point && point->
price == path_price[index];
7470 if (index > 0 && point && finite_positive(point->price)
7471 && !at_waypoint && !forced_waypoint) {
7472 if (path_index) *path_index = index;
7473 return path_price[index];
7482 if (index > 0 && index < 3 && at_waypoint && !forced_waypoint
7483 && !coof_fill_forced_) {
7484 if (path_index) *path_index = index;
7485 return path_price[index];
7487 if (index == 0 && coof_open_stop_next_waypoint_
7488 && (finite_positive(tick)
7489 ? source_decimal_tick(path_price[1], tick)
7490 == source_decimal_tick(path_price[0], tick)
7491 : path_price[1] == path_price[0])) {
7492 if (path_index) *path_index = 2;
7493 return path_price[2];
7495 if (path_index && index < 3) *path_index = index + 1;
7496 return index < 3 ? path_price[index + 1] : kNaN;
7501bool PineExecutionAdapter::coof_remaining_recrosses(
7502 double level,
bool long_position)
const noexcept {
7503 if (!finite_positive(level) || !coof_recalc_active_ || !coof_script_bar_valid_)
7505 const bool high_first = source_path_uses_high_first(coof_script_bar_);
7507 NativePathPhase::Open,
7508 high_first ? NativePathPhase::High : NativePathPhase::Low,
7509 high_first ? NativePathPhase::Low : NativePathPhase::High,
7510 NativePathPhase::Close,
7512 const double path_price[] = {
7513 coof_script_bar_.open,
7514 high_first ? coof_script_bar_.high : coof_script_bar_.low,
7515 high_first ? coof_script_bar_.low : coof_script_bar_.high,
7516 coof_script_bar_.close,
7518 const bool forced_waypoint = coof_current_fill_was_forced_waypoint();
7519 for (
int index = 0; index < 4; ++index) {
7520 if (path_phase[index] != coof_context_.coordinate.path_phase)
continue;
7522 int first = index + 1;
7523 if (index > 0 && point && finite_positive(point->price)
7524 && !source_same_point(point->price, path_price[index], staged_.syminfo.mintick)
7525 && !forced_waypoint) {
7528 bool crossed_adverse =
false;
7529 for (
int cursor = first; cursor < 4; ++cursor) {
7530 if (long_position) {
7531 if (path_price[cursor] < level) crossed_adverse =
true;
7532 else if (crossed_adverse && path_price[cursor] >= level)
return true;
7534 if (path_price[cursor] > level) crossed_adverse =
true;
7535 else if (crossed_adverse && path_price[cursor] <= level)
return true;
7549bool PineExecutionAdapter::coof_leg_reaches_exit(
7551 if (!coof_recalc_active_ || coof_first_open_ || !coof_script_bar_valid_)
return false;
7556 const double leg_end = coof_next_waypoint();
7557 if (!point || !finite_positive(point->price) || units == 0.0 || !finite_positive(leg_end))
7559 const double price = point->price;
7560 const bool closing_long = units > 0.0;
7565 ? row.exit_levels.limit : row.exit_levels.stop;
7566 if (!finite_positive(level))
return false;
7567 return above ? level >
price && level <= leg_end : level < price && level >= leg_end;
7570void PineExecutionAdapter::flush_coof_tail(
7571 bool openings_only,
bool include_next_open) {
7572 auto queued = std::move(pending_write(pending_coof_requests_));
7573 pending_write(pending_coof_requests_).clear();
7574 for (
auto& pending : queued) {
7575 if ((
pending.next_open && !include_next_open)
7576 || (openings_only && (!
pending.opening
7577 || std::holds_alternative<native_order::Market>(
7579 pending_write(pending_coof_requests_).push_back(std::move(pending));
7582 const auto accepted = submit_or_replace(std::move(
pending.request), std::move(
pending.snapshot),
7584 if (accepted &&
pending.family_key != 0)
7585 bracket_family(
pending.family_key).push_back(*accepted);
7589std::size_t PineExecutionAdapter::accepted_entries_in_cycle(
7590 bool is_long,
const SourceId&
id)
const {
7591 std::size_t accepted_in_cycle = 0;
7592 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
7593 for (
const auto& origin :
cohort.second.opened) {
7594 const auto placement = placement_.find(origin.incarnation);
7595 if (placement != placement_.end() &&
placement->second.is_long == is_long)
7596 ++accepted_in_cycle;
7599 for (
const auto& handle : live_handles_) {
7600 const auto placement = placement_.find(handle.incarnation);
7605 if (placement == placement_.end() || !
placement->second.opening
7606 ||
placement->second.is_long != is_long)
continue;
7608 ++accepted_in_cycle;
7614 const auto cohort = cohorts_by_id_.find_resident(
placement->second.source_id);
7615 if (cohort != cohorts_by_id_.end()
7616 && std::find(
cohort->second.opened.begin(),
cohort->second.opened.end(), handle)
7617 !=
cohort->second.opened.end())
continue;
7619 std::uint64_t incarnation;
7620 bool settled =
false;
7621 } opening{handle.incarnation};
7622 (void)require_host().visit_native_open_lot_values(
7623 &opening, [](
void* context, std::uint64_t origin,
double,
double units) {
7624 auto& opening = *
static_cast<Opening*
>(context);
7625 if (origin != opening.incarnation || units == 0.0)
return true;
7626 opening.settled =
true;
7629 if (opening.settled) ++accepted_in_cycle;
7631 return accepted_in_cycle;
7634bool PineExecutionAdapter::opposite_entry_opening_pending(
bool is_long)
const {
7635 return std::any_of(live_handles_.begin(), live_handles_.end(),
7636 [&](
const native_order::RequestHandle& handle) {
7637 const auto existing = placement_.find(handle.incarnation);
7638 return existing != placement_.end() && existing->second.opening
7639 && existing->second.family == PineOrderFamily::Entry
7640 && existing->second.is_long != is_long;
7645 double stop_price,
double qty,
const std::string& comment,
7646 const std::string& oca_name,
int oca_type,
int qty_type) {
7647 place_entry(
id, is_long, limit_price, stop_price, qty, comment, oca_name, oca_type, qty_type);
7648 if (
PF_RARE(!id_bound_exits_.empty())) rearm_id_bound_exits(
id);
7651void PineExecutionAdapter::place_entry(
const SourceId&
id,
bool is_long,
double limit_price,
7652 double stop_price,
double qty,
const std::string& comment,
7653 const std::string& oca_name,
int oca_type,
int qty_type) {
7654 require_cohort_ticket(&
id);
7659 fold_source_money();
7660 const bool default_sized = !std::isfinite(qty);
7661 const bool priced = !std::isnan(limit_price) || !std::isnan(stop_price);
7662 const bool explicit_fixed = !default_sized
7663 && (qty_type < 0 || qty_type == static_cast<int>(
QtyType::FIXED));
7664 const double normalized_qty = explicit_fixed
7665 ? explicit_entry_quantity_floor(std::abs(qty), staged_.
quantity_grid) : qty;
7666 const double signed_target = is_long ? normalized_qty : -normalized_qty;
7669 double flat_pending_opposite_market_units = 0.0;
7671 &&
detail::run_state(require_host()).kind == NativeLifecycleKind::Unconfigured) {
7676 request.intent = default_sized
7678 native_order::HostSizedKind::Open,
7679 is_long ? native_order::Side::Long : native_order::Side::Short}}
7683 request.trigger = trigger_for(limit_price, stop_price);
7684 request.group = group_for(oca_name, oca_type);
7692 snapshot.requested_qty = normalized_qty;
7695 snapshot.deferred_cohort = default_sized;
7696 snapshot.command_ordinal = ++command_ordinal_;
7697 snapshot.command_sequence = ++source_command_sequence_;
7698 snapshot.source_sequence = ++source_sequence_;
7699 snapshot.exit_levels.limit = limit_price;
7700 snapshot.exit_levels.stop = stop_price;
7701 snapshot.sizing = sizing_snapshot();
7702 pending_write(pending_entries_).push_back(
7703 {std::move(request), std::move(snapshot),
id});
7707 const int bar = source_point->decision.coordinate.interval_index;
7708 if (entry_attempt_bar_ != bar) {
7709 entry_attempt_bar_ = bar;
7710 entry_attempts_on_bar_ = 0;
7712 if (entry_attempts_on_bar_ != std::numeric_limits<std::uint32_t>::max())
7713 ++entry_attempts_on_bar_;
7715 bool close_precedes_entry = pending_same_bar_close_qty_ > 0.0;
7716 const double preceding_close_qty = pending_same_bar_close_qty_;
7724 const bool close_first_fact = source_point && current != 0.0
7725 && ((current > 0.0) != is_long)
7726 && close_first_script_bar_ == source_point->decision.script_bar_open_ms
7727 && close_first_side_ == (current > 0.0 ? 1 : -1);
7728 const bool close_first = close_first_fact
7730 native_order::RequestHandle preceding_close_request{};
7732 for (
auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
7733 const auto& handle = *it;
7734 const auto prior = placement_.find(handle.incarnation);
7735 if (prior == placement_.end())
continue;
7736 const auto& row = prior->second;
7740 && row.placement_script_open_ms
7741 == source_point->decision.script_bar_open_ms) {
7742 close_precedes_entry =
true;
7743 preceding_close_request = handle;
7748 limit_price = source_level_on_price_grid(
7750 stop_price = source_level_on_price_grid(
7752 const bool pure_stop_entry = std::isnan(limit_price)
7753 && finite_positive(stop_price);
7754 if (intraday_loss_orders_blocked()
7755 || (source_point && cap_placement_denied(source_point->decision))) {
7758 if (pure_stop_entry && preceding_close_qty > 0.0
7759 && !pending_same_bar_commands_.empty()) {
7763 flush_pending_same_bar_commands();
7765 for (
auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
7766 const auto prior = placement_.find(it->incarnation);
7767 if (prior == placement_.end()
7769 || prior->second.placement_script_open_ms
7770 != source_point->decision.script_bar_open_ms) {
7773 preceding_close_request = *it;
7778 if (config_.calc_on_order_fills && priced && !coof_recalc_active_
7779 && source_point && current != 0.0 && ((current > 0.0) == is_long)) {
7780 std::vector<native_order::RequestHandle> stale_recalc_entries;
7781 for (
const auto& handle : live_handles_) {
7782 const auto prior = placement_.find(handle.incarnation);
7783 if (prior == placement_.end())
continue;
7784 const auto& row = prior->second;
7786 && row.is_long == is_long && row.projection_created_during_coof
7787 && row.projection_created_bar
7788 == source_point->decision.coordinate.interval_index) {
7789 stale_recalc_entries.push_back(handle);
7792 for (
const auto& handle : stale_recalc_entries) {
7793 const auto cancelled = require_host().cancel(handle);
7794 if (
cancelled.status == native_order::CancelStatus::Cancelled)
7802 if (!default_sized) {
7803 const bool opposite_live = current != 0.0 && ((current > 0.0) != is_long);
7804 if (!opposite_live) {
7807 const double mark = source_point ? source_point->price : kNaN;
7808 const double margin = is_long ? config_.margin_long : config_.margin_short;
7809 const double fx = source_point
7810 ? active_staged_fx(source_point->decision.sub_bar_open_ms) : staged_.account_fx;
7811 const double equity = source_point
7812 ? require_host().native_marked_equity(source_point->price) : kNaN;
7817 double units = std::abs(normalized_qty);
7819 units = typed_quantity_units(std::abs(qty),
false, mark, fx);
7821 units = typed_quantity_units(
7822 percent_commission_live_equity(mark) * std::abs(qty) / 100.0,
true, mark, fx);
7832 if (current == 0.0 && explicit_fixed && source_point
7833 && same_point_pair_scope() && !(config_.pyramiding == 2 && !priced)
7834 && (config_.pyramiding <= 1 || same_point_two_leg_book(
7835 id, source_point->decision.script_bar_open_ms))) {
7836 const double pending = pending_opposite_market_units(
7837 is_long, source_point->decision.script_bar_open_ms,
id);
7838 if (std::isfinite(pending)) units +=
pending;
7847 const double admission_mark = !priced && current == 0.0 && is_long
7848 && explicit_fixed && config_.slippage > 0 && finite_positive(mark)
7849 && !config_.process_orders_on_close
7850 ? source_bar_fill_tick(mark, staged_.syminfo.mintick)
7851 + config_.slippage * staged_.syminfo.mintick
7853 const double required = units * admission_mark
7854 * staged_.syminfo.pointvalue * fx * margin / 100.0;
7858 const bool finite_explicit_requirement = std::isfinite(units)
7859 && std::isfinite(admission_mark) && std::isfinite(staged_.syminfo.pointvalue)
7860 && std::isfinite(fx);
7861 if (finite_positive(margin) && margin <= 100.0
7862 && ((!std::isfinite(required) && !finite_explicit_requirement)
7863 || !std::isfinite(equity))) {
7864 engine_command_value_invalid_or_count(
"margin.required", EngineValueStage::margin,
7865 EngineValueReason::nonfinite);
7880 && current == 0.0 && explicit_fixed && margin == 100.0
7881 && (!priced || (pure_stop_entry && config_.process_orders_on_close))
7882 && units == std::abs(normalized_qty) && finite_positive(units)
7883 && finite_positive(mark) && finite_positive(staged_.syminfo.mintick)
7884 && staged_.syminfo.pointvalue == 1.0 && fx == 1.0
7885 && staged_.account_fx_effective_from_ms.empty() && std::isfinite(equity);
7886 if (
PF_RARE(slipped_signal_scope)) {
7887 const double tick = staged_.syminfo.mintick;
7888 const double ticks = std::floor(mark / tick + 0.5);
7889 const double signal_close = ticks * tick;
7890 const double slipped = (ticks + (is_long ? 1.0 : -1.0) * config_.slippage) * tick;
7891 const double mirrored = gain_loss_signal_equity(nearest_tick(mark, tick));
7892 const double signal_equity = std::isfinite(mirrored) ? mirrored : equity;
7893 const double per_unit = source_money_round(
7894 source_money_round(signal_equity) / units);
7895 if (!(signal_equity < source_money_round(units * signal_close))
7896 && !(per_unit < slipped)) {
7899 if (pure_stop_entry) {
7900 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
7901 prior != live_by_source_key_.end()) {
7902 const auto prior_handle = prior->second;
7903 const auto result = require_host().cancel(prior_handle);
7904 if (result.status == native_order::CancelStatus::Cancelled)
7905 retire(prior_handle);
7918 if (!slipped_signal_scope && finite_positive(margin) && margin <= 100.0
7919 && (!std::isfinite(required) || !std::isfinite(equity)
7920 || required > equity)) {
7921 if (pure_stop_entry) {
7922 std::optional<native_order::RequestHandle> prior_handle;
7923 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
7924 prior != live_by_source_key_.end()) {
7925 prior_handle = prior->second;
7928 const auto result = require_host().cancel(*prior_handle);
7929 if (result.status == native_order::CancelStatus::Cancelled)
7930 retire(*prior_handle);
7950 bool signal_price_refused =
false;
7951 if (
PF_RARE(current != 0.0 && ((current > 0.0) != is_long) && !close_first && !priced
7955 && config_.default_qty_value == 100.0
7957 && finite_positive(normalized_qty))
7958 && (is_long ? config_.margin_long : config_.margin_short) == 100.0
7959 && config_.commission_value == 0.0 && config_.pyramiding >= 0 && config_.pyramiding <= 1
7960 && staged_.syminfo.pointvalue == 1.0 && staged_.account_fx == 1.0
7961 && staged_.account_fx_effective_from_ms.empty() && oca_name.empty()
7962 && finite_positive(staged_.syminfo.mintick) && !
cap.active()
7963 && !(risk_.max_intraday_loss > 0.0) && !(risk_.max_drawdown > 0.0)
7964 && risk_.max_cons_loss_days <= 0 && !(risk_.max_position_size > 0.0)
7965 && pinned_lot_grid() && !config_.calc_on_order_fills && !bar_magnifier_
7966 && unified_placement_book_clear())) {
7968 auto sizing = sizing_snapshot();
7969 double units = std::abs(normalized_qty);
7970 if (default_sized) {
7972 if (finite_positive(sizing.
mark)) {
7973 sizing.
price = default_market_sizing_price(sizing.
mark, is_long);
7974#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
7975 sizing.
price = fixture_adapter_execution_value(
"slippage.default.signal", sizing.
price);
7977 check_slippage_fill_price(sizing.
price, std::isfinite(sizing.
mark));
7978 mark_sizing_equity(sizing, sizing.
mark);
7979 units = default_sizing_units(sizing);
7982 if (finite_positive(units) && std::isfinite(sizing.
equity)
7983 && finite_positive(sizing.
mark)) {
7984 const double tick = staged_.syminfo.mintick;
7985 const double ticks = std::floor(sizing.
mark / tick + 0.5);
7986 const bool money_short = sizing.
equity < source_money_round(units * (ticks * tick));
7987 const bool price_short = source_money_round(source_money_round(sizing.
equity) / units)
7988 < (ticks + (is_long ? 1.0 : -1.0) * config_.slippage) * tick;
7989 signal_price_refused = !money_short && price_short;
7992 if (explicit_fixed && normalized_qty == 0.0 && current == 0.0 && !priced) {
7993 PlacementSnapshot shadow;
7995 shadow.source_id = id;
7996 shadow.comment = comment;
7997 shadow.oca_name = oca_name;
7998 shadow.oca_type = oca_type;
7999 shadow.qty_type = qty_type;
8000 shadow.requested_qty = 0.0;
8001 shadow.is_long = is_long;
8002 shadow.opening =
true;
8003 shadow.command_ordinal = ++command_ordinal_;
8004 shadow.command_sequence = ++source_command_sequence_;
8005 shadow.source_sequence = ++source_sequence_;
8006 shadow.projection_created_bar = source_point
8008 shadow.sizing = sizing_snapshot();
8009 pending_write(source_shadow_pending_).push_back({std::move(shadow),
id});
8012 if (current == 0.0 && priced && explicit_fixed) {
8013 for (
const auto& pending : pending_same_bar_commands_) {
8014 const auto& candidate =
pending.snapshot;
8016 || candidate.is_long == is_long
8017 || !candidate.frozen_market_instruction
8018 || !finite_positive(candidate.frozen_market_own_units)) {
8021 flat_pending_opposite_market_units += candidate.frozen_market_own_units;
8023 if (flat_pending_opposite_market_units > 0.0
8024 && pending_same_bar_commands_.size() == 1U)
8025 flush_pending_same_bar_commands(
true);
8026 else if (pending_same_bar_commands_.size() != 1U)
8027 flat_pending_opposite_market_units = 0.0;
8032 if (has_live_exit_policy())
for (
const auto& handle : live_handles_) {
8033 const auto existing = placement_.find(handle.incarnation);
8034 if (existing != placement_.end()
8035 && existing->second.pooc_global_full_exit_dynamic_qty) {
8036 existing->second.pooc_global_full_exit_dynamic_qty =
false;
8037 existing->second.pooc_global_full_exit_tracks_bound_adds =
false;
8040 const bool short_seed_final_candidate = current < 0.0 && !is_long
8041 && short_seed_long_candidate_.incarnation != 0;
8042 const bool opposite_opening_pending = opposite_entry_opening_pending(is_long);
8054 const bool opposite_opening_in_close_pass = std::any_of(
8055 live_handles_.begin(), live_handles_.end(),
8056 [&](
const native_order::RequestHandle& handle) {
8057 const auto existing = placement_.find(handle.incarnation);
8058 if (existing == placement_.end()) return false;
8059 const auto& row = existing->second;
8060 if (!row.opening || row.family != PineOrderFamily::Entry
8061 || row.is_long == is_long) {
8064 const bool market = !finite_positive(row.exit_levels.limit)
8065 && !finite_positive(row.exit_levels.stop);
8068 && row.placement_script_open_ms == source_point->decision.script_bar_open_ms);
8073 const bool p2_flat_market_candidate = current == 0.0
8074 && config_.pyramiding == 2 && !config_.process_orders_on_close
8075 && !config_.calc_on_order_fills && !coof_recalc_active_
8077 && config_.slippage == 0
8078 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
8079 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
8080 && risk_.max_position_size <= 0.0 && !risk_.halted && !cap.active()
8081 && explicit_fixed && !priced && oca_name.empty();
8082 if (std::any_of(pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
8083 [&](
const PendingSameBarCommand& command) {
8084 return command.unbatched_request && command.replacement_key == id;
8086 reroute_fixed_entries_before_request();
8088 const bool fixed_market_batch_candidate = fixed_unmargined_market_batch_scope()
8089 && !source_batch_mutated_
8090 && std::none_of(live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
8091 const auto prior = placement_.find(handle.incarnation);
8092 return prior != placement_.end() && prior->second.opening;
8094 && std::none_of(pending_entries_.begin(), pending_entries_.end(),
8095 [](
const auto& pending) { return pending.snapshot.opening; })
8096 && std::none_of(delayed_market_orders_.begin(), delayed_market_orders_.end(),
8097 [](
const auto& pending) { return pending.snapshot.opening; })
8098 && !close_precedes_entry && pending_same_bar_close_qty_ == 0.0
8099 && (!source_point || close_all_pending_script_bar_
8100 != source_point->decision.script_bar_open_ms);
8101 const bool same_bar_market_candidate = (same_bar_market_tx_scope()
8102 || p2_flat_market_candidate || fixed_market_batch_candidate)
8103 && !priced && oca_name.empty()
8104 && (qty_type < 0 || qty_type == static_cast<int>(
QtyType::FIXED))
8105 && (default_sized || finite_positive(qty));
8106 if (!same_bar_market_candidate) reroute_fixed_entries_before_request();
8107 if (!same_bar_market_candidate && config_.pyramiding == 2
8108 && !pending_same_bar_commands_.empty()) {
8109 source_batch_mutated_ =
true;
8110 flush_pending_same_bar_commands();
8112 const bool all_in_percent = default_sized && !priced && oca_name.empty()
8114 && config_.default_qty_value >= 100.0;
8120 const bool default_gross_over_cap_candidate = all_in_percent
8121 && std::abs(config_.default_qty_value - 100.0) < 1e-12
8122 && config_.pyramiding == 1 && !config_.process_orders_on_close
8123 && !config_.calc_on_order_fills && config_.slippage == 0
8124 && config_.commission_value == 0.0
8125 && std::abs(config_.margin_long - 100.0) < 1e-12
8126 && std::abs(config_.margin_short - 100.0) < 1e-12
8127 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
8128 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
8129 && risk_.max_position_size <= 0.0 && !risk_.halted && !cap.active();
8130 bool paired_all_in_reentry =
false;
8131 if (all_in_percent && current != 0.0 && ((current > 0.0) == is_long) && source_point) {
8132 for (
const auto& handle : live_handles_) {
8133 const auto prior = placement_.find(handle.incarnation);
8134 if (prior == placement_.end() || !prior->second.opening
8136 || prior->second.is_long == is_long) {
8139 paired_all_in_reentry =
true;
8148 const bool pooc_pass = config_.process_orders_on_close
8149 && !config_.calc_on_order_fills && !coof_recalc_active_;
8150 const bool pooc_pass_flattens = pooc_pass && close_precedes_entry && current != 0.0
8151 && preceding_close_qty + internal::kQtyEpsilon >= std::abs(current);
8152 if (!same_bar_market_candidate && current != 0.0
8153 && ((current > 0.0) == is_long) && config_.pyramiding == 0
8154 && !(priced && config_.process_orders_on_close) && !pooc_pass_flattens) {
8159 if (source_point) observe_intraday_cap_noop(is_long, source_point->decision);
8162 if (!same_bar_market_candidate && config_.pyramiding > 0 && current != 0.0
8163 && ((current > 0.0) == is_long)
8164 && !(priced && config_.process_orders_on_close
8165 && !coof_recalc_active_)) {
8166 auto accepted_in_cycle = accepted_entries_in_cycle(is_long,
id);
8170 if (
const auto records = open_ledger_records();
PF_RARE(records.has_value())) {
8172 for (
const auto& handle : live_handles_) {
8173 const auto placement = placement_.find(handle.incarnation);
8174 if (placement != placement_.end() &&
placement->second.opening
8177 && !pure_limit_entry(
placement->second)) {
8181 accepted_in_cycle = *records +
pending;
8185 if (!(config_.process_orders_on_close && close_precedes_entry)
8186 && accepted_in_cycle >=
static_cast<std::size_t
>(config_.pyramiding)
8187 && !short_seed_final_candidate && !paired_all_in_reentry
8188 && !default_gross_over_cap_candidate
8189 && !(config_.process_orders_on_close && opposite_opening_pending
8190 && (opposite_opening_in_close_pass
8192 <
static_cast<std::size_t
>(config_.pyramiding)))) {
8198 const auto prior = live_by_source_key_.find(checked_live_key(
id));
8199 if (prior != live_by_source_key_.end()) {
8200 const auto handle = prior->second;
8201 const auto result = require_host().cancel(handle);
8202 if (result.status == native_order::CancelStatus::Cancelled)
8209 const auto current_point = source_point;
8210 const bool close_all_precedes = current_point
8211 && close_all_pending_script_bar_ == current_point->decision.script_bar_open_ms;
8212 native_order::RequestHandle preceding_close_all{};
8213 const bool reverses = current != 0.0 && ((current > 0.0) != is_long) && !close_all_precedes;
8216 && reverses && !priced && config_.process_orders_on_close
8217 && !close_batch_callsites_.empty()) {
8221 close_batch_callsites_.clear();
8222 close_batch_bar_ = -1;
8223 close_batch_queue_sequence_ = 0;
8224 close_batch_pending_debt_ = 0.0;
8225 close_batch_admitted_total_ = 0.0;
8226 pending_same_bar_close_qty_ = 0.0;
8227 close_precedes_entry =
false;
8233 const bool affordability_reversal_candidate = reverses && !priced
8236 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
8238 && config_.default_qty_value > 100.0))
8244 const bool direction_blocked = (risk_.direction > 0 && !is_long)
8245 || (risk_.direction < 0 && is_long);
8246 if (default_sized && reverses && current_point) {
8247 for (
const auto& handle : live_handles_) {
8248 const auto pending = placement_.find(handle.incarnation);
8249 if (pending == placement_.end())
continue;
8250 const auto& prior =
pending->second;
8252 && !prior.is_long && prior.is_long == is_long && prior.replaced_opening
8253 && prior.replacement_predecessor_market
8254 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
8261 if (default_sized && reverses && is_long && current_point) {
8262 std::vector<native_order::RequestHandle> superseded_buy_replacements;
8263 for (
const auto& handle : live_handles_) {
8264 const auto pending = placement_.find(handle.incarnation);
8265 if (pending == placement_.end())
continue;
8266 const auto& prior =
pending->second;
8268 && prior.replaced_opening && prior.replacement_predecessor_market
8269 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
8270 superseded_buy_replacements.push_back(handle);
8273 for (
const auto& handle : superseded_buy_replacements) {
8274 cancel_bracket_origin(handle);
8275 const auto result = require_host().cancel(handle);
8276 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
8279 const bool cash_sized = qty_type ==
static_cast<int>(
QtyType::CASH);
8281 const bool typed_sized = !default_sized && (cash_sized || percent_sized);
8282 const bool fixed_priced_reverse = reverses && !default_sized && priced && !cash_sized;
8283 const bool cash_priced_reverse = reverses && !default_sized && priced && cash_sized;
8288 const bool default_stop_freeze = default_sized && pure_stop_entry
8289 && finite_positive(stop_price)
8291 const bool default_stop_scope = default_stop_freeze
8292 && config_.default_qty_value <= 100.0;
8293 double default_stop_sizing_price = kNaN;
8294 if (default_stop_freeze && finite_positive(staged_.syminfo.mintick)) {
8295 stop_price = directional_tick(
8297 const double signal = source_point
8298 ? nearest_tick(source_point->price, staged_.syminfo.mintick) : kNaN;
8299 if (config_.process_orders_on_close && source_level_on_price_grid(stop_price, staged_.syminfo.mintick) == source_level_on_price_grid(signal, staged_.syminfo.mintick)) {
8300 stop_price = source_level_on_price_grid(
8301 stop_price, staged_.syminfo.mintick);
8303 const bool marketable = finite_positive(signal)
8304 && (is_long ? stop_price <= signal : stop_price >= signal);
8306 const double slip = (is_long ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
8307 default_stop_sizing_price = (marketable ? signal : stop_price) + slip;
8308#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
8309 default_stop_sizing_price = fixture_adapter_execution_value(
8310 "slippage.default.stop", default_stop_sizing_price);
8312 check_slippage_fill_price(default_stop_sizing_price,
8313 std::isfinite(marketable ? signal : stop_price));
8315 if (default_sized || typed_sized || direction_blocked || affordability_reversal_candidate) {
8316 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
8317 is_long ? native_order::Side::Long : native_order::Side::Short};
8318 }
else if (fixed_priced_reverse || cash_priced_reverse) {
8319 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
8320 is_long ? native_order::Side::Long : native_order::Side::Short};
8321 }
else if (reverses) {
8322 request.intent = native_order::ReverseTo{signed_target};
8326 if (flat_pending_opposite_market_units > 0.0) {
8327 const double transaction = normalized_qty + flat_pending_opposite_market_units;
8333 if (!priced && !same_bar_market_candidate && current == 0.0 && explicit_fixed
8334 && finite_positive(qty) && oca_name.empty() && source_point
8335 && same_point_pair_scope()
8336 && (config_.pyramiding <= 1
8337 || same_point_two_leg_book(
id, source_point->decision.script_bar_open_ms))) {
8338 const double pending = pending_opposite_market_units(
8339 is_long, source_point->decision.script_bar_open_ms,
id);
8340 if (finite_positive(pending)) {
8341 const double transaction = normalized_qty +
pending;
8347 const bool coof_lower_path = coof_native_state.spec
8348 && coof_native_state.spec->intrabar.lower();
8357 bool coof_market_next_open =
false;
8358 bool coof_market_at_second_extreme =
false;
8359 double coof_second_extreme = kNaN;
8360 if (coof_recalc_active_ && !coof_first_open_ && !coof_lower_path && !priced
8361 && coof_fill_at_second_extreme()) {
8362 coof_second_extreme = source_path_uses_high_first(coof_script_bar_)
8363 ? coof_script_bar_.low : coof_script_bar_.high;
8364 coof_market_next_open = coof_fill_forced_;
8365 coof_market_at_second_extreme = !coof_fill_forced_;
8367 const bool coof_market_next_sub_bar = coof_recalc_active_ && !coof_first_open_
8368 && coof_lower_path && !priced && coof_waits_for_next_sub_bar();
8369 double native_limit = limit_price;
8370 double native_stop = stop_price;
8371 if (finite_positive(limit_price) && !finite_positive(stop_price)) {
8376 native_limit = source_trigger_threshold(
8378 }
else if (finite_positive(stop_price) && !finite_positive(limit_price)
8379 && !config_.calc_on_order_fills) {
8380 native_stop = source_trigger_threshold(
8383 const auto* pine_host = pine_view_of(&require_host());
8384 const bool nonpositive_priced = priced && pine_host
8385 && pine_host->scheduler_uses_aux_security_feed()
8386 && !finite_positive(native_limit) && !finite_positive(native_stop);
8387 request.trigger = nonpositive_priced
8389 is_long ? std::numeric_limits<double>::min()
8390 : std::numeric_limits<double>::max()}}
8391 : trigger_for(native_limit, native_stop);
8392 double coof_market_fill = kNaN;
8393 double coof_market_target = kNaN;
8394 if (coof_recalc_active_ && !coof_first_open_ && !coof_market_next_open
8395 && !coof_market_next_sub_bar && coof_script_bar_valid_
8396 && std::holds_alternative<native_order::Market>(
request.trigger)
8397 && (coof_market_at_second_extreme || !defer_coof_tail())) {
8403 int next_extreme_index = -1;
8404 const double next_extreme = coof_market_at_second_extreme
8405 ? coof_second_extreme : coof_next_waypoint(&next_extreme_index);
8406 if (coof_market_at_second_extreme) next_extreme_index = 2;
8407 coof_market_target = next_extreme;
8409 const double current_quote = point ? point->price : kNaN;
8410 coof_market_fill = source_bar_fill_tick(
8411 next_extreme, staged_.syminfo.mintick)
8412 + (is_long ? 1.0 : -1.0) * config_.slippage
8413 * staged_.syminfo.mintick;
8414#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
8415 coof_market_fill = fixture_adapter_execution_value(
"slippage.recalc.entry", coof_market_fill);
8417 check_slippage_fill_price(coof_market_fill, std::isfinite(next_extreme));
8418 if (finite_positive(coof_market_fill) && finite_positive(current_quote)
8419 && !source_same_point(current_quote, coof_market_fill, staged_.syminfo.mintick)) {
8420 const bool falling = coof_market_fill < current_quote;
8427 const bool extreme_target = next_extreme_index == 1
8428 || next_extreme_index == 2;
8429 const native_order::Limit
limit = extreme_target
8430 ? native_order::Limit{next_extreme, coof_market_fill != next_extreme}
8431 : native_order::Limit{coof_market_fill};
8445 coof_market_fill = nearest_tick(next_extreme, staged_.syminfo.mintick)
8446 + (is_long ? 1.0 : -1.0) * config_.slippage
8447 * staged_.syminfo.mintick;
8448#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
8449 coof_market_fill = fixture_adapter_execution_value(
"slippage.recalc.entry_tick", coof_market_fill);
8451 check_slippage_fill_price(coof_market_fill, std::isfinite(next_extreme));
8453 if (pure_stop_entry && explicit_fixed && current != 0.0
8454 && ((current > 0.0) != is_long) && source_point
8455 && !config_.process_orders_on_close && !config_.calc_on_order_fills) {
8456 auto* pine_host = pine_view_of(&require_host());
8457 const auto next = pine_host
8458 ? pine_host->scheduler_.next_source_bar(
8459 source_point->decision.coordinate.interval_index)
8460 : std::optional<Bar>{};
8462 for (
const auto& handle : live_handles_) {
8463 const auto found = placement_.find(handle.incarnation);
8464 if (found == placement_.end())
continue;
8465 const auto& prior = found->second;
8466 const bool current_exit_limit =
8468 && prior.projection_created_bar
8469 == source_point->decision.coordinate.interval_index
8470 && finite_positive(prior.exit_levels.limit);
8471 const bool both_reached = current > 0.0
8472 ? next->high >= prior.exit_levels.limit && next->low <= stop_price
8473 : next->low <= prior.exit_levels.limit && next->high >= stop_price;
8474 if (!current_exit_limit || !both_reached)
continue;
8476 request.owner = native_order::WaitForApplied{handle};
8481 request.group = group_for(oca_name, oca_type);
8482 if (close_all_precedes && !priced && current_point
8483 && !config_.process_orders_on_close) {
8484 for (
auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
8485 const auto close = placement_.find(it->incarnation);
8486 if (close == placement_.end()
8488 || close->second.placement_script_open_ms
8489 != current_point->decision.script_bar_open_ms) {
8492 preceding_close_all = *it;
8496 const bool sequenced_close_entry = pure_stop_entry
8497 || (!priced && (!staged_.quantity_grid || *staged_.quantity_grid < 1.0));
8498 if (preceding_close_all.incarnation == 0 && sequenced_close_entry
8499 && current != 0.0 && ((current > 0.0) != is_long))
8500 preceding_close_all = preceding_close_request;
8509 snapshot.paired_reversal_parent = preceding_close_all;
8510 snapshot.command_ordinal = ++command_ordinal_;
8511 snapshot.direction_gate = direction_blocked;
8512 snapshot.deferred_cohort = default_sized;
8513 if (reverses && source_point) {
8514 for (
const auto& handle : live_handles_) {
8515 const auto prior = placement_.find(handle.incarnation);
8516 if (prior != placement_.end()
8518 && !prior->second.from_entry.empty()
8519 && prior->second.projection_created_bar
8520 == source_point->decision.coordinate.interval_index) {
8521 snapshot.projection_after_close =
true;
8526 if (flat_pending_opposite_market_units > 0.0) {
8527 snapshot.paired_flat_market_candidate =
true;
8528 snapshot.paired_flat_market_own_qty = normalized_qty;
8529 snapshot.paired_flat_market_transaction_qty =
8530 normalized_qty + flat_pending_opposite_market_units;
8532 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
8534 "Pine source command sequence exhausted");
8536 snapshot.command_sequence = ++source_command_sequence_;
8540 snapshot.exit_levels.limit = limit_price;
8541 snapshot.exit_levels.stop = stop_price;
8542 snapshot.birth = capture_order_birth();
8545 snapshot.reverse_to = reverses || paired_all_in_reentry;
8546 snapshot.projection_after_close = close_precedes_entry;
8547 snapshot.close_first_entry = close_first_fact;
8548 snapshot.signal_price_refused = signal_price_refused;
8549 snapshot.sizing = sizing_snapshot();
8550 if (finite_positive(coof_market_fill)) {
8551 snapshot.forced_execution_price = coof_market_fill;
8552 snapshot.forced_waypoint_print = coof_market_target;
8554 bool coof_priced_next_open =
false;
8555 if (coof_recalc_active_ && !coof_first_open_ && !coof_lower_path && priced) {
8557 const double birth = point ? point->price : kNaN;
8558 int waypoint_index = -1;
8559 const double waypoint = coof_next_waypoint(&waypoint_index);
8560 bool reached =
false;
8561 bool limit_route =
false;
8562 if (finite_positive(stop_price)) {
8563 reached = is_long ? (waypoint >= stop_price && birth < stop_price)
8564 : (waypoint <= stop_price && birth > stop_price);
8565 }
else if (finite_positive(limit_price)) {
8567 reached = is_long ? (waypoint <= limit_price && birth > limit_price)
8568 : (waypoint >= limit_price && birth < limit_price);
8571 const double slipped = waypoint + (limit_route ? 0.0
8572 : (is_long ? 1.0 : -1.0) * config_.slippage
8573 * staged_.syminfo.mintick);
8574 snapshot.forced_execution_price = nearest_tick(
8575 slipped, staged_.syminfo.mintick);
8593 const bool pure_limit = finite_positive(limit_price) && !finite_positive(stop_price);
8594 const bool pure_stop = finite_positive(stop_price) && !finite_positive(limit_price);
8595 const double level = pure_limit ? limit_price : stop_price;
8596 const auto executable_at = [&](
double price) {
8598 : (is_long ?
price >= level :
price <= level);
8600 const bool forced_first_extreme = coof_fill_forced_ && waypoint_index == 2
8601 && !coof_fill_at_second_extreme();
8602 if (!reached && (pure_limit || pure_stop) && !config_.process_orders_on_close
8603 && (waypoint_index == 1 || waypoint_index == 2) && finite_positive(waypoint)
8604 && finite_positive(birth)
8605 && (!coof_fill_on_path_point() || forced_first_extreme)
8606 && executable_at(birth) && executable_at(waypoint)) {
8607 const double tick = staged_.syminfo.mintick;
8608 const double booked = source_bar_fill_tick(waypoint, tick);
8609 const bool falling = waypoint < birth;
8610 request.trigger = is_long == falling
8613 snapshot.forced_execution_price = nearest_tick(waypoint
8614 + (pure_limit ? 0.0 : (is_long ? 1.0 : -1.0) * config_.slippage * tick),
8616 }
else if (!reached && (pure_limit || pure_stop) && !config_.process_orders_on_close
8617 && (waypoint_index == 1 || waypoint_index == 2) && finite_positive(waypoint)
8618 && finite_positive(birth)
8619 && (!coof_fill_on_path_point() || forced_first_extreme)
8620 && executable_at(birth) && !executable_at(waypoint)) {
8631 request.trigger = native_order::StopLimit{waypoint, limit_price};
8633 request.trigger = native_order::Trail{std::abs(stop_price - waypoint), waypoint};
8634 snapshot.forced_execution_price = nearest_tick(stop_price
8635 + (is_long ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick,
8636 staged_.syminfo.mintick);
8651 coof_priced_next_open = (pure_limit || pure_stop)
8652 && !config_.process_orders_on_close && coof_fill_forced_
8653 && coof_fill_at_second_extreme();
8664 if (coof_recalc_active_ && coof_lower_path && priced && !config_.process_orders_on_close) {
8665 const bool lower_limit = finite_positive(limit_price) && !finite_positive(stop_price);
8666 const bool lower_stop = finite_positive(stop_price) && !finite_positive(limit_price);
8668 const double lower_birth = lower_point ? lower_point->price : kNaN;
8669 std::optional<PineScheduler::InputWaypoint> lower_next;
8670 if (coof_first_open_) {
8671 if (finite_positive(lower_birth))
8673 }
else if (
const auto* lower_host = pine_view_of(&require_host())) {
8674 lower_next = lower_host->scheduler_.next_input_waypoint(*lower_host, coof_context_);
8681 if ((lower_limit || lower_stop) && lower_next && lower_next->next_open
8682 && coof_context_.is_terminal_sub_bar) {
8683 coof_priced_next_open =
true;
8684 }
else if ((lower_limit || lower_stop) && lower_next
8685 && finite_positive(lower_next->price) && finite_positive(lower_birth)) {
8686 const double level = lower_limit ? limit_price : stop_price;
8687 const double waypoint = lower_next->price;
8688 const double tick = staged_.syminfo.mintick;
8689 const auto executable_at = [&](
double price) {
8691 : (is_long ?
price >= level :
price <= level);
8693 const bool same_point = coof_first_open_ || lower_next->next_open
8694 || source_same_point(lower_birth, waypoint, tick);
8695 if (executable_at(waypoint)) {
8697 request.trigger = native_order::Market{};
8699 const double booked = source_bar_fill_tick(waypoint, tick);
8700 const bool falling = waypoint < lower_birth;
8701 request.trigger = is_long == falling
8705 snapshot.forced_execution_price = nearest_tick(waypoint
8706 + (lower_limit ? 0.0 : (is_long ? 1.0 : -1.0) * config_.slippage * tick),
8708 }
else if (lower_limit && !same_point && (waypoint > lower_birth) == is_long) {
8709 request.trigger = native_order::StopLimit{waypoint, level};
8713 if (current == 0.0 && priced && current_point) {
8714 const auto is_opposite_market_predecessor = [&](
const PlacementSnapshot& prior) {
8716 && prior.is_long != is_long
8717 && prior.placement_script_open_ms
8718 == current_point->decision.script_bar_open_ms
8719 && !finite_positive(prior.exit_levels.limit)
8720 && !finite_positive(prior.exit_levels.stop)
8721 && !finite_positive(prior.exit_levels.trail_points)
8722 && !finite_positive(prior.exit_levels.trail_price)
8723 && !finite_positive(prior.exit_levels.trail_offset);
8725 snapshot.projection_opposite_market_predecessor = std::any_of(
8726 pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
8727 [&](
const PendingSameBarCommand& prior) {
8728 return is_opposite_market_predecessor(prior.snapshot);
8729 }) || std::any_of(pending_entries_.begin(), pending_entries_.end(),
8730 [&](
const PendingEntry& prior) {
8731 return is_opposite_market_predecessor(prior.snapshot);
8732 }) || std::any_of(live_handles_.begin(), live_handles_.end(),
8733 [&](
const native_order::RequestHandle& handle) {
8734 const auto prior = placement_.find(handle.incarnation);
8735 return prior != placement_.end()
8736 && is_opposite_market_predecessor(prior->second);
8742 const bool fills_ahead =
snapshot.projection_opposite_market_predecessor
8743 && same_point_pair_scope() && explicit_fixed && is_long
8744 && pure_stop_entry && finite_positive(current_point->price)
8745 && stop_price <= current_point->price
8746 && (config_.pyramiding <= 1 || same_point_two_leg_book(
8747 id, current_point->decision.script_bar_open_ms));
8748 const double predecessor_units = fills_ahead
8749 ? pending_opposite_market_units(
8750 is_long, current_point->decision.script_bar_open_ms,
id)
8752 if (fills_ahead && finite_positive(predecessor_units)) {
8754 }
else if (
snapshot.projection_opposite_market_predecessor) {
8756 request.intent = native_order::HostSized{native_order::HostSizedKind::Open,
8757 is_long ? native_order::Side::Long : native_order::Side::Short};
8758 snapshot.terms_priced_reverse =
true;
8761 if (default_sized && !priced && finite_positive(
snapshot.sizing.mark)) {
8762 snapshot.sizing.price = default_market_sizing_price(
snapshot.sizing.mark, is_long);
8763#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
8764 snapshot.sizing.price = fixture_adapter_execution_value(
"slippage.default.snapshot",
snapshot.sizing.price);
8766 check_slippage_fill_price(
snapshot.sizing.price, std::isfinite(
snapshot.sizing.mark));
8769 const auto predecessor = live_by_source_key_.find(checked_live_key(
id));
8770 snapshot.replaced_opening = predecessor != live_by_source_key_.end();
8772 const auto prior = placement_.find(predecessor->second.incarnation);
8773 snapshot.replacement_predecessor_market = prior != placement_.end()
8774 && !finite_positive(prior->second.exit_levels.limit)
8775 && !finite_positive(prior->second.exit_levels.stop);
8783 bool staged_reversal_replacement =
false;
8784 if (same_bar_market_candidate && !
snapshot.replaced_opening && reverses) {
8785 staged_reversal_replacement = std::any_of(pending_same_bar_commands_.begin(),
8786 pending_same_bar_commands_.end(), [&](
const PendingSameBarCommand& row) {
8787 return row.opening && !row.snapshot.frozen_market_targeted_close
8788 && row.replacement_key == id
8789 && row.snapshot.family == PineOrderFamily::Entry
8790 && row.snapshot.is_long == is_long;
8792 snapshot.replaced_opening = staged_reversal_replacement;
8793 snapshot.replacement_predecessor_market = staged_reversal_replacement;
8795 const bool special_sell_replacement = default_sized && reverses && !is_long
8797 if (special_sell_replacement && current_point) {
8798 std::vector<native_order::RequestHandle> superseded_siblings;
8799 for (
const auto& handle : live_handles_) {
8800 const auto pending = placement_.find(handle.incarnation);
8801 if (pending == placement_.end())
continue;
8802 const auto& prior =
pending->second;
8804 && prior.source_id !=
id && prior.is_long == is_long
8805 && prior.placement_script_open_ms == current_point->decision.script_bar_open_ms) {
8806 superseded_siblings.push_back(handle);
8809 for (
const auto& handle : superseded_siblings) {
8810 cancel_bracket_origin(handle);
8811 const auto result = require_host().cancel(handle);
8812 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
8814 std::vector<native_order::RequestHandle> carried_brackets;
8815 std::vector<SourceId> carried_ids;
8816 using CohortRow = std::remove_reference_t<
decltype(*cohorts_by_id_.begin())>;
8817 std::vector<const CohortRow*> cohorts;
8818 cohorts.reserve(cohorts_by_id_.held_size());
8819 for (
const auto& row : cohorts_by_id_.held_rows()) cohorts.push_back(&row);
8822 std::sort(cohorts.begin(), cohorts.end(),
8823 [](
const auto* a,
const auto* b) { return a->first < b->first; });
8824 for (
const auto* cohort : cohorts) {
8825 for (
const auto& origin :
cohort->second.opened) {
8826 const auto prior = placement_.find(origin.incarnation);
8827 if (prior != placement_.end() && prior->second.is_long != is_long) {
8828 carried_brackets.push_back(origin);
8829 carried_ids.push_back(
cohort->first);
8833 for (
const auto& origin : carried_brackets) cancel_bracket_origin(origin);
8834 std::vector<native_order::RequestHandle> dynamic_carried_legs;
8835 for (
const auto& handle : live_handles_) {
8836 const auto leg = placement_.find(handle.incarnation);
8837 if (leg == placement_.end())
continue;
8838 const auto family = leg->second.family;
8841 && std::find(carried_ids.begin(), carried_ids.end(), leg->second.from_entry)
8842 != carried_ids.end()) {
8843 dynamic_carried_legs.push_back(handle);
8846 for (
const auto& handle : dynamic_carried_legs) {
8847 const auto result = require_host().cancel(handle);
8848 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
8851 snapshot.terms_priced_reverse = fixed_priced_reverse || cash_priced_reverse
8852 || (reverses && typed_sized);
8853 snapshot.placement_cycle = current_position_cycle_;
8854 if (fixed_priced_reverse) {
8855 snapshot.frozen_reversal_transaction = std::abs(current) + normalized_qty;
8857 if (default_stop_freeze && finite_positive(default_stop_sizing_price)) {
8867 snapshot.sizing.price = default_stop_sizing_price;
8869 bool pooc_fee_sized =
false;
8870 if (default_sized && finite_positive(
snapshot.sizing.price)) {
8872 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)) {
8877 if (!priced && pooc_fee_configured_) {
8878 if (
const auto fee_units = pooc_fee_units(
snapshot.sizing)) {
8879 snapshot.sizing.frozen_units = *fee_units;
8880 pooc_fee_sized =
true;
8884 snapshot.sizing.at_fill = (config_.calc_on_order_fills && coof_recalc_active_)
8885 || (priced && !default_stop_freeze);
8900 if (config_.process_orders_on_close && !config_.calc_on_order_fills
8901 && config_.slippage > 0 && default_sized && !priced && !is_long
8904 && finite_positive(
snapshot.sizing.frozen_units)
8905 && finite_positive(
snapshot.sizing.mark) && std::isfinite(
snapshot.sizing.equity)
8907 const double required =
snapshot.sizing.frozen_units *
snapshot.sizing.mark
8908 * staged_.syminfo.pointvalue * config_.margin_short / 100.0;
8909 const double epsilon = std::max(1e-9, std::abs(
snapshot.sizing.equity) * 1e-12);
8910 if (required >
snapshot.sizing.equity + epsilon) {
8911 if (!reverses)
return;
8912 snapshot.affordability_close_only =
true;
8928 if (!config_.process_orders_on_close && !config_.calc_on_order_fills
8929 && !coof_recalc_active_ && config_.slippage > 0 && default_sized && !priced
8930 && !is_long && current == 0.0
8933 && finite_positive(
snapshot.sizing.frozen_units)
8934 && finite_positive(
snapshot.sizing.mark) && std::isfinite(
snapshot.sizing.equity)
8936 && std::none_of(live_handles_.begin(), live_handles_.end(),
8937 [&](
const native_order::RequestHandle& handle) {
8938 const auto existing = placement_.find(handle.incarnation);
8939 return existing != placement_.end() && existing->second.opening
8940 && existing->second.family == PineOrderFamily::Entry;
8942 const double signal = nearest_tick(
snapshot.sizing.mark, staged_.syminfo.mintick);
8943 const double required =
snapshot.sizing.frozen_units * signal
8944 * staged_.syminfo.pointvalue * config_.margin_short / 100.0;
8945 const double epsilon = std::max(1e-9, std::abs(
snapshot.sizing.equity) * 1e-12);
8946 if (required >
snapshot.sizing.equity + epsilon)
return;
8957 if (default_sized && !priced && !
snapshot.sizing.at_fill && !direction_blocked
8958 && !pooc_fee_sized && finite_positive(
snapshot.sizing.frozen_units)) {
8959 const auto* host_shape = std::get_if<native_order::HostSized>(&
request.intent);
8960 if (host_shape && host_shape->kind == native_order::HostSizedKind::Open) {
8961 if (
auto sized = default_sizing_intent(
snapshot.sizing, is_long)) {
8971 const double entry_margin = is_long ? config_.margin_long : config_.margin_short;
8972 const bool tv_money_scope = default_sized && !priced
8974 && std::abs(config_.default_qty_value - 100.0) < 1e-12
8975 && std::abs(entry_margin - 100.0) < 1e-12
8976 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
8977 && finite_positive(
snapshot.sizing.frozen_units)
8978 && finite_positive(
snapshot.sizing.price)
8979 && finite_positive(
snapshot.sizing.equity)
8980 && finite_positive(
snapshot.sizing.fx)
8981 && finite_positive(staged_.syminfo.pointvalue)
8982 && (*staged_.quantity_grid *
snapshot.sizing.price * staged_.syminfo.pointvalue
8988 if (tv_money_scope && !config_.process_orders_on_close) {
8989 const double notional_per_price =
snapshot.sizing.frozen_units
8990 * staged_.syminfo.pointvalue *
snapshot.sizing.fx;
8991 const double rounded_cost = source_money_round(notional_per_price *
snapshot.sizing.price);
8992 if (
snapshot.sizing.equity + 1e-9 < rounded_cost) {
8993 if (reverses && !
snapshot.projection_after_close) {
8994 snapshot.affordability_close_only =
true;
8995 snapshot.rounded_signal_cost_close_only =
true;
8996 }
else if (reverses)
8997 snapshot.affordability_close_only =
false;
8999 }
else if (!
snapshot.projection_after_close) {
9000 const double affordable_price = source_money_round(
9001 source_money_round(
snapshot.sizing.equity) / notional_per_price);
9002 if (std::isfinite(affordable_price) && affordable_price <
snapshot.sizing.price)
return;
9008 const bool affordability_scope = (!priced || pure_stop_entry) && (default_sized
9010 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
9012 && config_.default_qty_value > 100.0))
9014 snapshot.affordability_policy_active = affordability_scope;
9015 if (affordability_scope && finite_positive(
snapshot.sizing.mark)) {
9016 const double margin = is_long ? config_.margin_long : config_.margin_short;
9017 const double signal = nearest_tick(
snapshot.sizing.mark, staged_.syminfo.mintick);
9018 double own_units = normalized_qty;
9019 if (default_sized) {
9020 own_units = finite_positive(
snapshot.sizing.frozen_units)
9021 ?
snapshot.sizing.frozen_units : config_.default_qty_value;
9023 own_units = typed_quantity_units(std::abs(normalized_qty),
false, signal,
9026 own_units = typed_quantity_units(
9027 snapshot.sizing.equity * std::abs(normalized_qty) / 100.0,
true, signal,
9030 const double held = reverses ? 0.0
9031 : std::max(0.0, std::abs(current) - preceding_close_qty);
9032 double charged_held = held;
9033 if ((pooc_pass && close_all_precedes) || pooc_pass_flattens) {
9036 const auto* pine_host = pine_view_of(&require_host());
9037 const double before = pine_host ? pine_host->signed_position_size() : current;
9038 charged_held = before != 0.0 && ((before > 0.0) == is_long) ? std::abs(before) : 0.0;
9040 const double required = (charged_held + std::abs(own_units)) * signal * staged_.syminfo.pointvalue
9041 *
snapshot.sizing.fx * margin / 100.0;
9044 const bool finite_explicit_rejection = !default_sized && !reverses
9045 && std::isfinite(charged_held) && std::isfinite(own_units)
9046 && std::isfinite(signal) && std::isfinite(staged_.syminfo.pointvalue)
9047 && std::isfinite(
snapshot.sizing.fx) && std::isfinite(margin);
9049 && ((!std::isfinite(required) && !finite_explicit_rejection)
9050 || !std::isfinite(
snapshot.sizing.equity))) {
9051 engine_command_value_invalid_or_count(
"margin.required", EngineValueStage::margin,
9052 EngineValueReason::nonfinite);
9054 const double epsilon = std::max(1e-9, std::abs(
snapshot.sizing.equity) * 1e-12);
9056 snapshot.projection_affordability_signal_price = signal;
9057 snapshot.projection_affordability_held_qty = held;
9058 if (reverses && margin > 0.0 && std::isfinite(required)
9059 && std::isfinite(
snapshot.sizing.equity)
9060 && required >
snapshot.sizing.equity + epsilon) {
9061 snapshot.affordability_close_only =
true;
9062 }
else if (!reverses && margin > 0.0
9063 && (!std::isfinite(required) || !std::isfinite(
snapshot.sizing.equity)
9064 || required >
snapshot.sizing.equity + epsilon)) {
9070 if (pure_stop_entry) {
9071 std::optional<native_order::RequestHandle> prior_handle;
9072 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
9073 prior != live_by_source_key_.end()) {
9074 prior_handle = prior->second;
9077 const auto result = require_host().cancel(*prior_handle);
9078 if (result.status == native_order::CancelStatus::Cancelled)
9079 retire(*prior_handle);
9095 if (!affordability_scope && default_sized && !priced && !reverses
9096 && !same_bar_market_candidate && !short_seed_final_candidate
9097 && !paired_all_in_reentry && !default_gross_over_cap_candidate
9098 && !close_precedes_entry && !close_all_precedes
9099 && !config_.process_orders_on_close && !config_.calc_on_order_fills
9101 && config_.default_qty_value <= 100.0
9102 && current != 0.0 && ((current > 0.0) == is_long)
9103 && finite_positive(
snapshot.sizing.frozen_units)
9104 && finite_positive(
snapshot.sizing.mark)
9105 && add_margin_exceeds_equity(std::abs(current),
snapshot.sizing,
9106 is_long ? config_.margin_long : config_.margin_short,
9107 staged_.syminfo.mintick, staged_.syminfo.pointvalue)) {
9116 if (default_sized && config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED)
9117 && current == 0.0 && finite_positive(limit_price) && std::isnan(stop_price)
9118 && finite_positive(
snapshot.sizing.mark)) {
9119 const double margin = is_long ? config_.margin_long : config_.margin_short;
9120 const double signal = nearest_tick(
snapshot.sizing.mark, staged_.syminfo.mintick);
9121 const double required = std::abs(config_.default_qty_value) * signal
9122 * staged_.syminfo.pointvalue *
snapshot.sizing.fx * margin / 100.0;
9123 const double epsilon = std::max(1e-9, std::abs(
snapshot.sizing.equity) * 1e-12);
9124 if (margin > 0.0 && (!std::isfinite(required) || !std::isfinite(
snapshot.sizing.equity)
9125 || required >
snapshot.sizing.equity + epsilon)) {
9126 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
9127 prior != live_by_source_key_.end()) {
9128 const auto handle = prior->second;
9129 const auto result = require_host().cancel(handle);
9130 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
9142 && current == 0.0 && !reverses && config_.slippage == 0
9143 && config_.process_orders_on_close
9144 && finite_positive(
snapshot.sizing.frozen_units)
9145 && finite_positive(
snapshot.sizing.mark) && finite_positive(staged_.syminfo.mintick)
9146 && (is_long ? config_.margin_long : config_.margin_short) == 100.0
9147 && staged_.syminfo.pointvalue == 1.0 &&
snapshot.sizing.fx == 1.0
9148 && staged_.account_fx_effective_from_ms.empty()
9149 && std::isfinite(
snapshot.sizing.equity))) {
9150 const double tick = staged_.syminfo.mintick;
9151 const double signal_close = std::floor(
snapshot.sizing.mark / tick + 0.5) * tick;
9152 const double units =
snapshot.sizing.frozen_units;
9153 const double equity =
snapshot.sizing.equity;
9154 if (equity < source_money_round(units * signal_close)
9155 || source_money_round(source_money_round(equity) / units) < signal_close) {
9156 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
9157 prior != live_by_source_key_.end()) {
9158 const auto prior_handle = prior->second;
9159 const auto result = require_host().cancel(prior_handle);
9160 if (result.status == native_order::CancelStatus::Cancelled) retire(prior_handle);
9165 if (default_stop_scope && finite_positive(
snapshot.sizing.frozen_units)
9166 && finite_positive(
snapshot.sizing.mark)) {
9167 const double margin = is_long ? config_.margin_long : config_.margin_short;
9168 const double required =
snapshot.sizing.frozen_units *
snapshot.sizing.mark
9169 * staged_.syminfo.pointvalue *
snapshot.sizing.fx * margin / 100.0;
9175 const double stop_epsilon = std::max(
9176 1e-9, std::abs(
snapshot.sizing.equity) * 1e-12);
9177 const bool unfunded = margin > 0.0 && (!std::isfinite(required)
9178 || !std::isfinite(
snapshot.sizing.equity)
9179 || required >
snapshot.sizing.equity + stop_epsilon);
9180 if (unfunded && reverses) {
9186 snapshot.affordability_close_only =
true;
9187 }
else if (unfunded) {
9190 std::optional<native_order::RequestHandle> prior_handle;
9191 if (
const auto prior = live_by_source_key_.find(checked_live_key(
id));
9192 prior != live_by_source_key_.end()) {
9193 prior_handle = prior->second;
9196 const auto result = require_host().cancel(*prior_handle);
9197 if (result.status == native_order::CancelStatus::Cancelled) retire(*prior_handle);
9203 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
9204 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
9205 if (default_sized && reverses && !
snapshot.replaced_opening) {
9206 for (
auto it = live_handles_.rbegin(); it != live_handles_.rend(); ++it) {
9207 const auto prior = placement_.find(it->incarnation);
9208 if (prior == placement_.end())
continue;
9209 auto& prior_snapshot = prior->second;
9211 || prior_snapshot.is_long != is_long
9212 || prior_snapshot.placement_script_open_ms != point->decision.script_bar_open_ms) {
9215 const std::uint64_t group = prior_snapshot.sequential_group != 0
9216 ? prior_snapshot.sequential_group : ++next_sequential_group_;
9217 prior_snapshot.sequential_group = group; invalidate_retirement();
9218 if (prior_snapshot.sequential_rank == 0) prior_snapshot.sequential_rank = 1;
9225 if (current == 0.0 && config_.process_orders_on_close
9226 && !config_.calc_on_order_fills && default_sized
9227 && std::holds_alternative<native_order::Stop>(
request.trigger)
9228 && !finite_positive(limit_price) && oca_name.empty() && source_point) {
9229 stage_flat_children_before_parent(
9230 id, source_point->decision.coordinate.interval_index,
9231 source_point->decision.script_bar_open_ms);
9233 const EntrySubmissionContext submission_context{
9234 current, limit_price, stop_price, default_sized, priced, reverses,
9235 opposite_opening_pending, pure_stop_entry, coof_market_next_open,
9236 coof_priced_next_open, coof_market_at_second_extreme, paired_all_in_reentry};
9237 if (close_all_precedes && preceding_close_all.incarnation != 0) {
9241 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
9242 [&](
const PendingEntry& prior) {
9243 return prior.replacement_key == id
9244 && prior.snapshot.paired_reversal_parent == preceding_close_all;
9246 if (queued != pending_entries_.end()) {
9247 pending_write(*queued) = PendingEntry{std::move(request), std::move(snapshot),
id};
9251 if (same_bar_market_candidate && fixed_market_batch_candidate
9252 && !close_precedes_entry && pending_same_bar_close_qty_ == 0.0
9253 && !close_all_precedes) {
9254 auto unbatched_request =
request;
9255 auto unbatched_snapshot =
snapshot;
9256 const double own_units = default_sized
9257 ? floor_quantity_grid(config_.default_qty_value, staged_.quantity_grid)
9258 : explicit_entry_quantity_floor(std::abs(qty), staged_.quantity_grid);
9259 if (!finite_positive(own_units))
return;
9260 double projected_units = current;
9261 std::size_t projected_entries = current == 0.0 ? 0U
9262 : accepted_entries_in_cycle(current > 0.0,
id);
9264 for (
const auto& pending : pending_same_bar_commands_) {
9265 const auto& prior =
pending.snapshot;
9268 || !prior.frozen_market_instruction) {
9271 const bool opposite = projected_units != 0.0
9272 && ((projected_units > 0.0) != prior.is_long);
9273 projected_entries = opposite ? 1U : projected_entries + 1U;
9274 projected_units += prior.is_long ? prior.frozen_market_transaction_units
9275 : -prior.frozen_market_transaction_units;
9277 const bool same_side = projected_units != 0.0
9278 && ((projected_units > 0.0) == is_long);
9279 const auto entry_cap =
static_cast<std::size_t
>(std::max(1, config_.pyramiding));
9280 if (same_side && projected_entries >= entry_cap)
return;
9281 const double held_opposite = projected_units != 0.0 && !same_side
9282 ? std::abs(projected_units) : 0.0;
9283 const double transaction = own_units + held_opposite;
9286 snapshot.projection_over_pyramiding =
false;
9287 snapshot.frozen_market_instruction =
true;
9288 snapshot.frozen_market_own_units = own_units;
9289 snapshot.frozen_market_transaction_units = transaction;
9290 auto existing = std::find_if(pending_same_bar_commands_.begin(),
9291 pending_same_bar_commands_.end(), [&](
const PendingSameBarCommand& pending) {
9292 return !pending.snapshot.frozen_market_targeted_close
9293 && pending.replacement_key == id;
9295 PendingSameBarCommand
pending{std::move(request), std::move(snapshot), id,
true};
9296 pending.unbatched_request = std::move(unbatched_request);
9297 pending.unbatched_snapshot = std::move(unbatched_snapshot);
9298 pending.unbatched_context = submission_context;
9299 if (existing == pending_same_bar_commands_.end()) {
9300 pending_write(pending_same_bar_commands_).push_back(std::move(pending));
9302 source_batch_mutated_ =
true;
9303 pending_write(*existing) = std::move(pending);
9307 if (same_bar_market_candidate) {
9312 const double default_own = finite_positive(
snapshot.sizing.frozen_units)
9313 ?
snapshot.sizing.frozen_units : config_.default_qty_value;
9314 const double own_units = default_sized
9315 ? floor_quantity_grid(default_own, staged_.quantity_grid)
9316 : explicit_entry_quantity_floor(std::abs(qty), staged_.quantity_grid);
9317 bool opposite_market_pending =
false;
9318 bool opposite_entry_pending =
false;
9319 double opposite_pending_own = 0.0;
9321 if (!prior.opening || prior.source_id ==
id || prior.is_long == is_long) {
9324 if (prior.frozen_market_instruction
9325 && finite_positive(prior.frozen_market_own_units)) {
9326 opposite_market_pending =
true;
9327 opposite_pending_own += prior.frozen_market_own_units;
9329 opposite_entry_pending =
true;
9332 for (
const auto& pending : pending_same_bar_commands_) inspect_pending(
pending.snapshot);
9333 for (
const auto& pending : pending_entries_) inspect_pending(
pending.snapshot);
9335 for (
const auto& handle : live_handles_) {
9336 const auto prior = placement_.find(handle.incarnation);
9337 if (prior == placement_.end()
9338 || prior->second.placement_script_open_ms
9339 != point->decision.script_bar_open_ms) {
9342 inspect_pending(prior->second);
9345 std::size_t same_side_pending = 0;
9346 for (
const auto& pending : pending_same_bar_commands_) {
9348 &&
pending.snapshot.source_id !=
id
9349 && !
pending.snapshot.projection_over_pyramiding) {
9350 ++same_side_pending;
9353 for (
const auto& pending : pending_entries_) {
9354 if (
pending.snapshot.opening &&
pending.snapshot.is_long == is_long
9355 &&
pending.snapshot.source_id !=
id
9356 && !
pending.snapshot.projection_over_pyramiding) {
9357 ++same_side_pending;
9361 for (
const auto& handle : live_handles_) {
9362 const auto prior = placement_.find(handle.incarnation);
9363 if (prior == placement_.end()
9364 || prior->second.placement_script_open_ms
9365 != point->decision.script_bar_open_ms) {
9370 if (prior->second.opening && prior->second.is_long == is_long
9371 && prior->second.source_id !=
id
9372 && !prior->second.projection_over_pyramiding
9373 && prior->second.cancellation.cause
9375 ++same_side_pending;
9379 const bool same_side = (current != 0.0 && ((current > 0.0) == is_long))
9380 || (current == 0.0 && same_side_pending > 0);
9381 const std::size_t current_lots =
9382 (current != 0.0 && ((current > 0.0) == is_long))
9386 const std::size_t total_entries = current_lots + same_side_pending;
9387 const bool over_cap = same_side
9388 && ((config_.pyramiding == 0 && total_entries >= 1U)
9389 || (config_.pyramiding > 0
9390 && total_entries >=
static_cast<std::size_t
>(config_.pyramiding)));
9391 snapshot.projection_over_pyramiding = over_cap;
9392 if (current == 0.0) {
9393 for (
const auto& handle : live_handles_) {
9394 const auto found = placement_.find(handle.incarnation);
9395 if (found == placement_.end())
continue;
9396 auto& candidate = found->second;
9400 if (exit && candidate.from_entry ==
id
9401 && !(cohort_exposure_for(
id) > 0.0)) {
9402 candidate.reservation_deferred_to_pending_entry =
true;
9403 candidate.projection_remaining_qty = kNaN;
9407 if (over_cap && !opposite_market_pending && !opposite_entry_pending)
return;
9408 if (!(over_cap && !opposite_market_pending)
9409 && finite_positive(own_units)) {
9412 const double held_opposite_units = current != 0.0 && ((current > 0.0) != is_long)
9413 ? std::max(0.0, std::abs(current) - preceding_close_qty) : 0.0;
9414 const double held_opposite = staged_reversal_replacement ? 0.0 : held_opposite_units;
9415 const double transaction = own_units + held_opposite + opposite_pending_own;
9416 if (finite_positive(transaction)) {
9417 if (over_cap && opposite_market_pending
9418 && config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED)) {
9425 const double gross_units = std::abs(current) + own_units
9426 + opposite_pending_own;
9427 const double margin = is_long ? config_.margin_long : config_.margin_short;
9428 const double required = gross_units *
snapshot.sizing.price
9429 * staged_.syminfo.pointvalue *
snapshot.sizing.fx * margin / 100.0;
9430 if (!std::isfinite(required) || !std::isfinite(
snapshot.sizing.equity)
9431 || required >
snapshot.sizing.equity) {
9436 snapshot.frozen_market_instruction =
true;
9437 snapshot.frozen_market_own_units = own_units;
9438 snapshot.frozen_market_transaction_units = transaction;
9439 auto existing = std::find_if(pending_same_bar_commands_.begin(),
9440 pending_same_bar_commands_.end(), [&](
const PendingSameBarCommand& row) {
9441 return !row.snapshot.frozen_market_targeted_close
9442 && row.replacement_key == id;
9444 PendingSameBarCommand
pending{std::move(request), std::move(snapshot), id,
true};
9445 if (staged_reversal_replacement) {
9446 pending.staged_reversal =
true;
9447 pending.staged_reversal_held_units = held_opposite_units;
9449 if (existing == pending_same_bar_commands_.end()) {
9450 pending_write(pending_same_bar_commands_).push_back(std::move(pending));
9452 source_batch_mutated_ =
true;
9453 pending_write(*existing) = std::move(pending);
9459 if (preceding_close_all.incarnation != 0) {
9460 pending_write(pending_entries_).push_back({std::move(request), std::move(snapshot),
id});
9463 submit_entry_with_policy(std::move(request), std::move(snapshot),
id, submission_context);
9466void PineExecutionAdapter::submit_entry_with_policy(
9468 const EntrySubmissionContext& context) {
9469 const double current = context.current;
9470 const double limit_price = context.limit_price;
9471 const double stop_price = context.stop_price;
9472 const bool default_sized = context.default_sized;
9473 const bool priced = context.priced;
9474 const bool reverses = context.reverses;
9475 const bool opposite_opening_pending = context.opposite_opening_pending;
9476 const bool pure_stop_entry = context.pure_stop_entry;
9477 const bool coof_market_next_open = context.coof_market_next_open;
9478 const bool coof_priced_next_open = context.coof_priced_next_open;
9479 const bool coof_market_at_second_extreme = context.coof_market_at_second_extreme;
9480 const bool paired_all_in_reentry = context.paired_all_in_reentry;
9481 const bool is_long =
snapshot.is_long;
9482 const auto oca_name =
snapshot.oca_name;
9484 const bool source_same_side_market_add = default_sized
9486 && !config_.calc_on_order_fills
9487 && (config_.process_orders_on_close || !opposite_opening_pending)
9489 && ((current > 0.0) == is_long)
9490 && std::holds_alternative<native_order::Market>(
request.trigger);
9491 const bool close_first_percent_add = default_sized
9493 && std::abs(config_.default_qty_value - 100.0) < 1e-12
9494 && !config_.calc_on_order_fills && !config_.process_orders_on_close
9495 && config_.pyramiding > 1 && current != 0.0
9496 && ((current > 0.0) == is_long)
9497 && std::holds_alternative<native_order::Market>(
request.trigger);
9498 if (source_same_side_market_add || close_first_percent_add) {
9499 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
9500 [&](
const PendingEntry& value) { return value.replacement_key == id; });
9501 PendingEntry
pending{std::move(request), std::move(snapshot),
id};
9502 if (queued == pending_entries_.end()) pending_write(pending_entries_).push_back(std::move(pending));
9503 else pending_write(*queued) = std::move(pending);
9506 if (current == 0.0 && config_.process_orders_on_close && default_sized
9507 && std::holds_alternative<native_order::Stop>(
request.trigger)
9508 && !finite_positive(limit_price) && oca_name.empty()) {
9509 const auto token = named_entry_cancel_tokens_.find(
id);
9510 if (token != named_entry_cancel_tokens_.end()
9511 && token->second.entry_incarnation != 0
9512 && token->second.surviving_exit_incarnation != 0) {
9513 std::vector<std::uint64_t> child_families;
9514 for (
const auto& handle : live_handles_) {
9515 const auto child = placement_.find(handle.incarnation);
9516 if (child == placement_.end() || child->second.from_entry !=
id)
continue;
9517 const auto family = child->second.family;
9523 const auto key = key_for(child->second.source_id, child->second.from_entry);
9524 if (std::find(child_families.begin(), child_families.end(), key)
9525 == child_families.end()) {
9526 child_families.push_back(key);
9529 if (child_families.size() == 1) {
9530 snapshot.recreated_after_named_cancelled_entry_incarnation =
9531 token->second.entry_incarnation;
9532 snapshot.named_cancel_surviving_exit_incarnation =
9533 token->second.surviving_exit_incarnation;
9535 snapshot.retained_parent_topology =
true;
9540 snapshot.source_sequence = source_sequence_ + 1;
9541 if (child_families.size() != 1) named_entry_cancel_tokens_.erase(token);
9542 pending_write(pending_entries_).push_back({std::move(request), std::move(snapshot),
id});
9546 if (!priced && reverses) {
9547 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
9548 [&](
const PendingEntry& value) { return value.replacement_key == id; });
9549 if (queued != pending_entries_.end()) {
9554 pending_write(*queued) = PendingEntry{std::move(request), std::move(snapshot),
id};
9558 if (priced && reverses && !config_.process_orders_on_close
9559 && !config_.calc_on_order_fills) {
9560 auto queued = std::find_if(pending_entries_.begin(), pending_entries_.end(),
9561 [&](
const PendingEntry& value) { return value.replacement_key == id; });
9562 PendingEntry
pending{std::move(request), std::move(snapshot),
id};
9563 if (queued == pending_entries_.end()) pending_write(pending_entries_).push_back(std::move(pending));
9564 else pending_write(*queued) = std::move(pending);
9567 if (config_.process_orders_on_close && !close_batch_callsites_.empty()
9568 && !priced && !coof_recalc_active_) {
9569 pending_write(pending_entries_).push_back({std::move(request), std::move(snapshot),
id});
9577 const bool coof_flat_priced = config_.calc_on_order_fills && current == 0.0
9578 && priced && !coof_recalc_active_ && oca_name.empty();
9579 const bool ordinary_flat_pure_stop = [&]() {
9580 if (config_.process_orders_on_close || config_.calc_on_order_fills
9581 || current != 0.0 || !pure_stop_entry || !oca_name.empty()
9585 const auto* pine_host = pine_view_of(&require_host());
9586 const auto next = pine_host
9587 ? pine_host->scheduler_.next_source_bar(
9588 source_point->decision.coordinate.interval_index)
9589 : std::optional<Bar>{};
9590 return next && source_bar_fill_tick(next->open, staged_.syminfo.mintick)
9593 if (coof_flat_priced || ordinary_flat_pure_stop) {
9594 pending_write(pending_entries_).push_back({std::move(request), std::move(snapshot),
id});
9597 if (coof_market_next_open || coof_priced_next_open) {
9598 pending_write(pending_coof_requests_).push_back(
9599 {std::move(request), std::move(snapshot), id,
true, 0,
true});
9602 if (defer_coof_tail() && !coof_market_at_second_extreme) {
9603 pending_write(pending_coof_requests_).push_back({std::move(request), std::move(snapshot), id,
true, 0});
9606 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
true,
id);
9608 if (current < 0.0 && is_long) short_seed_long_candidate_ = *accepted;
9609 const auto entry = placement_.find(accepted->incarnation);
9610 if (
entry != placement_.end()) {
9611 const std::uint64_t entry_sequence =
entry->second.command_sequence;
9617 for (
const auto& handle : live_handles_) {
9618 if (handle == *accepted)
continue;
9619 const auto existing = placement_.find(handle.incarnation);
9620 if (existing == placement_.end())
continue;
9621 auto& prior = existing->second;
9622 if (!prior.reservation_expansion.capture()
9623 || prior.command_sequence >= entry_sequence) {
9626 prior.pooc_global_full_exit_dynamic_qty =
false;
9627 prior.pooc_global_full_exit_tracks_bound_adds =
false;
9628 prior.reservation_expansion.close_population(accepted->incarnation);
9631 if (default_sized && !priced && reverses && source_point
9632 && config_.default_qty_type
9634 && config_.default_qty_value <= 100.0) {
9635 if (
auto* pine_host = pine_view_of(&require_host())) {
9636 if (
const auto next = pine_host->scheduler_.next_source_bar(
9637 source_point->decision.coordinate.interval_index)) {
9638 NativeDecisionContext next_context = source_point->decision;
9639 ++next_context.coordinate.interval_index;
9640 next_context.coordinate.path_phase = NativePathPhase::Open;
9641 next_context.script_bar_open_ms = next->timestamp;
9642 next_context.sub_bar_open_ms = next->timestamp;
9643 apply_reversal_gap_bracket_policy(
9644 *next, next_context,
true);
9648 }
else if (paired_all_in_reentry) {
9654 SourceShadowPending shadow;
9656 shadow.snapshot.source_id = id;
9657 shadow.snapshot.is_long = is_long;
9658 shadow.snapshot.opening =
true;
9659 shadow.snapshot.sizing = sizing_snapshot();
9661 pending_write(source_shadow_pending_).push_back(std::move(shadow));
9665bool PineExecutionAdapter::enqueue_pooc_fifo_close(
9666 const SourceId&
id,
const std::string& comment,
9667 std::uint64_t token, std::uint64_t) {
9669 const int bar = point ? point->decision.coordinate.interval_index : -1;
9670 if (close_batch_bar_ != bar) {
9671 close_batch_bar_ = bar;
9672 close_batch_queue_sequence_ = 0;
9673 close_batch_callsites_.clear();
9674 close_batch_pending_debt_ = 0.0;
9675 close_batch_admitted_total_ = 0.0;
9678 auto existing = close_batch_callsites_.find(token);
9679 const CloseCallsiteState* prior = existing == close_batch_callsites_.end()
9680 ? nullptr : &existing->second;
9681 const auto logical = close_logical_units_.find(
id);
9682 const double unclosed = logical == close_logical_units_.end()
9683 ? 0.0 : logical->second;
9688 double pending_reserved = 0.0;
9689 for (
const auto& row : close_batch_callsites_) {
9690 if (row.second.active) pending_reserved += row.second.target;
9692 const bool same_id_reissue = prior && prior->active && prior->id == id;
9693 bool replacement_can_reuse_own_claim =
false;
9694 if (prior && prior->active && prior->id !=
id) {
9695 const bool another_targets_prior = std::any_of(
9696 close_batch_callsites_.begin(), close_batch_callsites_.end(),
9697 [&](
const auto& row) {
9698 return row.first != token && row.second.active
9699 && row.second.id == prior->id;
9701 replacement_can_reuse_own_claim = !another_targets_prior;
9703 if (same_id_reissue || replacement_can_reuse_own_claim)
9704 pending_reserved -= prior->target;
9707 const double available = std::max(0.0, held - pending_reserved);
9708 const double target = std::min(unclosed, available);
9712 if (!(target > internal::kQtyEpsilon))
return false;
9714 const double replaced_target = prior && prior->active ? prior->target : 0.0;
9715 close_batch_pending_debt_ += target;
9716 pending_same_bar_close_qty_ += target;
9717 if (token != 0) close_batch_admitted_total_ += target - replaced_target;
9719 auto& site = close_batch_callsites_[token];
9726 site.comment = comment;
9727 site.target = target;
9728 site.queue_sequence = ++close_batch_queue_sequence_;
9733 if (site.id !=
id) ++site.calls;
9735 site.comment = comment;
9736 site.target = target;
9741 if (close_batch_callsites_.empty())
return;
9742 std::vector<CloseCallsiteState> sites;
9743 sites.reserve(close_batch_callsites_.size());
9744 for (
const auto& row : close_batch_callsites_)
9745 if (row.second.active) sites.push_back(row.second);
9746 std::stable_sort(sites.begin(), sites.end(),
9747 [](
const CloseCallsiteState& left,
const CloseCallsiteState& right) {
9748 return left.queue_sequence < right.queue_sequence;
9751 std::vector<std::pair<std::uint64_t, double>> fifo_lots;
9752 if (
const auto* pine = pine_view_of(&require_host())) {
9753 fifo_lots.reserve(pine->pyramid_entries_.size());
9754 for (
const auto& lot : pine->pyramid_entries_)
9755 fifo_lots.emplace_back(lot.entry_incarnation, lot.qty);
9757 std::size_t fifo_prefix_size = 0;
9760 const auto source_fifo_prefix_openings = [&](
9761 const std::vector<std::pair<std::uint64_t, double>>& lots,
9762 double qty_limit) -> std::optional<native_order::BindOpenings> {
9763 fifo_prefix_size = 0;
9764 if (current_position_cycle_ <= 0 || !std::isfinite(qty_limit) || qty_limit <= 0.0)
9765 return std::nullopt;
9766 double qty_closed = 0.0;
9767 std::size_t prefix_size = 0;
9768 for (
const auto& lot : lots) {
9769 if (qty_closed >= qty_limit - internal::kQtyEpsilon)
break;
9770 if (!std::isfinite(lot.second) || lot.second <= 0.0)
return std::nullopt;
9771 const double close_qty = std::min(lot.second, qty_limit - qty_closed);
9772 if (lot.second - close_qty > internal::kQtyEpsilon)
return std::nullopt;
9774 qty_closed += close_qty;
9776 if (prefix_size == 0 || prefix_size == lots.size())
return std::nullopt;
9783 double closed = 0.0;
9784 double left = qty_limit;
9786 for (std::size_t index = 0; exact && index < prefix_size; ++index) {
9787 const double amount = std::min(lots[index].second, left);
9788 exact = amount == lots[index].second;
9790 left = qty_limit - closed;
9792 if (exact && left == 0.0)
return std::nullopt;
9795 std::unordered_set<std::uint64_t> included;
9796 for (std::size_t index = 0; index < prefix_size; ++index) {
9797 const auto incarnation = lots[index].first;
9798 if (incarnation == 0)
return std::nullopt;
9799 if (!included.insert(incarnation).second)
continue;
9801 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
9802 for (
const auto& opened : cohort.second.opened)
9803 if (opened.incarnation == incarnation) receipt = &opened;
9805 if (!receipt)
return std::nullopt;
9806 selection.openings.push_back(*receipt);
9810 for (std::size_t index = prefix_size; index < lots.size(); ++index)
9811 if (included.count(lots[index].first) != 0)
return std::nullopt;
9812 fifo_prefix_size = prefix_size;
9816 const auto drain_source_fifo_lots = [&](
9817 std::vector<std::pair<std::uint64_t, double>>& lots,
double qty) {
9818 if (fifo_prefix_size != 0) {
9819 lots.erase(lots.begin(), lots.begin()
9820 +
static_cast<std::ptrdiff_t
>(fifo_prefix_size));
9823 double closed = 0.0;
9825 std::size_t consumed = 0;
9826 for (
auto& lot : lots) {
9827 if (!(left > 0.0))
break;
9828 const double amount = std::min(lot.second, left);
9829 lot.second -= amount;
9831 left = qty - closed;
9832 if (lot.second == 0.0) ++consumed;
9834 lots.erase(lots.begin(), lots.begin() +
static_cast<std::ptrdiff_t
>(consumed));
9836 lots.erase(std::remove_if(lots.begin(), lots.end(), [](
const auto& lot) {
9837 return lot.second <= internal::kQtyEpsilon;
9848 bool frozen_close =
false;
9849 if (
PF_RARE(owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min())) {
9852 frozen_close = point && owed_follow_up_bar_ == point->decision.script_bar_open_ms
9853 && owed_follow_up_point_ == 3 && config_.process_orders_on_close
9854 && schedule.close_follow_up_after_script && schedule.frozen_reversal_close;
9856 for (
const auto& site : sites) {
9859 if (std::abs(physical.signed_units) <= internal::kQtyEpsilon)
continue;
9860 const double available = std::abs(physical.signed_units);
9861 const double target = std::min(site.target, available);
9863 const bool closes_full = target >= available - internal::kQtyEpsilon;
9864 if (
PF_RARE(frozen_close) && closes_full && fifo_lots.size() <= 1) {
9865 cancel_exit_orders_for_full_close(site.id);
9868 request.label =
"__close__" + site.id;
9869 request.comment = site.comment;
9874 snapshot.source_id = site.id;
9875 snapshot.comment = site.comment;
9876 snapshot.requested_qty = kNaN;
9877 snapshot.projection_remaining_qty = available;
9878 snapshot.qty_percent = 100.0;
9879 snapshot.is_long =
false;
9880 snapshot.crosses_zero =
true;
9881 snapshot.sizing = sizing_snapshot();
9882 snapshot.close_callsite_token = site.token;
9883 snapshot.close_batch_calls =
static_cast<std::uint32_t
>(site.calls);
9884 snapshot.close_first_id = site.first_id;
9885 const SourceId key =
"__pine_close_flush__" + std::to_string(site.token);
9886 (void)submit_or_replace(std::move(request), std::move(snapshot),
false, key);
9894 cancel_exit_orders_for_full_close(site.id);
9902 std::optional<native_order::BindOpenings> prefix;
9903 fifo_prefix_size = 0;
9904 if (!closes_full) prefix = source_fifo_prefix_openings(fifo_lots, target);
9911 const bool off_grid = staged_.quantity_grid
9914 request.intent = closes_full || prefix
9918 native_order::HostSizedKind::Close, std::nullopt}}
9920 request.label =
"__close__" + site.id;
9921 request.comment = site.comment;
9923 if (prefix) request.owner = std::move(*prefix);
9926 if (closes_full) fifo_lots.clear();
9927 else drain_source_fifo_lots(fifo_lots, target);
9930 snapshot.source_id = site.id;
9931 snapshot.comment = site.comment;
9932 snapshot.requested_qty = closes_full ? kNaN : target;
9933 snapshot.projection_remaining_qty = target;
9934 snapshot.qty_percent = closes_full ? 100.0 : (target / available * 100.0);
9935 snapshot.is_long =
false;
9936 snapshot.sizing = sizing_snapshot();
9937 snapshot.close_callsite_token = site.token;
9938 snapshot.close_batch_calls =
static_cast<std::uint32_t
>(site.calls);
9939 snapshot.close_first_id = site.first_id;
9940 const SourceId key =
"__pine_close_flush__" + std::to_string(site.token);
9941 (void)submit_or_replace(
9942 std::move(request), std::move(snapshot),
false, key);
9944 close_batch_callsites_.clear();
9945 close_batch_bar_ = -1;
9946 close_batch_queue_sequence_ = 0;
9947 close_batch_pending_debt_ = 0.0;
9948 close_batch_admitted_total_ = 0.0;
9951void PineExecutionAdapter::observe_close_ledger(
9966 switch (snapshot->family) {
9968 named = snapshot->close_first_id.empty() ? snapshot->source_id
9969 : snapshot->close_first_id;
9974 named = snapshot->from_entry;
9984 const std::uint64_t armed_for =
snapshot
9988 ?
snapshot->bracket_origin.incarnation : 0;
9989 book_pyramiding_records(named, armed_for, event.
closed_units);
9992void PineExecutionAdapter::credit_close_ledger(
const SourceId&
id,
double units) {
9993 close_logical_units_[id] += units;
9994 if (config_.close_entries_rule_any)
return;
9995 if (!close_ledger_records_.empty() && close_ledger_records_.back().id ==
id)
9996 close_ledger_records_.back().units += units;
9998 close_ledger_records_.push_back({id, units});
10001void PineExecutionAdapter::book_close_ledger(
const SourceId&
id,
double units) {
10002 if (!(units > 0.0) || close_ledger_records_.empty())
return;
10003 double left = units;
10004 const auto take = [&left](CloseLedgerRecord& record) {
10005 if (!(left > internal::kQtyEpsilon) || !(record.units > 0.0))
return;
10006 const double amount = std::min(record.units, left);
10007 record.units -= amount;
10012 for (
auto& record : close_ledger_records_)
10013 if (record.id ==
id) take(record);
10014 for (
auto& record : close_ledger_records_) take(record);
10015 std::vector<CloseLedgerRecord> kept;
10016 kept.reserve(close_ledger_records_.size());
10017 for (
auto& record : close_ledger_records_) {
10018 if (!(record.units > internal::kQtyEpsilon))
continue;
10019 if (!kept.empty() && kept.back().id == record.id)
10020 kept.back().units += record.units;
10022 kept.push_back(std::move(record));
10024 close_ledger_records_ = std::move(kept);
10025 close_logical_units_.clear();
10026 for (
const auto& record : close_ledger_records_)
10027 close_logical_units_[record.id] += record.units;
10044void PineExecutionAdapter::credit_pyramiding_record(
const SourceId&
id, std::uint64_t incarnation,
10046 if (!ledger_records_configured_ || !(units > 0.0))
return;
10047 pyramiding_records_.push_back({id, incarnation, units});
10050void PineExecutionAdapter::book_pyramiding_records(
const SourceId&
id, std::uint64_t armed_for,
10052 if (!(units > 0.0) || pyramiding_records_.empty())
return;
10053 double left = units;
10054 const auto take = [&left](PyramidingRecord& record) {
10055 if (!(left > internal::kQtyEpsilon) || !(record.units > 0.0))
return;
10056 const double amount = std::min(record.units, left);
10057 record.units -= amount;
10060 if (armed_for != 0) {
10061 for (
auto& record : pyramiding_records_)
10062 if (record.incarnation == armed_for && record.id ==
id) take(record);
10064 for (
auto& record : pyramiding_records_)
10065 if (record.id ==
id) take(record);
10066 for (
auto& record : pyramiding_records_) take(record);
10067 pyramiding_records_.erase(
10068 std::remove_if(pyramiding_records_.begin(), pyramiding_records_.end(),
10069 [](
const PyramidingRecord& record) {
10070 return !(record.units > internal::kQtyEpsilon);
10072 pyramiding_records_.end());
10078[[gnu::always_inline]]
inline std::optional<std::size_t>
10079PineExecutionAdapter::open_ledger_records()
const {
10081 return std::nullopt;
10082 return held_ledger_records();
10087std::optional<std::size_t> PineExecutionAdapter::held_ledger_records()
const {
10089 double units = 0.0;
10090 for (
const auto& record : pyramiding_records_) units += record.units;
10091 if (!(std::abs(units - held) <= std::max(1e-9, held * 1e-12)))
return std::nullopt;
10092 return pyramiding_records_.size();
10097std::uint64_t PineExecutionAdapter::entry_incarnation_of(
const SourceId& from_entry)
const {
10098 const auto cohort = cohorts_by_id_.find(from_entry);
10099 if (cohort == cohorts_by_id_.end() ||
cohort->second.opened.empty())
return 0;
10100 return cohort->second.opened.back().incarnation;
10105[[gnu::noinline, gnu::cold]]
10106bool PineExecutionAdapter::exit_tombstoned(
const SourceId& exit_id,
10107 const SourceId& from_entry)
const {
10109 || !pinned_lot_grid()) {
10112 const auto found = exit_tombstones_.find(exit_id +
"\x1f" + from_entry);
10113 return found != exit_tombstones_.end() && found->second != 0
10114 && found->second == entry_incarnation_of(from_entry);
10122void PineExecutionAdapter::record_close_first(
bool whole_position) {
10123 if (!whole_position)
return;
10125 if (!point)
return;
10127 if (held == 0.0)
return;
10128 close_first_script_bar_ = point->decision.script_bar_open_ms;
10129 close_first_side_ = held > 0.0 ? 1 : -1;
10133 double qty_percent,
bool immediately, std::uint64_t callsite_token) {
10134 if (intraday_loss_orders_blocked())
return;
10136 point && cap_placement_denied(point->decision)) {
10139 require_cohort_ticket();
10140 record_close_first(!immediately && !std::isfinite(qty)
10141 && (!std::isfinite(qty_percent) || qty_percent >= 100.0)
10142 && (
id.empty() || open_lot_of(
id)));
10143 reroute_fixed_entries_before_request();
10150 if (immediately && coof_recalc_active_ && !coof_first_open_
10151 && !coof_fill_on_path_point()) {
10152 immediately =
false;
10160 const std::uint64_t command_ordinal = ++command_ordinal_;
10162 close_all_pending_script_bar_ = point->decision.script_bar_open_ms;
10164 bool empty_entry =
false;
10165 bool opposite_entry =
false;
10166 bool empty_is_long =
false;
10167 for (
const auto& pending : pending_same_bar_commands_) {
10169 if (pending.snapshot.source_id.empty()) {
10170 empty_entry =
true;
10171 empty_is_long = pending.snapshot.is_long;
10175 for (
const auto& pending : pending_same_bar_commands_) {
10177 if (pending.snapshot.is_long != empty_is_long) {
10178 opposite_entry =
true;
10194 bool all_in_reversal =
false;
10195 if (!empty_entry && !config_.process_orders_on_close && !config_.calc_on_order_fills
10197 && config_.default_qty_value >= 100.0 - 1e-9
10198 && (!staged_.quantity_grid || *staged_.quantity_grid < 1.0)) {
10203 && candidate.is_long != held_long && std::isnan(candidate.requested_qty)
10204 && (!point || candidate.placement_script_open_ms
10205 == point->decision.script_bar_open_ms);
10207 for (
const auto& pending : pending_same_bar_commands_)
10208 all_in_reversal = all_in_reversal || reverses(pending.snapshot);
10209 for (
const auto& pending : pending_entries_)
10210 all_in_reversal = all_in_reversal || reverses(pending.snapshot);
10211 for (
const auto& handle : live_handles_) {
10212 const auto found = placement_.find(handle.incarnation);
10213 if (found != placement_.end())
10214 all_in_reversal = all_in_reversal || reverses(found->second);
10217 if ((empty_entry && opposite_entry) || all_in_reversal) {
10218 SourceShadowPending shadow;
10220 shadow.snapshot.
source_id =
"__pine_close_all";
10221 shadow.snapshot.
sizing = sizing_snapshot();
10222 shadow.label = shadow.snapshot.
source_id;
10223 pending_write(source_shadow_pending_).push_back(std::move(shadow));
10228 request.label =
"__pine_close_all";
10229 request.comment = comment;
10239 bool close_sized_before_call =
false;
10240 if (config_.process_orders_on_close && !immediately && !config_.calc_on_order_fills
10241 && !coof_recalc_active_ && policy_script_bar_valid_) {
10244 if (held != 0.0 && last_margin_call_at_script_close_
10245 && last_margin_call_script_bar_ == point->decision.script_bar_open_ms
10246 && last_margin_call_position_cycle_ == current_position_cycle_) {
10248 -(held + (held > 0.0 ? 1.0 : -1.0) * last_margin_call_closed_units_)};
10249 close_sized_before_call =
true;
10253 double coof_close_all_fill = kNaN;
10254 double coof_close_all_target = kNaN;
10255 if (coof_recalc_active_ && !coof_first_open_ && !immediately
10256 && coof_script_bar_valid_) {
10257 int next_extreme_index = -1;
10258 const double next_extreme = coof_next_waypoint(&next_extreme_index);
10259 coof_close_all_target = next_extreme;
10261 const double current_quote = point ? point->price : kNaN;
10263 coof_close_all_fill = source_bar_fill_tick(
10264 next_extreme, staged_.syminfo.mintick)
10265 + (buy ? 1.0 : -1.0) * config_.slippage
10266 * staged_.syminfo.mintick;
10267 if (finite_positive(coof_close_all_fill)
10268 && finite_positive(current_quote)
10269 && !source_same_point(current_quote, coof_close_all_fill,
10270 staged_.syminfo.mintick)) {
10277 const bool extreme_target = next_extreme_index == 1
10278 || next_extreme_index == 2;
10279 const double level = extreme_target ? next_extreme : coof_close_all_fill;
10281 extreme_target && coof_close_all_fill != level};
10282 const bool falling = coof_close_all_fill < current_quote;
10284 request.trigger = falling
10288 request.trigger = falling
10296 snapshot.source_id = request.label;
10297 snapshot.comment = comment;
10298 snapshot.command_ordinal = command_ordinal;
10299 snapshot.immediately = immediately;
10300 snapshot.sizing = sizing_snapshot();
10301 if (immediately && config_.slippage > 0 && coof_recalc_active_
10302 && coof_first_open_ && coof_script_bar_valid_) {
10306 coof_close_all_fill = source_bar_fill_tick(
10307 coof_script_bar_.open, staged_.syminfo.mintick)
10308 + (close_is_buy ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
10311 snapshot.forced_execution_price = coof_close_all_fill;
10312 snapshot.forced_waypoint_print = coof_close_all_target;
10313 snapshot.crosses_zero = close_sized_before_call;
10316 (void)callsite_token;
10317 const auto accepted = submit_or_replace(
10318 std::move(request), std::move(snapshot),
false,
"__pine_close_all");
10325 const bool pooc_ordinary_close_all = config_.process_orders_on_close
10326 && !immediately && !config_.calc_on_order_fills && !coof_recalc_active_;
10327 if (pooc_ordinary_close_all && accepted) {
10328 if (
auto* pine_host = pine_view_of(&require_host()))
10329 pine_host->freeze_script_position_view();
10331 if ((immediately || pooc_ordinary_close_all) && accepted) {
10332 (void)require_host().execute_current(
10333 {*accepted, NativeCurrentPriceRule::NearestTick});
10335 if (!accepted || config_.process_orders_on_close)
return;
10336 const auto close = placement_.find(accepted->incarnation);
10337 if (
close == placement_.end())
return;
10338 const auto side =
static_cast<PositionSide>(
close->second.projection_position_side);
10339 for (
const auto& handle : live_handles_) {
10340 if (handle == *accepted)
continue;
10341 const auto found = placement_.find(handle.incarnation);
10342 if (found == placement_.end())
continue;
10343 auto& pending = found->second;
10344 const bool pure_prior_stop = pending.opening
10346 && finite_positive(pending.exit_levels.stop)
10347 && !finite_positive(pending.exit_levels.limit)
10348 && !finite_positive(pending.exit_levels.trail_points)
10349 && !finite_positive(pending.exit_levels.trail_price)
10350 && !finite_positive(pending.exit_levels.trail_offset)
10351 && !pending.stop_limit_activated
10352 && pending.projection_created_bar <
close->second.projection_created_bar
10353 && pending.projection_position_side ==
static_cast<std::int32_t
>(side)
10355 && !pending.projection_over_pyramiding;
10356 const auto* pine_host = pine_view_of(&require_host());
10357 const bool has_physical_id = pine_host
10358 && pine_host->adapter_has_open_entry_id(pending.source_id);
10359 if (!pure_prior_stop || !has_physical_id)
continue;
10360 pending.preserved_by_close_all = *accepted; invalidate_retirement();
10361 pending.preserved_close_all_bar =
close->second.projection_created_bar;
10365 if (cur_pos != 0.0 && cur_pt) {
10366 std::vector<native_order::RequestHandle> opposite_entries;
10367 for (
const auto& handle : live_handles_) {
10368 if (handle == *accepted)
continue;
10369 const auto found = placement_.find(handle.incarnation);
10370 if (found == placement_.end())
continue;
10371 const auto& pend = found->second;
10372 const bool unpriced_market = !finite_positive(pend.exit_levels.limit)
10373 && !finite_positive(pend.exit_levels.stop)
10374 && !finite_positive(pend.exit_levels.trail_points)
10375 && !finite_positive(pend.exit_levels.trail_price)
10376 && !finite_positive(pend.exit_levels.trail_offset);
10379 && !pend.projection_over_pyramiding
10380 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
10381 opposite_entries.push_back(handle);
10384 for (
const auto& handle : opposite_entries) {
10385 const auto found = placement_.find(handle.incarnation);
10386 if (found == placement_.end())
continue;
10391 const auto result = require_host().cancel(handle);
10392 if (result.status == native_order::CancelStatus::Cancelled) {
10395 const double target = entry_snapshot.
is_long
10397 req.
intent = std::isnan(target)
10399 entry_snapshot.
is_long ? native_order::Side::Long : native_order::Side::Short}}
10404 pending_write(pending_entries_).push_back({std::move(req), std::move(entry_snapshot), replacement_key});
10405#ifdef PINEFORGE_CR277_DIAGNOSTIC
10406 cr277_pending_close_pushes[0].fetch_add(1, std::memory_order_relaxed);
10410 for (
auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
10412 && !it->snapshot.projection_over_pyramiding
10413 && it->snapshot.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
10414 it->snapshot.paired_reversal_parent = *accepted;
10415 it->snapshot.market_admission = {};
10416 pending_write(pending_entries_).push_back({std::move(it->request), std::move(it->snapshot), it->replacement_key});
10417 it = pending_write(pending_same_bar_commands_).erase(it);
10425 const auto openings = openings_for(
id);
10430 const bool ledger_sized = !config_.close_entries_rule_any && !immediately
10431 && std::isnan(qty) && std::isnan(qty_percent);
10435 const double ledger_units = [&] {
10436 if (!ledger_sized)
return 0.0;
10438 const double own = cohort_exposure_for(
id);
10439 return own > 0.0 && std::abs(ledger - own) <= internal::kQtyEpsilon ? own : ledger;
10442 if (openings.empty() && !(ledger_units > 0.0)) {
10443 record_dropped_close(
id, comment, qty, qty_percent, immediately, callsite_token);
10446 const bool has_pending_entry = std::any_of(
10447 pending_entries_.begin(), pending_entries_.end(),
10448 [&](
const PendingEntry& pe) { return pe.snapshot.source_id == id; })
10450 pending_same_bar_commands_.begin(), pending_same_bar_commands_.end(),
10451 [&](
const PendingSameBarCommand& pc) { return pc.snapshot.source_id == id; });
10453 const double closable = ledger_sized ? ledger_units : cohort_exposure_for(
id);
10454 if (!config_.process_orders_on_close && !
id.empty() && !(closable > 0.0)
10455 && !has_pending_entry) {
10456 record_dropped_close(
id, comment, qty, qty_percent, immediately, callsite_token);
10459 const std::uint64_t command_ordinal = ++command_ordinal_;
10467 if (!config_.calc_on_order_fills && !config_.process_orders_on_close
10468 && !pending_entries_.empty()) {
10470 const bool has_opposite = std::any_of(
10471 pending_entries_.begin(), pending_entries_.end(), [&](
const PendingEntry&
entry) {
10472 return live != 0.0 && entry.snapshot.is_long != (live > 0.0);
10474 const bool close_first_percent_add =
10476 && std::abs(config_.default_qty_value - 100.0) < 1e-12
10477 && std::all_of(pending_entries_.begin(), pending_entries_.end(),
10478 [](
const PendingEntry&
entry) {
10479 return entry.snapshot.family == PineOrderFamily::Entry
10480 && !std::isfinite(entry.snapshot.requested_qty)
10481 && !finite_positive(entry.snapshot.exit_levels.limit)
10482 && !finite_positive(entry.snapshot.exit_levels.stop);
10486 double requested_percent = std::isnan(qty_percent) ? 100.0 : qty_percent;
10487 double effective_qty = qty;
10489 if (config_.close_entries_rule_any && !immediately && std::isfinite(qty)) {
10490 const double matching = cohort_exposure_for(
id);
10491 requested_percent = matching > 1e-10
10492 ? std::clamp(std::abs(qty) / matching * 100.0, 0.0, 100.0)
10498 effective_qty = kNaN;
10500 const bool default_fifo_close = !config_.close_entries_rule_any
10501 && std::isnan(qty) && std::isnan(qty_percent);
10502 bool paired_reversal_close =
false;
10503 bool paired_reversal_whole_drop =
false;
10504 std::optional<native_order::RequestHandle> paired_reversal_parent;
10505 if (default_fifo_close && current != 0.0) {
10507 for (
const auto& handle : live_handles_) {
10508 const auto pending = placement_.find(handle.incarnation);
10509 if (pending == placement_.end())
continue;
10510 const auto& row = pending->second;
10512 && row.is_long != (current > 0.0)
10513 && (!point || row.placement_script_open_ms
10514 == point->decision.script_bar_open_ms)) {
10515 paired_reversal_close =
true;
10516 paired_reversal_parent = handle;
10519 if (row.signal_price_refused) {
10520 paired_reversal_whole_drop =
true;
10523 const bool rule5_scope =
10524 config_.default_qty_type
10526 && std::abs(config_.default_qty_value - 100.0) < 1e-12
10527 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
10528 && *staged_.quantity_grid < 1.0
10529 && staged_.syminfo.pointvalue == 1.0
10530 && row.sizing.fx == 1.0
10531 && staged_.account_fx_effective_from_ms.empty()
10532 && config_.commission_value == 0.0 && config_.slippage == 0
10533 && !config_.process_orders_on_close
10534 && !config_.calc_on_order_fills
10535 && finite_positive(row.sizing.frozen_units)
10536 && finite_positive(row.sizing.equity)
10537 && finite_positive(row.sizing.price);
10539 const double notional_per_price = row.sizing.frozen_units
10540 * staged_.syminfo.pointvalue * row.sizing.fx;
10541 const double rounded_cost = source_money_round(
10542 notional_per_price * row.sizing.price);
10543 const double affordable_price = source_money_round(
10544 source_money_round(row.sizing.equity)
10545 / notional_per_price);
10546 paired_reversal_whole_drop =
10547 row.sizing.equity + 1e-9 >= rounded_cost
10548 && std::isfinite(affordable_price)
10549 && affordable_price < row.sizing.price;
10557 if (current != 0.0) {
10558 pending_write(pending_entries_).erase(std::remove_if(pending_entries_.begin(), pending_entries_.end(),
10559 [&](
const PendingEntry&
entry) {
10560 return (current > 0.0) == entry.snapshot.is_long;
10561 }), pending_entries_.end());
10568 const bool frozen_same_bar_close = same_bar_market_tx_scope() && !immediately
10570 const bool deferred_percentage = config_.close_entries_rule_any
10571 && std::isnan(effective_qty) && std::isfinite(requested_percent)
10573 const bool pooc_close_basis = config_.process_orders_on_close;
10574 double frozen_qty = effective_qty;
10575 if (std::isnan(effective_qty) && (!deferred_percentage || immediately || frozen_same_bar_close
10576 || pooc_close_basis)) {
10578 const std::int64_t bar_key = point ? point->decision.script_bar_open_ms
10579 : require_host().native_decision_floor();
10581 const bool ledger_basis = ledger_sized && !config_.process_orders_on_close;
10582 const double source_basis = ledger_basis
10583 ? std::min(ledger_units, fallback_basis) : cohort_exposure_for(
id);
10584 const double script_basis = source_basis > 0.0 ? source_basis : fallback_basis;
10585 if (pooc_close_basis_count_ == 0 || bar_key != pooc_close_basis_last_bar_) {
10587 fold.
h = pooc_close_basis_digest_;
10589 fold.
d(script_basis);
10590 pooc_close_basis_digest_ = fold.
h;
10591 pooc_close_basis_last_bar_ = bar_key;
10592 ++pooc_close_basis_count_;
10594 frozen_qty = quantize_close_units(script_basis, requested_percent);
10596 const double close_basis = ledger_sized && !config_.process_orders_on_close
10597 && ledger_units > 0.0
10598 ? std::min(ledger_units, std::abs(current))
10599 : cohort_exposure_for(
id) > 0.0 ? cohort_exposure_for(
id) : std::abs(current);
10600 const bool closes_full_position = close_basis > 0.0
10601 && ((std::isfinite(frozen_qty) && frozen_qty >= close_basis - 1e-10)
10602 || (std::isnan(effective_qty) && requested_percent >= 100.0 - 1e-9));
10603 bool reversal_pair =
false;
10604 if (closes_full_position && current != 0.0) {
10607 && candidate.is_long != (current > 0.0)
10608 && (!finite_positive(candidate.exit_levels.limit)
10609 && !finite_positive(candidate.exit_levels.stop));
10611 for (
const auto& pending : pending_same_bar_commands_)
10612 reversal_pair = reversal_pair || opposite_entry(pending.snapshot);
10613 for (
const auto& handle : live_handles_) {
10614 const auto found = placement_.find(handle.incarnation);
10615 if (found != placement_.end())
10616 reversal_pair = reversal_pair || opposite_entry(found->second);
10619 const bool batched_pooc_fifo = default_fifo_close
10620 && !immediately && config_.process_orders_on_close
10621 && !config_.calc_on_order_fills
10622 && !coof_recalc_active_ && !
cap.active()
10624 if (batched_pooc_fifo) {
10625 const bool enqueued = enqueue_pooc_fifo_close(
10626 id, comment, callsite_token, command_ordinal);
10627 if (enqueued && closes_full_position)
10628 cancel_exit_orders_for_full_close(
id);
10631 if (closes_full_position && !reversal_pair) {
10632 cancel_exit_orders_for_full_close(
id);
10639 if (same_bar_market_tx_scope() && !immediately && current != 0.0
10640 && (!std::isnan(qty) || !std::isnan(qty_percent))) {
10641 const bool variable_default = config_.default_qty_type
10643 if (variable_default) {
10644 bool held_reentry =
false;
10645 bool opposite_reversal =
false;
10646 const bool held_long = current > 0.0;
10647 for (
const auto& pending : pending_same_bar_commands_) {
10649 if (pending.snapshot.source_id ==
id && pending.snapshot.is_long == held_long)
10650 held_reentry =
true;
10651 if (pending.snapshot.is_long != held_long) opposite_reversal =
true;
10653 if (held_reentry && opposite_reversal) {
10659 if (short_seed_context_is_live() && current < 0.0) {
10662 native_order::HostSizedKind::Close, std::nullopt};
10663 placeholder.
label =
"__close__" + id;
10665 std::numeric_limits<double>::min()};
10666 placeholder.
owner = owner_for_close(
id,
true);
10672 placeholder_snapshot.
qty_percent = requested_percent;
10673 placeholder_snapshot.
is_long =
false;
10676 placeholder_snapshot.
sizing = sizing_snapshot();
10677 (void)submit_or_replace(std::move(placeholder), std::move(placeholder_snapshot),
10678 false,
"__short_seed_partial_hold__" +
id);
10684 if (same_bar_market_tx_scope() && !immediately &&
id.size() != 0
10685 && std::isnan(qty) && std::isnan(qty_percent) && current != 0.0
10686 && finite_positive(frozen_qty)) {
10688 request.label =
"__close__" + id;
10689 request.comment = comment;
10692 snapshot.source_id = id;
10693 snapshot.from_entry = id;
10694 snapshot.comment = comment;
10695 snapshot.requested_qty = frozen_qty;
10696 snapshot.qty_percent = requested_percent;
10697 snapshot.command_ordinal = command_ordinal;
10698 snapshot.is_long =
false;
10699 snapshot.frozen_market_instruction =
true;
10700 snapshot.frozen_market_transaction_units = frozen_qty;
10701 snapshot.frozen_market_targeted_close =
true;
10702 snapshot.frozen_market_target_was_long = current > 0.0;
10703 snapshot.birth = capture_order_birth();
10704 snapshot.sizing = sizing_snapshot();
10706 snapshot.placement_cycle = current_position_cycle_;
10708 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
10709 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
10711 const SourceId replacement_key = callsite_token == 0
10713 :
"__pine_close_site__" + std::to_string(callsite_token);
10714 pending_write(pending_same_bar_commands_).push_back(
10715 {std::move(request), std::move(snapshot), replacement_key,
false});
10716 pending_same_bar_close_qty_ += frozen_qty;
10723 const auto* close_pine_view = pine_view_of(host_);
10724 std::vector<double> preceding_fifo_closes;
10725 if (default_fifo_close && !immediately && close_pine_view
10726 && close_pine_view->pyramid_entries_.size() > 1U) {
10728 for (
const auto& handle : live_handles_) {
10729 const auto found = placement_.find(handle.incarnation);
10730 if (found == placement_.end())
continue;
10731 const auto& pending = found->second;
10733 || !pending.from_entry.empty()
10734 || pending.frozen_market_instruction
10735 || !point || pending.placement_script_open_ms
10736 != point->decision.script_bar_open_ms)
continue;
10737 if (callsite_token != 0) {
10738 const auto prior = live_by_source_key_.find(checked_live_key(
10739 "__pine_close_site__" + std::to_string(callsite_token)));
10740 if (prior != live_by_source_key_.end() && prior->second == handle)
continue;
10742 preceding_fifo_closes.push_back(pending.projection_remaining_qty);
10745 const bool fractional_fifo_book = default_fifo_close && close_pine_view
10746 && close_pine_view->pyramid_entries_.size() > 1U
10747 && source_fifo_close_needs_resolution(
10748 frozen_qty, close_pine_view->pyramid_entries_, preceding_fifo_closes);
10749 const bool host_sized = !immediately
10750 && (fractional_fifo_book || (std::isnan(frozen_qty) && !default_fifo_close));
10751 const bool default_full_any = config_.close_entries_rule_any
10752 && std::isnan(qty) && std::isnan(qty_percent) && !immediately;
10753 const bool pooc_cap_full_close = default_fifo_close
10754 && config_.process_orders_on_close &&
cap.active();
10756 request.intent = host_sized
10761 request.label =
"__close__" + id;
10762 request.comment = comment;
10763 double coof_close_fill = kNaN;
10764 double coof_close_target = kNaN;
10771 bool coof_close_next_open =
false;
10772 bool coof_close_at_second_extreme =
false;
10773 bool coof_close_next_sub_bar =
false;
10774 if (coof_recalc_active_ && !coof_first_open_ && !immediately) {
10776 const bool lower_path = state.spec && state.spec->intrabar.lower();
10777 if (!lower_path && coof_fill_at_second_extreme()) {
10778 coof_close_next_open = coof_fill_forced_;
10779 coof_close_at_second_extreme = !coof_fill_forced_;
10780 if (coof_close_at_second_extreme) {
10781 const double endpoint = source_path_uses_high_first(coof_script_bar_)
10782 ? coof_script_bar_.low : coof_script_bar_.high;
10783 const bool buy = current < 0.0;
10784 coof_close_target = endpoint;
10785 coof_close_fill = source_bar_fill_tick(endpoint, staged_.syminfo.mintick)
10786 + (buy ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
10788 }
else if (lower_path) {
10789 coof_close_next_sub_bar = coof_waits_for_next_sub_bar();
10792 if (coof_recalc_active_ && !coof_first_open_ && !immediately
10793 && !coof_close_next_open && !coof_close_at_second_extreme
10794 && !coof_close_next_sub_bar
10795 && coof_script_bar_valid_) {
10796 int next_waypoint_index = -1;
10797 const double next_waypoint = coof_next_waypoint(&next_waypoint_index);
10799 const double current_quote = point ? point->price : kNaN;
10800 const bool buy = current < 0.0;
10801 coof_close_target = next_waypoint;
10802 coof_close_fill = source_bar_fill_tick(
10803 next_waypoint, staged_.syminfo.mintick)
10804 + (buy ? 1.0 : -1.0) * config_.slippage
10805 * staged_.syminfo.mintick;
10806#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
10807 coof_close_fill = fixture_adapter_execution_value(
"slippage.recalc.close", coof_close_fill);
10809 check_slippage_fill_price(coof_close_fill, std::isfinite(next_waypoint));
10810 if (finite_positive(coof_close_fill) && finite_positive(current_quote)
10811 && !source_same_point(current_quote, coof_close_fill, staged_.syminfo.mintick)) {
10823 const bool extreme_target = next_waypoint_index == 1
10824 || next_waypoint_index == 2;
10825 const double level = extreme_target ? next_waypoint : coof_close_fill;
10827 extreme_target && coof_close_fill != level};
10828 const bool falling = coof_close_fill < current_quote;
10830 request.trigger = falling
10834 request.trigger = falling
10844 request.owner = pooc_cap_full_close
10846 : default_fifo_close
10847 ? (paired_reversal_close && !paired_reversal_whole_drop
10848 && paired_reversal_parent
10850 *paired_reversal_parent}}
10852 : owner_for_close(
id, host_sized && !default_full_any);
10855 snapshot.from_entry = default_fifo_close
10856 && (!paired_reversal_close || paired_reversal_whole_drop) ?
SourceId{} : id;
10857 const bool exact_full_dynamic_close = host_sized && std::isnan(qty)
10858 && requested_percent >= 100.0;
10859 snapshot.comment = comment;
10860 snapshot.requested_qty = exact_full_dynamic_close ? kNaN : frozen_qty;
10861 snapshot.qty_percent = requested_percent;
10862 snapshot.projection_remaining_qty = frozen_qty;
10863 snapshot.command_ordinal = command_ordinal;
10864 snapshot.is_long =
false;
10865 if ((immediately || cohort_exposure_for(
id) >= std::abs(current) - internal::kQtyEpsilon) && config_.slippage > 0 && coof_recalc_active_
10866 && coof_first_open_ && coof_script_bar_valid_) {
10869 coof_close_fill = source_bar_fill_tick(
10870 coof_script_bar_.open, staged_.syminfo.mintick)
10871 + (current < 0.0 ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
10874 snapshot.forced_execution_price = coof_close_fill;
10875 snapshot.forced_waypoint_print = coof_close_target;
10876 snapshot.immediately = immediately; snapshot.deferred_cohort = host_sized; snapshot.sizing = sizing_snapshot();
10877 if (paired_reversal_parent && !paired_reversal_whole_drop)
10878 snapshot.paired_reversal_parent = *paired_reversal_parent;
10883 const bool all_in_percent = std::isnan(effective_qty)
10884 && requested_percent >= 100.0 - 1e-9
10886 && config_.default_qty_value >= 100.0;
10887 bool opposite_reversal_pair =
false;
10888 if (all_in_percent && current != 0.0 && !staged_.quantity_grid) {
10889 const bool held_long = current > 0.0;
10893 && candidate.is_long != held_long
10894 && (!point || candidate.placement_script_open_ms
10895 == point->decision.script_bar_open_ms);
10897 for (
const auto& pending : pending_same_bar_commands_)
10898 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
10899 for (
const auto& pending : pending_entries_)
10900 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
10901 for (
const auto& pending : pending_coof_requests_)
10902 opposite_reversal_pair = opposite_reversal_pair || same_bar(pending.snapshot);
10903 for (
const auto& handle : live_handles_) {
10904 const auto found = placement_.find(handle.incarnation);
10905 if (found != placement_.end())
10906 opposite_reversal_pair = opposite_reversal_pair || same_bar(found->second);
10909 if (opposite_reversal_pair) {
10915 hold_reversal_pair_brackets(
id);
10916 pending_write(source_shadow_pending_).push_back({snapshot,
"__close__" +
id});
10919 bool all_in_dependent_close =
false;
10920 if (all_in_percent) {
10923 std::optional<native_order::RequestHandle> reentry;
10924 for (
const auto& handle : live_handles_) {
10925 const auto found = placement_.find(handle.incarnation);
10926 if (found == placement_.end() || !found->second.opening
10928 || found->second.placement_script_open_ms
10929 != point->decision.script_bar_open_ms
10930 || found->second.source_id !=
id) {
10942 const auto found = placement_.find(reentry->incarnation);
10943 if (found != placement_.end() && found->second.projection_over_pyramiding) {
10944 const auto& held = found->second;
10945 bool earlier_opposite =
false;
10946 for (
const auto& handle : live_handles_) {
10947 const auto prior = placement_.find(handle.incarnation);
10948 if (prior == placement_.end())
continue;
10949 const auto& row = prior->second;
10951 && row.is_long != held.is_long
10952 && row.placement_script_open_ms == held.placement_script_open_ms
10953 && row.source_sequence < held.source_sequence) {
10954 earlier_opposite =
true;
10958 if (!earlier_opposite) reentry.reset();
10963 all_in_dependent_close =
true;
10964 hold_reversal_pair_brackets(
id);
10968 if (coof_recalc_active_ && !coof_first_open_ && !immediately
10969 && !finite_positive(coof_close_fill)
10970 && coof_script_bar_valid_
10971 && std::holds_alternative<native_order::Market>(request.trigger)) {
10973 const double current_quote = point ? point->price : kNaN;
10974 const double next_waypoint = next_source_path_waypoint(
10975 coof_script_bar_, coof_context_.coordinate.path_phase, current_quote,
10976 source_path_uses_high_first(coof_script_bar_),
10977 staged_.syminfo.mintick, config_.slippage);
10979 const double next_fill = nearest_tick(
10980 next_waypoint + (buy ? 1.0 : -1.0) * config_.slippage
10981 * staged_.syminfo.mintick,
10982 staged_.syminfo.mintick);
10983 if (finite_positive(next_fill) && finite_positive(current_quote)
10984 && next_fill != current_quote) {
10985 const bool falling = next_fill < current_quote;
10987 request.trigger = falling
10991 request.trigger = falling
10995 snapshot.forced_execution_price = next_fill;
11000 const SourceId replacement_key = default_fifo_close
11001 ? (callsite_token == 0
11003 :
"__pine_close_site__" + std::to_string(callsite_token))
11004 : (callsite_token == 0 ?
SourceId{}
11005 :
id +
"#close#" + std::to_string(callsite_token));
11006 if (coof_close_next_open) {
11007 snapshot.forced_execution_price = kNaN;
11009 pending_write(pending_coof_requests_).push_back({
11010 std::move(request), std::move(snapshot), replacement_key,
11017 if (default_fifo_close && !config_.process_orders_on_close && callsite_token != 0) {
11018 const auto prior = live_by_source_key_.find(checked_live_key(replacement_key));
11020 if (prior != live_by_source_key_.end() && point) {
11021 const auto row = placement_.find(prior->second.incarnation);
11023 && row->second.placement_script_open_ms == point->decision.script_bar_open_ms) {
11024 const SourceId& first = row->second.close_first_id.empty()
11025 ? row->second.source_id : row->second.close_first_id;
11026 if (first !=
id) snapshot.close_first_id = first;
11031 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false, replacement_key);
11032 if (!accepted && all_in_dependent_close) {
11033 pending_write(source_shadow_pending_).push_back({shadow_snapshot,
"__close__" +
id});
11035 const bool pooc_immediate_fifo = config_.process_orders_on_close
11036 && !coof_recalc_active_
11037 && !config_.close_entries_rule_any && closes_full_position
11039 if ((immediately || (config_.process_orders_on_close
11040 && (
cap.active() || pooc_immediate_fifo))) && accepted) {
11041 const auto outcome = require_host().execute_current(
11042 {*accepted, NativeCurrentPriceRule::NearestTick});
11043 if (
const auto* applied = std::get_if<native_order::ExecutionAppliedEvent>(&outcome)) {
11044 consume_cohort_units(
id, *applied);
11045 current_debited_applied_ordinals_.insert(applied->ordinal);
11046 if (applied->terminal) retire(*accepted);
11052 if (intraday_loss_orders_blocked())
return;
11053 require_cohort_ticket();
11054 reroute_fixed_entries_before_request();
11056 if (held == 0.0)
return;
11058 if (point && cap_placement_denied(point->decision))
return;
11063 close_first_script_bar_ = point->decision.script_bar_open_ms;
11064 close_first_side_ = held > 0.0 ? 1 : -1;
11065 close_all_pending_script_bar_ = point->decision.script_bar_open_ms;
11067 if (config_.calc_on_order_fills) {
11069 const std::int64_t script_open = point ? point->decision.script_bar_open_ms
11070 : require_host().native_decision_floor();
11071 std::vector<native_order::RequestHandle> newborns;
11072 for (
const auto& handle : live_handles_) {
11073 const auto placement = placement_.find(handle.incarnation);
11074 if (placement != placement_.end() && placement->second.opening
11076 && placement->second.placement_script_open_ms == script_open) {
11077 newborns.push_back(handle);
11080 for (
const auto& handle : newborns) {
11081 const auto result = require_host().cancel(handle);
11082 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
11089 snapshot.source_id = request.label;
11090 snapshot.sizing = sizing_snapshot();
11096 if (
PF_RARE(owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min()) && point
11097 && owed_follow_up_bar_ == point->decision.script_bar_open_ms && owed_follow_up_point_ == 3
11098 && config_.process_orders_on_close && held < 0.0
11101 snapshot.crosses_zero =
true;
11103 const auto accepted = submit_or_replace(
11104 std::move(request), std::move(snapshot),
false,
"__pine_close_all");
11105 if (!accepted || config_.process_orders_on_close)
return;
11106 const auto close = placement_.find(accepted->incarnation);
11107 if (
close == placement_.end())
return;
11108 const auto side =
static_cast<PositionSide>(
close->second.projection_position_side);
11109 for (
const auto& handle : live_handles_) {
11110 if (handle == *accepted)
continue;
11111 const auto found = placement_.find(handle.incarnation);
11112 if (found == placement_.end())
continue;
11113 auto& pending = found->second;
11114 const bool pure_prior_stop = pending.opening
11116 && finite_positive(pending.exit_levels.stop)
11117 && !finite_positive(pending.exit_levels.limit)
11118 && !finite_positive(pending.exit_levels.trail_points)
11119 && !finite_positive(pending.exit_levels.trail_price)
11120 && !finite_positive(pending.exit_levels.trail_offset)
11121 && !pending.stop_limit_activated
11122 && pending.projection_created_bar <
close->second.projection_created_bar
11123 && pending.projection_position_side ==
static_cast<std::int32_t
>(side)
11125 && !pending.projection_over_pyramiding;
11126 const auto* pine_host = pine_view_of(&require_host());
11127 const bool has_physical_id = pine_host
11128 && pine_host->adapter_has_open_entry_id(pending.source_id);
11129 if (!pure_prior_stop || !has_physical_id)
continue;
11130 pending.preserved_by_close_all = *accepted; invalidate_retirement();
11131 pending.preserved_close_all_bar =
close->second.projection_created_bar;
11135 if (cur_pos != 0.0 && cur_pt) {
11136 std::vector<native_order::RequestHandle> opposite_entries;
11137 for (
const auto& handle : live_handles_) {
11138 if (handle == *accepted)
continue;
11139 const auto found = placement_.find(handle.incarnation);
11140 if (found == placement_.end())
continue;
11141 const auto& pend = found->second;
11142 const bool unpriced_market = !finite_positive(pend.exit_levels.limit)
11143 && !finite_positive(pend.exit_levels.stop)
11144 && !finite_positive(pend.exit_levels.trail_points)
11145 && !finite_positive(pend.exit_levels.trail_price)
11146 && !finite_positive(pend.exit_levels.trail_offset);
11149 && !pend.projection_over_pyramiding
11150 && pend.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
11151 opposite_entries.push_back(handle);
11154 for (
const auto& handle : opposite_entries) {
11155 const auto found = placement_.find(handle.incarnation);
11156 if (found == placement_.end())
continue;
11161 const auto result = require_host().cancel(handle);
11162 if (result.status == native_order::CancelStatus::Cancelled) {
11165 const double target = entry_snapshot.
is_long
11167 req.
intent = std::isnan(target)
11169 entry_snapshot.
is_long ? native_order::Side::Long : native_order::Side::Short}}
11174 pending_write(pending_entries_).push_back({std::move(req), std::move(entry_snapshot), replacement_key});
11175#ifdef PINEFORGE_CR277_DIAGNOSTIC
11176 cr277_pending_close_pushes[1].fetch_add(1, std::memory_order_relaxed);
11180 for (
auto it = pending_same_bar_commands_.begin(); it != pending_same_bar_commands_.end();) {
11182 && !it->snapshot.projection_over_pyramiding
11183 && it->snapshot.placement_script_open_ms == cur_pt->decision.script_bar_open_ms) {
11184 it->snapshot.paired_reversal_parent = *accepted;
11185 it->snapshot.market_admission = {};
11186 pending_write(pending_entries_).push_back({std::move(it->request), std::move(it->snapshot), it->replacement_key});
11187 it = pending_write(pending_same_bar_commands_).erase(it);
11200bool PineExecutionAdapter::margins_disabled() const noexcept {
11207bool PineExecutionAdapter::whole_level_exit(
double limit_price,
double stop_price,
11208 double trail_points,
double trail_price,
11209 double profit_ticks,
double loss_ticks,
double qty,
11210 double qty_percent)
const noexcept {
11211 return !config_.calc_on_order_fills && std::isnan(qty)
11212 && (std::isnan(qty_percent) || qty_percent >= 100.0)
11213 && std::isnan(trail_points) && std::isnan(trail_price)
11214 && std::isnan(profit_ticks) && std::isnan(loss_ticks)
11215 && (finite_positive(limit_price) || finite_positive(stop_price));
11218void PineExecutionAdapter::observe_pending_scan(PendingScanCounter& work)
const noexcept {
11220 work.max_live_handles = std::max<std::uint64_t>(work.max_live_handles, live_handles_.size());
11221 work.max_placement = std::max<std::uint64_t>(work.max_placement, placement_.size());
11222 work.max_pending_bracket_legs = std::max<std::uint64_t>(
11223 work.max_pending_bracket_legs, pending_bracket_legs_.size());
11224 work.max_resident_cohorts = std::max<std::uint64_t>(
11225 work.max_resident_cohorts, cohorts_by_id_.resident_size());
11226 work.max_logical_cohorts = std::max(work.max_logical_cohorts, cohorts_by_id_.size());
11231bool PineExecutionAdapter::priced_entry_order_working(
const SourceId*
id)
const {
11232 auto& work = pending_scan_work_.priced_entry;
11233 observe_pending_scan(work);
11234 const auto entry_of = [&](
const PlacementSnapshot& row) {
11237 && (
id ==
nullptr || row.source_id == *
id)
11238 && (finite_positive(row.exit_levels.limit) || finite_positive(row.exit_levels.stop));
11240 for (
const auto& pending : pending_entries_)
11241 if (entry_of(
pending.snapshot))
return true;
11242 for (
const auto& pending : pending_same_bar_commands_)
11243 if (entry_of(
pending.snapshot))
return true;
11244 for (
const auto& handle : live_handles_) {
11245 ++work.live_handle_iterations;
11246 ++work.placement_find_calls;
11247 const auto row = placement_.find(handle.incarnation);
11248 if (row != placement_.end() && entry_of(row->second))
return true;
11253bool PineExecutionAdapter::entry_order_pending(
const SourceId&
id)
const {
11254 return entry_order_working(&
id);
11259bool PineExecutionAdapter::entry_order_working(
const SourceId*
id)
const {
11260 auto& work = pending_scan_work_.entry;
11261 observe_pending_scan(work);
11262 const auto entry_of = [&](
const PlacementSnapshot& row) {
11263 return row.opening && (
id ==
nullptr || row.source_id == *id)
11266 for (
const auto& pending : pending_entries_)
11267 if (entry_of(
pending.snapshot))
return true;
11268 for (
const auto& pending : pending_same_bar_commands_)
11269 if (entry_of(
pending.snapshot))
return true;
11270 for (
const auto& pending : pending_coof_requests_)
11271 if (entry_of(
pending.snapshot))
return true;
11272 for (
const auto& delayed : delayed_market_orders_)
11273 if (entry_of(delayed.snapshot))
return true;
11274 for (
const auto& handle : live_handles_) {
11275 ++work.live_handle_iterations;
11276 ++work.placement_find_calls;
11277 const auto row = placement_.find(handle.incarnation);
11278 if (row != placement_.end() && entry_of(row->second))
return true;
11286bool PineExecutionAdapter::open_lot_of(
const SourceId&
id)
const {
11287 auto& work = pending_scan_work_.open_lot;
11288 observe_pending_scan(work);
11289 const auto&
host = require_host();
11291 for (
const auto& lot :
host.native_open_lots(kNaN)) {
11292 ++work.open_lot_iterations;
11293 if (lot.entry_label ==
id)
return true;
11301bool PineExecutionAdapter::standing_exit(
const SourceId& exit_id,
11302 const SourceId& from_entry)
const {
11303 auto& work = pending_scan_work_.standing_exit;
11304 observe_pending_scan(work);
11305 const auto standing = [&](
const PlacementSnapshot& row) {
11309 return exit && !row.void_issue && row.source_id ==
exit_id
11310 && row.from_entry == from_entry;
11312 for (
const auto& handle : live_handles_) {
11313 ++work.live_handle_iterations;
11314 ++work.placement_find_calls;
11315 const auto row = placement_.find(handle.incarnation);
11316 if (row != placement_.end() && standing(row->second))
return true;
11318 for (
const auto& pending : pending_bracket_legs_)
11319 if (standing(
pending.snapshot))
return true;
11320 for (
const auto& pending : pending_coof_requests_)
11321 if (standing(
pending.snapshot))
return true;
11322 for (
const auto& delayed : delayed_market_orders_)
11323 if (standing(delayed.snapshot))
return true;
11324 return std::any_of(pending_relative_exits_.begin(), pending_relative_exits_.end(),
11325 [&](
const PendingRelativeExit& value) {
11326 return value.exit_id == exit_id && value.from_entry == from_entry;
11334 if (source_sequence_ == std::numeric_limits<std::uint64_t>::max())
11336 "Pine source sequence exhausted");
11337 row.void_issue =
false;
11338 row.chain_origin_sequence = ++source_sequence_;
11345void PineExecutionAdapter::execute_or_withdraw_close(native_order::RequestHandle close,
11348 (void)require_host().execute_current({
close, NativeCurrentPriceRule::NearestTick});
11351 const auto result = require_host().cancel(
close);
11352 if (result.status == native_order::CancelStatus::Cancelled) retire(
close);
11358void PineExecutionAdapter::withdraw_void_exits(
const SourceId& entry_id) {
11359 const auto voided = [&](
const PlacementSnapshot& row) {
11360 return row.void_issue && row.from_entry ==
entry_id;
11362 pending_write(pending_bracket_legs_).erase(
11363 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
11364 [&](
const PendingBracketLeg& row) { return voided(row.snapshot); }),
11365 pending_bracket_legs_.end());
11366 pending_write(pending_coof_requests_).erase(
11367 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
11368 [&](
const PendingCoofRequest& row) { return voided(row.snapshot); }),
11369 pending_coof_requests_.end());
11370 delayed_market_orders_.erase(
11371 std::remove_if(delayed_market_orders_.begin(), delayed_market_orders_.end(),
11372 [&](
const DelayedMarketOrder& row) { return voided(row.snapshot); }),
11373 delayed_market_orders_.end());
11374 std::vector<native_order::RequestHandle> handles;
11375 for (
const auto& handle : live_handles_) {
11376 const auto row = placement_.find(handle.incarnation);
11377 if (row != placement_.end() && voided(row->second)) handles.push_back(handle);
11379 if (handles.empty())
return;
11380 for (
const auto& handle : handles) {
11381 const auto result = require_host().cancel(handle);
11382 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
11384 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
11385 family->second.remove_all(handles);
11386 if (family->second.empty()) family = bracket_families_.erase(family);
11389 refresh_pending_view();
11402void PineExecutionAdapter::withdraw_unplaced_cap_adds(
const NativeDecisionContext& context) {
11404 if (physical.signed_units == 0.0)
return;
11405 const auto records = open_ledger_records();
11406 if ((records ? *records : physical.lot_count)
11407 <
static_cast<std::size_t
>(config_.pyramiding)) {
11410 std::vector<native_order::RequestHandle> unplaced;
11411 for (
const auto& handle : live_handles_) {
11412 const auto row = placement_.find(handle.incarnation);
11413 if (row == placement_.end())
continue;
11414 const auto&
entry = row->second;
11416 || !
entry.projection_over_pyramiding
11417 ||
entry.is_long != (physical.signed_units > 0.0)
11418 || !(finite_positive(
entry.exit_levels.limit)
11419 || finite_positive(
entry.exit_levels.stop))
11420 ||
entry.placement_script_open_ms >= context.script_bar_open_ms
11421 ||
entry.placement_script_open_ms != last_broker_open_ms_
11422 || cohort_exposure_for(
entry.source_id) > 0.0 || open_lot_of(
entry.source_id)) {
11425 unplaced.push_back(handle);
11427 for (
const auto& handle : unplaced) {
11428 const auto result = require_host().cancel(handle);
11429 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
11436void PineExecutionAdapter::bind_pending_exits_to_fill(
const SourceId& entry_id) {
11437 const auto bind = [&](PlacementSnapshot& row) {
11438 if (row.pending_bound_exit && row.from_entry == entry_id) { row.pending_bound_exit =
false; invalidate_retirement(); }
11440 for (
auto& pending : pending_bracket_legs_)
bind(
pending.snapshot);
11441 for (
auto& pending : pending_coof_requests_)
bind(
pending.snapshot);
11442 for (
auto& delayed : delayed_market_orders_)
bind(delayed.snapshot);
11443 for (
auto& shadow : source_shadow_pending_)
bind(shadow.snapshot);
11444 for (
const auto& handle : live_handles_) {
11445 const auto row = placement_.find(handle.incarnation);
11446 if (row != placement_.end())
bind(row->second);
11455void PineExecutionAdapter::stash_id_bound_exits(
const SourceId& entry_id) {
11457 ||
entry_id.empty() || !entry_order_pending(entry_id)) {
11460 std::vector<native_order::RequestHandle> handles;
11461 for (
const auto& handle : live_handles_) {
11462 const auto row = placement_.find(handle.incarnation);
11463 if (row == placement_.end())
continue;
11464 const auto&
snapshot = row->second;
11469 handles.push_back(handle);
11470 const bool kept = std::any_of(id_bound_exits_.begin(), id_bound_exits_.end(),
11471 [&](
const IdBoundExit& stashed) {
11472 return stashed.exit_id == snapshot.source_id
11473 && stashed.from_entry == snapshot.from_entry;
11482 if (handles.empty())
return;
11483 for (
const auto& handle : handles) {
11484 const auto result = require_host().cancel(handle);
11485 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
11487 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
11488 family->second.remove_all(handles);
11489 if (family->second.empty()) family = bracket_families_.erase(family);
11492 refresh_pending_view();
11503void PineExecutionAdapter::settle_void_global_exits(
bool market_entry) {
11504 const auto voided = [](
const PlacementSnapshot& row) {
return row.void_global_issue; };
11505 if (!market_entry) {
11506 for (
auto& row : pending_bracket_legs_) row.snapshot.void_global_issue =
false;
11507 for (
auto& row : pending_coof_requests_) row.snapshot.void_global_issue =
false;
11508 for (
auto& row : delayed_market_orders_) row.snapshot.void_global_issue =
false;
11509 for (
const auto& handle : live_handles_) {
11510 const auto row = placement_.find(handle.incarnation);
11511 if (row != placement_.end() && voided(row->second))
11512 row->second.void_global_issue =
false;
11516 pending_write(pending_bracket_legs_).erase(
11517 std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
11518 [&](
const PendingBracketLeg& row) { return voided(row.snapshot); }),
11519 pending_bracket_legs_.end());
11520 pending_write(pending_coof_requests_).erase(
11521 std::remove_if(pending_coof_requests_.begin(), pending_coof_requests_.end(),
11522 [&](
const PendingCoofRequest& row) { return voided(row.snapshot); }),
11523 pending_coof_requests_.end());
11524 delayed_market_orders_.erase(
11525 std::remove_if(delayed_market_orders_.begin(), delayed_market_orders_.end(),
11526 [&](
const DelayedMarketOrder& row) { return voided(row.snapshot); }),
11527 delayed_market_orders_.end());
11528 std::vector<native_order::RequestHandle> handles;
11529 for (
const auto& handle : live_handles_) {
11530 const auto row = placement_.find(handle.incarnation);
11531 if (row != placement_.end() && voided(row->second)) handles.push_back(handle);
11533 if (handles.empty())
return;
11534 const auto working = require_host().native_working_requests();
11535 handles.erase(std::remove_if(handles.begin(), handles.end(),
11536 [&](
const native_order::RequestHandle& handle) {
11537 return std::none_of(working.begin(), working.end(),
11538 [&](const NativeWorkingRequest& request) {
11539 return request.definition->handle == handle;
11541 }), handles.end());
11542 if (handles.empty())
return;
11543 for (
const auto& handle : handles) {
11544 const auto result = require_host().cancel(handle);
11545 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
11547 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
11548 family->second.remove_all(handles);
11549 if (family->second.empty()) family = bracket_families_.erase(family);
11552 refresh_pending_view();
11557void PineExecutionAdapter::rearm_id_bound_exits(
const SourceId& entry_id) {
11558 if (!entry_order_pending(entry_id))
return;
11559 std::vector<IdBoundExit> rearm;
11560 for (
auto row = id_bound_exits_.begin(); row != id_bound_exits_.end();) {
11561 if (row->from_entry == entry_id) {
11562 rearm.push_back(std::move(*row));
11563 row = id_bound_exits_.erase(row);
11568 for (
const auto& row : rearm) {
11569 exit(row.exit_id, row.from_entry, row.levels.limit, row.levels.stop,
11570 row.levels.trail_points, row.levels.trail_offset, row.levels.trail_price,
11571 row.qty_percent, row.comment, row.qty, row.oca_name, row.levels.profit_ticks,
11572 row.levels.loss_ticks);
11577 double limit_price,
double stop_price,
double trail_points,
11578 double trail_offset,
double trail_price,
double qty_percent,
11579 const std::string& comment,
double qty,
11580 const std::string& oca_name,
double profit_ticks,
11581 double loss_ticks) {
11582 const double requested_qty_percent = qty_percent;
11583 if (intraday_loss_orders_blocked())
return;
11585 point && cap_placement_denied(point->decision)) {
11588 const bool cohort_candidate = !from_entry.empty()
11589 && (!std::isnan(limit_price) || !std::isnan(stop_price)
11590 || !std::isnan(profit_ticks) || !std::isnan(loss_ticks)
11591 || !std::isnan(trail_points) || !std::isnan(trail_price));
11592 require_cohort_ticket(cohort_candidate ? &from_entry :
nullptr);
11593 reroute_fixed_entries_before_request();
11594 if (!from_entry.empty()) {
11595 const auto token = named_entry_cancel_tokens_.find(from_entry);
11596 if (token != named_entry_cancel_tokens_.end()) {
11597 bool recreated_parent = std::any_of(
11598 pending_entries_.begin(), pending_entries_.end(),
11599 [&](
const PendingEntry& pending) {
11600 return pending.snapshot.opening
11601 && pending.snapshot.source_id == from_entry;
11603 for (
const auto& handle : live_handles_) {
11604 const auto parent = placement_.find(handle.incarnation);
11605 if (parent != placement_.end() && parent->second.opening
11607 && parent->second.source_id == from_entry) {
11608 recreated_parent =
true;
11615 if (!recreated_parent) named_entry_cancel_tokens_.erase(token);
11618 const bool actionable = !std::isnan(limit_price) || !std::isnan(stop_price)
11619 || !std::isnan(profit_ticks) || !std::isnan(loss_ticks)
11620 || !std::isnan(trail_points) || !std::isnan(trail_price);
11644 const bool void_issue = !from_entry.empty() && !(cohort_exposure_for(from_entry) > 0.0)
11645 && !(!config_.close_entries_rule_any
11647 && !entry_order_pending(from_entry) && !open_lot_of(from_entry)
11648 && !standing_exit(exit_id, from_entry);
11656 const bool pending_bound_exit = !from_entry.empty() && !void_issue
11658 && margins_disabled()
11659 && whole_level_exit(limit_price, stop_price, trail_points, trail_price, profit_ticks,
11660 loss_ticks, qty, requested_qty_percent)
11662 && !(cohort_exposure_for(from_entry) > 0.0)
11664 && priced_entry_order_working(&from_entry) && !open_lot_of(from_entry);
11676 const bool void_global_issue = from_entry.empty()
11678 && !config_.calc_on_order_fills
11680 && !entry_order_working(
nullptr);
11685 if (!pending_same_bar_commands_.empty()
11688 flush_pending_same_bar_commands();
11690 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
11692 "Pine source command sequence exhausted");
11694 const std::uint64_t command_sequence = ++source_command_sequence_;
11700 const double source_trail_points = trail_points;
11701 const double source_trail_offset = trail_offset;
11702 const double source_trail_price = trail_price;
11703 const bool has_trail_request = !std::isnan(source_trail_points)
11704 || !std::isnan(source_trail_price);
11709 const SourceId partial_exit_key = exit_id +
"\x1f" + from_entry;
11714 const double live_exit_units = std::max(
11715 0.0, std::abs(physical.signed_units) - pending_same_bar_close_qty_);
11716 double reissue_percent = std::isnan(qty_percent)
11717 ? 100.0 : std::clamp(qty_percent, 0.0, 100.0);
11718 if (!std::isnan(qty) && live_exit_units > internal::kQtyEpsilon)
11719 reissue_percent = std::min(qty, live_exit_units) / live_exit_units * 100.0;
11720 const bool partial_reissue = reissue_percent < 100.0 - internal::kFullPercentEps;
11721 if (
const auto consumed = consumed_partial_exit_cycles_.lookup(partial_exit_key);
11722 consumed && partial_reissue
11723 && live_exit_units > internal::kQtyEpsilon
11724 && *consumed == current_position_cycle_) {
11727 if (partial_reissue && exit_tombstoned(exit_id, from_entry))
return;
11730 if (from_entry.empty()
11731 && per_entry_exit_call(exit_id, from_entry, limit_price, stop_price, source_trail_points,
11732 source_trail_offset, source_trail_price, qty_percent, comment,
11733 qty, oca_name, profit_ticks, loss_ticks, command_sequence)) {
11736 double entry_price = require_host().position_avg_price();
11737 const double tick = staged_.syminfo.mintick;
11738 bool parent_long = physical.signed_units > 0.0;
11739 bool known_parent_level = physical.signed_units != 0.0;
11746 const bool parent_filled_this_cycle = physical.signed_units != 0.0
11747 && !from_entry.empty()
11749 const auto filled = cohorts_by_id_.find(from_entry);
11750 return filled != cohorts_by_id_.end() && !filled->second.opened.empty();
11753 if (parent_filled_this_cycle)
return;
11757 if (!from_entry.empty() && parent.source_id != from_entry) {
11772 if (from_entry.empty() && physical.signed_units != 0.0
11782 if (cohort_exposure_for(from_entry) > 0.0
11783 && ((physical.signed_units > 0.0) == parent.is_long)) {
11786 parent_long = parent.is_long;
11787 known_parent_level = finite_positive(parent.exit_levels.limit);
11788 if (known_parent_level) entry_price = parent.exit_levels.limit;
11790 for (
const auto& parent : pending_same_bar_commands_)
11791 observe_staged_parent(parent.snapshot);
11792 for (
const auto& parent : pending_entries_)
11793 observe_staged_parent(parent.snapshot);
11794 for (
const auto& parent : pending_coof_requests_)
11795 observe_staged_parent(parent.snapshot);
11796 const auto cohort = cohorts_by_id_.find(from_entry);
11797 for (
const auto& handle : live_handles_) {
11798 const auto parent = placement_.find(handle.incarnation);
11799 const bool already_opened = cohort != cohorts_by_id_.end()
11800 && std::find(cohort->second.opened.begin(), cohort->second.opened.end(), handle)
11801 != cohort->second.opened.end();
11802 if (parent != placement_.end() && !already_opened)
11803 observe_staged_parent(parent->second);
11807 if (cohort != cohorts_by_id_.end() && !parent_filled_this_cycle) {
11808 for (
const auto position : live_origin_positions(cohort->second)) {
11809 const auto& origin = cohort->second.origins[position];
11810 const auto parent = placement_.find(origin.incarnation);
11811 if (parent == placement_.end() || !parent->second.opening
11812 || !origin_is_pending(origin)
11813 || std::find(cohort->second.opened.begin(), cohort->second.opened.end(), origin)
11814 != cohort->second.opened.end()) {
11826 const bool same_side_live = physical.signed_units != 0.0
11827 && (physical.signed_units > 0.0) == parent->second.is_long
11828 && cohort_exposure_for(from_entry) > 0.0;
11829 if (same_side_live)
continue;
11830 parent_long = parent->second.is_long;
11834 known_parent_level =
false;
11838 if (known_parent_level && finite_positive(entry_price) && finite_positive(tick)) {
11839 const bool long_side = physical.signed_units != 0.0 ? physical.signed_units > 0.0 : parent_long;
11840 if (!finite_positive(limit_price) && finite_positive(profit_ticks)) {
11841 limit_price = entry_price + (long_side ? 1.0 : -1.0) * profit_ticks * tick;
11842 if (!std::isfinite(limit_price)) {
11843 engine_command_value_invalid_or_count(
"relative_price.level",
11844 EngineValueStage::relative_price, EngineValueReason::numeric_overflow);
11847 if (!finite_positive(stop_price) && finite_positive(loss_ticks)) {
11848 stop_price = entry_price - (long_side ? 1.0 : -1.0) * loss_ticks * tick;
11849 if (!std::isfinite(stop_price)) {
11850 engine_command_value_invalid_or_count(
"relative_price.level",
11851 EngineValueStage::relative_price, EngineValueReason::numeric_overflow);
11854 if (std::isfinite(source_trail_points)) {
11855 const double trail_ticks = source_trail_points_ticks(
11857 trail_price = directional_tick(entry_price
11858 + (long_side ? 1.0 : -1.0) * trail_ticks * tick,
11860 if (std::isfinite(trail_ticks) && !std::isfinite(trail_price)) {
11861 engine_command_value_invalid_or_count(
"relative_price.level",
11862 EngineValueStage::relative_price, EngineValueReason::numeric_overflow);
11866 limit_price = source_level_on_price_grid(limit_price, tick);
11867 stop_price = source_level_on_price_grid(stop_price, tick);
11877 std::optional<double> native_trail_offset_ticks;
11878 if (has_trail_request && std::isfinite(source_trail_offset)
11879 && source_trail_offset >= 0.0 && finite_positive(tick)) {
11880 const double offset_ticks = std::floor(source_trail_offset);
11881 native_trail_offset_ticks = offset_ticks == 0.0 ? 0.5 : offset_ticks;
11883 const bool unresolved_trail = has_trail_request
11884 && !finite_positive(trail_price) && std::isfinite(source_trail_points);
11885 const bool unresolved_ticks =
11886 (!finite_positive(limit_price) && finite_positive(profit_ticks))
11887 || (!finite_positive(stop_price) && finite_positive(loss_ticks));
11888 const bool unresolved_relative = unresolved_trail || unresolved_ticks;
11890 if (unresolved_relative && !void_issue) {
11891 PendingRelativeExit pending;
11892 pending.exit_id = exit_id; pending.from_entry = from_entry;
11893 pending.trail_points = source_trail_points; pending.trail_offset = source_trail_offset;
11894 pending.trail_price = source_trail_price; pending.qty_percent = qty_percent;
11895 pending.comment = comment; pending.qty = qty; pending.oca_name = oca_name;
11896 pending.profit_ticks = profit_ticks; pending.loss_ticks = loss_ticks;
11897 pending.void_global = void_global_issue;
11898 auto existing = std::find_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
11899 [&](
const PendingRelativeExit& value) {
11900 return value.exit_id == exit_id && value.from_entry == from_entry;
11902 if (existing == pending_relative_exits_.end()) {
11903 pending_relative_exits_.push_back(std::move(pending));
11905 const auto same = [](
double left,
double right) {
11906 return same_double_bits(left, right) || (std::isnan(left) && std::isnan(right));
11908 const bool unchanged = same(existing->trail_points, pending.trail_points)
11909 && same(existing->trail_offset, pending.trail_offset)
11910 && same(existing->trail_price, pending.trail_price)
11911 && same(existing->qty_percent, pending.qty_percent)
11912 && same(existing->qty, pending.qty)
11913 && same(existing->profit_ticks, pending.profit_ticks)
11914 && same(existing->loss_ticks, pending.loss_ticks)
11915 && existing->comment == pending.comment
11916 && existing->oca_name == pending.oca_name;
11918 if (!unchanged) withdraw_anchored_relative_legs(&exit_id, &from_entry);
11919 *existing = std::move(pending);
11922 if (unresolved_relative) {
11923 pending_write(source_shadow_pending_).erase(
11924 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
11925 [&](
const SourceShadowPending& row) {
11926 return row.snapshot.source_id == exit_id
11927 && row.snapshot.from_entry == from_entry;
11929 source_shadow_pending_.end());
11935 if (finite_non_negative(limit_price) || finite_non_negative(stop_price)) {
11938 if (void_issue)
return;
11952 shadow.
exit_levels = {limit_price, stop_price, source_trail_points,
11953 source_trail_offset, source_trail_price,
11954 profit_ticks, loss_ticks};
11955 shadow.
sizing = sizing_snapshot();
11956 pending_write(source_shadow_pending_).push_back({std::move(shadow), exit_id});
11960 if (std::isnan(qty) && qty_percent == 100.0) {
11961 const auto cohort = cohorts_by_id_.find(from_entry);
11962 if (cohort != cohorts_by_id_.end() && !cohort->second.origins.members().empty()) {
11963 const auto origin = cohort->second.origins.back();
11968 const auto parent = placement_.contains(origin.incarnation)
11969 ? placement_.find(origin.incarnation) : placement_.end();
11971 parent->second.has_full_entry_bracket =
true;
11974 const bool dynamic = std::isnan(qty);
11975 const auto family_key = key_for(exit_id, from_entry);
11976 const bool defer_for_same_bar_priority = !config_.calc_on_order_fills
11977 && std::any_of(pending_entries_.begin(), pending_entries_.end(),
11978 [](
const PendingEntry& pending) {
11979 return pending.snapshot.retained_parent_topology;
11981 const bool defer_for_same_bar_add_exit = dynamic
11982 && !config_.calc_on_order_fills && !coof_recalc_active_
11983 && physical.signed_units != 0.0 && !from_entry.empty()
11984 && (!std::isfinite(qty_percent) || qty_percent >= 100.0)
11985 && std::any_of(pending_entries_.begin(), pending_entries_.end(),
11986 [&](
const PendingEntry& pending) {
11987 const auto& add = pending.snapshot;
11988 return add.opening && add.family == PineOrderFamily::Entry
11989 && add.source_id == from_entry
11990 && add.is_long == (physical.signed_units > 0.0)
11991 && !finite_positive(add.exit_levels.limit)
11992 && !finite_positive(add.exit_levels.stop)
11993 && !finite_positive(add.exit_levels.trail_offset)
11994 && add.oca_name.empty();
12005 const auto same_dynamic_percent = [](
double prior_percent,
double request_percent) {
12006 if (std::isfinite(prior_percent) && std::isfinite(request_percent)
12007 && prior_percent >= 100.0 - 1e-12
12008 && request_percent >= 100.0 - 1e-12) {
12011 return same_double_bits(prior_percent, request_percent);
12013 const auto unchanged_dynamic_leg = [&](
PineOrderFamily family,
double level) {
12014 const SourceId replacement_key = exit_id +
"\x1f" + from_entry
12015 + std::to_string(
static_cast<int>(family));
12016 const auto live = live_by_source_key_.find(checked_live_key(replacement_key));
12017 if (live == live_by_source_key_.end())
return false;
12018 const auto placement = placement_.find(live->second.incarnation);
12019 if (placement == placement_.end())
return false;
12020 const auto& prior = placement->second;
12021 if (prior.family != family || !prior.deferred_cohort || !std::isnan(prior.requested_qty)
12022 || prior.source_id != exit_id || prior.from_entry != from_entry
12023 || prior.comment != comment || prior.oca_name != oca_name
12024 || prior.oca_type != 0
12025 || !same_dynamic_percent(prior.qty_percent, qty_percent)
12026 || prior.bracket_origin.incarnation != 0
12027 || !same_double_bits(prior.exit_levels.limit, limit_price)
12028 || !same_double_bits(prior.exit_levels.stop, stop_price)
12029 || !std::isnan(prior.exit_levels.trail_points)
12030 || !std::isnan(prior.exit_levels.trail_offset)
12031 || !std::isnan(prior.exit_levels.trail_price)
12032 || !std::isnan(prior.exit_levels.profit_ticks)
12033 || !std::isnan(prior.exit_levels.loss_ticks)
12034 || reissue_revives_declined_exit(prior)) {
12038 && same_double_bits(prior.exit_levels.limit, level))
12040 && same_double_bits(prior.exit_levels.stop, level));
12047 const bool plain_dynamic_bracket = dynamic && !coof_recalc_active_
12048 && !materializing_relative_ && !config_.calc_on_order_fills
12049 && (from_entry.empty() || cohort_exposure_for(from_entry) > 0.0)
12050 && physical.signed_units != 0.0 && std::isnan(trail_points)
12051 && std::isnan(trail_offset) && std::isnan(trail_price)
12052 && std::isnan(profit_ticks) && std::isnan(loss_ticks)
12053 && (finite_positive(limit_price) || finite_positive(stop_price));
12054 if (plain_dynamic_bracket
12055 && (!finite_positive(limit_price)
12057 && (!finite_positive(stop_price)
12062 if (void_issue)
break;
12063 const auto live = live_by_source_key_.find(checked_live_key(
12064 exit_id +
"\x1f" + from_entry + std::to_string(
static_cast<int>(family))));
12065 if (live == live_by_source_key_.end())
continue;
12066 if (
const auto row = placement_.find(live->second.incarnation);
12067 row != placement_.end() && row->second.void_issue) {
12068 unvoid_exit(row->second);
12079 ? owner_for_close(from_entry, !materializing_relative_)
12081 const SourceId dynamic_group_name = dynamic
12082 ? (oca_name.empty() ? exit_id +
"\x1f" + from_entry : oca_name) :
SourceId{};
12083 const SourceId dynamic_key_prefix = dynamic ? exit_id +
"\x1f" + from_entry :
SourceId{};
12084 const auto pending_default_reversal_parent = [&](
const PlacementSnapshot& parent) {
12086 || parent.source_id != from_entry) {
12093 if (physical.signed_units == 0.0)
return false;
12094 return !std::isfinite(parent.requested_qty)
12095 && parent.is_long != (physical.signed_units > 0.0);
12097 bool binds_pending_reversal_entry =
false;
12098 for (
const auto& pending : pending_same_bar_commands_)
12099 binds_pending_reversal_entry = binds_pending_reversal_entry
12100 || pending_default_reversal_parent(pending.snapshot);
12101 for (
const auto& pending : pending_entries_)
12102 binds_pending_reversal_entry = binds_pending_reversal_entry
12103 || pending_default_reversal_parent(pending.snapshot);
12104 for (
const auto& handle : live_handles_) {
12105 const auto found = placement_.find(handle.incarnation);
12106 if (found != placement_.end()
12107 && pending_default_reversal_parent(found->second)) {
12108 binds_pending_reversal_entry =
true;
12111 double reserved_exit_qty = kNaN;
12112 double pending_parent_units = 0.0;
12115 if (!reservation_point || !parent.opening
12117 || parent.source_id != from_entry
12118 || parent.placement_script_open_ms
12119 != reservation_point->decision.script_bar_open_ms
12120 || parent.projection_over_pyramiding) {
12123 const double units = finite_positive(parent.requested_qty)
12124 ? std::abs(parent.requested_qty) : parent.frozen_market_own_units;
12125 if (finite_positive(units)) pending_parent_units += units;
12127 for (
const auto& handle : live_handles_) {
12128 const auto parent = placement_.find(handle.incarnation);
12129 if (parent != placement_.end()) observe_pending_parent(parent->second);
12131 for (
const auto& parent : pending_entries_) observe_pending_parent(parent.snapshot);
12135 const bool fifo_named_entry = !config_.close_entries_rule_any && !from_entry.empty();
12142 const bool ancestry_sized_target = !from_entry.empty() && std::isfinite(limit_price)
12143 && std::isnan(stop_price) && std::isnan(profit_ticks)
12144 && std::isnan(loss_ticks) && !has_trail_request;
12148 double completed_entry_units = 0.0;
12149 if (std::isnan(qty) && std::isfinite(requested_qty_percent)
12150 && requested_qty_percent < 100.0 && physical.signed_units != 0.0
12151 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()) {
12152 const auto& host = require_host();
12153 const auto* pine = pine_view_of(host_);
12154 const std::size_t base = pine ? pine->trades_.base() : 0;
12155 observe_position_tail_rows();
12156 if (!position_tail_summary_)
12157 position_tail_summary_ = std::make_shared<detail::AdapterPositionTailSummary>();
12158 auto& summary = *position_tail_summary_;
12159#ifdef PINEFORGE_POSITION_TAIL_BEFORE_QUERY
12160 PINEFORGE_POSITION_TAIL_BEFORE_QUERY(summary);
12162 const bool certified = ancestry_sized_target
12163 ? summary.ancestry(physical.signed_units > 0.0, [&](
const auto& include) {
12166 using CohortRow = std::remove_reference_t<decltype(*cohorts_by_id_.begin())>;
12167 summary.for_each_live_cohort([&](const void* row) {
12168 const auto& cohort = *static_cast<const CohortRow*>(row);
12169 if (fifo_named_entry && cohort.first != from_entry) return;
12170 for (const auto& origin : cohort.second.live_units_by_origin)
12171 include(origin.first);
12174 : summary.margin(physical.signed_units > 0.0,
12175 fifo_named_entry ? &from_entry :
nullptr, completed_entry_units,
12177 const auto include_completed = [&](
bool is_long, std::uint64_t incarnation,
12178 bool margin_call,
const auto& named_entry_matches) {
12179 if (is_long != (physical.signed_units > 0.0))
return false;
const auto held = cohorts_by_id_.held_rows();
12180 return ancestry_sized_target
12181 ? std::any_of(held.begin(), held.end(),
12182 [&](
const auto& cohort) {
12183 return (!fifo_named_entry || cohort.first == from_entry)
12184 && cohort.second.live_units_by_origin.count(
12187 : margin_call && (!fifo_named_entry || named_entry_matches());
12191 host.closed_trade_count(), base,
12192 [&](std::size_t index) ->
Trade {
12193 return host.get_trade(static_cast<int>(index));
12194 }, position_tail_spill_.get(), position_open_script_bar_,
12195 include_completed, from_entry, summary.work,
12197#ifdef PINEFORGE_POSITION_TAIL_OBSERVE
12199 PINEFORGE_POSITION_TAIL_OBSERVE(completed_entry_units, certified);
12202 const double book_basis = (fifo_named_entry
12203 ? std::min(std::abs(physical.signed_units), cohort_exposure_for(from_entry))
12204 :
std::abs(physical.signed_units)) + (ancestry_sized_target ? 0.0 : completed_entry_units);
12205 const double live_reservation_basis = binds_pending_reversal_entry ? 0.0
12206 : std::max(0.0, book_basis
12207 - pending_same_bar_close_qty_ + pending_parent_units);
12208 const double percentage_basis = ancestry_sized_target
12209 ? book_basis + completed_entry_units + pending_parent_units
12210 : live_reservation_basis;
12211 const bool reservation_ok = compute_exit_reservation(exit_id, from_entry, qty, qty_percent,
12212 live_reservation_basis, percentage_basis, reserved_exit_qty);
12213 if (!reservation_ok) {
12218 exit_cancel_bracket(exit_id, from_entry, comment);
12222 const OrderBirth exit_birth = capture_order_birth();
12224 exit_birth, has_trail_request);
12225 const bool historical_cascade =
12227 const bool pooc_short_tick_scope = source_point
12228 && config_.process_orders_on_close && !config_.calc_on_order_fills
12229 && modeled_input() && source_point->decision.sub_count <= 1
12230 && physical.signed_units < 0.0 && physical.lot_count == 1
12231 && position_open_script_bar_ < source_point->decision.script_bar_open_ms
12232 && config_.pyramiding == 0 && config_.slippage == 0
12234 && std::abs(staged_.syminfo.pointvalue - 1.0) < 1e-12
12235 && active_staged_fx(source_point->decision.sub_bar_open_ms) == 1.0
12236 && staged_.account_fx_effective_from_ms.empty()
12238 && (!std::isfinite(qty_percent) || qty_percent >= 100.0 - 1e-9)
12239 && oca_name.empty() && !has_trail_request
12240 && finite_positive(staged_.syminfo.mintick);
12242 if (pooc_short_tick_scope && source_point) {
12243 const double close = nearest_tick(source_point->price, staged_.syminfo.mintick);
12244 if (finite_positive(stop_price) && close >= stop_price)
12246 else if (finite_positive(limit_price) && close <= limit_price)
12250 bool defer_marketable_coof_stop =
false;
12253 bool coof_stop_at_leg_end =
false;
12254 bool coof_limit_waypoint_qualified =
false;
12255 double coof_limit_waypoint_price = kNaN;
12256 double coof_stop_waypoint_price = kNaN;
12257 if (coof_recalc_active_ && !coof_first_open_ && historical_cascade
12258 && coof_script_bar_valid_) {
12260 const bool ordinary_path = !native.spec || native.spec->intrabar.is_none();
12262 if (ordinary_path && point && finite_positive(point->price)) {
12263 const auto phase = coof_context_.coordinate.path_phase;
12264 const bool high_first = source_path_uses_high_first(coof_script_bar_);
12265 double endpoint = next_source_path_waypoint(
12266 coof_script_bar_, phase, point->price, high_first,
12267 staged_.syminfo.mintick, config_.slippage);
12276 const double own_extreme = phase == NativePathPhase::High
12277 ? coof_script_bar_.high
12278 : (phase == NativePathPhase::Low ? coof_script_bar_.low : kNaN);
12279 const bool at_own_extreme = finite_positive(own_extreme)
12280 && coof_fill_at_path_point(own_extreme);
12281 const bool fill_ends_leg = at_own_extreme && !coof_fill_forced_;
12282 if (at_own_extreme && coof_fill_forced_) {
12283 endpoint = next_source_path_waypoint(
12284 coof_script_bar_, phase, own_extreme, high_first,
12285 staged_.syminfo.mintick, config_.slippage);
12288 const bool closing_long = physical.signed_units > 0.0;
12290 && finite_positive(limit_price) && finite_positive(endpoint)) {
12299 const bool marketable = closing_long
12300 ? point->price >= limit_price : point->price <= limit_price;
12309 const double reach_level =
12311 ? source_trigger_threshold(limit_price, staged_.syminfo.mintick,
12312 !closing_long,
true)
12314 const bool endpoint_satisfies = closing_long
12315 ? endpoint >= reach_level : endpoint <= reach_level;
12316 const bool endpoint_ahead = closing_long
12317 ? endpoint > point->price : endpoint < point->price;
12324 const bool in_flight_remainder = !fill_ends_leg && !marketable
12325 && endpoint_satisfies && endpoint_ahead
12327 const bool later_same_open = phase == NativePathPhase::Open
12329 if (in_flight_remainder
12330 || (later_same_open && endpoint_satisfies && endpoint_ahead)) {
12335 const bool off_grid_endpoint = finite_positive(staged_.syminfo.mintick)
12336 && source_bar_fill_tick(endpoint, staged_.syminfo.mintick) != endpoint;
12337 trigger = native_order::Limit{endpoint, off_grid_endpoint};
12338 coof_limit_waypoint_qualified =
true;
12339 coof_limit_waypoint_price = endpoint;
12340 }
else if (later_same_open) {
12343 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
12344 trigger = native_order::StopLimit{
12345 endpoint, threshold ? threshold->price : limit_price};
12346 coof_limit_waypoint_qualified = endpoint_satisfies;
12347 }
else if (marketable && !endpoint_ahead && !endpoint_satisfies
12348 && coof_remaining_recrosses(limit_price, closing_long)) {
12353 const auto* threshold = std::get_if<native_order::Limit>(&trigger);
12354 trigger = native_order::StopLimit{
12355 endpoint, threshold ? threshold->price : limit_price};
12358 && finite_positive(stop_price)) {
12359 const bool marketable = closing_long
12360 ? point->price <= stop_price : point->price >= stop_price;
12361 if (marketable && fill_ends_leg) {
12368 trigger = native_order::Stop{point->price};
12369 coof_stop_waypoint_price = point->price;
12370 coof_stop_at_leg_end =
true;
12371 }
else if (marketable && phase != NativePathPhase::Open
12372 && std::isfinite(qty) && finite_positive(endpoint)
12373 && !source_same_point(point->price, endpoint, staged_.syminfo.mintick)) {
12374 trigger = native_order::Stop{endpoint};
12375 coof_stop_waypoint_price = endpoint;
12376 }
else if (marketable && phase == NativePathPhase::Open
12377 && finite_positive(endpoint)) {
12388 const double tick = staged_.syminfo.mintick;
12389 const bool through_at_extreme = closing_long
12390 ? endpoint <= stop_price : endpoint >= stop_price;
12391 if (source_same_point(point->price, endpoint, tick)) {
12392 trigger = native_order::Stop{point->price};
12393 coof_stop_waypoint_price = point->price;
12394 coof_stop_at_leg_end =
true;
12395 }
else if (through_at_extreme) {
12396 trigger = native_order::Stop{endpoint};
12397 coof_stop_waypoint_price = endpoint;
12399 trigger = native_order::Trail{std::abs(stop_price - endpoint), endpoint};
12400 coof_stop_waypoint_price = nearest_tick(stop_price
12401 + (closing_long ? -1.0 : 1.0) * config_.slippage * tick, tick);
12404 coof_stop_at_leg_end =
true;
12406 defer_marketable_coof_stop = marketable;
12419 const auto opening_parent = placement_.find(coof_market_entry_recalc_incarnation_);
12421 const bool carried_opening_parent = opening_point && opening_parent != placement_.end()
12422 && opening_parent->second.placement_script_open_ms
12423 < opening_point->decision.script_bar_open_ms;
12424 const bool opening_high = coof_script_bar_valid_
12425 && source_decimal_tick(coof_script_bar_.open, staged_.syminfo.mintick)
12426 == source_decimal_tick(coof_script_bar_.high, staged_.syminfo.mintick);
12427 const bool opening_market_short = opening_market_short_scope(family);
12428 if (coof_recalc_active_ && (coof_first_open_ || opening_market_short)
12430 && finite_positive(stop_price) && physical.signed_units != 0.0) {
12433 const bool ordinary_path = !native.spec || native.spec->intrabar.is_none();
12434 if (ordinary_path && point && finite_positive(point->price)
12435 && (coof_first_open_ || point->decision.coordinate.path_phase == NativePathPhase::Open)
12436 && (physical.signed_units > 0.0 ? stop_price > point->price
12437 : stop_price < point->
price)) {
12438 trigger = native_order::Stop{point->price};
12439 coof_stop_waypoint_price = point->price;
12440 coof_stop_at_leg_end =
true;
12441 if (opening_market_short) {
12442 trigger = native_order::Market{};
12443 defer_marketable_coof_stop =
false;
12453 bool coof_open_limit_rests =
false;
12455 && finite_positive(limit_price) && physical.signed_units != 0.0
12456 && coof_script_bar_valid_) {
12459 const bool ordinary_path = !native.spec || native.spec->intrabar.is_none();
12460 const double open_print = coof_script_bar_.open;
12461 if (ordinary_path && point && finite_positive(point->price)
12462 && finite_positive(open_print)) {
12463 const bool closing_long = physical.signed_units > 0.0;
12464 if (closing_long ? limit_price <= open_print : limit_price >= open_print) {
12465 trigger = native_order::Limit{limit_price};
12466 coof_limit_waypoint_price =
12467 source_bar_fill_tick(open_print, staged_.syminfo.mintick);
12468 coof_stop_at_leg_end =
true;
12470 coof_open_limit_rests =
true;
12474 if (pooc_short_tick_scope) {
12475 const double tick = staged_.syminfo.mintick;
12485 trigger = native_order::Limit{source_trigger_threshold(
12486 limit_price, tick,
true,
true)};
12488 && finite_positive(stop_price)) {
12489 trigger = native_order::Stop{source_trigger_threshold(
12490 stop_price, tick,
true,
false)};
12495 bool defer_new_instance,
12496 native_order::RequestHandle bracket_origin = {}) {
12497 native_order::Request
request;
12500 native_order::HostSizedKind::Close, std::nullopt}}
12502 native_order::ExplicitUnits{qty}}};
12504 request.owner = std::move(owner);
12505 request.group = std::move(group);
12513 snapshot.qty_percent = qty_percent;
snapshot.deferred_cohort = host_sized;
12514 snapshot.reservation_deferred_to_pending_entry =
12515 binds_pending_reversal_entry;
12517 snapshot.void_global_issue = void_global_issue;
12518 snapshot.pending_bound_exit = pending_bound_exit;
12520 snapshot.command_sequence = command_sequence;
12521 snapshot.bracket_origin = std::move(bracket_origin);
12522 snapshot.exit_levels = {limit_price, stop_price, source_trail_points,
12523 source_trail_offset, source_trail_price,
12524 profit_ticks, loss_ticks};
12526 snapshot.birth_reach = exit_birth_reach;
12527 snapshot.trail_activation_level = trail_price;
12531 snapshot.retained_trail_best = exit_sizing.price;
12532 snapshot.placement_cycle = current_position_cycle_;
12533 if (source_point) {
12535 projection_bar_index(source_point->decision);
12536 snapshot.projection_position_side = physical.signed_units > 0.0
12538 : (physical.signed_units < 0.0
12541 snapshot.placement_script_open_ms =
12542 source_point->decision.script_bar_open_ms;
12543 snapshot.placement_sub_open_ms = source_point->decision.sub_bar_open_ms;
12545 initialize_l4c_policy(snapshot, {});
12546 if (finite_positive(coof_limit_waypoint_price))
12547 snapshot.forced_execution_price = coof_limit_waypoint_price;
12548 else if (finite_positive(coof_stop_waypoint_price))
12549 snapshot.forced_execution_price = coof_stop_waypoint_price;
12557 const auto live_stop = live_by_source_key_.find(checked_live_key(replacement_key));
12558 const bool stop_leg_rests = live_stop != live_by_source_key_.end() && [&] {
12559 const auto prior = placement_.find(live_stop->second.incarnation);
12560 return prior != placement_.end()
12562 && same_double_bits(prior->second.exit_levels.stop, stop_price)
12563 && !finite_positive(prior->second.forced_execution_price);
12565 if (coof_recalc_active_ && !coof_first_open_ && historical_cascade
12567 && finite_positive(stop_price) && !stop_leg_rests) {
12569 const double birth = point ? point->price : kNaN;
12570 const double waypoint = coof_next_waypoint();
12571 const bool long_position = physical.signed_units > 0.0;
12572 const bool reached_on_next_leg = long_position
12573 ? (birth > stop_price && waypoint <= stop_price)
12574 : (birth < stop_price && waypoint >= stop_price);
12575 if (reached_on_next_leg) {
12576 snapshot.forced_execution_price = source_bar_fill_tick(
12577 waypoint, staged_.syminfo.mintick)
12578 + (long_position ? -1.0 : 1.0) * config_.slippage
12579 * staged_.syminfo.mintick;
12583 if (std::isfinite(reserved_exit_qty)) {
12584 snapshot.projection_remaining_qty = reserved_exit_qty;
12585 const bool foreign_pending_opening = std::any_of(
12586 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
12587 const auto candidate = placement_.find(handle.incarnation);
12588 return candidate != placement_.end()
12589 && candidate->second.opening
12590 && candidate->second.source_id != from_entry;
12592 pending_entries_.begin(), pending_entries_.end(),
12593 [&](
const PendingEntry& candidate) {
12594 return candidate.snapshot.opening
12595 && candidate.snapshot.source_id != from_entry;
12597 const double percent = std::isfinite(requested_qty_percent)
12598 ? requested_qty_percent : 100.0;
12600 || (!binds_pending_reversal_entry
12601 && physical.signed_units != 0.0
12602 && (percent < 100.0 - 1e-9
12603 || reserved_exit_qty < live_reservation_basis - 1e-9
12604 || (source_position > 0.0
12605 && reserved_exit_qty < source_position - 1e-9)
12606 || foreign_pending_opening));
12607 }
else if (!binds_pending_reversal_entry && host_sized
12608 && !std::isfinite(
snapshot.requested_qty)
12609 && source_position > 0.0) {
12610 const double percent = std::isfinite(
snapshot.qty_percent)
12612 snapshot.projection_remaining_qty = quantize_close_units(source_position, percent);
12613 if (!from_entry.empty()) {
12614 std::map<SourceId, double> reserved_by_exit;
12615 for (
const auto& handle : live_handles_) {
12616 const auto existing = placement_.find(handle.incarnation);
12617 if (existing == placement_.end())
continue;
12618 const auto& prior = existing->second;
12622 if (!exit || prior.from_entry != from_entry
12623 || prior.source_id == exit_id || prior.void_issue
12624 || !std::isfinite(prior.projection_remaining_qty)) {
12627 auto& held = reserved_by_exit[prior.source_id];
12628 held = std::max(held, prior.projection_remaining_qty);
12631 for (
const auto& row : reserved_by_exit)
reserved += row.second;
12632 const double available = std::max(0.0, source_position - reserved);
12633 snapshot.projection_remaining_qty = std::min(
12634 snapshot.projection_remaining_qty, available);
12635 snapshot.fixed_exit_reservation = percent < 100.0
12636 || !reserved_by_exit.empty();
12639 if (config_.process_orders_on_close && from_entry.empty() && source_position > 0.0) {
12641 ? std::abs(
snapshot.requested_qty)
12642 : (std::isfinite(
snapshot.projection_remaining_qty)
12643 ?
snapshot.projection_remaining_qty : source_position);
12645 for (
const auto& handle : live_handles_) {
12646 const auto existing = placement_.find(handle.incarnation);
12647 if (existing == placement_.end())
continue;
12648 const auto& prior = existing->second;
12649 const bool global_exit = prior.from_entry.empty()
12653 if (!global_exit || prior.source_id == exit_id
12654 || !std::isfinite(prior.projection_remaining_qty)) {
12657 reserved += std::max(0.0, prior.projection_remaining_qty);
12659 const double available = std::max(0.0, source_position - reserved);
12660 requested = std::min(requested, available);
12661 if (!(requested > 0.0))
return;
12664 if (defer_marketable_coof_stop) {
12665 if (broker_open_epoch_ == std::numeric_limits<std::uint64_t>::max())
12667 "source delayed market epoch exhausted");
12670 projection_bar_index(point->decision);
12671 snapshot.placement_script_open_ms =
12672 point->decision.script_bar_open_ms;
12673 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
12675 snapshot.projection_created_during_coof =
true;
12676 snapshot.projection_coof_at_terminal = coof_context_.is_terminal_sub_bar;
12677 snapshot.projection_coof_mid_bar = !coof_context_.is_terminal_sub_bar;
12678 delayed_market_orders_.push_back({
12679 std::move(request), std::move(snapshot), replacement_key,
12680 broker_open_epoch_ + 1U,
true});
12683 if (coof_recalc_active_ && physical.signed_units != 0.0) {
12685 const double birth = point ? point->price : kNaN;
12686 const bool long_position = physical.signed_units > 0.0;
12688 && finite_positive(stop_price)
12689 && (long_position ? stop_price > birth : stop_price < birth);
12691 && finite_positive(limit_price)
12692 && (long_position ? limit_price < birth : limit_price > birth);
12695 const auto qualified_recross = [&] {
12696 if (coof_limit_waypoint_qualified)
return true;
12697 if (!coof_remaining_recrosses(limit_price, long_position))
return false;
12699 native_order::RequestHandle parent_handle{};
12700 if (
const auto cohort = cohorts_by_id_.find(from_entry);
12701 cohort != cohorts_by_id_.end() && !
cohort->second.opened.empty()) {
12702 parent_handle =
cohort->second.opened.back();
12703 const auto found = placement_.find(parent_handle.incarnation);
12704 if (found != placement_.end()) parent = &found->second;
12706 const bool plain_market_parent = parent
12708 && !finite_positive(parent->exit_levels.limit)
12709 && !finite_positive(parent->exit_levels.stop)
12710 && !finite_positive(parent->exit_levels.trail_offset)
12711 && !finite_positive(parent->exit_levels.trail_price);
12712 if (!plain_market_parent)
return false;
12713 const double next_waypoint = coof_next_waypoint();
12714 const bool reachable_stop = finite_positive(stop_price)
12716 ? (next_waypoint <= stop_price
12717 || coof_script_bar_.close <= stop_price)
12718 : (next_waypoint >= stop_price
12719 || coof_script_bar_.close >= stop_price));
12720 if (reachable_stop)
return false;
12721 const bool competing_opening = std::any_of(
12722 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
12723 if (handle == parent_handle) return false;
12724 const auto candidate = placement_.find(handle.incarnation);
12725 return candidate != placement_.end()
12726 && candidate->second.opening;
12728 if (competing_opening)
return false;
12729 const bool direct_partial = point && immediate_close_placed_on(
12730 point->decision.coordinate.interval_index);
12731 return !direct_partial;
12733 if ((wrong_stop || (wrong_limit && !coof_open_limit_rests))
12734 && !coof_stop_at_leg_end
12735 && (coof_first_open_ || wrong_stop || !qualified_recross())) {
12736 snapshot.defer_until_post_parent_calculation =
true;
12737 delayed_market_orders_.push_back({
12738 std::move(request), std::move(snapshot), replacement_key,
12739 broker_open_epoch_ + 1U});
12744 const bool stage_chart_tick_scope = config_.calc_on_order_fills
12745 && !config_.process_orders_on_close
12746 && coof_recalc_active_ && !defer_coof_tail() && !coof_stop_at_leg_end
12747 && (!native.spec || native.spec->intrabar.is_none())
12750 if (stage_chart_tick_scope) {
12751 snapshot.projection_created_during_coof =
true;
12752 snapshot.projection_coof_at_terminal = coof_context_.is_terminal_sub_bar;
12753 snapshot.projection_coof_mid_bar = !coof_context_.is_terminal_sub_bar;
12754 auto queued = std::find_if(
12755 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
12756 [&](
const PendingBracketLeg& row) {
12757 return row.replacement_key == replacement_key;
12759 PendingBracketLeg staged{std::move(request), std::move(snapshot),
12760 replacement_key, family_key};
12761 if (queued == pending_bracket_legs_.end())
12762 pending_write(pending_bracket_legs_).push_back(std::move(staged));
12764 pending_write(*queued) = std::move(staged);
12767 if (
auto queued = std::find_if(
12768 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
12769 [&](
const PendingBracketLeg& row) {
12770 return row.replacement_key == replacement_key
12771 && row.snapshot.defer_until_post_parent_calculation;
12772 }); queued != pending_bracket_legs_.end()) {
12773 const std::uint64_t predecessor =
12774 queued->snapshot.legs.target().incarnation != 0
12775 ? queued->snapshot.legs.target().incarnation
12776 : queued->snapshot.projection_predecessor;
12777 snapshot.projection_predecessor = predecessor;
12778 if (!queued->snapshot.void_issue ||
snapshot.void_issue)
12779 snapshot.source_sequence = queued->snapshot.source_sequence;
12780 snapshot.defer_until_post_parent_calculation =
true;
12783 projection_bar_index(point->decision);
12784 snapshot.projection_position_side =
12787 pending_write(*queued) = PendingBracketLeg{std::move(request), std::move(snapshot),
12788 replacement_key, family_key};
12791 if (defer_coof_tail() && !finite_positive(coof_stop_waypoint_price)) {
12792 pending_write(pending_coof_requests_).push_back({std::move(request), std::move(snapshot), replacement_key,
12793 false, family_key,
true});
12796 if (
snapshot.reservation_deferred_to_pending_entry
12797 && physical.signed_units == 0.0
12798 && !(cohort_exposure_for(
snapshot.from_entry) > 0.0)) {
12799 auto queued = std::find_if(pending_bracket_legs_.begin(),
12800 pending_bracket_legs_.end(), [&](
const PendingBracketLeg& row) {
12801 return row.replacement_key == replacement_key;
12803 PendingBracketLeg staged{std::move(request), std::move(snapshot),
12804 replacement_key, family_key};
12805 if (queued == pending_bracket_legs_.end())
12806 pending_write(pending_bracket_legs_).push_back(std::move(staged));
12808 pending_write(*queued) = std::move(staged);
12811 bool defer_for_live_parent =
false;
12821 const auto existing_leg = live_by_source_key_.find(checked_live_key(replacement_key));
12822 if (!defer_for_same_bar_priority
12827 || row.source_id ==
snapshot.from_entry);
12829 bool has_pending_parent =
false;
12830 for (
const auto& entry : pending_entries_) {
12831 if (matches_parent(entry.snapshot)) {
12832 has_pending_parent =
true;
12836 for (
const auto& entry : pending_same_bar_commands_) {
12837 if (matches_parent(entry.snapshot)) {
12838 has_pending_parent =
true;
12842 for (
const auto& live : live_handles_) {
12843 const auto parent = placement_.find(live.incarnation);
12844 if (parent == placement_.end())
continue;
12845 if (!matches_parent(parent->second))
continue;
12846 has_pending_parent =
true;
12849 if (has_pending_parent
12850 && (existing_leg != live_by_source_key_.end()
12851 ||
snapshot.from_entry.empty())) {
12852 defer_for_live_parent =
true;
12855 if (defer_for_same_bar_priority || defer_for_live_parent) {
12856 if (
const auto existing = live_by_source_key_.find(checked_live_key(replacement_key));
12857 existing != live_by_source_key_.end()) {
12858 const auto predecessor = existing->second;
12859 const auto previous = placement_.find(predecessor.incarnation);
12860 if (previous != placement_.end()) {
12861 snapshot.projection_predecessor = predecessor.incarnation;
12862 if (!previous->second.void_issue ||
snapshot.void_issue)
12863 snapshot.source_sequence = previous->second.source_sequence;
12865 (void)require_host().cancel(predecessor);
12866 retire(predecessor);
12869 snapshot.projection_created_bar = projection_bar_index(point->decision);
12870 snapshot.projection_created_bar_pinned =
true;
12873 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
12874 [&](
const PendingBracketLeg& row) {
12875 return row.replacement_key == replacement_key;
12877 PendingBracketLeg staged{std::move(request), std::move(snapshot), replacement_key,
12879 if (queued == pending_bracket_legs_.end())
12880 pending_write(pending_bracket_legs_).push_back(std::move(staged));
12882 pending_write(*queued) = std::move(staged);
12885 if (defer_new_instance && live_by_source_key_.find(checked_live_key(replacement_key))
12886 == live_by_source_key_.end()) {
12887 auto queued = std::find_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
12888 [&](
const PendingBracketLeg& row) { return row.replacement_key == replacement_key; });
12889 PendingBracketLeg staged{std::move(request), std::move(snapshot), replacement_key, family_key};
12890 if (queued == pending_bracket_legs_.end()) pending_write(pending_bracket_legs_).push_back(std::move(staged));
12891 else pending_write(*queued) = std::move(staged);
12900 for (
auto stale = pending_bracket_legs_.begin();
12901 stale != pending_bracket_legs_.end();) {
12902 if (
stale->replacement_key == replacement_key)
12903 stale = pending_write(pending_bracket_legs_).erase(
stale);
12907 const std::uint64_t placement_high_water = placement_.high_water();
12909 && opening_market_short && coof_stop_at_leg_end
12910 && opening_point && opening_point->decision.coordinate.path_phase == NativePathPhase::Open && std::holds_alternative<native_order::Market>(
request.trigger);
12911 snapshot.coof_open_stop_next_waypoint = opening_stop_now && !coof_first_open_
12912 && opening_high && !carried_opening_parent;
12913 const bool continue_after_open =
snapshot.coof_open_stop_next_waypoint;
12914 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
12917 auto& family = bracket_family(family_key);
12922 if (accepted->incarnation > placement_high_water || !family.holds(*accepted)) {
12923 family.push_back(*accepted);
12925 if (opening_stop_now) {
12926 const auto outcome = require_host().execute_current(
12927 {*accepted, NativeCurrentPriceRule::NearestTick});
12928 if (continue_after_open
12929 && std::holds_alternative<native_order::ExecutionAppliedEvent>(outcome)) {
12930 coof_open_stop_next_waypoint_ =
true;
12937 native_order::RequestHandle pending_origin{};
12938 bool defer_until_parent =
false;
12939 if (!from_entry.empty()) {
12940 const auto cohort = cohorts_by_id_.find(from_entry);
12941 if (cohort != cohorts_by_id_.end() &&
cohort->second.opened.empty()) {
12942 for (
const auto position : live_origin_positions(
cohort->second)) {
12943 const auto& origin =
cohort->second.origins[position];
12944 if (origin_is_pending(origin)) {
12945 pending_origin = origin;
12946 defer_until_parent =
true;
12953 if (defer_until_parent) {
12954 const auto cohort = cohorts_by_id_.find(from_entry);
12955 if (cohort != cohorts_by_id_.end())
12956 owner = native_order::BindCohort{
cohort->second.handle};
12957 defer_until_parent =
false;
12959 submit_one(std::move(owner),
true,
12960 dynamic_key_prefix + std::to_string(
static_cast<int>(family)),
12961 group_for(dynamic_group_name, 1,
static_cast<std::int64_t
>(family)),
12962 defer_until_parent || defer_for_same_bar_add_exit,
12970 if (from_entry.empty()) {
12971 const auto group_name = oca_name.empty() ?
exit_id +
"\x1f" + from_entry : oca_name;
12972 submit_one(native_order::Independent{},
true,
12973 exit_id +
"\x1f" + from_entry + std::to_string(
static_cast<int>(family)),
12974 group_for(group_name, 1,
static_cast<std::int64_t
>(family)),
false);
12984 const auto cohort = cohort_for(from_entry);
12985 const auto found = cohorts_by_id_.find(from_entry);
12986 std::vector<native_order::RequestHandle> origins;
12987 if (found != cohorts_by_id_.end()) {
12996 const auto& roster = found->second.origins;
12997 std::vector<std::size_t> positions;
12998 if (
const auto* zero = roster.positions_of(0))
12999 positions.insert(positions.end(), zero->begin(), zero->end());
13000 for (
const auto& live : live_handles_)
13001 if (origin_is_pending(live)) roster.append_positions_of(live, positions);
13002 for (
const auto& opened : found->second.opened)
13003 roster.append_positions_of(opened, positions);
13004 std::sort(positions.begin(), positions.end());
13005 positions.erase(std::unique(positions.begin(), positions.end()), positions.end());
13006 for (
const auto position : positions) origins.push_back(roster[position]);
13008 if (origins.empty()) origins.push_back({});
13009 for (
const auto& origin : origins) {
13010 const std::string origin_key = std::to_string(origin.incarnation);
13011 const auto replacement_key =
exit_id +
"\x1f" + from_entry +
"\x1f"
13012 + std::to_string(
static_cast<int>(family)) +
"\x1f" + origin_key;
13013 const auto group_name = oca_name.empty()
13014 ?
exit_id +
"\x1f" + from_entry +
"\x1f" + origin_key
13015 : oca_name + (origin.incarnation != 0 ?
"\x1f" + origin_key :
"");
13016 const bool has_live_leg = live_by_source_key_.find(checked_live_key(replacement_key))
13017 != live_by_source_key_.end();
13018 const bool origin_opened = found != cohorts_by_id_.end()
13019 && std::find(found->second.opened.begin(), found->second.opened.end(), origin)
13020 != found->second.opened.end();
13023 const auto consumed_origin_leg = [&] {
13024 if (
const auto archived = settled_bracket_families_.find(family_key);
13025 archived != settled_bracket_families_.end()) {
13028 archived->second.count > kIndexedFrom ? 1 : 0);
13032 (void)bracket_family(family_key);
13034 return origin_leg_consumed(family_key, family, origin);
13036 if (origin.incarnation != 0 && !has_live_leg && !origin_is_pending(origin)
13037 && (!origin_opened || consumed_origin_leg())) {
13045 submit_one(std::move(owner),
true, replacement_key,
13046 group_for(group_name, 1,
static_cast<std::int64_t
>(family)),
13047 !has_live_leg, origin);
13050 const bool exit_is_buy = !parent_long;
13059 const auto cycle_cohort = from_entry.empty()
13060 ? cohorts_by_id_.end() : cohorts_by_id_.find(from_entry);
13061 const bool from_entry_filled_this_cycle = from_entry.empty()
13062 || (cycle_cohort != cohorts_by_id_.end()
13063 && !cycle_cohort->second.opened.empty());
13064 bool placed_absolute_leg =
false;
13065 if (finite_non_negative(limit_price)) {
13067 exit_limit_trigger(limit_price, tick, exit_is_buy)});
13068 placed_absolute_leg =
true;
13069 }
else if (std::isfinite(limit_price) && limit_price < 0.0
13070 && physical.signed_units > 0.0 && from_entry_filled_this_cycle) {
13073 placed_absolute_leg =
true;
13083 if (finite_non_negative(stop_price)
13084 || (std::isfinite(stop_price) && stop_price < 0.0)) {
13085 const double native_stop = source_trigger_threshold(
13086 stop_price, tick, exit_is_buy,
false);
13088 placed_absolute_leg =
true;
13090 bool trail_one_shot =
false;
13091 if (has_trail_request && finite_positive(trail_price)) {
13097 double native_trail_price = trail_price;
13098 bool trail_already_reached =
false;
13099 double placement_print = kNaN;
13100 const bool zero_distance = native_trail_offset_ticks
13101 && std::isfinite(source_trail_offset)
13102 && std::floor(source_trail_offset) == 0.0;
13103 if (finite_positive(tick)) {
13105 const bool buy_close = physical.signed_units != 0.0
13106 ? physical.signed_units < 0.0 : exit_is_buy;
13110 const bool already_reached = point
13111 && source_trail_reached_at(point->price, trail_price, tick, buy_close);
13112 trail_already_reached = already_reached;
13113 if (point) placement_print = point->price;
13114 const bool no_trailing_distance = !native_trail_offset_ticks || zero_distance;
13120 trail_one_shot = no_trailing_distance && !already_reached;
13121 if (zero_distance && point) {
13126 if (already_reached) {
13127 native_trail_price = nearest_tick(point->price, tick);
13131 if (trail_one_shot) {
13132 const double one_shot_level = one_shot_trail_trigger(
13133 native_trail_price, source_trail_offset, tick, exit_is_buy,
13134 !std::isfinite(source_trail_offset) || zero_distance);
13143 const bool slipped_touch = zero_distance && config_.slippage > 0
13144 && finite_positive(tick);
13146 native_order::Limit{one_shot_level, slipped_touch});
13147 }
else if (native_trail_offset_ticks) {
13151 std::optional<double> native_arm_price =
13152 source_trail_arm_level(native_trail_price, tick, exit_is_buy);
13154 if (trail_already_reached)
13155 native_arm_price.reset();
13156 native_order::Trail native_trail{
13157 0.0, native_arm_price,
13158 native_order::TrailTicks{*native_trail_offset_ticks}};
13174 double best_seed = trail_price;
13175 if (trail_already_reached && std::isfinite(placement_print)) {
13176 best_seed = exit_is_buy ? std::min(best_seed, placement_print)
13177 : std::max(best_seed, placement_print);
13179 if (finite_positive(best_seed)) native_trail.best_seed = best_seed;
13181 }
else if (trail_already_reached) {
13191 const double slipped = trail_price + (exit_is_buy ? 1.0 : -1.0)
13192 * config_.slippage * tick;
13194 directional_tick(slipped, tick, exit_is_buy)});
13197 const bool exit_at_activation_trail = has_trail_request
13198 && (!std::isfinite(source_trail_offset)
13199 || std::floor(source_trail_offset) == 0.0);
13200 if (!trail_one_shot && exit_at_activation_trail && !finite_positive(stop_price)
13201 && finite_positive(trail_price)) {
13204 const bool already_armed = source_trail_reached_at(
13205 point->price, trail_price, tick, !long_side);
13206 if (already_armed) {
13213 const double carried_best = long_side
13214 ? std::max(point->price, trail_price)
13215 : std::min(point->
price, trail_price);
13226 if (placed_absolute_leg && !has_trail_request) {
13227 const bool drop_limit = std::isnan(limit_price) && std::isnan(profit_ticks);
13228 const bool drop_stop = std::isnan(stop_price) && std::isnan(loss_ticks);
13230 return row.source_id ==
exit_id && row.from_entry == from_entry
13231 && !std::isfinite(row.trail_activation_level)
13235 if (drop_limit || drop_stop) {
13236 std::vector<native_order::RequestHandle> withdrawn;
13237 for (
const auto& handle : live_handles_) {
13238 const auto found = placement_.find(handle.incarnation);
13239 if (found != placement_.end() && dropped(found->second))
13240 withdrawn.push_back(handle);
13242 for (
const auto& handle : withdrawn) {
13243 const auto result = require_host().cancel(handle);
13244 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
13246 pending_write(pending_bracket_legs_).erase(std::remove_if(pending_bracket_legs_.begin(),
13247 pending_bracket_legs_.end(), [&](
const PendingBracketLeg& leg) {
13248 return dropped(leg.snapshot);
13249 }), pending_bracket_legs_.end());
13250 delayed_market_orders_.erase(std::remove_if(delayed_market_orders_.begin(),
13251 delayed_market_orders_.end(), [&](
const DelayedMarketOrder& order) {
13252 return dropped(order.snapshot);
13253 }), delayed_market_orders_.end());
13256 if (defer_for_same_bar_priority && !from_entry.empty())
13257 named_entry_cancel_tokens_.erase(from_entry);
13258 if (pooc_short_tick_scope && source_point) {
13259 bool competing_entry =
false;
13260 for (
const auto& handle : live_handles_) {
13261 const auto found = placement_.find(handle.incarnation);
13262 if (found != placement_.end() && found->second.opening) {
13263 competing_entry =
true;
13267 if (!competing_entry) {
13268 const double close = nearest_tick(
13269 source_point->price, staged_.syminfo.mintick);
13273 + std::to_string(
static_cast<int>(selected));
13274 const auto live = live_by_source_key_.find(checked_live_key(replacement_key));
13275 if (live != live_by_source_key_.end()) {
13276 const auto found = placement_.find(live->second.incarnation);
13277 if (found != placement_.end()
13278 && found->second.projection_predecessor != 0
13279 && cohort_exposure_for(from_entry) > 0.0) {
13281 cancel_bracket_siblings(live->second);
13282 native_order::Request
request;
13286 request.trigger = native_order::Market{};
13287 immediate.forced_execution_price = close;
13288 immediate.projection_predecessor = live->second.incarnation;
13289 immediate.projection_predecessor_exit =
true;
13290 const auto accepted = submit_or_replace(
13291 std::move(request), std::move(immediate),
false,
13292 "__pooc_current_exit__" + exit_id +
"\x1f" + from_entry);
13301 auto* pine_host = view.current_exit_keeps_position_view
13302 ? pine_view_of(&require_host()) : nullptr;
13303 const double pre_fill_average = require_host().position_avg_price();
13304 if (pine_host) pine_host->freeze_script_position_view();
13305 (void)require_host().execute_current(
13306 {*accepted, NativeCurrentPriceRule::NearestTick});
13307 if (pine_host && view.current_exit_keeps_average_price)
13308 pine_host->hold_script_average_price(pre_fill_average);
13316 if (!placed_absolute_leg
13317 && !(has_trail_request && finite_positive(trail_price))) {
13321 exit_cancel_bracket(exit_id, from_entry, comment);
13322 pending_write(source_shadow_pending_).erase(
13323 std::remove_if(source_shadow_pending_.begin(), source_shadow_pending_.end(),
13324 [&](
const SourceShadowPending& row) {
13325 return row.snapshot.source_id == exit_id
13326 && row.snapshot.from_entry == from_entry;
13328 source_shadow_pending_.end());
13331 : (!std::isnan(stop_price) || !std::isnan(loss_ticks)
13338 snapshot.projection_remaining_qty = reserved_exit_qty;
13339 snapshot.qty_percent = qty_percent;
13340 snapshot.command_sequence = command_sequence;
13341 snapshot.source_sequence = ++source_sequence_;
13344 snapshot.projection_position_side = physical.signed_units > 0.0
13346 : (physical.signed_units < 0.0
13349 snapshot.placement_cycle = current_position_cycle_;
13350 snapshot.exit_levels = {limit_price, stop_price, source_trail_points,
13351 source_trail_offset, source_trail_price,
13352 profit_ticks, loss_ticks};
13353 snapshot.sizing = sizing_snapshot();
13354 pending_write(source_shadow_pending_).push_back({std::move(snapshot),
exit_id});
13360 bool pre_script_drain) {
13361 auto queued = std::move(pending_write(pending_bracket_legs_));
13362 pending_write(pending_bracket_legs_).clear();
13375 const bool parent_walked_after_leg
13377 && position_open_bar_index_
13378 == point->decision.coordinate.interval_index
13379 && position_open_phase_ != NativePathPhase::Open;
13380 const auto resolve_parent_handle
13382 -> std::optional<native_order::RequestHandle> {
13383 auto origin = row.bracket_origin;
13384 if (origin.incarnation == 0 && !row.from_entry.empty()) {
13385 const auto cohort = cohorts_by_id_.find(row.from_entry);
13386 if (cohort != cohorts_by_id_.end()
13387 && !cohort->second.opened.empty()) {
13388 origin = cohort->second.opened.back();
13391 if (placement_.find(origin.incarnation) == placement_.end())
return std::nullopt;
13396 const auto handle = resolve_parent_handle(row);
13397 if (!handle)
return nullptr;
13398 return &placement_.find(handle->incarnation)->second;
13408 const auto parent_precedes_leg
13430 const bool retained_parent_first
13433 std::uint64_t surviving) {
13434 const double percent = std::isfinite(row.
qty_percent)
13444 == point->decision.coordinate.interval_index - 1
13449 && percent >= 100.0 - internal::kFullPercentEps
13460 std::uint64_t parent_incarnation,
13461 std::uint64_t child_predecessor) {
13462 const auto cancelled
13464 const auto surviving
13471 && cancelled != 0 && cancelled < parent_incarnation
13472 && cancelled != child_predecessor
13473 && surviving > cancelled && surviving < parent_incarnation
13475 == point->decision.coordinate.interval_index - 1
13490 std::vector<std::pair<std::string, std::string>> ranked_retained_brackets;
13491 if (pre_script_drain && retained_parent_first && point
13492 && position_open_bar_index_
13493 == point->decision.coordinate.interval_index
13494 && config_.process_orders_on_close && !config_.calc_on_order_fills
13495 && !coof_recalc_active_ && point->decision.sub_count <= 1) {
13496 std::vector<std::pair<std::string, std::string>> book_keys;
13513 const auto held = from_entry_units(row.
from_entry);
13514 if (!held || *held > 0.0)
return false;
13518 for (
const auto& live : live_handles_) {
13519 const auto other = placement_.find(live.incarnation);
13520 if (other != placement_.end() && names_entry(other->second))
return false;
13522 for (
const auto& staged : queued)
13523 if (names_entry(staged.snapshot))
return false;
13524 for (
const auto&
entry : pending_entries_)
13525 if (names_entry(
entry.snapshot))
return false;
13526 for (
const auto& command : pending_same_bar_commands_)
13527 if (names_entry(command.snapshot))
return false;
13528 for (
const auto& pending : pending_coof_requests_)
13529 if (names_entry(pending.snapshot))
return false;
13530 for (
const auto& delayed : delayed_market_orders_)
13531 if (names_entry(delayed.snapshot))
return false;
13535 if (inert_exit(row))
return;
13537 if (std::find(book_keys.begin(), book_keys.end(), key)
13538 == book_keys.end())
13539 book_keys.push_back(key);
13541 for (
const auto& live : live_handles_) {
13542 const auto row = placement_.find(live.incarnation);
13543 if (row != placement_.end()) carry_key(row->second);
13545 for (
const auto& staged : queued) carry_key(staged.snapshot);
13546 for (
const auto&
entry : pending_entries_) carry_key(
entry.snapshot);
13547 for (
const auto& command : pending_same_bar_commands_)
13548 carry_key(command.snapshot);
13549 for (
const auto& pending : pending_coof_requests_)
13550 carry_key(pending.snapshot);
13551 for (
const auto& delayed : delayed_market_orders_)
13552 carry_key(delayed.snapshot);
13553 for (
const auto& leg : queued) {
13554 const auto bracket = std::make_pair(leg.snapshot.
source_id,
13556 if (book_keys.size() != 1 || book_keys.front() != bracket)
continue;
13557 const auto handle = resolve_parent_handle(leg.snapshot);
13558 if (!handle)
continue;
13559 const auto parent = placement_.find(handle->incarnation);
13560 if (parent == placement_.end()
13565 if (!exact_fresh_parent(parent->second, handle->incarnation,
13568 for (
const auto& sibling : queued) {
13569 if (sibling.snapshot.source_id != bracket.first
13570 || sibling.snapshot.from_entry != bracket.second)
continue;
13571 if (!exact_retained_child(sibling.snapshot,
13574 if (std::find(ranked_retained_brackets.begin(),
13575 ranked_retained_brackets.end(), bracket)
13576 == ranked_retained_brackets.end())
13577 ranked_retained_brackets.push_back(bracket);
13587 const auto parent_walks_first
13589 if (!pre_script_drain)
return false;
13590 const auto* parent = resolve_parent(leg);
13591 if (parent ==
nullptr)
return false;
13593 return std::find(ranked_retained_brackets.begin(),
13594 ranked_retained_brackets.end(),
13596 != ranked_retained_brackets.end();
13598 return !parent_walked_after_leg
13599 || parent_precedes_leg(leg, *parent);
13601 if (pre_script_drain) {
13602 std::vector<PendingBracketLeg> resting;
13603 resting.reserve(queued.size());
13604 for (
auto& leg : queued) {
13605 const bool has_parent = resolve_parent(leg.snapshot) !=
nullptr;
13606 if (has_parent && !parent_walks_first(leg.snapshot)) {
13607 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13609 resting.push_back(std::move(leg));
13612 queued = std::move(resting);
13618 std::stable_sort(queued.begin(), queued.end(), [](
const PendingBracketLeg& left,
13619 const PendingBracketLeg& right) {
13620 const auto left_origin = left.snapshot.bracket_origin.incarnation;
13621 const auto right_origin = right.snapshot.bracket_origin.incarnation;
13622 if (left_origin == right_origin) return false;
13623 return left_origin < right_origin;
13630 for (
auto& leg : queued) {
13639 && margins_disabled()
13647 && priced_entry_order_working(
nullptr)) {
13648 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13652 && !(cohort_exposure_for(leg.snapshot.
from_entry) > 0.0)) {
13653 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13667 const auto cohort = cohorts_by_id_.find(leg.snapshot.
from_entry);
13668 if (cohort != cohorts_by_id_.end() && cohort->second.opened.empty()) {
13669 for (
const auto& origin : cohort->second.origins) {
13671 if (!origin_is_pending(origin))
continue;
13672 const auto parent = placement_.find(origin.incarnation);
13673 if (parent == placement_.end() || !parent->second.
opening
13681 const std::string origin_key = std::to_string(origin.incarnation);
13682 const std::string family_tag = std::to_string(
13683 static_cast<int>(leg.snapshot.
family));
13685 leg.replacement_key = leg.snapshot.
source_id +
"\x1f"
13686 + leg.snapshot.
from_entry +
"\x1f" + family_tag +
"\x1f" + origin_key;
13687 const auto group_name = leg.snapshot.
oca_name.empty()
13689 +
"\x1f" + origin_key
13690 : leg.snapshot.
oca_name +
"\x1f" + origin_key;
13691 leg.request.group = group_for(group_name, 1,
13692 static_cast<std::int64_t
>(leg.snapshot.
family));
13705 const bool parent_held = cohort != cohorts_by_id_.end()
13706 && cohort->second.opened.empty()
13708 && std::any_of(pending_entries_.begin(), pending_entries_.end(),
13709 [&](
const PendingEntry& held) {
13710 return held.snapshot.opening
13711 && held.snapshot.family == PineOrderFamily::Entry
13712 && held.snapshot.source_id == leg.snapshot.from_entry
13713 && held.snapshot.placement_script_open_ms
13714 == leg.snapshot.placement_script_open_ms
13715 && held.snapshot.command_sequence
13716 < leg.snapshot.command_sequence;
13723 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13727 bool retained_parent_pending =
false;
13731 const auto cohort = cohorts_by_id_.find(leg.snapshot.
from_entry);
13732 if (cohort != cohorts_by_id_.end()) {
13741 const bool no_open_lot = cohort->second.opened.empty()
13743 const auto live = no_open_lot
13744 ? live_origin_positions(cohort->second) : std::vector<std::size_t>{};
13745 retained_parent_pending = std::any_of(live.begin(), live.end(),
13746 [&](std::size_t position) {
13747 return origin_is_pending(cohort->second.origins[position]);
13754 || retained_parent_pending) {
13755 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13759 && !post_calculation && !parent_walks_first(leg.snapshot)) {
13760 pending_write(pending_bracket_legs_).push_back(std::move(leg));
13763 bool execute_after_calculation =
false;
13772 const bool priced_exit_leg
13777 && priced_exit_leg && policy_script_bar_valid_ && !leg.snapshot.
void_issue) {
13778 const auto cohort = cohorts_by_id_.find(leg.snapshot.
from_entry);
13779 if (cohort != cohorts_by_id_.end() && !cohort->second.opened.empty()) {
13780 const auto parent = placement_.find(cohort->second.opened.back().incarnation);
13781 if (parent != placement_.end() && parent->second.
opening) {
13782 const bool is_stop_leg
13784 const double level = is_stop_leg
13787 const bool high_first = source_path_uses_high_first(policy_script_bar_);
13788 const bool parent_before_child = is_stop_leg
13789 ? (parent->second.
is_long ? high_first : !high_first) :
true;
13796 const double tick = staged_.syminfo.mintick;
13797 const bool raw_bar = config_.calc_on_order_fills
13798 || !finite_positive(tick);
13799 const double bar_high = raw_bar ? policy_script_bar_.high
13800 : source_decimal_tick(policy_script_bar_.high, tick);
13801 const double bar_low = raw_bar ? policy_script_bar_.low
13802 : source_decimal_tick(policy_script_bar_.low, tick);
13803 const bool touched = is_stop_leg
13804 ? (parent->second.
is_long ? bar_low <= level
13805 : bar_high >= level)
13806 : (parent->second.
is_long ? bar_high >= level
13807 : bar_low <= level);
13808 const double fill_price
13810 const bool right_side = is_stop_leg
13811 || (parent->second.
is_long ? level >= fill_price
13812 : level <= fill_price);
13813 execute_after_calculation = parent_before_child && touched && right_side;
13814 if (execute_after_calculation) {
13815 const double units = std::abs(
13830 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
false,
13831 leg.replacement_key);
13833 bracket_family(leg.family_key).push_back(*accepted);
13834 if (execute_after_calculation) {
13835 (void)require_host().execute_current(
13836 {*accepted, NativeCurrentPriceRule::NearestTick});
13842void PineExecutionAdapter::materialize_pending_bracket_legs(
13846 ? nullptr : &parent_it->second;
13847 const bool retained_parent = parent && parent->
opening
13851 pending_write(pending_bracket_legs_).erase(std::remove_if(
13852 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
13853 [&](
const PendingBracketLeg& leg) {
13854 return leg.snapshot.oca_name == parent->oca_name;
13855 }), pending_bracket_legs_.end());
13857 std::uint64_t retained_family = 0;
13858 bool materialize_retained =
false;
13862 const PendingBracketLeg* representative =
nullptr;
13863 bool one_family =
true;
13864 for (
const auto& leg : pending_bracket_legs_) {
13865 if (leg.snapshot.from_entry != parent->
source_id)
continue;
13866 if (!representative) {
13867 representative = ⋚
13868 retained_family = leg.family_key;
13869 }
else if (leg.family_key != retained_family) {
13870 one_family =
false;
13873 bool foreign =
false;
13874 if (representative) {
13877 foreign = placement_.erased_non_margin_id_outside(
13878 parent->
source_id, representative->snapshot.source_id);
13882 [&](std::uint64_t,
const PlacementSnapshot& snapshot) {
13884 &&
snapshot.source_id != representative->snapshot.source_id
13888 for (
const auto& row : placement_) {
13889 if (foreign)
break;
13890 const auto&
snapshot = row.second;
13892 &&
snapshot.source_id != representative->snapshot.source_id
13898 const bool exact_child = representative
13899 && representative->snapshot.projection_predecessor
13901 && representative->snapshot.source_sequence < parent->
source_sequence
13902 && !std::isfinite(representative->snapshot.requested_qty)
13903 && (!std::isfinite(representative->snapshot.qty_percent)
13904 || representative->snapshot.qty_percent >= 100.0)
13905 && finite_positive(representative->snapshot.exit_levels.stop)
13906 && finite_positive(representative->snapshot.exit_levels.limit)
13907 && representative->snapshot.oca_name.empty()
13908 && representative->snapshot.oca_type == 0;
13909 materialize_retained = one_family && !foreign && exact_child
13910 && live_handles_.size() == 1 && live_handles_.front() ==
event.handle();
13912 if (retained_parent && !materialize_retained) {
13913 for (
auto& leg : pending_bracket_legs_) {
13914 if (leg.snapshot.from_entry == parent->
source_id)
13915 leg.snapshot.defer_until_post_parent_calculation =
true;
13918 std::vector<PendingBracketLeg> ready;
13919 for (
auto it = pending_bracket_legs_.begin(); it != pending_bracket_legs_.end();) {
13920 const bool matches_parent = it->snapshot.from_entry.empty()
13922 const bool selected = retained_parent
13923 ? (materialize_retained && it->family_key == retained_family
13924 && it->snapshot.from_entry == parent->
source_id)
13925 : (!it->
snapshot.defer_until_post_parent_calculation
13926 && (it->
snapshot.bracket_origin == event.handle()
13927 || (it->
snapshot.bracket_origin.incarnation == 0 && matches_parent)));
13929 pending_write(it->snapshot).bracket_origin =
event.handle();
13930 ready.push_back(std::move(*it));
13931 it = pending_write(pending_bracket_legs_).erase(it);
13936 for (
auto& leg : ready) {
13940 if (!retained_parent && leg.snapshot.bracket_origin == event.
handle()
13941 && std::isfinite(leg.snapshot.requested_qty)
13942 && std::holds_alternative<native_order::BindCohort>(leg.request.owner)
13943 && !config_.close_entries_rule_any) {
13944 leg.request.owner = owner_for_close(leg.snapshot.from_entry,
false);
13946 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
13947 false, leg.replacement_key);
13948 if (accepted) bracket_family(leg.family_key).push_back(*accepted);
13953 bool explicit_brackets_only,
double current_open) {
13954 require_cohort_ticket();
13955 auto delayed = std::move(delayed_market_orders_);
13956 delayed_market_orders_.clear();
13957 for (
auto&
order : delayed) {
13958 const auto family =
order.snapshot.family;
13959 const bool explicit_bracket = (
order.snapshot.defer_until_post_parent_calculation
13960 || (std::isfinite(
order.snapshot.requested_qty)
13961 &&
order.snapshot.bracket_origin.incarnation != 0))
13964 const bool coof_delayed_price =
order.snapshot.projection_created_during_coof
13967 if (
order.release_open_epoch <= broker_open_epoch_
13968 && (!explicit_brackets_only || explicit_bracket || coof_delayed_price)) {
13974 const auto live_existing = live_by_source_key_.find(checked_live_key(
order.replacement_key));
13975 if (live_existing != live_by_source_key_.end()) {
13976 const auto found_p = placement_.find(live_existing->second.incarnation);
13977 if (found_p != placement_.end()
13978 && found_p->second.command_sequence >
order.snapshot.command_sequence) {
13982 const bool execute_coof_open =
order.execute_at_open
13983 && finite_positive(current_open) && !
order.snapshot.void_issue;
13984 if (execute_coof_open) {
13986 order.snapshot.forced_execution_price = current_open;
13988 const auto family_key = key_for(
13989 order.snapshot.source_id,
order.snapshot.from_entry);
13990 const auto accepted = submit_or_replace(
13991 std::move(
order.request), std::move(
order.snapshot),
13993 order.replacement_key);
13997 bracket_family(family_key).push_back(*accepted);
13999 if (accepted && execute_coof_open) {
14000 (void)require_host().execute_current(
14001 {*accepted, NativeCurrentPriceRule::NearestTick});
14004 delayed_market_orders_.push_back(std::move(
order));
14010 require_cohort_ticket();
14015 host_ !=
nullptr && delayed_market_orders_.empty()
14016 && pending_same_bar_commands_.empty()
14017 && pending_entries_.empty())) {
14018 pending_same_bar_close_qty_ = 0.0;
14021 reroute_incomplete_fixed_batch(
false);
14023 flush_pending_same_bar_commands(
false,
false);
14024 auto queued = std::move(pending_write(pending_entries_));
14025 pending_write(pending_entries_).clear();
14026 auto deferred = std::remove_if(queued.begin(), queued.end(), [&](PendingEntry&
entry) {
14027 const auto parent = entry.snapshot.paired_reversal_parent;
14028 if (parent.incarnation == 0) return false;
14029 const auto found = placement_.find(parent.incarnation);
14030 if (found == placement_.end()
14031 || (found->second.family != PineOrderFamily::Close
14032 && found->second.family != PineOrderFamily::CloseAll)) {
14035 pending_write(pending_entries_).push_back(std::move(
entry));
14038 queued.erase(deferred, queued.end());
14039 const bool recreated_parent = std::any_of(queued.begin(), queued.end(),
14040 [](
const PendingEntry& entry) {
14041 return entry.snapshot.retained_parent_topology;
14043 if (recreated_parent) {
14044 for (
auto& entry : queued) {
14045 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot),
true,
14046 entry.replacement_key);
14050 if (!queued.empty() && !pending_bracket_legs_.empty()) {
14051 auto brackets = std::move(pending_write(pending_bracket_legs_));
14052 pending_write(pending_bracket_legs_).clear();
14055 std::size_t index = 0;
14056 bool entry =
false;
14058 std::vector<Candidate> ordered;
14059 ordered.reserve(queued.size() + brackets.size());
14060 for (std::size_t index = 0; index < queued.size(); ++index) {
14061 ordered.push_back({queued[index].snapshot.is_long ? 1 : 2, index,
true});
14064 for (std::size_t index = 0; index < brackets.size(); ++index) {
14065 const auto&
snapshot = brackets[index].snapshot;
14068 rank = queued_position < 0.0 ? 1 : 2;
14072 ordered.push_back({rank, index,
false});
14074 std::stable_sort(ordered.begin(), ordered.end(), [](
const Candidate& left,
14075 const Candidate& right) {
14076 return left.rank < right.rank;
14078 for (
const auto& candidate : ordered) {
14079 if (candidate.entry) {
14080 auto& entry = queued[candidate.index];
14081 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot),
true,
14082 entry.replacement_key);
14085 auto& leg = brackets[candidate.index];
14086 const auto accepted = submit_or_replace(std::move(leg.request), std::move(leg.snapshot),
14087 false, leg.replacement_key);
14088 if (accepted) bracket_family(leg.family_key).push_back(*accepted);
14092 std::stable_sort(queued.begin(), queued.end(), [](
const PendingEntry& left,
14093 const PendingEntry& right) {
14094 const auto* left_stop = std::get_if<native_order::Stop>(&left.request.trigger);
14095 const auto* right_stop = std::get_if<native_order::Stop>(&right.request.trigger);
14096 const int left_rank = !left_stop ? 2 : (left.snapshot.is_long ? 0 : 1);
14097 const int right_rank = !right_stop ? 2 : (right.snapshot.is_long ? 0 : 1);
14098 return left_rank < right_rank;
14100 for (
auto& entry : queued) {
14101 (void)submit_or_replace(std::move(entry.request), std::move(entry.snapshot),
true,
14102 entry.replacement_key);
14106void PineExecutionAdapter::reroute_fixed_entries_before_request(
bool invalidate_batch) {
14107 require_cohort_ticket();
14109 if (!point)
return;
14110 const auto script_open = point->decision.script_bar_open_ms;
14111 const bool frozen_entry = std::any_of(pending_same_bar_commands_.begin(),
14112 pending_same_bar_commands_.end(), [&](
const PendingSameBarCommand& command) {
14113 return command.unbatched_request && command.unbatched_snapshot
14114 && command.snapshot.placement_script_open_ms == script_open;
14116 if (!frozen_entry)
return;
14117 if (invalidate_batch) source_batch_mutated_ =
true;
14118 auto queued = std::move(pending_write(pending_same_bar_commands_));
14119 pending_write(pending_same_bar_commands_).clear();
14120 pending_same_bar_close_qty_ = 0.0;
14123 for (
auto& command : queued) {
14124 const bool restore_command =
command.unbatched_request &&
command.unbatched_snapshot
14125 &&
command.snapshot.placement_script_open_ms == script_open;
14126 if (restore_command) {
14133 && finite_positive(
snapshot.requested_qty)
14136 && same_point_pair_scope() && same_point_two_leg_book(
14138 const double pending = pending_opposite_market_units(
14141 if (finite_positive(pending)) {
14142 const double transaction = floor_quantity_grid(
14145 snapshot.is_long ? transaction : -transaction};
14149 if (restore_command) {
14150 auto context =
command.unbatched_context;
14151 context.opposite_opening_pending = opposite_entry_opening_pending(
14153 submit_entry_with_policy(std::move(
command.request), std::move(
command.snapshot),
14154 command.replacement_key, context);
14156 (void)submit_or_replace(std::move(
command.request), std::move(
command.snapshot),
14162void PineExecutionAdapter::reroute_incomplete_fixed_batch(
bool invalidate_batch) {
14163 bool fixed_long =
false;
14164 bool fixed_short =
false;
14165 for (
const auto& command : pending_same_bar_commands_) {
14166 if (!
command.unbatched_request)
continue;
14167 if (
command.snapshot.is_long) fixed_long =
true;
14168 else fixed_short =
true;
14170 if ((fixed_long || fixed_short)
14171 && (!(fixed_long && fixed_short) || !fixed_unmargined_market_batch_scope())) {
14172 reroute_fixed_entries_before_request(invalidate_batch);
14176void PineExecutionAdapter::flush_pending_same_bar_commands(
14177 bool flat_pair_follows,
bool invalidate_batch) {
14178 require_cohort_ticket();
14179 reroute_incomplete_fixed_batch(invalidate_batch);
14180 std::vector<PendingSameBarCommand> queued;
14181 queued.swap(pending_write(pending_same_bar_commands_));
14186 struct KeepStorage {
14187 std::vector<PendingSameBarCommand>& queued;
14188 std::vector<PendingSameBarCommand>& member;
14191 if (member.empty() && member.capacity() == 0) member.swap(queued);
14193 } keep_storage{queued, pending_same_bar_commands_};
14194 pending_same_bar_close_qty_ = 0.0;
14195 if (queued.empty())
return;
14198 const auto apply_known_reversal_gap = [&](
const auto& accepted) {
14199 if (!accepted || batch_start == 0.0
14200 || config_.default_qty_type
14202 || config_.default_qty_value > 100.0) {
14205 const auto placed = placement_.find(accepted->incarnation);
14206 if (placed == placement_.end()
14207 || ((batch_start > 0.0) == placed->second.is_long)) {
14211 auto* pine_host = pine_view_of(&require_host());
14212 if (!point || !pine_host)
return;
14213 const auto next = pine_host->scheduler_.next_source_bar(
14214 point->decision.coordinate.interval_index);
14216 NativeDecisionContext next_context = point->decision;
14217 ++next_context.coordinate.interval_index;
14218 next_context.coordinate.path_phase = NativePathPhase::Open;
14219 next_context.script_bar_open_ms = next->timestamp;
14220 next_context.sub_bar_open_ms = next->timestamp;
14221 apply_reversal_gap_bracket_policy(
14222 *next, next_context,
true);
14224 const bool variable_short_context = batch_start < 0.0
14225 && config_.default_qty_type !=
static_cast<int>(
QtyType::FIXED);
14226 const bool full_short_seed = queued.size() == 3U
14228 && queued[0].snapshot.is_long
14230 && !queued[1].snapshot.is_long
14231 && queued[2].snapshot.frozen_market_targeted_close
14232 && queued[0].snapshot.source_id != queued[1].snapshot.source_id
14233 && queued[0].snapshot.source_id != queued[2].snapshot.source_id
14234 && queued[1].snapshot.source_id == queued[2].snapshot.source_id;
14235 const bool partial_short_seed = queued.size() == 2U
14237 && queued[0].snapshot.is_long
14239 && !queued[1].snapshot.is_long
14240 && std::any_of(live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
14241 const auto found = placement_.find(handle.incarnation);
14242 if (found == placement_.end()) return false;
14243 const auto& placeholder = found->second;
14244 return placeholder.family == PineOrderFamily::Close
14245 && placeholder.deferred_cohort
14246 && placeholder.source_id == queued[1].snapshot.source_id
14247 && placeholder.from_entry == queued[1].snapshot.source_id
14248 && placeholder.command_ordinal > queued[1].snapshot.command_ordinal;
14250 const bool potential_short_seed = full_short_seed || partial_short_seed;
14255 if (potential_short_seed && batch_start < 0.0) {
14256 for (
auto& command : queued) {
14257 if (!
command.staged_reversal)
continue;
14258 command.snapshot.frozen_market_transaction_units +=
command.staged_reversal_held_units;
14259 command.snapshot.replaced_opening =
false;
14260 command.snapshot.replacement_predecessor_market =
false;
14265 const bool p2_candidate_scope = config_.pyramiding == 2
14266 && !config_.process_orders_on_close && !config_.calc_on_order_fills
14267 && !coof_recalc_active_ && config_.slippage == 0
14269 && std::abs(config_.margin_long - 100.0) < 1e-12
14270 && std::abs(config_.margin_short - 100.0) < 1e-12
14271 && risk_.direction == 0 && risk_.max_cons_loss_days == 0
14272 && risk_.max_drawdown <= 0.0 && risk_.max_intraday_loss <= 0.0
14273 && risk_.max_position_size <= 0.0 && !risk_.halted && !
cap.active();
14274 const bool prior_entry_like = std::any_of(
14275 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
14276 const auto found = placement_.find(handle.incarnation);
14277 return found != placement_.end()
14278 && (found->second.family == PineOrderFamily::Entry
14279 || found->second.family == PineOrderFamily::Order);
14281 const bool p2_explicit_pair = p2_candidate_scope && !source_batch_mutated_
14282 && !prior_entry_like && queued.size() == 2U
14283 && queued[0].opening && queued[1].opening
14286 && queued[0].snapshot.source_id != queued[1].snapshot.source_id
14287 && queued[0].snapshot.is_long != queued[1].snapshot.is_long
14288 && queued[0].snapshot.command_ordinal < queued[1].snapshot.command_ordinal
14289 && finite_positive(queued[0].
snapshot.requested_qty)
14290 && finite_positive(queued[1].
snapshot.requested_qty)
14291 && queued[0].snapshot.oca_name.empty()
14292 && queued[1].snapshot.oca_name.empty()
14293 && queued[0].snapshot.frozen_market_instruction
14294 && queued[1].snapshot.frozen_market_instruction;
14295 if (config_.pyramiding == 2 && !p2_explicit_pair && !potential_short_seed
14296 && !fixed_unmargined_market_batch_scope()) {
14301 std::stable_sort(queued.begin(), queued.end(), [&](
const auto& left,
14302 const auto& right) {
14303 const bool left_replaces = live_by_source_key_.find(
14304 checked_live_key(left.snapshot.source_id)) != live_by_source_key_.end();
14305 const bool right_replaces = live_by_source_key_.find(
14306 checked_live_key(right.snapshot.source_id)) != live_by_source_key_.end();
14307 return left_replaces && !right_replaces;
14318 std::vector<bool> flat_pair_member(queued.size(),
false);
14319 if (batch_start == 0.0 && same_point_pair_scope() && !source_batch_mutated_
14320 && queued.size() == 1U && queued[0].opening
14322 const auto& row = queued[0].snapshot;
14323 std::size_t opposite = 0;
14324 bool foreign =
false;
14325 for (
const auto& handle : live_handles_) {
14326 const auto found = placement_.find(handle.incarnation);
14327 if (found == placement_.end() || !found->second.opening
14332 const auto& leg = found->second;
14333 const bool priced_leg = finite_positive(leg.exit_levels.stop)
14334 != finite_positive(leg.exit_levels.limit);
14336 && leg.placement_script_open_ms == row.placement_script_open_ms
14337 && leg.is_long != row.is_long && priced_leg
14338 && finite_positive(leg.requested_qty) && leg.oca_name.empty()
14339 && !leg.birth.from_fill()
14340 && (leg.qty_type < 0 || leg.qty_type ==
static_cast<int>(
QtyType::FIXED));
14341 if (this_bar) ++opposite;
14342 else foreign =
true;
14344 flat_pair_member[0] = !foreign
14345 && (flat_pair_follows ? opposite == 0 : opposite == 1);
14347 for (std::size_t i = 0; i < queued.size(); ++i) {
14351 snapshot.paired_flat_market_candidate =
false;
14352 snapshot.paired_flat_market_own_qty = kNaN;
14353 snapshot.paired_flat_market_peer_seq = 0;
14354 snapshot.paired_flat_market_transaction_qty = kNaN;
14355 snapshot.frozen_market_instruction =
false;
14356 const double own = finite_positive(
snapshot.frozen_market_own_units)
14358 if (
command.opening && finite_positive(own) && flat_pair_member[i]) {
14359 const double units = finite_positive(
snapshot.frozen_market_transaction_units)
14360 ?
snapshot.frozen_market_transaction_units : own;
14363 }
else if (
command.opening && finite_positive(own)) {
14364 request.intent = native_order::HostSized{
14365 native_order::HostSizedKind::Open,
14366 snapshot.is_long ? native_order::Side::Long
14367 : native_order::Side::Short};
14370 (void)submit_or_replace(std::move(request), std::move(snapshot),
14375 if (variable_short_context && !potential_short_seed) {
14380 for (
auto& command : queued) {
14383 if (
snapshot.frozen_market_targeted_close) {
14384 request.intent = native_order::HostSized{
14385 native_order::HostSizedKind::Close, std::nullopt};
14388 snapshot.frozen_market_targeted_close =
false;
14389 snapshot.frozen_market_instruction =
false;
14391 snapshot.frozen_market_instruction =
false;
14393 const auto accepted = submit_or_replace(
14394 std::move(request), std::move(snapshot),
command.opening,
14396 apply_known_reversal_gap(accepted);
14406 const bool cap_close_continuation =
14407 cap.configuration().count_pooc_full_close && queued.size() == 2U
14408 && std::count_if(queued.begin(), queued.end(), [](
const auto& command) {
14409 return command.snapshot.frozen_market_targeted_close;
14413 if (queued.size() > 1) {
14414 std::stable_sort(queued.begin(), queued.end(), [&](
const PendingSameBarCommand& left,
14415 const PendingSameBarCommand& right) {
14416 const auto buy_rank = [](const PendingSameBarCommand& command) {
14417 if (command.snapshot.frozen_market_targeted_close)
14418 return command.snapshot.frozen_market_target_was_long ? 1 : 0;
14419 return command.snapshot.is_long ? 0 : 1;
14421 const int left_rank = buy_rank(left);
14422 const int right_rank = buy_rank(right);
14423 if (left_rank != right_rank)
return left_rank < right_rank;
14424 if (cap_close_continuation
14425 && left.snapshot.frozen_market_targeted_close
14426 != right.snapshot.frozen_market_targeted_close) {
14427 return left.snapshot.frozen_market_targeted_close;
14433 const bool single_entry = queued.size() == 1
14434 && !queued.front().snapshot.frozen_market_targeted_close;
14435 const bool one_entry_one_close = queued.size() == 2U
14436 && std::count_if(queued.begin(), queued.end(), [](
const auto& command) {
14437 return command.opening && !command.snapshot.frozen_market_targeted_close;
14439 && std::count_if(queued.begin(), queued.end(), [](
const auto& command) {
14440 return command.snapshot.frozen_market_targeted_close;
14442 double simulated = batch_start;
14443 std::optional<native_order::RequestHandle> short_seed_long;
14444 std::optional<native_order::RequestHandle> short_seed_materialize;
14445 std::optional<native_order::RequestHandle> short_seed_final;
14446 std::optional<native_order::RequestHandle> cap_close_parent;
14447 for (std::size_t i = 0; i < queued.size(); ++i) {
14450 const bool targeted_close =
command.snapshot.frozen_market_targeted_close;
14452 bool opening =
command.opening;
14453 const bool long_candidate = batch_start < 0.0 && opening
14455 const bool final_short_candidate = batch_start < 0.0 && opening
14457 const bool materialize_candidate = batch_start < 0.0
14458 &&
snapshot.frozen_market_targeted_close
14459 && !
snapshot.frozen_market_target_was_long;
14461 if (!
snapshot.frozen_market_targeted_close) {
14462 double units =
snapshot.frozen_market_transaction_units;
14463 if (cap_close_continuation && simulated == 0.0
14464 && finite_positive(
snapshot.frozen_market_own_units)) {
14465 units =
snapshot.frozen_market_own_units;
14467 if (!finite_positive(units))
continue;
14468 if (p2_explicit_pair
14469 && units >
snapshot.frozen_market_own_units + 1e-10) {
14470 const double margin =
snapshot.is_long
14471 ? config_.margin_long : config_.margin_short;
14472 const double required = units *
snapshot.sizing.price
14473 * staged_.syminfo.pointvalue *
snapshot.sizing.fx
14475 if (!std::isfinite(required) || !std::isfinite(
snapshot.sizing.equity)
14476 || required >
snapshot.sizing.equity) {
14480 if (single_entry && !fixed_unmargined_market_batch_scope()) {
14481 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), opening,
14483 apply_known_reversal_gap(accepted);
14484 if (accepted && long_candidate) short_seed_long = *accepted;
14485 if (accepted && final_short_candidate) short_seed_final = *accepted;
14488 const bool affordability_reversal =
snapshot.affordability_policy_active
14489 && batch_start != 0.0
14490 && ((batch_start > 0.0) !=
snapshot.is_long)
14491 && (
snapshot.affordability_close_only || one_entry_one_close);
14492 if (affordability_reversal) {
14493 request.intent = native_order::HostSized{
14494 native_order::HostSizedKind::Open,
14495 snapshot.is_long ? native_order::Side::Long
14496 : native_order::Side::Short};
14499 snapshot.is_long ? units : -units};
14501 simulated +=
snapshot.is_long ? units : -units;
14503 const double target =
snapshot.requested_qty;
14504 if (!finite_positive(target) || simulated == 0.0)
continue;
14505 const bool target_long =
snapshot.frozen_market_target_was_long;
14506 const bool still_target_side = (simulated > 0.0) == target_long;
14507 const double units = std::min(target, std::abs(simulated));
14508 if (!finite_positive(units))
continue;
14509 if (still_target_side) {
14510 request.intent = cap_close_continuation
14511 && units >= std::abs(simulated) - 1e-10
14514 native_order::ExplicitUnits{units}}};
14515 simulated += simulated > 0.0 ? -units : units;
14521 for (std::size_t later = i + 1; later < queued.size(); ++later) {
14522 const auto& sibling = queued[later];
14523 if (!sibling.snapshot.frozen_market_targeted_close
14524 && sibling.snapshot.frozen_market_instruction
14525 && sibling.snapshot.source_id ==
snapshot.source_id
14526 && sibling.snapshot.is_long == target_long) {
14531 if (!artifact)
continue;
14532 const double signed_units = simulated > 0.0 ? units : -units;
14534 simulated += signed_units;
14540 if (cap_close_continuation && opening && cap_close_parent)
14541 request.owner = native_order::WaitForApplied{*cap_close_parent};
14542 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot), opening,
14544 if (accepted && targeted_close)
14545 cap_close_parent = *accepted;
14546 apply_known_reversal_gap(accepted);
14547 if (accepted && long_candidate) short_seed_long = *accepted;
14548 if (accepted && materialize_candidate) short_seed_materialize = *accepted;
14549 if (accepted && final_short_candidate) short_seed_final = *accepted;
14551 if (short_seed_long && short_seed_materialize && short_seed_final) {
14552 const auto materialize_snapshot = placement_.find(short_seed_materialize->incarnation);
14553 const auto long_snapshot = placement_.find(short_seed_long->incarnation);
14554 const auto final_snapshot = placement_.find(short_seed_final->incarnation);
14555 if (materialize_snapshot != placement_.end() && long_snapshot != placement_.end()
14556 && final_snapshot != placement_.end()) {
14558 plan.long_entry = *short_seed_long;
14559 plan.materialize_long = *short_seed_materialize;
14560 plan.final_short = *short_seed_final;
14561 plan.seed_id = materialize_snapshot->second.source_id;
14562 plan.long_entry_id = long_snapshot->second.source_id;
14563 plan.final_short_id = final_snapshot->second.source_id;
14564 plan.materialize_label =
"__close__" +
plan.seed_id;
14565 plan.seed_qty = std::abs(batch_start);
14566 plan.seed_cycle = current_position_cycle_;
14567 pending_short_seed_ = {std::move(plan), broker_open_epoch_ + 1U,
true};
14572 short_seed_ = pending_short_seed_.plan;
14573 maybe_activate_short_seed_plan();
14578void PineExecutionAdapter::materialize_relative_exits(
14580 if (pending_relative_exits_.empty() || !finite_positive(staged_.syminfo.mintick)) {
14583 withdraw_anchored_relative_legs(
nullptr, &opening.source_id);
14586 std::vector<PendingRelativeExit>
pending;
14587 std::vector<PendingRelativeExit> global_pending;
14592 const bool market_opening = !config_.calc_on_order_fills
14593 && !finite_positive(opening.exit_levels.limit) && !finite_positive(opening.exit_levels.stop);
14594 for (
auto it = pending_relative_exits_.begin(); it != pending_relative_exits_.end();) {
14595 if (it->from_entry == opening.source_id) {
14596 pending.push_back(std::move(*it));
14597 it = pending_relative_exits_.erase(it);
14598 }
else if (market_opening && it->from_entry.empty() && !it->void_global) {
14599 global_pending.push_back(std::move(*it));
14600 it = pending_relative_exits_.erase(it);
14608 if (!armed_relative_legs_adoptable(opening, event, pending))
14609 withdraw_anchored_relative_legs(
nullptr, &opening.source_id);
14610 for (
auto& value : global_pending)
pending.push_back(std::move(value));
14611 const double tick = staged_.syminfo.mintick;
14612 for (
const auto& value : pending) {
14613 double limit = kNaN;
14614 double stop = kNaN;
14615 double offset =
value.trail_offset;
14619 const double side = opening.is_long ? 1.0 : -1.0;
14620 if (finite_positive(
value.profit_ticks)) {
14621 limit = directional_tick(event.resolved_price + side *
value.profit_ticks * tick,
14622 tick, opening.is_long);
14623 if (std::isfinite(event.resolved_price) && !std::isfinite(limit)) {
14624 engine_command_value_invalid_or_count(
"relative_price.level",
14625 EngineValueStage::relative_price, EngineValueReason::numeric_overflow);
14628 if (finite_positive(
value.loss_ticks)) {
14629 stop = directional_tick(event.resolved_price - side *
value.loss_ticks * tick,
14630 tick, !opening.is_long);
14631 if (std::isfinite(event.resolved_price) && !std::isfinite(stop)) {
14632 engine_command_value_invalid_or_count(
"relative_price.level",
14633 EngineValueStage::relative_price, EngineValueReason::numeric_overflow);
14640 materializing_relative_ =
true;
14641 materializing_parent_ =
event.handle();
14645 value.from_entry.empty() ?
value.profit_ticks : kNaN,
14646 value.from_entry.empty() ?
value.loss_ticks : kNaN);
14648 materializing_relative_ =
false;
14649 materializing_parent_ = {};
14652 materializing_relative_ =
false;
14653 materializing_parent_ = {};
14660 withdraw_anchored_relative_legs(
nullptr, &opening.source_id);
14663void PineExecutionAdapter::withdraw_anchored_relative_legs(
14668 withdraw_anchored_relative_readers(exit_id ? &exit_text :
nullptr,
14669 from_entry ? &entry_text :
nullptr);
14672void PineExecutionAdapter::withdraw_anchored_relative_readers(
14674 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
14675 const auto& leg = anchored_relative_legs_[index];
14685 const native_order::RequestHandle handle = leg.handle;
14686 (void)require_host().cancel(handle);
14687 anchored_relative_legs_.erase(anchored_relative_legs_.begin()
14688 +
static_cast<std::ptrdiff_t
>(index));
14689 ++anchored_relative_stats_.withdrawn;
14693bool PineExecutionAdapter::anchorable_relative_exit(
14694 const PendingRelativeExit& value, native_order::RequestHandle& parent,
14695 bool& parent_long)
const {
14702 if (
value.from_entry.empty() || config_.close_entries_rule_any || !std::isnan(
value.qty))
14704 if (!finite_positive(staged_.syminfo.mintick))
return false;
14705 const auto staged_same_id = [&](
const PlacementSnapshot& row) {
14706 return row.opening && row.source_id ==
value.from_entry;
14708 for (
const auto& row : pending_same_bar_commands_)
14709 if (staged_same_id(row.snapshot))
return false;
14710 for (
const auto& row : pending_entries_)
14711 if (staged_same_id(row.snapshot))
return false;
14712 for (
const auto& row : pending_coof_requests_)
14713 if (staged_same_id(row.snapshot))
return false;
14714 for (
const auto& row : delayed_market_orders_)
14715 if (staged_same_id(row.snapshot))
return false;
14716 std::size_t parents = 0;
14717 native_order::RequestHandle only{};
14718 bool only_long =
true;
14719 for (
const auto& handle : live_handles_) {
14720 const auto found = placement_.find(handle.incarnation);
14721 if (found == placement_.end() || !found->second.opening
14722 || found->second.source_id !=
value.from_entry) {
14727 only_long = found->second.is_long;
14730 if (parents != 1)
return false;
14739 for (
const auto& shape : relative_leg_shapes(value, only_long))
14740 if (std::holds_alternative<native_order::Trail>(shape.trigger))
return false;
14742 parent_long = only_long;
14746std::vector<PineExecutionAdapter::RelativeLegShape>
14747PineExecutionAdapter::relative_leg_shapes(
const PendingRelativeExit& value,
14748 bool parent_long)
const {
14753 std::vector<RelativeLegShape> shapes;
14754 const double side = parent_long ? 1.0 : -1.0;
14755 if (finite_positive(
value.profit_ticks)) {
14757 side *
value.profit_ticks,
value.profit_ticks});
14759 if (finite_positive(
value.loss_ticks)) {
14761 -side *
value.loss_ticks,
value.loss_ticks});
14763 if (std::isfinite(
value.trail_points) && !finite_positive(
value.trail_price)) {
14764 const double trail_ticks = source_trail_points_ticks(
14765 value.trail_points, staged_.syminfo.mintick);
14766 const bool has_offset = std::isfinite(
value.trail_offset) &&
value.trail_offset >= 0.0;
14767 const double offset_ticks = has_offset ? std::floor(
value.trail_offset) : kNaN;
14768 if (trail_ticks >= 1.0 && has_offset && offset_ticks >= 1.0) {
14772 native_order::Trail{0.0, 0.0,
14773 native_order::TrailTicks{offset_ticks}},
14774 side * trail_ticks, trail_ticks});
14775 }
else if (trail_ticks >= 1.0 && (has_offset || !std::isfinite(
value.trail_offset))) {
14779 native_order::Limit{0.0, has_offset && config_.slippage > 0},
14780 side * trail_ticks, trail_ticks});
14787 if (anchored_relative_legs_.empty() && pending_relative_exits_.empty())
return;
14792 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
14793 if (!anchored_relative_legs_[index].armed) { ++index;
continue; }
14794 const SourceId exit_id = anchored_relative_legs_[index].exit_id;
14795 const SourceId from_entry = anchored_relative_legs_[index].from_entry;
14796 withdraw_anchored_relative_legs(&exit_id, &from_entry);
14800 for (std::size_t index = 0; index < anchored_relative_legs_.size();) {
14801 const auto& leg = anchored_relative_legs_[index];
14802 const bool queued = std::any_of(
14803 pending_relative_exits_.begin(), pending_relative_exits_.end(),
14804 [&](
const PendingRelativeExit& value) {
14805 return value.exit_id == leg.exit_id && value.from_entry == leg.from_entry;
14807 if (queued) { ++index;
continue; }
14808 const SourceId exit_id = leg.exit_id;
14809 const SourceId from_entry = leg.from_entry;
14810 withdraw_anchored_relative_legs(&exit_id, &from_entry);
14813 if (pending_relative_exits_.empty())
return;
14817 std::vector<SourceId> parents;
14818 for (
const auto& value : pending_relative_exits_) {
14819 if (std::find(parents.begin(), parents.end(), value.from_entry) == parents.end())
14820 parents.push_back(value.from_entry);
14822 for (
const auto& from_entry : parents) {
14824 bool parent_long =
true;
14825 bool anchorable =
true;
14826 for (
const auto& value : pending_relative_exits_) {
14827 if (value.from_entry != from_entry)
continue;
14828 anchorable = anchorable && anchorable_relative_exit(value, parent, parent_long);
14830 const bool anchored = std::any_of(
14831 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
14832 [&](
const AnchoredRelativeLeg& leg) { return leg.from_entry == from_entry; });
14836 const bool complete = anchorable && std::all_of(
14837 pending_relative_exits_.begin(), pending_relative_exits_.end(),
14838 [&](
const PendingRelativeExit& value) {
14839 if (value.from_entry != from_entry) return true;
14840 const auto shapes = relative_leg_shapes(value, parent_long);
14841 const auto legs = static_cast<std::size_t>(std::count_if(
14842 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
14843 [&](const AnchoredRelativeLeg& leg) {
14844 return leg.exit_id == value.exit_id
14845 && leg.from_entry == from_entry && leg.parent == parent;
14847 return legs == shapes.size();
14849 if (complete)
continue;
14850 withdraw_anchored_relative_legs(
nullptr, &from_entry);
14852 if (!anchorable)
continue;
14853 for (
const auto& value : pending_relative_exits_) {
14854 if (value.from_entry != from_entry)
continue;
14855 const SourceId group_name = value.oca_name.empty()
14856 ? value.exit_id +
"\x1f" + value.from_entry : value.oca_name;
14857 for (
auto& shape : relative_leg_shapes(value, parent_long)) {
14858 AnchoredRelativeLeg leg;
14859 leg.exit_id = value.exit_id;
14860 leg.from_entry = value.from_entry;
14861 leg.family = shape.family;
14862 leg.parent = parent;
14863 leg.parent_long = parent_long;
14864 leg.operand_ticks = shape.operand_ticks;
14865 leg.trail_offset = value.trail_offset;
14867 native_order::HostSizedKind::Close, std::nullopt};
14868 leg.request.
label = value.exit_id;
14869 leg.request.
comment = value.comment;
14870 leg.request.
trigger = std::move(shape.trigger);
14875 parent, native_order::NativeArmVisibility::PendingUntilArmed,
14876 native_order::NativeArmFirstMatch::AfterArmPrint,
14877 native_order::NativeArmScope::Book};
14878 leg.request.
group = group_for(group_name, 1,
14879 static_cast<std::int64_t
>(shape.family));
14880 if (
auto* member = std::get_if<native_order::Member>(&leg.request.
group)) {
14885 if (anchored_cohort_sequence_
14886 == std::numeric_limits<std::int64_t>::max()) {
14888 "Pine anchored OCA member sequence exhausted");
14890 member->cohort = -(++anchored_cohort_sequence_);
14893 shape.signed_ticks,
true, native_order::NativeAnchorRounding::Directional};
14894 const auto result = require_host().submit(leg.request);
14895 if (result.status != native_order::SubmitStatus::Accepted || !result.handle)
14897 leg.handle = *result.handle;
14898 anchored_relative_legs_.push_back(std::move(leg));
14899 ++anchored_relative_stats_.anchored;
14905bool PineExecutionAdapter::armed_relative_legs_adoptable(
14907 const std::vector<PendingRelativeExit>& pending)
const {
14910 if (!pending_entries_.empty() || !pending_same_bar_commands_.empty()
14911 || !delayed_market_orders_.empty() || !pending_coof_requests_.empty()
14912 || coof_recalc_active_) {
14917 std::size_t expected = 0;
14918 for (
const auto& value : pending) {
14919 for (
const auto& shape : relative_leg_shapes(value, opening.
is_long)) {
14920 const auto children = std::count_if(
14921 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
14922 [&](
const AnchoredRelativeLeg& leg) {
14923 return leg.exit_id == value.exit_id && leg.from_entry == value.from_entry
14924 && leg.family == shape.family
14925 && leg.request.trigger.index() == shape.trigger.index();
14927 if (children != 1)
return false;
14931 std::size_t armed = 0;
14932 for (
const auto& leg : anchored_relative_legs_) {
14933 if (leg.from_entry != opening.
source_id)
continue;
14934 if (leg.parent != event.
handle() || !leg.armed || leg.parent_long != opening.
is_long
14935 || !std::isfinite(leg.installed_level)) {
14940 return armed == expected && armed != 0;
14946bool resting_limit_tick_reach(
const NativeStrategyHost* host,
double threshold,
14947 bool exit_is_buy)
noexcept {
14959 if (held == 0.0 || (held < 0.0) != exit_is_buy)
return false;
14963 if (native.spec && !native.spec->intrabar.is_none())
return false;
14972 if (point && finite_positive(point->price)
14973 && (exit_is_buy ? point->price <= threshold : point->price >= threshold)) {
14980double PineExecutionAdapter::exit_limit_trigger(
double limit_price,
double tick,
14981 bool exit_is_buy)
const noexcept {
14992 if (!finite_positive(tick))
return limit_price;
14993 const double threshold = source_trigger_threshold(limit_price, tick, exit_is_buy,
true);
14994 if (config_.calc_on_order_fills && nearest_tick(limit_price, tick) == limit_price
14995 && !resting_limit_tick_reach(host_, threshold, exit_is_buy)) {
14996 return limit_price;
15001double PineExecutionAdapter::one_shot_trail_trigger(
double activation,
double source_trail_offset,
15002 double tick,
bool exit_is_buy,
15003 bool quantized_activation)
const noexcept {
15004 double one_shot_level = activation;
15005 if (!std::isfinite(source_trail_offset)) {
15006 const double slipped = one_shot_level
15007 + (exit_is_buy ? 1.0 : -1.0) * config_.slippage * tick;
15008 one_shot_level = directional_tick(slipped, tick, exit_is_buy);
15027 if (!exit_is_buy) one_shot_level = source_level_on_price_grid(one_shot_level, tick);
15040 if (quantized_activation && finite_positive(tick)) {
15041 one_shot_level = source_trigger_threshold(one_shot_level, tick, exit_is_buy,
true);
15043 return one_shot_level;
15048 const auto found = std::find_if(
15049 anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
15050 [&](
const AnchoredRelativeLeg& leg) { return leg.handle == view.leg; });
15051 if (found == anchored_relative_legs_.end())
return std::nullopt;
15052 auto& leg = *found;
15053 const double tick = staged_.syminfo.mintick;
15055 leg.installed_level = kNaN;
15058 if (!finite_positive(tick))
return std::nullopt;
15064 const bool exit_is_buy = !leg.parent_long;
15065 double installed = kNaN;
15067 installed = exit_limit_trigger(
15068 source_level_on_price_grid(view.
kernel_level, tick), tick, exit_is_buy);
15070 installed = source_trigger_threshold(
15071 source_level_on_price_grid(view.
kernel_level, tick), tick, exit_is_buy,
false);
15073 const double trail_price = directional_tick(view.
kernel_level, tick, leg.parent_long);
15074 installed = std::holds_alternative<native_order::Trail>(leg.request.
trigger)
15075 ? source_trail_arm_level(trail_price, tick, exit_is_buy)
15076 : one_shot_trail_trigger(trail_price, leg.trail_offset, tick, exit_is_buy,
true);
15085 const bool trail_arm = std::holds_alternative<native_order::Trail>(leg.request.
trigger);
15086 if (!(trail_arm ? finite_positive(installed) : finite_non_negative(installed))) {
15087 leg.installed_level = kNaN;
15090 leg.installed_level = installed;
15096 const std::string&) {
15097 reroute_fixed_entries_before_request();
15098 const auto key = key_for(exit_id, from_entry);
15099 pending_relative_exits_.erase(std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
15100 [&](
const PendingRelativeExit& value) {
15101 return value.exit_id == exit_id && value.from_entry == from_entry;
15102 }), pending_relative_exits_.end());
15103 if (!materializing_relative_) withdraw_anchored_relative_legs(&exit_id, &from_entry);
15104 pending_write(pending_bracket_legs_).erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
15105 [&](
const PendingBracketLeg& leg) { return leg.family_key == key; }), pending_bracket_legs_.end());
15108 std::vector<native_order::RequestHandle> handles;
15109 std::size_t settled = 0;
15111 bool found_family =
false;
15112 if (
const auto found = bracket_families_.find(key); found != bracket_families_.end()) {
15113 handles = found->second.members();
15114 settled = found->second.settled_count();
15115 settled_run = found->second.run_identity();
15116 bracket_families_.erase(found);
15117 found_family =
true;
15118 }
else if (
const auto archived = settled_bracket_families_.find(key);
15119 archived != settled_bracket_families_.end()) {
15120 settled = archived->second.count;
15121 settled_run = archived->second.run;
15122 settled_bracket_digest_ ^= settled_bracket_hash(key, archived->second);
15123 settled_bracket_families_.erase(archived);
15124 found_family =
true;
15126 if (!found_family)
return;
15135 constexpr std::uint64_t kNeverIssued = std::numeric_limits<std::uint64_t>::max();
15136 for (std::size_t index = 0; index < settled; ++index)
15139 for (
const auto& handle : handles) {
15140 const bool working = consumer ==
nullptr
15141 || as_native_consumer(*consumer).request_core().find_live(handle) !=
nullptr;
15142 const auto target = working ? handle
15144 const auto result = require_host().cancel(target);
15145 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
15150 reroute_fixed_entries_before_request();
15151 stash_id_bound_exits(
id);
15152 if (
PF_RARE(!id_bound_exits_.empty())) {
15153 id_bound_exits_.erase(std::remove_if(id_bound_exits_.begin(), id_bound_exits_.end(),
15154 [&](
const IdBoundExit& row) { return row.exit_id == id; }), id_bound_exits_.end());
15156 NamedEntryCancelToken token;
15157 for (
const auto& handle : live_handles_) {
15158 const auto snapshot = placement_.find(handle.incarnation);
15159 if (snapshot == placement_.end())
continue;
15160 const auto family = snapshot->second.family;
15162 token.entry_incarnation = handle.incarnation;
15165 && snapshot->second.from_entry ==
id
15166 && token.surviving_exit_incarnation == 0) {
15167 token.surviving_exit_incarnation = handle.incarnation;
15170 if (token.entry_incarnation != 0) named_entry_cancel_tokens_[id] = token;
15171 else named_entry_cancel_tokens_.erase(
id);
15172 const auto same_bar_before = pending_same_bar_commands_.size();
15173 pending_write(pending_same_bar_commands_).erase(std::remove_if(pending_same_bar_commands_.begin(),
15174 pending_same_bar_commands_.end(), [&](
const PendingSameBarCommand& command) {
15175 return command.snapshot.source_id == id;
15176 }), pending_same_bar_commands_.end());
15177 if (pending_same_bar_commands_.size() != same_bar_before)
15178 source_batch_mutated_ =
true;
15179 pending_same_bar_close_qty_ = 0.0;
15180 for (
const auto& command : pending_same_bar_commands_) {
15181 if (command.snapshot.frozen_market_targeted_close)
15182 pending_same_bar_close_qty_ += command.snapshot.requested_qty;
15184 pending_relative_exits_.erase(std::remove_if(pending_relative_exits_.begin(), pending_relative_exits_.end(),
15185 [&](
const PendingRelativeExit& value) { return value.exit_id == id || value.from_entry == id; }),
15186 pending_relative_exits_.end());
15187 withdraw_anchored_relative_legs(&
id,
nullptr);
15188 withdraw_anchored_relative_legs(
nullptr, &
id);
15189 pending_write(pending_entries_).erase(std::remove_if(pending_entries_.begin(), pending_entries_.end(),
15190 [&](
const PendingEntry&
entry) { return entry.snapshot.source_id == id; }), pending_entries_.end());
15191 pending_write(pending_bracket_legs_).erase(std::remove_if(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
15192 [&](
const PendingBracketLeg& leg) { return leg.snapshot.source_id == id; }),
15193 pending_bracket_legs_.end());
15196 pending_margin_revivals_.erase(std::remove_if(pending_margin_revivals_.begin(),
15197 pending_margin_revivals_.end(), [&](
const PendingMarginRevival& revival) {
15198 return revival.snapshot.source_id == id;
15199 }), pending_margin_revivals_.end());
15200 std::vector<native_order::RequestHandle> matches;
15201 for (
const auto& handle : live_handles_) {
15202 const auto snapshot = placement_.find(handle.incarnation);
15203 if (snapshot != placement_.end() && snapshot->second.source_id ==
id
15204 && !coof_leg_reaches_exit(snapshot->second)) {
15205 matches.push_back(handle);
15208 std::vector<std::pair<std::uint64_t, std::uint64_t>> cancelled;
15209 for (
const auto& handle : matches) {
15210 const auto result = require_host().cancel(handle);
15211 if (result.status == native_order::CancelStatus::Cancelled) {
15213 cancelled.emplace_back(handle.incarnation, result.event_ordinal);
15216 retire_cancelled_exits(&
id, cancelled);
15218 per_entry_exits_.erase(std::remove_if(per_entry_exits_.begin(), per_entry_exits_.end(),
15219 [&](
const PerEntryExit&
exit) { return exit.exit_id == id; }), per_entry_exits_.end());
15223 reroute_fixed_entries_before_request();
15224 id_bound_exits_.clear();
15228 close_margin_cancelled_bar_ = point->decision.script_bar_open_ms;
15229 const auto handles = live_handles_;
15230 std::vector<std::pair<std::uint64_t, std::uint64_t>> cancelled;
15231 for (
const auto& handle : handles) {
15232 const auto row = placement_.find(handle.incarnation);
15233 if (row != placement_.end() && coof_leg_reaches_exit(row->second))
continue;
15234 const auto result = require_host().cancel(handle);
15235 if (result.status == native_order::CancelStatus::Cancelled) {
15237 cancelled.emplace_back(handle.incarnation, result.event_ordinal);
15240 retire_cancelled_exits(
nullptr, cancelled);
15241 pending_margin_revivals_.clear();
15242 clear_bracket_families();
15243 pending_write(pending_bracket_legs_).clear();
15244 pending_write(pending_entries_).clear();
15245 delayed_market_orders_.clear();
15246 deferred_open_marketable_sells_.clear();
15247 throttled_reopen_rearm_.clear();
15248 pending_write(pending_same_bar_commands_).clear();
15249 pending_same_bar_close_qty_ = 0.0;
15250 pending_relative_exits_.clear();
15251 withdraw_anchored_relative_legs(
nullptr,
nullptr);
15252 per_entry_exits_.clear();
15256 double limit_price,
double stop_price,
15257 const std::string& oca_name,
int oca_type) {
15258 if (intraday_loss_orders_blocked())
return;
15260 point && cap_placement_denied(point->decision)) {
15263 require_cohort_ticket(&
id);
15264 reroute_fixed_entries_before_request();
15266 if (!pending_same_bar_commands_.empty()) {
15267 source_batch_mutated_ =
true;
15268 flush_pending_same_bar_commands();
15271 if (!pending_entries_.empty()) {
15274 if (
id ==
"__close__") {
15276 std::vector<native_order::RequestHandle> replaced_close_all;
15277 for (
const auto& handle : live_handles_) {
15278 const auto found = placement_.find(handle.incarnation);
15279 if (found != placement_.end()
15281 && (!point || found->second.placement_script_open_ms
15282 == point->decision.script_bar_open_ms)) {
15283 replaced_close_all.push_back(handle);
15286 for (
const auto& handle : replaced_close_all) {
15287 const auto result = require_host().cancel(handle);
15288 if (result.status != native_order::CancelStatus::Cancelled)
continue;
15289 for (
auto row : placement_) {
15290 if (row.second.preserved_by_close_all == handle) {
15291 row.second.preserved_by_close_all = {}; invalidate_retirement();
15292 row.second.preserved_close_all_bar = -1;
15298 if (has_live_exit_policy())
for (
const auto& handle : live_handles_) {
15299 const auto existing = placement_.find(handle.incarnation);
15300 if (existing != placement_.end()
15301 && existing->second.pooc_global_full_exit_dynamic_qty) {
15302 existing->second.pooc_global_full_exit_dynamic_qty =
false;
15303 existing->second.pooc_global_full_exit_tracks_bound_adds =
false;
15307 double risk_coof_forced_price = kNaN;
15308 double coof_market_fill = kNaN;
15309 bool delay_after_default_pair =
false;
15310 limit_price = source_level_on_price_grid(
15312 stop_price = source_level_on_price_grid(
15314 const bool default_sized = std::isnan(qty);
15315 const double normalized_qty = default_sized ? qty
15316 : floor_quantity_grid(std::abs(qty), staged_.quantity_grid);
15322 request.intent = (default_sized || oca_type == 1)
15324 is_long ? native_order::Side::Long : native_order::Side::Short}}
15326 request.label = id;
15327 double native_limit = limit_price;
15328 double native_stop = stop_price;
15329 if (finite_positive(limit_price) && !finite_positive(stop_price)) {
15330 native_limit = source_trigger_threshold(
15332 }
else if (finite_positive(stop_price) && !finite_positive(limit_price)
15333 && !config_.calc_on_order_fills) {
15334 native_stop = source_trigger_threshold(
15337 request.trigger = trigger_for(native_limit, native_stop);
15338 if (coof_recalc_active_ && coof_first_open_) {
15340 if (point && finite_positive(limit_price)
15341 && (is_long ? limit_price >= point->price
15342 : limit_price <= point->
price)) {
15343 coof_market_fill = source_bar_fill_tick(
15344 point->price, staged_.syminfo.mintick);
15347 if (std::holds_alternative<native_order::Market>(request.trigger)) {
15349 if (coof_recalc_active_ && !coof_first_open_) {
15350 const double next_waypoint = coof_next_waypoint();
15351 const double current_quote = point ? point->price : kNaN;
15352 coof_market_fill = source_bar_fill_tick(
15353 next_waypoint, staged_.syminfo.mintick)
15354 + (is_long ? 1.0 : -1.0) * config_.slippage
15355 * staged_.syminfo.mintick;
15356#ifdef PINEFORGE_VALUE_INVALID_FIXTURES
15357 coof_market_fill = fixture_adapter_execution_value(
"slippage.recalc.order", coof_market_fill);
15359 check_slippage_fill_price(coof_market_fill, std::isfinite(next_waypoint));
15360 if (finite_positive(next_waypoint) && finite_positive(current_quote)
15361 && !source_same_point(current_quote, next_waypoint, staged_.syminfo.mintick)) {
15362 const bool falling = next_waypoint < current_quote;
15364 request.trigger = falling
15368 request.trigger = falling
15374 if (coof_recalc_active_ && !coof_first_open_
15375 && risk_.max_intraday_loss > 0.0) {
15376 double target = kNaN;
15378 switch (point->decision.coordinate.path_phase) {
15379 case NativePathPhase::Open: {
15380 const bool high_first = source_path_uses_high_first(coof_script_bar_);
15381 target = high_first ? coof_script_bar_.high : coof_script_bar_.low;
15384 case NativePathPhase::High: target = coof_script_bar_.high;
break;
15385 case NativePathPhase::Low: target = coof_script_bar_.low;
break;
15388 if (finite_positive(target)
15389 && !source_same_point(point->price, target, staged_.syminfo.mintick)
15390 && risk_coof_direct_script_bar_
15391 != point->decision.script_bar_open_ms) {
15392 risk_coof_forced_price = target;
15393 risk_coof_direct_script_bar_ = point->decision.script_bar_open_ms;
15397 std::vector<std::pair<std::uint64_t, native_order::RequestHandle>> same_bar_defaults;
15398 for (
const auto& handle : live_handles_) {
15399 const auto pending = placement_.find(handle.incarnation);
15400 if (pending == placement_.end())
continue;
15401 const auto& row = pending->second;
15403 && !std::isfinite(row.requested_qty)
15404 && (!point || row.placement_script_open_ms
15405 == point->decision.script_bar_open_ms)
15406 && !finite_positive(row.exit_levels.limit)
15407 && !finite_positive(row.exit_levels.stop)) {
15408 same_bar_defaults.push_back({row.source_sequence, handle});
15411 if (same_bar_defaults.size() >= 2) {
15412 delay_after_default_pair =
true;
15415 request.group = group_for(oca_name, oca_type);
15416 if (oca_type == 1) {
15423 if (default_sized && oca_type == 2) {
15424 if (
auto* member = std::get_if<native_order::Member>(&request.group)) {
15428 member->effect = native_order::GroupEffect::Cancel;
15433 snapshot.oca_type = oca_type;
15438 snapshot.requested_qty = qty;
15439 snapshot.is_long = is_long;
15440 snapshot.exit_levels.limit = limit_price;
15441 snapshot.exit_levels.stop = stop_price;
15442 snapshot.forced_execution_price = finite_positive(risk_coof_forced_price)
15443 ? risk_coof_forced_price : coof_market_fill;
15444 if (source_command_sequence_ == std::numeric_limits<std::uint64_t>::max()) {
15446 "Pine source command sequence exhausted");
15448 snapshot.command_sequence = ++source_command_sequence_;
15449 snapshot.sizing = sizing_snapshot();
15450 if (default_sized && finite_positive(snapshot.sizing.price)
15452 || config_.default_qty_type ==
static_cast<int>(
QtyType::CASH))) {
15453 snapshot.sizing.frozen_units = default_sizing_units(snapshot.sizing);
15454 snapshot.sizing.at_fill = config_.calc_on_order_fills && coof_recalc_active_;
15456 if (delay_after_default_pair) {
15457 if (broker_open_epoch_ == std::numeric_limits<std::uint64_t>::max())
15459 "source delayed market epoch exhausted");
15462 snapshot.placement_script_open_ms = point->decision.script_bar_open_ms;
15463 snapshot.placement_sub_open_ms = point->decision.sub_bar_open_ms;
15466 delayed_market_orders_.push_back({std::move(request), std::move(snapshot), id,
15467 broker_open_epoch_ + 1U});
15470 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
true,
id);
15471 if (!accepted)
return;
15472 const auto entry = placement_.find(accepted->incarnation);
15473 if (
entry == placement_.end())
return;
15474 const std::uint64_t entry_sequence =
entry->second.command_sequence;
15475 if (has_live_exit_policy())
for (
const auto& handle : live_handles_) {
15476 if (handle == *accepted)
continue;
15477 const auto existing = placement_.find(handle.incarnation);
15478 if (existing == placement_.end())
continue;
15479 auto& prior = existing->second;
15480 if (!prior.reservation_expansion.capture()
15481 || prior.command_sequence >= entry_sequence) {
15484 prior.pooc_global_full_exit_dynamic_qty =
false;
15485 prior.pooc_global_full_exit_tracks_bound_adds =
false;
15486 prior.reservation_expansion.close_population(accepted->incarnation);
15492 auto result = resolve_source_terms(facts);
15493 if (
PF_RARE(close_call_after_script_bar_ != std::numeric_limits<std::int64_t>::min())) {
15494 const auto snapshot = placement_.find(facts.target.incarnation);
15495 if (snapshot != placement_.end())
15496 apply_frozen_reversal_close(snapshot->second, facts, result);
15511void PineExecutionAdapter::apply_frozen_reversal_close(
15516 ||
source.placement_script_open_ms != close_call_after_script_bar_
15517 || !result.
units || !std::isfinite(*result.
units)
15518 || facts.position.signed_units == 0.0
15519 || (facts.position.signed_units > 0.0) ==
source.is_long
15520 || (result.
shape != native_order::OpeningShape::CloseOpposite
15521 && result.
shape != native_order::OpeningShape::ReverseTo)) {
15526 const double frozen = close_call_after_script_book_;
15527 const double book = std::abs(facts.position.signed_units);
15528 const double guard = std::max(1e-9, frozen * 1e-12);
15530 if (!finite_positive(frozen) || !(frozen > book + guard)
15531 || std::abs(frozen - close_call_after_script_units_ - book) > guard) {
15534 const double own = result.
shape == native_order::OpeningShape::ReverseTo ? *result.
units : 0.0;
15535 result.
units = frozen + own;
15536 result.
shape = native_order::OpeningShape::Transact;
15539native_order::ExecutionTerms PineExecutionAdapter::resolve_source_terms(
15540 const NativeExecutionTermsFacts& facts)
const {
15541 native_order::ExecutionTerms result{
facts.default_resolved_price, std::nullopt,
15542 native_order::OpeningShape::Transact};
15543 const auto snapshot = placement_.find(
facts.target.incarnation);
15544 if (snapshot == placement_.end()) {
15550 if (
facts.definition
15551 &&
facts.definition->origin == native_order::RequestOrigin::KernelLiquidation) {
15552 double fire =
facts.trigger_level ? *
facts.trigger_level :
facts.raw_price;
15553 if (
facts.is_buy) fire = nearest_tick(fire, staged_.syminfo.mintick);
15554 if (finite_positive(fire))
15559 const auto& source =
snapshot->second;
15567 const bool explicit_zero_trail =
15572 const bool sampled_one_price_gap = host_state.spec
15573 && !host_state.spec->intrabar.is_none()
15574 &&
facts.cursor.point.provenance == NativePriceProvenance::ModeledOHLCOpen
15575 &&
facts.price_kind == native_order::NativeCandidatePriceKind::PointPrice;
15577 && std::holds_alternative<native_order::Limit>(
facts.definition->request.trigger);
15578 const auto* one_shot_rest = trail_limit_one_shot
15579 ? std::get_if<native_order::Limit>(&
facts.definition->request.trigger) : nullptr;
15580 const auto* trail_active = std::get_if<native_order::TrailActive>(&
facts.trigger_state);
15581 const auto retained_trail_source_price = [&]() -> std::optional<double> {
15586 || explicit_zero_trail
15587 || !finite_positive(staged_.syminfo.mintick)
15588 ||
facts.cursor.point.path_phase == NativePathPhase::Open) {
15589 return std::nullopt;
15592 if (trail_active && std::isfinite(trail_active->best_at_trigger)) {
15593 best =
facts.is_buy ? std::min(best, trail_active->best_at_trigger)
15594 : std::max(best, trail_active->best_at_trigger);
15603 : std::max(best, source.trail_activation_level);
15606 * staged_.syminfo.mintick;
15607 const double level = best + (
facts.is_buy ? offset : -offset);
15608 const double slipped = level + (
facts.is_buy ? 1.0 : -1.0)
15609 * config_.slippage * staged_.syminfo.mintick;
15610 return directional_tick(slipped, staged_.syminfo.mintick,
facts.is_buy);
15612 const auto observed_tick_trail_price = [&]() -> std::optional<double> {
15614 ||
facts.cursor.point.provenance != NativePriceProvenance::ObservedPrint) {
15615 return std::nullopt;
15617 return facts.default_resolved_price;
15619 const bool placement_reached_trail_activation =
15622 staged_.syminfo.mintick,
facts.is_buy);
15623 const bool zero_trail_first_activation = explicit_zero_trail && trail_active
15624 && !placement_reached_trail_activation
15628 + staged_.syminfo.mintick * 1e-6)
15631 - staged_.syminfo.mintick * 1e-6));
15632 const auto zero_trail_source_price = [&]() -> std::optional<double> {
15633 if (!explicit_zero_trail || sampled_one_price_gap || !
facts.trigger_level
15635 return std::nullopt;
15637 const double open = policy_script_bar_.open;
15640 const bool reached_at_placement = source_trail_reached_at(
15641 placement, activation, staged_.syminfo.mintick,
facts.is_buy);
15642 const bool open_beyond =
facts.is_buy
15643 ? open <= activation : open >= activation;
15644 const bool high_first = source_path_uses_high_first(policy_script_bar_);
15645 const bool adverse_first =
facts.is_buy ? high_first : !high_first;
15646 const bool same_open = std::isfinite(placement)
15647 && std::abs(open - placement) <= staged_.syminfo.mintick * 0.5;
15648 if (reached_at_placement && same_open)
15649 return nearest_tick(open, staged_.syminfo.mintick);
15650 if (!reached_at_placement && !open_beyond)
15651 return nearest_tick(activation, staged_.syminfo.mintick);
15652 if (!reached_at_placement && open_beyond && adverse_first)
15653 return directional_tick(open, staged_.syminfo.mintick,
facts.is_buy);
15654 return std::nullopt;
15656 const auto zero_trail_policy_price = [&]() -> std::optional<double> {
15657 if (!explicit_zero_trail || !policy_script_bar_valid_
15659 || !finite_positive(staged_.syminfo.mintick)) {
15660 return std::nullopt;
15662 const double tick = staged_.syminfo.mintick;
15663 const bool long_side = !
facts.is_buy;
15664 const double open = policy_script_bar_.open;
15667 const auto print = [&](
double value) {
15668 return std::floor(value / tick + 0.5) * tick;
15670 const auto level = [&](
double value) {
15671 return directional_tick(value, tick,
facts.is_buy);
15673 const bool placement_armed = std::isfinite(placement)
15674 && (long_side ? nearest_tick(placement, tick) >= activation
15675 : nearest_tick(placement, tick) <= activation);
15676 const bool open_reaches = long_side
15677 ? open >= activation : open <= activation;
15678 const bool open_favorable = std::isfinite(placement)
15680 const auto preopen = trail_state_at_open_.find(
facts.target.incarnation);
15681 if (preopen != trail_state_at_open_.end() && preopen->second.activated
15682 && std::isfinite(preopen->second.current_level)) {
15683 const bool adverse_gap = long_side
15684 ? open <= preopen->second.current_level
15685 : open >= preopen->second.current_level;
15687 return directional_tick(open, tick,
facts.is_buy);
15689 const bool high_first = source_path_uses_high_first(policy_script_bar_);
15690 const bool favorable_first = long_side ? high_first : !high_first;
15691 const double open_print = source_bar_fill_tick(open, tick);
15694 const double placement_best = placement_armed
15695 ? (long_side ? std::max(placement, activation) : std::min(
placement, activation))
15697 const double carried_best = preopen != trail_state_at_open_.end()
15698 && preopen->second.activated
15699 && std::isfinite(preopen->second.best_price)
15700 ? preopen->second.best_price : placement_best;
15701 const bool carried_armed = (preopen != trail_state_at_open_.end()
15702 && preopen->second.activated) || placement_armed;
15703 if (carried_armed && std::isfinite(carried_best)) {
15704 const bool adverse_gap = long_side ? open <= carried_best
15705 : open >= carried_best;
15706 if (adverse_gap)
return open_print;
15708 const bool zero_offset_open_arms = carried_armed
15709 ? (long_side ? open > carried_best : open < carried_best)
15711 const bool print_at_open_level = zero_offset_open_arms
15712 && (long_side ? open_print <= open : open_print >= open);
15713 if (print_at_open_level)
15714 return directional_tick(open, tick,
facts.is_buy);
15715 if (zero_offset_open_arms) {
15716 if (!favorable_first)
return level(open);
15717 return level(long_side ? policy_script_bar_.high
15718 : policy_script_bar_.low);
15720 bool armed = placement_armed;
15721 bool armed_from_open =
false;
15723 if (!std::isfinite(best)) best = open;
15724 if (!armed && open_reaches && open_favorable) {
15726 armed_from_open =
true;
15730 if (!armed_from_open && long_side && open <= best)
return print(open);
15731 if (!armed_from_open && !long_side && open >= best)
return print(open);
15732 if (armed_from_open || open_favorable) {
15734 if (print(open) == level(open))
return print(open);
15740 path[1] = policy_script_bar_.high;
15741 path[2] = policy_script_bar_.low;
15743 path[1] = policy_script_bar_.low;
15744 path[2] = policy_script_bar_.high;
15746 path[3] = policy_script_bar_.close;
15747 for (
int i = 1; i < 4; ++i) {
15748 const double from = path[i - 1];
15749 const double to = path[i];
15751 const bool reached = long_side
15752 ? (to >= activation && to > from)
15753 : (to <= activation && to < from);
15755 return one_shot_rest
15756 ? source_one_shot_reach_tick(one_shot_rest->price, tick,
facts.is_buy)
15757 : level(activation);
15761 const bool favorable = long_side ? to > best : to < best;
15766 const double stop = level(best);
15767 const bool crossed = long_side
15768 ? (to <= stop && from > stop)
15769 : (to >= stop && from < stop);
15770 if (crossed)
return stop;
15772 if (trail_active && std::isfinite(trail_active->best_at_trigger))
15773 return level(trail_active->best_at_trigger);
15774 return std::nullopt;
15776 const auto& trigger =
facts.definition->request.trigger;
15777 const bool limit_fill = std::holds_alternative<native_order::Limit>(trigger)
15778 || std::holds_alternative<native_order::StopLimit>(trigger);
15779 const auto owner_tick_fill = [&](
double price) {
15786 const double owner_form = nearest_tick(
price, staged_.syminfo.mintick);
15787 std::optional<double> level;
15788 if (
const auto* limit = std::get_if<native_order::Limit>(&trigger)) {
15789 level =
limit->price;
15790 }
else if (
const auto* stop_limit = std::get_if<native_order::StopLimit>(&trigger)) {
15791 level = stop_limit->limit;
15793 const bool within = !level
15794 || (
facts.is_buy ? owner_form <= *level : owner_form >= *level);
15795 return within ? owner_form : source_decimal_tick(
price, staged_.syminfo.mintick);
15797 const auto source_forced_fill = [&](
double forced) {
15798 return nearest_tick(forced, staged_.syminfo.mintick) == forced
15799 ? owner_tick_fill(forced)
15800 : source_bar_fill_tick(forced, staged_.syminfo.mintick);
15802 const bool non_open =
facts.cursor.point.path_phase != NativePathPhase::Open;
15803 const bool source_gap_point = !non_open
15804 ||
facts.cursor.point.provenance == NativePriceProvenance::ObservedPrint;
15805 const auto source_stop_fill = [&]() {
15808 const double level = finite_positive(source_level) ? source_level
15809 : (
facts.trigger_level ? *
facts.trigger_level :
facts.default_resolved_price);
15810 const double slipped = level + (
facts.is_buy ? 1.0 : -1.0)
15811 * config_.slippage * staged_.syminfo.mintick;
15812 return directional_tick(slipped, staged_.syminfo.mintick,
facts.is_buy);
15814 const auto source_bar_fill = [&]() {
15818 const double rounded = source_bar_fill_tick(
15819 facts.raw_price, staged_.syminfo.mintick);
15820 const double slipped = rounded + (
facts.is_buy ? 1.0 : -1.0)
15821 * config_.slippage * staged_.syminfo.mintick;
15822 return directional_tick(slipped, staged_.syminfo.mintick,
facts.is_buy);
15832 const auto newborn_stop_through = [&]() -> std::optional<double> {
15834 || config_.calc_on_order_fills
15835 ||
facts.price_kind != native_order::NativeCandidatePriceKind::PointPrice
15836 ||
facts.cursor.point.provenance != NativePriceProvenance::Confirmed
15837 ||
facts.cursor.point.path_phase == NativePathPhase::Open
15838 ||
facts.cursor.point.path_phase == NativePathPhase::None
15839 || !host_state.spec || !host_state.spec->intrabar.is_none()) {
15840 return std::nullopt;
15842 const auto* pine = pine_view_of(&require_host());
15843 if (!pine)
return std::nullopt;
15844 const PyramidEntry* newest =
nullptr;
15845 for (
const auto& lot : pine->pyramid_entries_) {
15847 && (!newest || lot.entry_incarnation > newest->entry_incarnation)) {
15851 if (!newest || newest->entry_bar_index !=
facts.cursor.point.interval_index)
15852 return std::nullopt;
15856 if (!finite_positive(stop) || (
facts.is_buy ? stop >
facts.raw_price : stop <
facts.raw_price))
15857 return std::nullopt;
15861 const auto opening = placement_.find(newest->entry_incarnation);
15865 || std::abs(newest->price -
facts.raw_price)
15866 > 0.5 * staged_.syminfo.mintick + 1e-9 * std::abs(
facts.raw_price)) {
15867 return std::nullopt;
15869 return newest->price;
15871 const auto source_stop_resolved = [&]() {
15876 if (
facts.cursor.point.provenance == NativePriceProvenance::ObservedPrint)
15877 return source_bar_fill();
15878 if (
const auto print = newborn_stop_through()) {
15879 const double slipped = *print + (
facts.is_buy ? 1.0 : -1.0)
15880 * config_.slippage * staged_.syminfo.mintick;
15881 return directional_tick(slipped, staged_.syminfo.mintick,
facts.is_buy);
15885 const double level = finite_positive(source_level) ? source_level
15886 : (
facts.trigger_level ? *
facts.trigger_level :
facts.default_resolved_price);
15893 double open_px =
facts.raw_price;
15894 if (policy_script_bar_valid_
15895 &&
facts.cursor.point.path_phase == NativePathPhase::Open
15896 &&
facts.cursor.point.provenance == NativePriceProvenance::ModeledOHLCOpen
15897 && host_state.spec) {
15898 const auto* synthesized = host_state.spec->intrabar.synthesized_path();
15899 const auto* lower = host_state.spec->intrabar.lower();
15900 const bool one_price = synthesized !=
nullptr
15901 || (lower && lower->sample_eligibility
15903 if (one_price) open_px = policy_script_bar_.open;
15911 const bool armed_after_open_fill = non_open
15913 && policy_script_bar_valid_
15914 && !config_.process_orders_on_close && !config_.calc_on_order_fills
15915 && host_state.spec && host_state.spec->intrabar.is_none()
15916 &&
facts.cursor.point.provenance == NativePriceProvenance::Confirmed
15917 &&
facts.raw_price == policy_script_bar_.open;
15918 if (armed_after_open_fill) open_px = policy_script_bar_.open;
15919 const bool open_gapped = (!non_open || armed_after_open_fill)
15920 && std::isfinite(level)
15921 && (
facts.is_buy ? open_px >= level : open_px <= level);
15929 if (!open_gapped) {
15932 : source_stop_fill();
15936 const double rounded = source_bar_fill_tick(
15937 open_px, staged_.syminfo.mintick);
15938 const double slipped = rounded + (
facts.is_buy ? 1.0 : -1.0)
15939 * config_.slippage * staged_.syminfo.mintick;
15940 return directional_tick(slipped, staged_.syminfo.mintick,
facts.is_buy);
15942 const auto source_limit_fill = [&]() {
15949 const double ticked = directional_tick(*
facts.trigger_level, staged_.syminfo.mintick,
15952 return facts.is_buy ? std::min(ticked, *
facts.trigger_level)
15953 : std::max(ticked, *
facts.trigger_level);
15956 &&
facts.cursor.point.provenance == NativePriceProvenance::Confirmed;
15963 const bool limit_armed_after_open_fill = non_open
15965 && policy_script_bar_valid_
15966 && !config_.process_orders_on_close && !config_.calc_on_order_fills
15967 && host_state.spec && host_state.spec->intrabar.is_none()
15968 &&
facts.cursor.point.provenance == NativePriceProvenance::Confirmed
15969 &&
facts.raw_price == policy_script_bar_.open;
15970 if (!non_open || deferred_open_gap || limit_armed_after_open_fill) {
15973 if (raw_oca_reduce) {
15974 return nearest_tick(
facts.raw_price, staged_.syminfo.mintick);
15979 return owner_tick_fill(
facts.raw_price);
15981 return source_bar_fill_tick(
facts.raw_price, staged_.syminfo.mintick);
15988 if (std::holds_alternative<native_order::StopLimit>(trigger) && !armed_pure_limit_entry) {
15989 const double projected = directional_tick(
facts.raw_price, staged_.syminfo.mintick,
15997 const double limit = std::get<native_order::StopLimit>(trigger).limit;
15998 if (
facts.is_buy ? projected > limit : projected < limit) {
15999 const double print_on_grid = source_level_on_price_grid(
16000 facts.raw_price, staged_.syminfo.mintick);
16001 if (source_bar_fill_tick(print_on_grid, staged_.syminfo.mintick)
16002 == print_on_grid) {
16003 return print_on_grid;
16016 const double projected = directional_tick(
16017 level, staged_.syminfo.mintick, !
facts.is_buy);
16018 const double level_on_grid = source_level_on_price_grid(
16019 level, staged_.syminfo.mintick);
16020 const bool grid_level = source_bar_fill_tick(
16021 level_on_grid, staged_.syminfo.mintick) == level_on_grid;
16022 const bool wrong_side =
facts.is_buy
16023 ? projected > level
16024 : projected < level;
16029 const auto* native_limit = std::get_if<native_order::Limit>(&trigger);
16030 const bool native_admits = native_limit
16031 && (
facts.is_buy ? projected <= native_limit->price
16032 : projected >= native_limit->price);
16033 if (grid_level && wrong_side && !native_admits) {
16034 return level_on_grid;
16038 const auto source_trail_one_shot_fill = [&]() {
16044 const double tick = staged_.syminfo.mintick;
16053 + (
facts.is_buy ? 1.0 : -1.0) * config_.slippage * tick;
16054 double level = directional_tick(slipped, tick,
facts.is_buy);
16055 if (!
facts.is_buy) level = source_level_on_price_grid(level, tick);
16056 const bool open_through =
facts.is_buy
16057 ?
facts.raw_price <= level :
facts.raw_price >= level;
16058 if (
facts.cursor.point.path_phase == NativePathPhase::Open && open_through) {
16059 return directional_tick(
16060 facts.default_resolved_price, tick,
facts.is_buy);
16062 const double ticked = directional_tick(level, tick, !
facts.is_buy);
16063 return facts.is_buy ? std::min(ticked, level) : std::max(ticked, level);
16066 &&
facts.cursor.point.path_phase == NativePathPhase::Open) {
16067 return directional_tick(
16068 facts.default_resolved_price, staged_.syminfo.mintick,
16071 return source_limit_fill();
16081 const bool core_sized = std::holds_alternative<native_order::Sized>(
16082 facts.definition->request.intent);
16084 && !std::holds_alternative<native_order::HostSized>(
facts.definition->request.intent)) {
16085 if (std::holds_alternative<native_order::Market>(trigger)) {
16087 }
else if (limit_fill) {
16092 && std::holds_alternative<native_order::Stop>(trigger)
16093 &&
facts.trigger_level) {
16108 &&
facts.cursor.point.path_phase != NativePathPhase::Open) {
16115 : std::max(result.resolved_price, constraint);
16118 &&
facts.trigger_level
16119 &&
facts.cursor.point.path_phase != NativePathPhase::Open) {
16126 && ((risk_.direction > 0 && !source.
is_long)
16127 || (risk_.direction < 0 && source.
is_long));
16128 if (live_direction_gate) {
16129 const bool opposite =
facts.position.signed_units != 0.0
16130 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16133 result.
shape = native_order::OpeningShape::CloseOpposite;
16138 result.
units = 0.0;
16142 const bool market_like = std::holds_alternative<native_order::Market>(trigger);
16149 }
else if (limit_fill) {
16162 &&
facts.trigger_level
16163 &&
facts.price_kind == native_order::NativeCandidatePriceKind::TriggerLevel) {
16165 &&
facts.cursor.point.path_phase == NativePathPhase::Open) {
16167 }
else if (observed_tick_trail_price) {
16169 }
else if (retained_trail_source_price) {
16171 }
else if (
const auto source_price = zero_trail_policy_price()) {
16173 }
else if (
const auto source_price = zero_trail_source_price()) {
16175 }
else if (trail_limit_one_shot) {
16177 }
else if (explicit_zero_trail) {
16178 if (zero_trail_first_activation) {
16182 const double best = trail_active
16183 ? trail_active->best_at_trigger :
facts.default_resolved_price;
16185 best, staged_.syminfo.mintick,
facts.is_buy);
16188 &&
facts.cursor.point.path_phase == NativePathPhase::Open) {
16195 facts.default_resolved_price, staged_.syminfo.mintick,
facts.is_buy);
16202 *
facts.trigger_level, staged_.syminfo.mintick,
facts.is_buy);
16204 }
else if (observed_tick_trail_price) {
16206 }
else if (retained_trail_source_price) {
16208 }
else if (
const auto source_price = zero_trail_policy_price()) {
16210 }
else if (
const auto source_price = zero_trail_source_price()) {
16212 }
else if (trail_limit_one_shot &&
facts.trigger_level && !sampled_one_price_gap) {
16214 }
else if (explicit_zero_trail &&
facts.trigger_level && !sampled_one_price_gap) {
16220 : directional_tick(trail_active ? trail_active->best_at_trigger
16221 :
facts.default_resolved_price,
16222 staged_.syminfo.mintick,
facts.is_buy);
16224 &&
facts.price_kind == native_order::NativeCandidatePriceKind::PointPrice
16225 && (
facts.cursor.point.path_phase == NativePathPhase::Open
16227 &&
facts.cursor.point.provenance
16228 == NativePriceProvenance::Confirmed))) {
16233 facts.default_resolved_price, staged_.syminfo.mintick);
16237 &&
facts.trigger_level &&
facts.cursor.point.path_phase != NativePathPhase::Open) {
16252 : std::max(result.resolved_price, constraint);
16258 && policy_script_bar_valid_
16259 &&
facts.cursor.point.path_phase != NativePathPhase::Open) {
16260 const bool closing_long =
facts.position.signed_units > 0.0;
16262 if (!finite_positive(activation)
16264 && finite_positive(staged_.syminfo.mintick)) {
16265 const double ticks = source_trail_points_ticks(
16267 activation = require_host().position_avg_price()
16268 + (closing_long ? 1.0 : -1.0) * ticks * staged_.syminfo.mintick;
16269 activation = directional_tick(
16270 activation, staged_.syminfo.mintick, closing_long);
16272 const bool armed_inside_this_bar = closing_long
16273 ? policy_script_bar_.open < activation
16274 : policy_script_bar_.open > activation;
16275 if (finite_positive(activation) && armed_inside_this_bar) {
16283 ? source_one_shot_reach_tick(
16284 one_shot_rest->price, staged_.syminfo.mintick,
facts.is_buy)
16285 : directional_tick(activation, staged_.syminfo.mintick,
facts.is_buy);
16288 if ((std::holds_alternative<native_order::Stop>(trigger)
16289 || std::holds_alternative<native_order::Trail>(trigger)
16291 &&
facts.trigger_level && !explicit_zero_trail && !trail_limit_one_shot
16293 &&
facts.cursor.point.provenance == NativePriceProvenance::Confirmed)
16294 && !retained_trail_source_price && !observed_tick_trail_price) {
16295 result.
resolved_price = std::holds_alternative<native_order::Stop>(trigger)
16296 ? source_stop_resolved()
16297 : (source_gap_point ? source_bar_fill() : source_stop_fill());
16301 }
else if (
const auto* touch = std::get_if<native_order::Limit>(&trigger);
16302 touch && touch->fill_through && trail_limit_one_shot
16308 const double tick = staged_.syminfo.mintick;
16311 tick,
facts.is_buy);
16319 const bool explicit_source_exit = finite_positive(source.
requested_qty)
16323 if (explicit_source_exit
16326 result.
grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
16331 && !(
facts.scope_exposure_units > 0.0)) {
16332 result.
units = 0.0;
16338 result.
grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
16350 const auto cover_full_scope = [&](
double units) {
16352 && !config_.close_entries_rule_any
16353 && std::holds_alternative<execution::Book>(
facts.scope)) {
16354 const double close_units = std::min(units,
facts.scope_exposure_units);
16355 const double boundary_units = source_fifo_close_units(
16356 close_units, require_host().native_open_lots(kNaN));
16357 if (boundary_units != close_units) {
16358 result.
grid_policy = native_order::ExecutionGridPolicy::ExplicitUnits;
16359 return boundary_units;
16361 units = close_units;
16363 if (
facts.scope_exposure_units > 0.0
16364 && units >=
facts.scope_exposure_units - internal::kQtyEpsilon) {
16372 if (
const auto* host_close = std::get_if<native_order::HostSized>(
16373 &
facts.definition->request.intent);
16375 && host_close->kind == native_order::HostSizedKind::Close) {
16377 native_order::ExecutionGridPolicy::ExplicitUnits;
16379 return facts.scope_exposure_units;
16383 if (fill_time_global_exit(source)) {
16385 if (percent >= 100.0 - internal::kFullPercentEps) {
16386 result.
units = cover_full_scope(
facts.scope_exposure_units);
16393 double units = quantize_close_units(
facts.scope_exposure_units, percent);
16396 result.
units = cover_full_scope(units);
16399 const bool has_projected_remaining =
16402 if (has_projected_remaining) {
16407 if (
const auto* host_close = std::get_if<native_order::HostSized>(
16408 &
facts.definition->request.intent);
16409 host_close && host_close->kind == native_order::HostSizedKind::Close) {
16411 native_order::ExecutionGridPolicy::ExplicitUnits;
16413 result.
units = cover_full_scope(
16419 &&
facts.scope_exposure_units > 0.0
16423 const auto& owner =
facts.definition->request.owner;
16424 const auto* armed = std::get_if<native_order::WaitForApplied>(&owner);
16425 const bool book_owner = std::holds_alternative<native_order::Independent>(owner)
16426 || (armed && armed->scope == native_order::NativeArmScope::Book);
16427 const bool whole_scope_owner = !book_owner || source.
from_entry.empty();
16429 ? std::min(*result.
units,
facts.scope_exposure_units)
16430 : cover_full_scope(
facts.scope_exposure_units);
16451 result.
units = cover_full_scope(
facts.scope_exposure_units);
16459 if (std::isnan(percent)) percent = 100.0;
16460 const bool selected_exit =
16464 && std::holds_alternative<native_order::SelectedExposure>(
facts.scope);
16465 result.
units = selected_exit && percent == 100.0
16466 ? cover_full_scope(
facts.scope_exposure_units)
16467 : cover_full_scope(
16468 quantize_close_units(
facts.scope_exposure_units, percent));
16473 const bool opposite =
facts.position.signed_units != 0.0
16474 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16477 result.
shape = native_order::OpeningShape::CloseOpposite;
16490 const bool opposite_now =
facts.position.signed_units != 0.0
16491 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16493 const bool stopped_flat_reversal = !opposite_now
16494 &&
facts.position.signed_units == 0.0
16495 && std::holds_alternative<native_order::Stop>(trigger)
16497 result.
units = opposite_now ?
facts.opposite_book_units
16499 result.
shape = opposite_now ? native_order::OpeningShape::CloseOpposite
16500 : native_order::OpeningShape::Transact;
16504 if (typed_quantity) {
16505 const auto typed = typed_entry_units(source, result.
resolved_price,
16507 if (!typed)
return result;
16508 own_units = *typed;
16510 result.
units = own_units;
16513 if (
facts.position.signed_units == 0.0
16518 result.
shape = native_order::OpeningShape::Transact;
16523 && std::abs(
facts.position.signed_units - (source.
is_long
16527 result.
shape = native_order::OpeningShape::CloseOpposite;
16533 result.
shape = opposite_now ? native_order::OpeningShape::ReverseTo
16534 : native_order::OpeningShape::Transact;
16539 result.
units = typed_quantity
16543 }
else if (core_sized) {
16549 const auto* published = std::get_if<native_order::RemainingUnits>(&
facts.remaining);
16552 "core-sized quantity without the core's quotient");
16554 result.
units = default_sizing_lot_floor(published->q);
16568 || config_.default_qty_type
16571 }
else if (config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED)) {
16572 result.
units = config_.default_qty_value;
16576 PineSizingSnapshot sizing;
16579 sizing.mark =
price;
16587 result.
units = default_sizing_units(sizing);
16591 const bool opposite =
facts.position.signed_units != 0.0
16592 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16593 const bool keep_mc_close_surplus = [&]() {
16595 bool receipt_origin_live =
false;
16596 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
16597 const auto units =
cohort.second.live_units_by_origin.find(
16599 if (units !=
cohort.second.live_units_by_origin.end()
16600 && units->second > 0.0) {
16601 receipt_origin_live =
true;
16606 const auto* pine_host = pine_view_of(&require_host());
16607 const bool magnifier = pine_host
16608 && pine_host->scheduler_.bar_magnifier_enabled();
16610 - std::abs(
facts.position.signed_units);
16613 ==
facts.cursor.point.interval_index - 1
16615 && last_margin_call_event_ordinal_ == last_applied_ordinal_
16618 == pine_host->adapter_broker_fill_event_sequence()
16619 && !config_.process_orders_on_close && !config_.calc_on_order_fills
16620 && !coof_recalc_active_ && !magnifier
16621 && std::holds_alternative<native_order::Market>(trigger)
16627 &&
facts.position.signed_units > 0.0
16628 &&
facts.position.lot_count == 1U && receipt_origin_live
16629 &&
facts.position.signed_units
16631 && std::isfinite(close_surplus)
16632 && std::abs(close_surplus - 1.0) < 1e-6;
16647 const bool market_or_stop = std::holds_alternative<native_order::Market>(trigger)
16648 || std::holds_alternative<native_order::Stop>(trigger);
16649 bool unified_admit =
false;
16652 result.
units = 0.0;
16653 result.
shape = native_order::OpeningShape::Transact;
16658 if (
PF_RARE(unified_configured_ && !keep_mc_close_surplus
16659 && (admission_rules.unified_placement
16660 || admission_rules.fill_price_recheck))
16661 && unified_admission_terms(source, facts, market_or_stop, opposite, result,
16669 const bool default_money_candidate = !unified_admit
16670 && std::holds_alternative<native_order::Market>(trigger)
16673 && std::abs(config_.default_qty_value - 100.0) < 1e-12
16674 && std::abs((source.
is_long ? config_.margin_long : config_.margin_short) - 100.0)
16676 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
16677 && result.
units && finite_positive(*result.
units)
16680 && finite_positive(source.
sizing.
fx)
16681 && finite_positive(staged_.syminfo.pointvalue);
16682 if (default_money_candidate) {
16683 bool ordinary_book =
true;
16684 for (
const auto& handle : live_handles_) {
16685 if (handle ==
facts.target)
continue;
16686 const auto peer = placement_.find(handle.incarnation);
16687 if (peer == placement_.end())
continue;
16688 const auto& row = peer->second;
16691 && !finite_positive(row.exit_levels.limit)
16692 && !finite_positive(row.exit_levels.stop)
16693 && !finite_positive(row.exit_levels.trail_points)
16694 && !finite_positive(row.exit_levels.trail_price);
16695 if (!unpriced_close) {
16696 ordinary_book =
false;
16700 const bool low_value_lot = *staged_.quantity_grid * source.
sizing.
price
16701 * staged_.syminfo.pointvalue * source.
sizing.
fx < 1.0;
16702 const bool ordinary_fractional = *staged_.quantity_grid < 1.0
16703 && staged_.syminfo.pointvalue == 1.0 && source.
sizing.
fx == 1.0
16704 && staged_.account_fx_effective_from_ms.empty()
16705 && config_.commission_value == 0.0 && config_.slippage == 0
16706 && !config_.process_orders_on_close && !config_.calc_on_order_fills
16707 && config_.pyramiding >= 0 && config_.pyramiding <= 1
16708 && !
cap.active() && risk_.max_intraday_loss <= 0.0
16709 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
16711 const bool whole_lot_tie_scope = *staged_.quantity_grid == 1.0
16712 && staged_.syminfo.pointvalue == 1.0 && source.
sizing.
fx == 1.0
16713 && staged_.account_fx_effective_from_ms.empty()
16714 && config_.commission_value == 0.0 && config_.slippage == 0
16715 && !config_.process_orders_on_close && !config_.calc_on_order_fills
16716 && config_.pyramiding >= 0 && config_.pyramiding <= 1
16717 && !
cap.active() && risk_.max_intraday_loss <= 0.0
16718 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
16724 const bool whole_flat_money = *staged_.quantity_grid == 1.0
16725 && staged_.syminfo.pointvalue == 1.0 && source.
sizing.
fx == 1.0
16726 && staged_.account_fx_effective_from_ms.empty()
16727 && config_.commission_value == 0.0 && config_.slippage == 0
16728 && !config_.calc_on_order_fills
16729 && finite_positive(staged_.syminfo.mintick)
16731 && !
cap.active() && risk_.max_intraday_loss <= 0.0
16732 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
16736 &&
facts.position.signed_units == 0.0
16737 && std::none_of(live_handles_.begin(), live_handles_.end(),
16738 [&](
const auto& handle) {
16739 if (handle == facts.target) return false;
16740 const auto peer = placement_.find(handle.incarnation);
16741 return peer != placement_.end() && peer->second.opening;
16743 if (whole_flat_money) {
16744 const double rounded_cost = source_money_round(
16747 const auto* pine = pine_view_of(host_);
16748 if (pine && std::isfinite(pine->net_profit_sum_)
16749 && same_double_bits(percent_commission_live_equity(source.
sizing.
mark),
16751 settled = pine->initial_capital_
16752 + source_money_round(pine->net_profit_sum_);
16755 for (
const auto& lot : pine->pyramid_entries_) {
16756 settled += direction * (source.
sizing.
mark - lot.price) * lot.qty
16757 * staged_.syminfo.pointvalue * source.
sizing.
fx
16758 - pine->open_entry_commission(lot);
16761 const bool no_gap_tie = nearest_tick(
16763 && rounded_cost == source_money_round(source.
sizing.
equity);
16764 if (std::isfinite(rounded_cost) && source.
sizing.
equity < rounded_cost
16765 && (settled < rounded_cost || no_gap_tie)) {
16766 result.
units = 0.0;
16767 result.
shape = native_order::OpeningShape::Transact;
16772 const bool pooc_flat_money = config_.process_orders_on_close
16776 &&
facts.position.signed_units == 0.0
16779 && config_.pyramiding >= 0 && config_.pyramiding <= 1
16780 && config_.commission_value == 0.0 && config_.slippage >= 0
16781 && finite_positive(staged_.syminfo.mintick)
16782 && *staged_.quantity_grid < 1.0
16783 && staged_.syminfo.pointvalue == 1.0 && source.
sizing.
fx == 1.0
16784 && staged_.account_fx == 1.0
16785 && staged_.account_fx_effective_from_ms.empty()
16786 && (!native_state.spec || native_state.spec->intrabar.is_none())
16788 && risk_.max_intraday_loss <= 0.0
16789 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
16790 && nearest_tick(
facts.raw_price, staged_.syminfo.mintick)
16791 == nearest_tick(source.
sizing.
mark, staged_.syminfo.mintick);
16812 if (whole_lot_tie_scope) {
16815 const auto* pine = pine_view_of(host_);
16816 if (pine && std::isfinite(pine->net_profit_sum_)
16817 && same_double_bits(percent_commission_live_equity(source.
sizing.
mark),
16819 settled = pine->initial_capital_
16820 + source_money_round(pine->net_profit_sum_);
16823 for (
const auto& lot : pine->pyramid_entries_) {
16824 settled += direction * (source.
sizing.
mark - lot.price) * lot.qty
16825 * staged_.syminfo.pointvalue * source.
sizing.
fx
16826 - pine->open_entry_commission(lot);
16830 const bool short_settled = settled < cost;
16831 if (std::isfinite(cost) && short_raw && short_settled) {
16832 result.
units = opposite ?
facts.opposite_book_units : 0.0;
16833 result.
shape = opposite ? native_order::OpeningShape::CloseOpposite
16834 : native_order::OpeningShape::Transact;
16837 if (std::isfinite(cost) && short_raw
16840 && cost == source_money_round(source.
sizing.
equity)) {
16841 result.
units = 0.0;
16842 result.
shape = native_order::OpeningShape::Transact;
16846 if (low_value_lot || ordinary_fractional || pooc_flat_money) {
16847 const double notional_per_price = *result.
units
16848 * staged_.syminfo.pointvalue * source.
sizing.
fx;
16849 const double rounded_cost = source_money_round(
16857 const bool exact_money_scope = low_value_lot && ordinary_fractional
16860 && config_.pyramiding <= 1;
16861 const double money_guard = exact_money_scope ? 0.0 : 1e-9;
16862 if (source.
sizing.
equity + money_guard < rounded_cost) {
16863 result.
units = keep_mc_close_surplus ? 1.0
16864 : (opposite ?
facts.opposite_book_units : 0.0);
16865 result.
shape = keep_mc_close_surplus
16866 ? native_order::OpeningShape::ReverseTo
16867 : (opposite ? native_order::OpeningShape::CloseOpposite
16868 : native_order::OpeningShape::Transact);
16872 const double affordable_price = source_money_round(
16873 source_money_round(source.
sizing.
equity) / notional_per_price);
16874 if (std::isfinite(affordable_price)
16876 result.
units = 0.0;
16877 result.
shape = native_order::OpeningShape::Transact;
16886 const double margin = source.
is_long ? config_.margin_long : config_.margin_short;
16887 const double own = result.
units ? *result.
units : 0.0;
16888 const double fill = nearest_tick(result.
resolved_price, staged_.syminfo.mintick);
16889 const double required = own * fill * staged_.syminfo.pointvalue *
facts.active_fx
16894 const auto* pine_host = pine_view_of(&require_host());
16896 : pine_host ? pine_host->marked_equity_at(fill,
facts.active_fx)
16897 : require_host().native_marked_equity(fill);
16898 const double epsilon = std::max(
16899 1e-9, std::abs(equity) * 1e-12);
16900 affordability_close_only = margin > 0.0 && std::isfinite(required)
16901 && (!std::isfinite(equity) || required > equity + epsilon);
16903 if (affordability_close_only) {
16905 result.
units = 0.0;
16908 result.
units = keep_mc_close_surplus ? 1.0
16909 :
facts.opposite_book_units;
16910 result.
shape = keep_mc_close_surplus
16911 ? native_order::OpeningShape::ReverseTo
16912 : native_order::OpeningShape::CloseOpposite;
16917 const bool opposite =
facts.position.signed_units != 0.0
16918 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16921 result.
shape = native_order::OpeningShape::CloseOpposite;
16927 bool paired =
false;
16930 const auto pairs = [&](
const PlacementSnapshot& peer) {
16933 && peer.sequential_rank != 0 && peer.sequential_rank != source.
sequential_rank
16934 && peer.has_full_entry_bracket
16935 && !(source.
is_long && peer.replaced_opening
16936 && peer.replacement_predecessor_market);
16939 [&](std::uint64_t,
const PlacementSnapshot& peer) {
return pairs(peer); });
16940 for (
const auto& row : placement_) {
16941 if (pairs(row.second)) {
16947 const bool opposite =
facts.position.signed_units != 0.0
16948 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16950 result.
units = std::max(std::abs(
facts.position.signed_units), *result.
units);
16951 result.
shape = native_order::OpeningShape::Transact;
16964 const bool opposite_at_fill =
facts.position.signed_units != 0.0
16965 && ((
facts.position.signed_units > 0.0) != source.
is_long);
16966 const auto live_side =
facts.position.signed_units > 0.0
16969 const bool prior_cycle_close_only = opposite_at_fill
16970 && created_side != live_side
16974 const bool deferred_flip_from_flat =
facts.position.signed_units == 0.0
16982 && result.
units && finite_positive(*result.
units);
16983 if (deferred_flip_from_flat) {
16988 result.
shape = native_order::OpeningShape::Transact;
16990 if (opposite_at_fill) {
16992 const bool replacement_transaction = source.
reverse_to
16995 && (std::holds_alternative<native_order::Market>(trigger)
16997 && config_.default_qty_type
17001 const auto dual_stop_peer = [&](std::uint64_t incarnation,
const PlacementSnapshot& peer) {
17002 return incarnation !=
facts.target.incarnation && peer.opening
17004 && peer.projection_position_side
17007 && peer.is_long != source.
is_long
17008 && finite_positive(peer.exit_levels.stop)
17009 && !finite_positive(peer.exit_levels.limit);
17015 if (flat_dual_candidate)
17017 const bool flat_dual_stop = flat_dual_candidate
17018 && std::any_of(placement_.begin(), placement_.end(),
17019 [&](
const auto& row) { return dual_stop_peer(row.first, row.second); });
17020 result.
shape = source_close_precedes || replacement_transaction
17021 ? native_order::OpeningShape::Transact
17022 : prior_cycle_close_only
17023 ? native_order::OpeningShape::CloseOpposite
17025 ? native_order::OpeningShape::Transact
17026 : native_order::OpeningShape::ReverseTo;
17031bool PineExecutionAdapter::partial_exit_sibling_live(
17033 return std::any_of(live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
17034 if (handle == target) return false;
17035 const auto sibling = placement_.find(handle.incarnation);
17036 if (sibling == placement_.end()) return false;
17037 const auto family = sibling->second.family;
17040 if (sibling->second.command_sequence == source.command_sequence
17041 && sibling->second.bracket_origin == source.bracket_origin) return false;
17042 return sibling->second.source_id == source.source_id
17043 && sibling->second.from_entry == source.from_entry
17044 && (family == PineOrderFamily::ExitLimit || family == PineOrderFamily::ExitStop
17045 || family == PineOrderFamily::ExitTrail);
17054 if (found == placement_.end())
return;
17055 const auto&
source = found->second;
17058 || !((std::isfinite(
source.requested_qty) &&
source.requested_qty > 0.0)
17059 || (std::isfinite(
source.qty_percent) &&
source.qty_percent < 100.0 - 1e-9))
17060 || partial_exit_sibling_live(
source, view.
target))
return;
17063 consumed_partial_exit_cycles_.preflight_joined_single(
source.source_id,
'\x1f',
source.from_entry);
17067 require_tree_precommit(view);
17069 if (snapshot != placement_.end()) {
17070 const auto&
source = snapshot->second;
17080 const bool stale_close_for_new_position =
17085 && physical.signed_units != 0.0
17086 && position_open_bar_index_ >
source.projection_created_bar
17087 && !
source.frozen_market_targeted_close
17088 && !(short_seed_.active && view.
target == short_seed_.materialize_long);
17089 if (stale_close_for_new_position)
return NativePrecommitVerdict::Refuse;
17096 const bool leftover_flat_stop = view.
definition
17097 && std::holds_alternative<native_order::Stop>(view.
definition->request.trigger)
17098 && !finite_positive(
source.exit_levels.limit);
17105 && physical.signed_units == 0.0
17106 && leftover_flat_stop
17107 && policy_script_bar_valid_
17108 && !config_.calc_on_order_fills
17109 && modeled_input()) {
17110 for (
const auto& handle : live_handles_) {
17111 if (handle == view.
target)
continue;
17112 const auto found = placement_.find(handle.incarnation);
17113 if (found == placement_.end())
continue;
17114 const auto& prior = found->second;
17116 || prior.is_long !=
source.is_long
17117 || !finite_positive(prior.exit_levels.stop)
17118 || finite_positive(prior.exit_levels.limit)
17120 || prior.source_sequence >=
source.source_sequence) {
17124 prior.projection_position_side);
17137 if (prior.projection_created_bar ==
source.projection_created_bar) {
17140 const bool source_open_mkt =
source.is_long
17141 ? policy_script_bar_.open >=
source.exit_levels.stop
17142 : policy_script_bar_.open <=
source.exit_levels.stop;
17143 const bool prior_open_mkt = prior.is_long
17144 ? policy_script_bar_.open >= prior.exit_levels.stop
17145 : policy_script_bar_.open <= prior.exit_levels.stop;
17146 if (source_open_mkt != prior_open_mkt)
continue;
17147 const bool prior_touched = prior.is_long
17148 ? policy_script_bar_.high >= prior.exit_levels.stop
17149 : policy_script_bar_.low <= prior.exit_levels.stop;
17150 if (!prior_touched)
continue;
17151 if (!throttled_rearm_already_queued(throttled_reopen_rearm_,
source))
17152 throttled_reopen_rearm_.push_back(
source);
17153 return NativePrecommitVerdict::Refuse;
17158 && physical.signed_units == 0.0
17159 && entry_openings_this_interval_ > 0
17161 && leftover_flat_stop
17162 && !config_.calc_on_order_fills
17163 && modeled_input()) {
17167 if (!throttled_rearm_already_queued(throttled_reopen_rearm_,
source))
17168 throttled_reopen_rearm_.push_back(
source);
17169 return NativePrecommitVerdict::Refuse;
17172 && physical.signed_units != 0.0
17173 && ((physical.signed_units > 0.0) !=
source.is_long);
17179 if (risk_.halted)
return NativePrecommitVerdict::Refuse;
17180 if (risk_.max_cons_loss_days > 0
17181 && day_ledger_.consecutive_loss_days >= risk_.max_cons_loss_days) {
17182 return NativePrecommitVerdict::Refuse;
17187 && std::abs(physical.signed_units) >= risk_.max_position_size) {
17188 return NativePrecommitVerdict::Refuse;
17195 if (!config_.close_entries_rule_any && !
source.from_entry.empty()) {
17196 const auto cohort = cohorts_by_id_.find(
source.from_entry);
17197 if (cohort == cohorts_by_id_.end() || cohort->second.opened.empty()) {
17198 return NativePrecommitVerdict::Refuse;
17201 const bool restored_after_margin =
source.restored_after_margin
17202 || (
source.legs.last_action()
17203 &&
source.legs.last_action()->cause.phase
17204 == exit_legs::Phase::AfterMargin
17205 && !
source.legs.dormant());
17207 && !
source.legs.retired(exit_legs::Leg::Trail);
17208 if (!retained_trail
17209 && (
source.legs.dormant()
17210 || (!restored_after_margin
17211 && follows_same_bar_declined_reversal(
source, view)))) {
17212 return NativePrecommitVerdict::Refuse;
17220 if (snapshot == placement_.end())
return NativePrecommitVerdict::Refuse;
17221 const auto&
source = snapshot->second;
17222 if (
cap.active() &&
cap.budget().latched()
17223 &&
source.source_id !=
"__intraday_cap_close__") {
17224 const auto& transfer =
cap.budget().transfer();
17225 const bool inherited = transfer
17227 && transfer->close_fill == cap_latest_fill_;
17228 if (!inherited)
return NativePrecommitVerdict::Refuse;
17242 const auto beside_frozen_close_lot = [&]() {
17243 if (last_margin_call_script_bar_ !=
source.placement_script_open_ms
17244 || !config_.process_orders_on_close || !
source.opening
17247 || !std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17248 || finite_positive(
source.exit_levels.limit) || finite_positive(
source.exit_levels.stop)
17252 const auto lots = require_host().native_open_lots(kNaN);
17253 if (lots.empty())
return false;
17254 const auto first = placement_.find(lots.front().entry_incarnation);
17255 return first != placement_.end() && first->second.crosses_zero
17258 && first->second.placement_script_open_ms ==
source.placement_script_open_ms;
17261 && physical_now.signed_units != 0.0
17262 && ((physical_now.signed_units > 0.0) ==
source.is_long)) {
17263 const bool beside = !
source.affordability_close_only &&
PF_RARE(beside_frozen_close_lot());
17264 if (beside &&
source.deferred_cohort && !std::isfinite(
source.requested_qty))
17265 return NativePrecommitVerdict::AdmitWithHostMargin;
17266 if (config_.pyramiding == 0 && !beside)
return NativePrecommitVerdict::Refuse;
17274 return NativePrecommitVerdict::AdmitWithHostMargin;
17286 || (
source.frozen_market_instruction && fixed_unmargined_market_batch_scope())) {
17287 return NativePrecommitVerdict::AdmitWithHostMargin;
17292 const bool close_first_admitted =
source.close_first_entry
17296 && unified_admission_scope(
17298 std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17299 || std::holds_alternative<native_order::Stop>(view.
definition->request.trigger),
17301 if (close_first_admitted)
return NativePrecommitVerdict::AdmitWithHostMargin;
17304 ? NativePrecommitVerdict::AdmitWithHostMargin : NativePrecommitVerdict::Refuse;
17306 const double placed_reversal_units = finite_positive(
source.projection_affordability_held_qty)
17307 ?
source.projection_affordability_held_qty :
source.projection_tv_carry_qty;
17309 && std::holds_alternative<native_order::Stop>(view.
definition->request.trigger)
17310 && physical_now.signed_units == 0.0
17314 && finite_positive(placed_reversal_units)
17315 && finite_positive(
source.sizing.equity)) {
17317 - placed_reversal_units;
17318 const double margin_pct =
source.is_long ? config_.margin_long : config_.margin_short;
17319 const double required = own_units * view.
resolved_price * staged_.syminfo.pointvalue
17321 const double guard = std::max(1e-9, std::abs(
source.sizing.equity) * 1e-12);
17322 if (std::abs(margin_pct - 100.0) < 1e-12
17323 && finite_positive(own_units) && std::isfinite(required)
17324 && required <=
source.sizing.equity + guard) {
17325 return NativePrecommitVerdict::AdmitWithHostMargin;
17339 if (config_.pyramiding > 0
17340 && physical_now.lot_count >=
static_cast<std::size_t
>(config_.pyramiding)) {
17341 return NativePrecommitVerdict::Refuse;
17343 const double margin_pct =
source.is_long ? config_.margin_long : config_.margin_short;
17344 if (!(margin_pct > 0.0) || !std::isfinite(margin_pct))
17345 return NativePrecommitVerdict::AdmitWithHostMargin;
17347 const double own_units = finite_positive(
source.sizing.frozen_units)
17348 && !std::isfinite(
source.requested_qty)
17350 const double unit_margin = view.
resolved_price * staged_.syminfo.pointvalue * fx
17351 * margin_pct / 100.0;
17352 const double equity = finite_positive(
source.sizing.equity)
17354 const double guard = std::max(
17355 {1e-9, std::abs(equity) * 1e-12,
17356 staged_.quantity_grid ? *staged_.quantity_grid * unit_margin : 0.0});
17357 if (!finite_positive(own_units) || !std::isfinite(unit_margin)
17358 || !std::isfinite(equity) || own_units * unit_margin > equity + guard) {
17359 return NativePrecommitVerdict::Refuse;
17361 return NativePrecommitVerdict::AdmitWithHostMargin;
17371 const double gap_stop_slip = (
source.is_long ? 1.0 : -1.0) * config_.slippage
17372 * staged_.syminfo.mintick;
17374 && std::holds_alternative<native_order::Stop>(view.
definition->request.trigger)
17375 && !std::isfinite(
source.requested_qty)
17377 && config_.default_qty_value <= 100.0 && !
source.sizing.at_fill
17379 && physical_now.signed_units == 0.0
17380 && finite_positive(
source.sizing.frozen_units)
17381 && finite_positive(
source.sizing.equity) && finite_positive(
source.exit_levels.stop)
17382 &&
source.sizing.price ==
source.exit_levels.stop + gap_stop_slip
17385 const double margin_pct =
source.is_long ? config_.margin_long : config_.margin_short;
17387 * staged_.syminfo.pointvalue *
source.sizing.fx * margin_pct / 100.0;
17388 const double epsilon = std::max(1e-9, std::abs(
source.sizing.equity) * 1e-12);
17389 if (!std::isfinite(cost) || cost >
source.sizing.equity + epsilon)
17390 return NativePrecommitVerdict::Refuse;
17399 const bool pooc_default_all_in = !std::isfinite(
source.requested_qty)
17401 && config_.default_qty_value == 100.0
17402 && finite_positive(
source.sizing.frozen_units)
17403 && finite_positive(
source.sizing.equity) &&
source.sizing.fx == 1.0;
17404 const bool pooc_explicit_fixed = finite_positive(
source.requested_qty)
17406 && finite_positive(
source.projection_affordability_equity)
17407 &&
source.projection_affordability_held_qty == 0.0;
17408 const bool pooc_money_scope = config_.process_orders_on_close
17410 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17414 && !
source.projection_after_close && !
source.birth.from_fill()
17415 &&
source.projection_predecessor == 0 && !
source.replaced_opening
17417 && physical_now.signed_units == 0.0
17418 && config_.pyramiding >= 0 && config_.pyramiding <= 1
17419 && config_.margin_long == 100.0 && config_.commission_value == 0.0
17420 && config_.slippage >= 0 && finite_positive(staged_.syminfo.mintick)
17421 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
17422 && *staged_.quantity_grid < 1.0
17423 && staged_.syminfo.pointvalue == 1.0 && staged_.account_fx == 1.0
17424 &&
source.sizing.fx == 1.0 && staged_.account_fx_effective_from_ms.empty()
17425 && (!native_state.spec || native_state.spec->intrabar.is_none())
17427 && risk_.max_intraday_loss <= 0.0 && risk_.max_drawdown <= 0.0
17428 && risk_.max_cons_loss_days == 0
17429 && (pooc_default_all_in || pooc_explicit_fixed);
17430 if (pooc_money_scope) {
17431 const double units = pooc_default_all_in
17433 const double equity = pooc_default_all_in
17434 ?
source.sizing.equity :
source.projection_affordability_equity;
17435 const double signal = pooc_default_all_in
17436 ?
source.sizing.mark :
source.projection_affordability_signal_price;
17437 const double admission_price = pooc_default_all_in
17439 : nearest_tick(signal + config_.slippage * staged_.syminfo.mintick,
17440 staged_.syminfo.mintick);
17441 const double rounded_cost = source_money_round(units * signal);
17442 const double affordable_price = source_money_round(
17443 source_money_round(equity) / units);
17444 if (!finite_positive(units) || !finite_positive(equity)
17445 || !finite_positive(signal) || !finite_positive(admission_price)
17446 || equity + 1e-9 < rounded_cost
17447 || (std::isfinite(affordable_price)
17448 && affordable_price < admission_price)) {
17449 return NativePrecommitVerdict::Refuse;
17451 return NativePrecommitVerdict::AdmitWithHostMargin;
17454 const double margin_pct =
source.is_long ? config_.margin_long : config_.margin_short;
17463 const bool explicit_money_scope =
17464 std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17465 && std::isfinite(
source.requested_qty) &&
source.requested_qty > 0.0
17468 && physical.signed_units == 0.0
17469 &&
source.projection_position_side
17471 && !
source.projection_after_close &&
source.projection_predecessor == 0
17474 && live_handles_.size() == 1
17475 && config_.margin_long == 100.0 && config_.margin_short == 100.0
17476 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
17477 && *staged_.quantity_grid < 1.0
17478 && staged_.syminfo.pointvalue == 1.0 &&
source.sizing.fx == 1.0
17479 && staged_.account_fx == 1.0
17480 && staged_.account_fx_effective_from_ms.empty()
17481 && config_.slippage == 0
17483 && !config_.process_orders_on_close && !config_.calc_on_order_fills
17484 && config_.pyramiding >= 0 && config_.pyramiding <= 1
17485 && !
cap.active() && risk_.max_intraday_loss <= 0.0
17486 && risk_.max_drawdown <= 0.0 && risk_.max_cons_loss_days == 0
17487 && finite_positive(
source.projection_affordability_equity)
17488 && finite_positive(
source.projection_affordability_signal_price)
17489 && finite_positive(explicit_units)
17490 && *staged_.quantity_grid *
source.projection_affordability_signal_price < 1.0;
17491 if (explicit_money_scope) {
17492 const double equity =
source.projection_affordability_equity;
17493 const double signal =
source.projection_affordability_signal_price;
17494 const double rounded_cost = source_money_round(explicit_units * signal);
17495 const double affordable_price = source_money_round(
17496 source_money_round(equity) / explicit_units);
17497 if (equity + 1e-9 < rounded_cost
17498 || (std::isfinite(affordable_price) && affordable_price < signal)) {
17499 return NativePrecommitVerdict::Refuse;
17502 const bool same_side = physical.signed_units != 0.0
17503 && ((physical.signed_units > 0.0) ==
source.is_long);
17504 const bool reversal = physical.signed_units != 0.0
17505 && ((physical.signed_units > 0.0) !=
source.is_long);
17506 double units = same_side
17525 const auto placed_side =
static_cast<PositionSide>(
source.projection_position_side);
17526 const double placed_held =
source.projection_affordability_held_qty;
17527 const bool same_point_as_placed =
17529 || (config_.process_orders_on_close && !config_.calc_on_order_fills
17533 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17536 && std::isfinite(placed_held) && placed_held >= 0.0
17537 && same_point_as_placed
17538 && std::abs(physical.signed_units) > placed_held + 1e-10
17539 && config_.pyramiding > 0
17540 && physical.lot_count <
static_cast<std::size_t
>(config_.pyramiding)) {
17561 &&
source.is_long && !config_.process_orders_on_close && config_.slippage > 0
17562 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17563 && std::isfinite(
source.requested_qty)
17567 && staged_.syminfo.pointvalue == 1.0 && fx == 1.0 &&
source.sizing.fx == 1.0
17568 && staged_.account_fx_effective_from_ms.empty()
17569 && finite_positive(staged_.syminfo.mintick) && finite_positive(view.
resolved_price)
17570 && std::isfinite(
source.sizing.equity) && finite_positive(units))) {
17571 const double tick = staged_.syminfo.mintick;
17582 const double exec = std::floor(view.
resolved_price / tick + 0.5) * tick;
17583 const double per_unit = source_money_round(
17584 source_money_round(
source.sizing.equity) / units);
17585 return per_unit < exec
17586 ? NativePrecommitVerdict::Refuse : NativePrecommitVerdict::AdmitWithHostMargin;
17588 const double quote = (std::floor(view.
resolved_price / tick + 0.5)
17589 - (
source.is_long ? 1.0 : -1.0) * config_.slippage) * tick;
17590 return source.sizing.equity < source_money_round(units * quote)
17591 ? NativePrecommitVerdict::Refuse : NativePrecommitVerdict::AdmitWithHostMargin;
17603 && finite_positive(
source.projection_affordability_signal_price))
17605 const double required = units * add_price * staged_.syminfo.pointvalue * fx
17606 * margin_pct / 100.0;
17610 double equity = reversal
17612 : (finite_positive(
source.sizing.equity)
17614 const bool pooc_slipped_signal = config_.process_orders_on_close
17616 &&
source.projection_position_side
17618 && physical.signed_units == 0.0
17619 && std::holds_alternative<native_order::Market>(
17621 && finite_positive(
source.projection_affordability_signal_price);
17622 if (pooc_slipped_signal) {
17623 const double signal_fill = nearest_tick(
17624 source.projection_affordability_signal_price
17625 + (
source.is_long ? 1.0 : -1.0) * config_.slippage
17626 * staged_.syminfo.mintick,
17627 staged_.syminfo.mintick);
17628 const double signal_threshold = units * signal_fill
17629 * staged_.syminfo.pointvalue * fx * margin_pct / 100.0;
17630 if (std::isfinite(signal_threshold))
17631 equity = std::max(equity, signal_threshold);
17633 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
17634 if (!(margin_pct > 0.0) || !std::isfinite(margin_pct)) {
17635 return NativePrecommitVerdict::AdmitWithHostMargin;
17643 const bool market_reversal = reversal
17644 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17645 && finite_positive(
source.projection_affordability_equity)
17646 && finite_positive(
source.projection_affordability_signal_price);
17647 if (market_reversal) {
17648 const double raw_fill = finite_positive(view.
raw_price)
17650 const double admit_price = std::max(
17651 source.projection_affordability_signal_price,
17652 nearest_tick(raw_fill, staged_.syminfo.mintick));
17653 const double placement_equity =
source.projection_affordability_equity;
17654 const double reversal_required = units * admit_price
17655 * staged_.syminfo.pointvalue * fx * margin_pct / 100.0;
17656 const double guard = std::max(1e-9, std::abs(placement_equity) * 1e-12);
17657 if (!std::isfinite(reversal_required)
17658 || reversal_required > placement_equity + guard) {
17659 return NativePrecommitVerdict::Refuse;
17661 return NativePrecommitVerdict::AdmitWithHostMargin;
17663 if (!std::isfinite(required) || !std::isfinite(equity) || required > equity + epsilon) {
17664 return NativePrecommitVerdict::Refuse;
17666 return NativePrecommitVerdict::AdmitWithHostMargin;
17676 return NativePrecommitVerdict::Refuse;
17678 || (std::isfinite(
source.qty_percent) &&
source.qty_percent < 100.0 - 1e-9))
17679 && exit_tombstoned(
source.source_id,
source.from_entry))) {
17680 return NativePrecommitVerdict::Refuse;
17682 const auto& bounds =
source.leg_activation.bounds();
17685 if (bounds && current_position_cycle_ > 0) {
17686 const bool ready = stop_leg
17688 : (limit_leg ?
source.leg_activation.limit_ready(
17690 if (!ready)
return NativePrecommitVerdict::Refuse;
17692 if (
source.legs.retired(exit_legs::Leg::Stop)
17693 &&
source.legs.retired(exit_legs::Leg::Limit)
17694 &&
source.legs.retired(exit_legs::Leg::Trail)) {
17695 return NativePrecommitVerdict::Refuse;
17699 && !
source.projection_after_close
17701 && config_.default_qty_value >= 100.0
17702 && finite_positive(
source.sizing.frozen_units)) {
17707 const double margin =
source.is_long ? config_.margin_long : config_.margin_short;
17710 * staged_.syminfo.pointvalue * fx * margin / 100.0;
17711 const double equity =
source.sizing.equity;
17712 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
17713 const bool nested_price_gap_scope = margin == 100.0
17714 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17715 && finite_positive(staged_.syminfo.mintick)
17716 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
17717 && *staged_.quantity_grid < 1.0
17718 && staged_.syminfo.pointvalue == 1.0 &&
source.sizing.fx == 1.0
17719 && staged_.account_fx_effective_from_ms.empty()
17721 && config_.commission_value == 0.0 && config_.slippage == 0
17722 && !config_.process_orders_on_close && !config_.calc_on_order_fills
17724 const bool nested_price_gap_affordable = nested_price_gap_scope
17725 && source_price_band_affordable(equity,
source.sizing.frozen_units,
17727 if (!std::isfinite(required) || !std::isfinite(equity)
17728 || (nested_price_gap_scope && !nested_price_gap_affordable)
17729 || (required > equity + guard && !nested_price_gap_affordable)) {
17730 return NativePrecommitVerdict::Refuse;
17733 const bool variable_default = config_.default_qty_type !=
static_cast<int>(
QtyType::FIXED);
17737 const auto fill_records = open_ledger_records();
17740 && !pure_limit_entry(
source))) {
17743 if (held_at_fill != 0.0 && ((held_at_fill > 0.0) ==
source.is_long)
17744 && *fill_records >=
static_cast<std::size_t
>(config_.pyramiding)) {
17745 return NativePrecommitVerdict::Refuse;
17751 if (variable_default && !
source.frozen_market_instruction && config_.pyramiding > 0
17755 >=
static_cast<std::size_t
>(config_.pyramiding)) {
17756 return NativePrecommitVerdict::Refuse;
17759 ? config_.margin_short : config_.margin_long;
17760 if (!(margin_pct > 0.0) || !std::isfinite(margin_pct))
17761 return NativePrecommitVerdict::AdmitWithHostMargin;
17762 const double fraction = margin_pct / 100.0;
17763 if (finite_positive(
source.sizing.frozen_units) && !
source.sizing.at_fill) {
17764 const double frozen_required = std::abs(
source.sizing.frozen_units)
17765 *
source.sizing.price * staged_.syminfo.pointvalue *
source.sizing.fx * fraction;
17770 if (!std::isfinite(frozen_required) || !std::isfinite(
source.sizing.equity)) {
17771 return NativePrecommitVerdict::Refuse;
17774 const bool live_slippage_changed = state.spec
17775 && config_.slippage !=
static_cast<int>(state.spec->slippage_ticks);
17777 && finite_positive(
source.exit_levels.stop)
17778 && !finite_positive(
source.exit_levels.limit)
17779 && !std::isfinite(
source.requested_qty);
17780 if (placement_sized_stop && live_slippage_changed) {
17784 return NativePrecommitVerdict::AdmitWithHostMargin;
17790 if (active_fx ==
source.sizing.fx) {
17793 const bool reversal = physical.signed_units != 0.0
17794 && ((physical.signed_units > 0.0) !=
source.is_long);
17809 const auto bare_same_bar_book = [&] {
17810 if (!delayed_market_orders_.empty())
return false;
17811 for (
const auto& handle : live_handles_) {
17812 if (handle == view.
target)
continue;
17813 const auto found = placement_.find(handle.incarnation);
17814 if (found == placement_.end())
continue;
17815 const auto& row = found->second;
17818 if (finite_positive(row.exit_levels.limit)
17819 || finite_positive(row.exit_levels.stop)
17820 || row.projection_created_bar !=
source.projection_created_bar)
17825 const bool same_open_reversal = reversal
17827 && std::holds_alternative<native_order::Market>(
17829 && (
source.projection_position_side
17831 || (
source.projection_position_side ==
static_cast<std::int32_t
>(
17833 && bare_same_bar_book()))
17834 && !
source.projection_after_close
17837 && !position_open_priced_ && config_.slippage == 0
17838 && !config_.process_orders_on_close && !config_.calc_on_order_fills
17840 if (same_open_reversal) {
17841 const double held_fraction = (physical.signed_units > 0.0
17842 ? config_.margin_long : config_.margin_short) / 100.0;
17843 fill_required += std::abs(physical.signed_units) * view.
resolved_price
17844 * staged_.syminfo.pointvalue * active_fx * held_fraction;
17846 const bool variable_batch =
source.frozen_market_instruction
17847 && (config_.default_qty_type ==
static_cast<int>(
QtyType::CASH)
17849 && config_.default_qty_value < 100.0));
17854 const bool all_in_reversal = reversal
17856 && config_.default_qty_value >= 100.0;
17857 const bool close_then_open_margin_checkpoint = all_in_reversal
17858 &&
source.projection_after_close && margin_pct == 100.0
17859 && std::holds_alternative<native_order::Market>(
17872 const bool all_in_true_flat_opening = !reversal
17874 && !std::isfinite(
source.requested_qty)
17875 && physical.signed_units == 0.0
17876 &&
source.projection_position_side
17878 && !
source.projection_after_close
17879 && config_.default_qty_type
17881 && std::abs(config_.default_qty_value - 100.0) < 1e-12
17882 && std::abs(margin_pct - 100.0) < 1e-12
17883 && finite_positive(
source.sizing.equity);
17884 const double fill_equity = (variable_batch || all_in_reversal
17885 || all_in_true_flat_opening || same_open_reversal)
17887 const double float_guard = std::max(
17888 1e-9, std::abs(
source.sizing.equity) * 1e-12);
17889 bool paired_market_opening =
false;
17890 for (
const auto& handle : live_handles_) {
17891 if (handle == view.
target)
continue;
17892 const auto peer = placement_.find(handle.incarnation);
17893 if (peer == placement_.end())
continue;
17894 const auto& row = peer->second;
17896 && row.placement_script_open_ms ==
source.placement_script_open_ms
17897 && !finite_positive(row.exit_levels.limit)
17898 && !finite_positive(row.exit_levels.stop)) {
17899 paired_market_opening =
true;
17904 const bool magnified = native_state.spec
17905 && !native_state.spec->intrabar.is_none();
17907 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17908 && finite_positive(staged_.syminfo.mintick)
17909 && !std::isfinite(
source.requested_qty)
17911 && config_.default_qty_value == 100.0
17912 && std::abs((
source.is_long ? config_.margin_long : config_.margin_short)
17914 && staged_.quantity_grid && *staged_.quantity_grid > 0.0
17915 && *staged_.quantity_grid < 1.0
17916 && staged_.syminfo.pointvalue == 1.0 &&
source.sizing.fx == 1.0
17917 && staged_.account_fx_effective_from_ms.empty()
17919 && config_.commission_value == 0.0 && config_.slippage == 0
17920 && !config_.process_orders_on_close && !config_.calc_on_order_fills
17921 && !magnified && !
source.birth.from_fill()
17922 && !
source.projection_after_close
17924 && ((physical.signed_units == 0.0
17925 &&
source.projection_position_side
17927 && !paired_market_opening)
17929 const bool price_gap_affordable = price_gap_scope
17930 && source_price_band_affordable(
source.sizing.equity,
17932 if (price_gap_scope && !price_gap_affordable)
17933 return NativePrecommitVerdict::Refuse;
17935 &&
source.projection_position_side
17937 && !
source.projection_after_close
17938 && physical.signed_units == 0.0
17940 && config_.default_qty_value == 100.0
17941 && !config_.process_orders_on_close;
17942 if (true_flat_gap_scope
17943 && fill_required >
source.sizing.equity + float_guard
17944 && !price_gap_affordable) {
17945 return NativePrecommitVerdict::Refuse;
17948 &&
source.projection_after_close && physical.signed_units == 0.0) {
17957 return NativePrecommitVerdict::AdmitWithHostMargin;
17966 const bool same_side_add = physical.signed_units != 0.0
17967 && (physical.signed_units > 0.0) ==
source.is_long;
17976 const auto placed_side =
static_cast<PositionSide>(
source.projection_position_side);
17977 const double placed_held =
source.projection_tv_carry_qty;
17979 && std::holds_alternative<native_order::Market>(view.
definition->request.trigger)
17982 && std::isfinite(placed_held) && placed_held >= 0.0
17984 && std::abs(physical.signed_units) > placed_held + 1e-10
17985 && config_.pyramiding > 0
17986 && physical.lot_count <
static_cast<std::size_t
>(config_.pyramiding)) {
17988 * view.
resolved_price * staged_.syminfo.pointvalue * active_fx * fraction;
17990 double admission_guard = float_guard;
17991 if (!reversal && !same_side_add && staged_.quantity_grid) {
17992 admission_guard = std::max(admission_guard,
17994 * staged_.syminfo.pointvalue * active_fx * fraction);
17996 if (!std::isfinite(fill_required) || !std::isfinite(fill_equity)
17997 || (fill_required > fill_equity + admission_guard
17998 && (same_open_reversal || !price_gap_affordable)
17999 && !close_then_open_margin_checkpoint)) {
18000 return NativePrecommitVerdict::Refuse;
18003 return NativePrecommitVerdict::AdmitWithHostMargin;
18012 const bool opening_margin_checkpoint =
18014 && finite_positive(
source.requested_qty)
18017 if (opening_margin_checkpoint)
return NativePrecommitVerdict::AdmitWithHostMargin;
18020 return NativePrecommitVerdict::Refuse;
18022 return NativePrecommitVerdict::AdmitWithHostMargin;
18027constexpr std::int64_t kChartDayCivilSpan = std::int64_t{1} << 40;
18034[[gnu::noinline]] std::int64_t civil_chart_day_key(AdapterLookupIndex* index,
18035 std::int64_t secs)
noexcept {
18036 const std::int64_t days = secs / 86400 - (secs % 86400 < 0 ? 1 : 0);
18038 const std::int64_t key = std::int64_t{date.
day} * 100 + date.
month;
18039 if (index !=
nullptr) {
18040 index->day_lo = std::max(days * 86400, -kChartDayCivilSpan + 1);
18041 index->day_hi = std::min(days * 86400 + 86400, kChartDayCivilSpan);
18042 index->day_key = key;
18050[[gnu::noinline]] std::int64_t libc_chart_day_key(
const std::string& timezone,
bool utc_chart,
18051 std::time_t seconds)
noexcept {
18053 const auto utc = [&]() {
18054 return ::gmtime_r(&seconds, &fields) !=
nullptr;
18057 if (!utc())
return std::numeric_limits<std::int64_t>::min();
18060 tz_util::ScopedTimezone guard(timezone);
18061 if (::localtime_r(&seconds, &fields) ==
nullptr) {
18062 if (!utc())
return std::numeric_limits<std::int64_t>::min();
18068 if (!utc())
return std::numeric_limits<std::int64_t>::min();
18071 return static_cast<std::int64_t
>(
fields.tm_mday) * 100
18072 +
static_cast<std::int64_t
>(
fields.tm_mon + 1);
18078 const std::time_t seconds =
static_cast<std::time_t
>(timestamp_ms / 1000);
18079 const std::string& timezone = staged_.chart_timezone;
18080 const bool utc_chart = timezone.empty() || timezone ==
"UTC" || timezone ==
"Etc/UTC";
18085 const std::int64_t secs =
static_cast<std::int64_t
>(seconds);
18086 if (secs > -kChartDayCivilSpan && secs < kChartDayCivilSpan) {
18091 AdapterLookupIndex* index = parked_lookup_index(host_,
this, run_counter_);
18092 if (index !=
nullptr && index->day_lo <= secs && secs < index->day_hi)
18093 return index->day_key;
18094 return civil_chart_day_key(index, secs);
18097 return libc_chart_day_key(timezone, utc_chart, seconds);
18106 static_cast<int>(key / 100),
static_cast<int>(key % 100)};
18109compat::pine::Calculation PineExecutionAdapter::cap_calculation(
18110 const NativeDecisionContext& context)
const {
18112 const auto* pine_host = pine_view_of(&require_host());
18113 const bool magnifier = pine_host
18114 ? pine_host->scheduler_.bar_magnifier_enabled()
18115 : context.sub_count > 1;
18117 coof_recalc_active_, magnifier,
18118 !modeled_input() && state.phase == NativeRunPhase::Warmup,
18121 context.coordinate.interval_index};
18124compat::pine::MatchedAttempt PineExecutionAdapter::cap_attempt(
18126 const native_order::ExecutionAppliedEvent* applied)
const {
18129 kind = (finite_positive(
snapshot.exit_levels.limit)
18130 || finite_positive(
snapshot.exit_levels.stop))
18138 std::size_t prefill_entries = position.lot_count;
18140 if (applied->closed_units > 0.0) {
18146 }
else if (applied->opened_units != 0.0
18147 && applied->cycle_before != applied->cycle_after) {
18149 prefill_entries = 0;
18150 }
else if (applied->opened_units != 0.0
18151 && applied->opened_lot_incarnation != 0
18152 && prefill_entries > 0) {
18160 prefill_entries = 0;
18163 const int live_entries = prefill_entries
18164 >
static_cast<std::size_t
>(std::numeric_limits<int>::max())
18165 ? std::numeric_limits<int>::max() : static_cast<int>(prefill_entries);
18166 return {kind, incarnation,
snapshot.projection_created_bar,
18167 snapshot.is_long, side, live_entries,
18168 config_.pyramiding};
18171bool PineExecutionAdapter::cap_placement_denied(
const NativeDecisionContext& context) {
18175bool PineExecutionAdapter::intraday_loss_orders_blocked() const noexcept {
18176 return risk_.intraday_block_day != std::numeric_limits<std::int64_t>::min()
18177 && risk_.intraday_block_day == day_ledger_.current_day;
18180void PineExecutionAdapter::update_risk_state(
double mark_price) {
18181 if (std::isfinite(mark_price)) {
18182 const double equity = require_host().native_marked_equity(mark_price);
18183 if (std::isfinite(equity)) {
18184 if (!std::isfinite(risk_.observed_peak_equity)
18185 || equity > risk_.observed_peak_equity) {
18186 risk_.observed_peak_equity = equity;
18188 const double drawdown = risk_.observed_peak_equity - equity;
18189 if (drawdown > risk_.observed_max_drawdown)
18190 risk_.observed_max_drawdown = drawdown;
18193 if (risk_.halted)
return;
18194 if (risk_.max_drawdown > 0.0 && std::isfinite(risk_.observed_peak_equity)) {
18195 const double threshold = risk_.max_drawdown_percent
18196 ? risk_.observed_peak_equity * risk_.max_drawdown / 100.0
18197 : risk_.max_drawdown;
18198 if (std::isfinite(risk_.max_drawdown) && !std::isfinite(threshold)) {
18199 engine_command_value_invalid_or_count(
"risk.threshold",
18200 EngineValueStage::risk, EngineValueReason::numeric_overflow);
18202 if (risk_.observed_max_drawdown >= threshold) {
18203 risk_.halted =
true;
18207 if (risk_.max_cons_loss_days > 0
18208 && day_ledger_.consecutive_loss_days >= risk_.max_cons_loss_days) {
18209 risk_.halted =
true;
18213bool PineExecutionAdapter::intraday_loss_breached(
double mark_price)
const {
18214 if (!(risk_.max_intraday_loss > 0.0) || intraday_loss_orders_blocked()
18215 || !std::isfinite(day_ledger_.intraday_start_equity)
18216 || !std::isfinite(mark_price)) {
18219 const double equity = require_host().native_marked_equity(mark_price);
18220 const double loss = day_ledger_.intraday_start_equity - equity;
18221 const double threshold = risk_.max_intraday_loss_percent
18222 ? day_ledger_.intraday_start_equity * risk_.max_intraday_loss / 100.0
18223 : risk_.max_intraday_loss;
18224 if (std::isfinite(risk_.max_intraday_loss) && !std::isfinite(threshold)) {
18225 engine_command_value_invalid_or_count(
"risk.threshold",
18226 EngineValueStage::risk, EngineValueReason::numeric_overflow);
18228 if (!(threshold > 0.0) || !(loss > 0.0) || !std::isfinite(loss))
return false;
18229 const double epsilon = 1e-9 * std::max(1.0, std::abs(threshold));
18230 return loss + epsilon >= threshold;
18246 double mark_price, std::int64_t sub_bar_open_ms)
const {
18249 if (position.signed_units < 0.0) {
18252 mark_price = nearest_tick(mark_price, staged_.syminfo.mintick);
18254 money.
mark = mark_price;
18255 money.
held = std::abs(position.signed_units);
18256 const double margin_pct = position.signed_units > 0.0
18257 ? config_.margin_long : config_.margin_short;
18258 if (!source_margin_call_enabled_ || !(money.
held > 0.0)
18259 || !finite_positive(mark_price) || !finite_positive(margin_pct)
18260 || !finite_positive(staged_.syminfo.pointvalue)) {
18263 const double fx = active_staged_fx(sub_bar_open_ms);
18264 const double fraction = margin_pct / 100.0;
18265 money.
scale = staged_.syminfo.pointvalue * fx * fraction;
18266 money.
short_book = position.signed_units < 0.0;
18267 money.
unit_margin = mark_price * staged_.syminfo.pointvalue * fx * fraction;
18270 if (staged_.quantity_grid && *staged_.quantity_grid > 0.0
18271 && staged_.account_fx_effective_from_ms.empty()) {
18272 const double lot_value = *staged_.quantity_grid * mark_price
18273 * staged_.syminfo.pointvalue * fx;
18274 if (std::isfinite(lot_value) && lot_value < 1.0) {
18282 money.
equity = percent_commission_live_equity(mark_price);
18283 if (
const double mirrored = gain_loss_signal_equity(mark_price); std::isfinite(mirrored))
18284 money.
equity = mirrored;
18290 && config_.commission_value > 0.0) {
18291 money.
equity = strategy_equity_at(mark_price, std::nullopt);
18301 const SourceMarginMoney& money,
bool opening_checkpoint,
bool gated,
double print)
const {
18304 const double raw_minimum = opening_basis
18307 if (!(raw_minimum > 0.0) || !std::isfinite(raw_minimum))
return 0.0;
18317 && std::isfinite(gain_loss_signal_equity(money.
mark));
18318 if (raw_minimum <= internal::kQtyEpsilon && !dust_unit)
return 0.0;
18319 double minimum = raw_minimum;
18320 if (staged_.quantity_grid) {
18328 const double floor_basis = opening_basis
18331 minimum = std::floor(floor_basis / *staged_.quantity_grid)
18332 * *staged_.quantity_grid;
18334 double units = minimum > 0.0 ? 4.0 * minimum : 0.0;
18335 if (units > 0.0 && staged_.quantity_grid) {
18336 units = std::floor(units / *staged_.quantity_grid + 1e-6)
18337 * *staged_.quantity_grid;
18339 if (!(units > 0.0) && staged_.quantity_grid
18340 && *staged_.quantity_grid <= 1.0
18341 && (raw_minimum > internal::kQtyEpsilon || dust_unit) && raw_minimum < 1.0) {
18342 const double candidate = std::min(1.0, money.
held);
18343 const double rounded = floor_quantity_grid(candidate, staged_.quantity_grid);
18344 const double guard = std::max(1e-12, std::abs(candidate) * 1e-12);
18358 const double slip = config_.slippage > 0
18359 ? config_.slippage * staged_.syminfo.mintick : 0.0;
18362 * (finite_positive(print) ? print : money.
mark + (short_book ? slip : -slip))
18364 if ((candidate >= money.
held - guard || std::abs(rounded - candidate) <= guard)
18368 units = std::min(money.
held, units);
18371 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units))
return 0.0;
18373 && short_call_vetoed(money, units, opening_checkpoint, print))
18391bool PineExecutionAdapter::short_call_vetoed(
18392 const SourceMarginMoney& money,
double units,
bool opening_checkpoint,
double print)
const {
18394 || !money.short_book || !(config_.
slippage > 0) || !finite_positive(money.scale)
18399 if (!finite_positive(fill))
return false;
18400 return !(units * fill * money.scale > money.held * fill * money.scale - money.equity);
18419 const std::vector<NativeOpenLot>& lots,
const SourceMarginMoney& money,
double called,
18420 double fill, std::int64_t sub_bar_open_ms)
const {
18426 && staged_.quantity_grid && *staged_.quantity_grid == 1.0;
18427 if (!money.
valid || !(money.
required > money.
equity) || !(called > internal::kQtyEpsilon)
18428 || !staged_.quantity_grid || !(*staged_.quantity_grid > 0.0)
18429 || !(*staged_.quantity_grid < 1.0 || whole_share) || !finite_positive(fill)
18430 || (config_.commission_value != 0.0
18434 const double scale = staged_.syminfo.pointvalue * active_staged_fx(sub_bar_open_ms);
18435 const double fee = config_.commission_value / 100.0;
18437 double remaining = called;
18438 double booked = 0.0;
18439 double last_short = 0.0;
18440 for (
const auto& lot : lots) {
18441 if (!(remaining > internal::kQtyEpsilon))
break;
18442 if (!(lot.signed_units > 0.0))
return 0.0;
18443 const double taken = std::min(remaining, lot.signed_units);
18444 remaining -= taken;
18445 const double lagged = shortfall - booked - taken * lot.entry_price * scale;
18446 if (lagged > 0.0) last_short = lagged;
18447 booked += taken * fill * scale * (1.0 - fee);
18449 if (!(last_short > 0.0) || !std::isfinite(last_short))
return 0.0;
18450 const double grid = *staged_.quantity_grid;
18451 const double minimum = std::floor(source_money_round(last_short) / money.
unit_margin / grid)
18453 double units = std::floor(4.0 * minimum / grid + 1e-6) * grid;
18454 units = std::min(units, money.
held - called);
18455 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units))
return 0.0;
18470 if (config_.slippage == 0)
return source_bar_fill_tick(fire, staged_.syminfo.mintick);
18471 const double rounded = source_bar_fill_tick(fire, staged_.syminfo.mintick);
18472 const double slipped = rounded + (close_is_buy ? 1.0 : -1.0)
18473 * config_.slippage * staged_.syminfo.mintick;
18474 return directional_tick(slipped, staged_.syminfo.mintick, close_is_buy);
18485 if (!staged_.quantity_grid || *staged_.quantity_grid != 1.0
18487 || config_.default_qty_value != 100.0 || config_.commission_value != 0.0) {
18490 bool rounded_tie_opening =
false;
18491 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
18492 for (
const auto& origin : cohort.second.opened) {
18493 const auto opening = placement_.find(origin.incarnation);
18494 if (opening == placement_.end())
continue;
18495 const auto& row = opening->second;
18497 * staged_.syminfo.pointvalue * row.sizing.fx;
18498 if (row.opening && std::isfinite(cost)
18499 && cost == source_money_round(row.sizing.equity)
18500 && cost > row.sizing.equity) {
18501 rounded_tie_opening =
true;
18505 if (rounded_tie_opening)
break;
18507 if (!rounded_tie_opening)
return false;
18508 for (
const auto& handle : live_handles_) {
18509 const auto pending = placement_.find(handle.incarnation);
18510 if (pending == placement_.end())
continue;
18511 const auto& row = pending->second;
18512 if (row.opening && (finite_positive(row.exit_levels.limit)
18513 || finite_positive(row.exit_levels.stop))) {
18537 if (point.
kind == NativeMarginCheckKind::FxRoll)
return false;
18544 if (point.
kind == NativeMarginCheckKind::IntrabarSample) {
18552 if (kernel_margin_resize_point_ == ordinal) {
18561 }
else if (kernel_margin_path_point_ != ordinal) {
18583 if (!bar_magnifier_)
return false;
18585 if (held == 0.0)
return true;
18586 if (held > 0.0 ? !(config_.margin_long < 100.0) : config_.process_orders_on_close)
18588 return !any_live_row(live_handles_, placement_, [](
const PlacementSnapshot& row) {
18599 if (!money.valid)
return std::nullopt;
18601 decision.
required = money.required;
18602 decision.
equity = money.equity;
18624 && std::abs(config_.margin_long - 100.0) < 1e-12) {
18641bool PineExecutionAdapter::submit_margin_call_slice(
18643 bool opening_checkpoint, std::optional<double> resolved_execution_price) {
18646 mark_price = money.mark;
18647 if (!money.valid)
return false;
18653 !resolved_execution_price.has_value());
18654 if (!(units > 0.0))
return false;
18663 double close_base = mark_price;
18664 if (opening_checkpoint && std::isfinite(config_.
slippage)
18666 close_base = source_bar_fill_tick(
18667 mark_price - (position.signed_units > 0.0 ? 1.0 : -1.0)
18671 return submit_margin_call_units(close_base, context, units,
true, resolved_execution_price);
18674bool PineExecutionAdapter::submit_margin_call_units(
18675 double mark_price,
const NativeDecisionContext& context,
double units,
18676 bool force_execution_price, std::optional<double> resolved_execution_price) {
18678 const double held = std::abs(position.signed_units);
18679 if (!(units > 0.0) || !std::isfinite(units) || !(held > 0.0)
18680 || !finite_positive(mark_price)) {
18683 units = std::min(units, held);
18684 native_order::Request
request;
18685 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
18687 request.comment =
"Margin call";
18692 if (force_execution_price) {
18703 resolved_execution_price ? *resolved_execution_price
18706 snapshot.sizing = sizing_snapshot();
18707 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
18709 if (!accepted)
return false;
18710 if (
PF_RARE(open_call_sequence_)) unrepeated_margin_calls_.push_back(accepted->incarnation);
18711 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
18723[[gnu::always_inline]]
inline void PineExecutionAdapter::fold_source_money() {
18724 const auto* pine = pine_view_of(host_);
18726 const std::uint64_t count = pine->closed_trade_count();
18727 if (count == source_money_folded_)
return;
18728 fold_source_money_through(*pine, count);
18734void PineExecutionAdapter::fold_source_money_through(
const PineStrategyHost& pine,
18735 std::uint64_t count) {
18736 if (count < source_money_folded_) {
18738 if (count < prefix.base)
18740 "source-money rewind precedes the sealed prefix");
18741 source_gains_ = prefix.source_gains;
18742 source_losses_ = prefix.source_losses;
18743 source_money_folded_ = prefix.base;
18745 for (; source_money_folded_ < count; ++source_money_folded_) {
18746 const double profit = pine.closed_trade(source_money_folded_).pnl;
18747 if (profit > 0.0) source_gains_ += profit;
18748 else source_losses_ += profit;
18761[[gnu::always_inline]]
inline bool PineExecutionAdapter::gain_loss_regime()
const {
18763 && gain_loss_run_regime();
18768bool PineExecutionAdapter::gain_loss_run_regime()
const {
18769 const auto* pine = pine_view_of(host_);
18770 if (pine ==
nullptr || host_ ==
nullptr)
return false;
18772 if (spec ==
nullptr || spec->fee_value != 0.0)
return false;
18773 for (
const auto& fact : open_entry_fees_) {
18774 if (fact.nonpercent_fee != 0.0)
return false;
18776 return pine->pyramid_entries_.size() <= 1;
18788double PineExecutionAdapter::gain_loss_signal_equity(
double mark)
const {
18789 return gain_loss_regime() ? gain_loss_mirror_equity(mark) : kNaN;
18800double PineExecutionAdapter::gain_loss_closed_equity()
const {
18802 const auto* pine = pine_view_of(host_);
18803 if (!pine)
return kNaN;
18804 double gains = source_gains_;
18805 double losses = source_losses_;
18806 const std::uint64_t count = pine->closed_trade_count();
18807 if (count < source_money_folded_)
return kNaN;
18808 for (std::uint64_t index = source_money_folded_; index < count; ++index) {
18809 const double profit = pine->closed_trade(index).pnl;
18810 if (profit > 0.0) gains += profit;
18811 else losses += profit;
18813 const double equity = pine->initial_capital_ + (gains + losses);
18814 return std::isfinite(equity) ? equity : kNaN;
18823double PineExecutionAdapter::gain_loss_mirror_equity(
double mark)
const {
18824 const auto* pine = pine_view_of(host_);
18825 if (!pine || !finite_positive(mark))
return kNaN;
18826 double gains = source_gains_;
18827 double losses = source_losses_;
18828 const std::uint64_t count = pine->closed_trade_count();
18829 if (count < source_money_folded_)
return kNaN;
18830 for (std::uint64_t index = source_money_folded_; index < count; ++index) {
18831 const double profit = pine->closed_trade(index).pnl;
18832 if (profit > 0.0) gains += profit;
18833 else losses += profit;
18837 double weighted = 0.0;
18838 double units = 0.0;
18839 for (
const auto& lot : pine->pyramid_entries_) {
18840 weighted += lot.price * lot.qty;
18843 if (!finite_positive(units) || !std::isfinite(weighted))
return kNaN;
18845 open = side * (mark - weighted / units) * units;
18847 const double equity = (pine->initial_capital_ + (gains + losses)) + open;
18848 return std::isfinite(equity) ? equity : kNaN;
18858bool PineExecutionAdapter::unified_admission_scope(
18860 double units,
bool beside_exits)
const {
18867 || !finite_positive(units) || finite_positive(source.exit_levels.limit)
18868 || !source.oca_name.empty() || source.birth.from_fill() || source.crosses_zero
18869 || !pinned_lot_grid()
18870 || config_.calc_on_order_fills || bar_magnifier_
18871 || config_.commission_value != 0.0 || config_.pyramiding < 0 || config_.pyramiding > 1
18872 || staged_.syminfo.pointvalue != 1.0 || source.sizing.fx != 1.0
18873 || staged_.account_fx != 1.0 || !staged_.account_fx_effective_from_ms.empty()
18874 || !finite_positive(staged_.syminfo.mintick) || !finite_positive(source.sizing.mark)
18875 || !std::isfinite(source.sizing.equity) ||
cap.active()
18876 || risk_.max_intraday_loss > 0.0 || risk_.max_drawdown > 0.0
18877 || risk_.max_cons_loss_days > 0 || risk_.max_position_size > 0.0) {
18880 if (signed_units != 0.0 && ((signed_units > 0.0) == source.is_long))
return false;
18887 const auto placed =
static_cast<PositionSide>(source.projection_position_side);
18895 if (finite_positive(source.exit_levels.stop) && placed !=
PositionSide::FLAT)
return false;
18903 if (finite_positive(source.exit_levels.stop) && !std::isfinite(source.requested_qty))
18905 const bool default_all_in = !std::isfinite(source.requested_qty)
18907 && config_.default_qty_value == 100.0;
18908 const bool explicit_units = std::isfinite(source.requested_qty)
18909 && (source.qty_type < 0 || source.qty_type ==
static_cast<int>(
QtyType::FIXED));
18910 if (!default_all_in && !explicit_units)
return false;
18911 const double margin = source.is_long ? config_.margin_long : config_.margin_short;
18912 if (!finite_positive(margin) || margin > 100.0)
return false;
18913 for (
const auto& handle : live_handles_) {
18914 const auto peer = placement_.find(handle.incarnation);
18915 if (peer == placement_.end())
continue;
18916 const auto& row = peer->second;
18917 if (row.source_id == source.source_id && row.source_sequence == source.source_sequence)
18921 && !finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop)
18922 && !finite_positive(row.exit_levels.trail_points)
18923 && !finite_positive(row.exit_levels.trail_price);
18929 && !(beside_exits && stop_or_trail_leg)) {
18944bool PineExecutionAdapter::unified_admission_terms(
18946 bool market_or_stop,
bool opposite, native_order::ExecutionTerms& result,
18947 bool& admitted)
const {
18949 const double unified_units = result.units && finite_positive(*result.units)
18951 : (std::isfinite(source.requested_qty)
18952 ? floor_quantity_grid(std::abs(source.requested_qty), staged_.quantity_grid)
18954 const bool book_clear = unified_admission_scope(source, market_or_stop,
18955 facts.position.signed_units, unified_units);
18957 && !(opposite && admission_rules.tie_reversal_beside_exits
18958 && admission_rules.unified_placement
18959 && unified_admission_scope(source, market_or_stop,
facts.position.signed_units,
18960 unified_units,
true))) {
18963 const double tick = staged_.syminfo.mintick;
18964 const double units = unified_units;
18965 const double side = source.is_long ? 1.0 : -1.0;
18966 const double margin = (source.is_long ? config_.margin_long : config_.margin_short) / 100.0;
18967 const double equity = source.sizing.equity;
18968 const double signal_ticks = std::floor(source.sizing.mark / tick + 0.5);
18969 const double signal_close = signal_ticks * tick;
18978 const double cost = source_money_round(units * signal_close * margin);
18979 const bool default_all_in = !std::isfinite(source.requested_qty)
18981 && config_.default_qty_value == 100.0;
18982 const bool whole_units = staged_.quantity_grid && *staged_.quantity_grid == 1.0;
18983 if (!(default_all_in && whole_units && margin == 1.0 && equity < cost
18984 && !(source_money_round(equity) < cost))) {
18987 result.units =
facts.opposite_book_units;
18988 result.shape = native_order::OpeningShape::CloseOpposite;
18991 if (admission_rules.unified_placement
18992 && equity < source_money_round(units * signal_close * margin)) {
18993 result.units = opposite ?
facts.opposite_book_units : 0.0;
18994 result.shape = opposite ? native_order::OpeningShape::CloseOpposite
18995 : native_order::OpeningShape::Transact;
19002 if (!(source.close_first_entry && admission_rules.close_first_admission)) {
19003 const double per_unit = margin == 1.0
19004 ? source_money_round(source_money_round(equity) / units)
19005 : source_money_round(source_money_round(equity) / (units * margin));
19006 const bool placement_short = admission_rules.unified_placement
19008 && per_unit < (signal_ticks + side * config_.slippage) * tick;
19009 const double quote = finite_positive(
facts.raw_price) ?
facts.raw_price
19010 : result.resolved_price;
19011 const bool fill_short = admission_rules.fill_price_recheck
19012 && !unified_fill_admits(source, units, quote);
19013 if (placement_short || fill_short) {
19014 result.units = 0.0;
19015 result.shape = native_order::OpeningShape::Transact;
19019 result.units = units;
19020 if (!opposite)
return true;
19033[[gnu::always_inline]]
inline bool PineExecutionAdapter::pinned_lot_grid()
const {
19034 return lot_grid_pinned_;
19042[[gnu::noinline, gnu::cold]]
19043bool PineExecutionAdapter::unified_placement_book_clear()
const {
19044 for (
const auto& handle : live_handles_) {
19045 const auto peer = placement_.find(handle.incarnation);
19046 if (peer == placement_.end())
continue;
19047 const auto& row = peer->second;
19050 && !finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop)
19051 && !finite_positive(row.exit_levels.trail_points)
19052 && !finite_positive(row.exit_levels.trail_price);
19055 for (
const auto& pending : pending_same_bar_commands_)
19056 if (
pending.opening)
return false;
19057 for (
const auto& pending : pending_entries_)
19058 if (
pending.snapshot.opening)
return false;
19059 for (
const auto& pending : delayed_market_orders_)
19060 if (
pending.snapshot.opening)
return false;
19069[[gnu::noinline, gnu::cold]]
19070bool PineExecutionAdapter::unified_fill_admits(
const PlacementSnapshot& source,
double units,
19071 double quote)
const {
19072 const double tick = staged_.syminfo.mintick;
19073 if (!finite_positive(units) || !finite_positive(quote) || !finite_positive(tick))
return true;
19074 const double margin = (source.is_long ? config_.margin_long : config_.margin_short) / 100.0;
19075 const double equity = source.sizing.equity;
19076 const double per_unit = margin == 1.0
19077 ? source_money_round(source_money_round(equity) / units)
19078 : source_money_round(source_money_round(equity) / (units * margin));
19079 const double side = source.is_long ? 1.0 : -1.0;
19080 const double exec = (std::floor(quote / tick + 0.5) + side * config_.slippage) * tick;
19081 return !(per_unit < exec);
19095[[gnu::always_inline]]
inline bool PineExecutionAdapter::slipped_long_margin_scope()
const {
19097 && source_margin_call_enabled_ && slipped_long_book_scope();
19102bool PineExecutionAdapter::slipped_long_book_scope()
const {
19104 if (!(position.signed_units > 0.0) || position.lot_count != 1 ||
cap.active()
19105 || risk_.max_intraday_loss > 0.0 || risk_.max_drawdown > 0.0
19106 || risk_.max_cons_loss_days > 0 || pine_view_of(&require_host()) ==
nullptr
19107 || !pending_bracket_legs_.empty() || !pending_entries_.empty()
19108 || !pending_same_bar_commands_.empty() || !pending_relative_exits_.empty()
19109 || !source_shadow_pending_.empty()) {
19112 for (
const auto& handle : live_handles_) {
19113 const auto found = placement_.find(handle.incarnation);
19119 const CohortFacts* sole_cohort =
nullptr;
19120 for (
const auto& row : cohorts_by_id_.held_rows()) {
19121 if (row.second.opened.empty())
continue;
19122 if (sole_cohort !=
nullptr)
return false;
19123 sole_cohort = &row.second;
19125 if (sole_cohort ==
nullptr || sole_cohort->opened.size() != 1)
return false;
19126 const auto placement = placement_.find(sole_cohort->opened.front().incarnation);
19130 && !finite_positive(
placement->second.exit_levels.limit)
19131 && !finite_positive(
placement->second.exit_levels.stop)
19132 && !finite_positive(
placement->second.exit_levels.trail_offset);
19150[[gnu::noinline, gnu::cold]]
19151double PineExecutionAdapter::slipped_long_margin_units(
double quote)
const {
19152 const auto* pine = pine_view_of(&require_host());
19154 const double held = position.signed_units;
19155 const double step = *staged_.quantity_grid;
19156 if (!pine || !finite_positive(quote) || !(held > 0.0))
return 0.0;
19157 const double required = source_money_round(held * quote);
19158 const double gain_loss = gain_loss_closed_equity();
19159 const double marked = (std::isfinite(gain_loss) ? gain_loss : pine->closed_trade_equity())
19160 + held * (quote - pine->position_entry_price_);
19161 const double deficit = required - marked;
19162 if (!(deficit > 0.0) || !std::isfinite(deficit))
return 0.0;
19166 if (std::floor(source_money_round(deficit) / quote / step) > 0.0 || !(deficit / quote < 1.0))
19170 const double free_cash = pine->closed_trade_equity() - held * pine->position_entry_price_;
19172 return std::min(1.0, held);
19182[[gnu::noinline, gnu::cold]]
19183bool PineExecutionAdapter::slipped_long_unit_shortfall(
double quote)
const {
19184 const auto* pine = pine_view_of(&require_host());
19186 if (!pine || !finite_positive(quote) || !(held > 0.0))
return false;
19187 const double gain_loss = gain_loss_closed_equity();
19188 const double deficit = source_money_round(held * quote)
19189 - ((std::isfinite(gain_loss) ? gain_loss : pine->closed_trade_equity())
19190 + held * (
quote - pine->position_entry_price_));
19191 return deficit > 0.0 && deficit /
quote < 1.0
19192 && std::floor(source_money_round(deficit) / quote / *staged_.quantity_grid) == 0.0;
19203[[gnu::noinline, gnu::cold]]
19204bool PineExecutionAdapter::close_all_at_next_point(
19205 const Bar& bar,
int fired,
19206 const std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>>& closes,
19207 const NativeDecisionContext& context) {
19209 || !finite_positive(staged_.syminfo.mintick))
19212 if (!(position.signed_units > 0.0))
return false;
19214 for (
const auto&
close : closes) {
19215 if (std::find(live_handles_.begin(), live_handles_.end(),
close.first)
19216 != live_handles_.end()) {
19220 if (!live)
return false;
19221 const bool high_first = source_path_uses_high_first(bar);
19222 const double next = fired == 1 ? (high_first ? bar.low : bar.high) : bar.
close;
19223 const double tick = staged_.syminfo.mintick;
19224 const double quote = std::floor(next / tick + 0.5) * tick;
19225 native_order::Request
request;
19232 snapshot.sizing = sizing_snapshot();
19233 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
19234 "__pine_close_all");
19235 if (!accepted)
return false;
19236 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
19240bool PineExecutionAdapter::submit_tv_money_long_margin_call(
19241 const Bar& bar,
const NativeDecisionContext& context,
int* fired_waypoint) {
19246 const auto grid = staged_.quantity_grid;
19251 && (!source_margin_call_enabled_
19252 || std::abs(config_.margin_long - 100.0) > 1e-12
19253 || config_.commission_value != 0.0 || config_.slippage != 0
19254 || !grid || !(*grid > 0.0) || *grid > 1.0
19255 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
19256 || !staged_.account_fx_effective_from_ms.empty()
19257 ||
cap.active() || risk_.max_intraday_loss > 0.0
19258 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
19259 || last_margin_call_script_bar_ == context.script_bar_open_ms))) {
19269 if (!source_margin_call_enabled_
19270 || position.signed_units <= 0.0 || position.lot_count != 1
19271 || std::abs(config_.margin_long - 100.0) > 1e-12
19272 || config_.commission_value != 0.0 || config_.slippage != 0
19273 || !grid || !(*grid > 0.0) || *grid > 1.0
19274 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
19275 || active_staged_fx(context.sub_bar_open_ms) != 1.0
19276 || !staged_.account_fx_effective_from_ms.empty()
19277 ||
cap.active() || risk_.max_intraday_loss > 0.0
19278 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
19279 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
19282 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue
19283 * active_staged_fx(context.sub_bar_open_ms);
19284 if (!config_.process_orders_on_close
19285 && (config_.pyramiding < 0 || config_.pyramiding > 1)
19286 && std::isfinite(lot_value) && lot_value >= 1.0) {
19292 if (position_open_priced_
19293 && (!std::isfinite(lot_value) || lot_value >= 1.0)) {
19301 && fired_waypoint !=
nullptr;
19302 if (config_.process_orders_on_close) {
19303 for (
const auto& handle : live_handles_) {
19304 const auto found = placement_.find(handle.incarnation);
19305 if (found != placement_.end()
19308 && found->second.placement_script_open_ms == context.script_bar_open_ms)) {
19315 if (position_open_script_bar_ == context.script_bar_open_ms) {
19320 if (position_open_phase_ != NativePathPhase::Open)
return false;
19323 const bool high_first = source_path_uses_high_first(bar);
19324 const double path[] = {bar.open, high_first ? bar.high : bar.low,
19325 high_first ? bar.low : bar.high, bar.close};
19326 const double quantity = position.signed_units;
19327 const double point_value = staged_.syminfo.pointvalue;
19333 const auto* pine = pine_view_of(&require_host());
19334 const bool supported_guard_scope = pine
19335 && pine->position_entry_count_ == 1 && pine->pyramid_entries_.size() == 1
19336 && pine->net_profit_sum_ == pine->net_profit_roundoff_value_
19337 && std::isfinite(pine->net_profit_roundoff_bound_);
19349 const bool exact_residual = rules.long_open_close_checks
19350 && finite_positive(staged_.syminfo.mintick)
19351 && supported_guard_scope && pine->net_profit_roundoff_bound_ == 0.0;
19352 for (
int index = begin; index != 4; ++index) {
19353 if (exact_residual) {
19354 if ((index == 1 || index == 2) && path[index] == bar.high
19355 && bar.high != bar.low) {
19358 const double tick = staged_.syminfo.mintick;
19359 const double price = std::floor(path[index] / tick + 0.5) * tick;
19360 if (!finite_positive(
price))
continue;
19361 const double gain_loss = gain_loss_closed_equity();
19362 const double free_cash = (std::isfinite(gain_loss) ? gain_loss
19363 : pine->closed_trade_equity())
19364 - quantity * pine->position_entry_price_;
19365 const double residual = source_money_round(quantity *
price) - quantity *
price;
19366 if (!(free_cash < residual) || free_cash < 0.0)
continue;
19367 const double units = std::min(1.0, quantity);
19368 const double rounded_units = std::round(units / *grid) * *grid;
19369 const double guard = std::max({1e-12, std::abs(units) * 1e-12,
19370 std::abs(*grid) * 1e-9});
19371 if (units < quantity - guard && std::abs(rounded_units - units) > guard)
19373 if (fired_waypoint) *fired_waypoint = index;
19374 return submit_margin_call_units(
price, context, units,
true);
19376 const double price = path[index];
19377 if (!finite_positive(
price))
continue;
19378 const double exact_value = quantity *
price * point_value;
19379 const double equity = require_host().native_marked_equity(
price);
19380 const double rounded_value = source_money_round(exact_value);
19384 double arithmetic_guard = 1e-7;
19385 if (supported_guard_scope) {
19386 const double entry_value = quantity * pine->position_entry_price_ * point_value;
19387 const double money_scale = std::max({
19388 std::abs(pine->closed_trade_equity()),
19389 std::abs(pine->BacktestEngine::open_profit(
price)),
19390 std::abs(entry_value), std::abs(exact_value), std::abs(equity),
19391 std::abs(rounded_value)});
19392 const double evaluation_guard = 8.0
19393 * std::numeric_limits<double>::epsilon() * money_scale;
19394 const double supported_guard = std::nextafter(
19395 evaluation_guard + pine->net_profit_roundoff_bound_,
19396 std::numeric_limits<double>::infinity());
19397 if (std::isfinite(supported_guard))
19398 arithmetic_guard = std::min(arithmetic_guard, supported_guard);
19400 if (!std::isfinite(exact_value) || !std::isfinite(equity)
19401 || equity + arithmetic_guard < exact_value
19402 || !(equity + arithmetic_guard < rounded_value)) {
19405 const double units = std::min(1.0, quantity);
19406 const double rounded_units = std::round(units / *grid) * *grid;
19407 const double guard = std::max({1e-12, std::abs(units) * 1e-12,
19408 std::abs(*grid) * 1e-9});
19409 if (units < quantity - guard && std::abs(rounded_units - units) > guard)
19414 if (fired_waypoint) *fired_waypoint = index;
19415 return submit_margin_call_units(
price, context, units,
true);
19420bool PineExecutionAdapter::slipped_pooc_opening_money_scope(
19421 const Bar& bar,
const NativeDecisionContext& context)
const {
19427 const auto grid = staged_.quantity_grid;
19428 if (!source_margin_call_enabled_ || !modeled_input()
19429 || !config_.process_orders_on_close || config_.slippage <= 0
19430 || position.signed_units <= 1.0 || position.lot_count != 1
19431 || std::abs(config_.margin_long - 100.0) > 1e-12
19432 || config_.pyramiding < 0 || config_.pyramiding > 1
19433 || config_.commission_value != 0.0
19434 || !grid || !(*grid > 0.0) || *grid >= 1.0
19435 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
19436 || active_staged_fx(context.sub_bar_open_ms) != 1.0
19437 || !staged_.account_fx_effective_from_ms.empty()
19438 || context.driver_statistics.intrabar_path_enabled
19439 ||
cap.active() || risk_.max_intraday_loss > 0.0
19440 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
19441 || position_open_phase_ != NativePathPhase::Close
19442 || position_open_priced_
19443 || position_open_script_bar_ == std::numeric_limits<std::int64_t>::min()
19444 || !live_handles_.empty() || !pending_bracket_legs_.empty()
19445 || !pending_entries_.empty() || !pending_same_bar_commands_.empty()
19446 || !pending_relative_exits_.empty() || !pending_coof_requests_.empty()
19447 || !source_shadow_pending_.empty()) {
19451 const CohortFacts* sole_cohort =
nullptr;
19452 for (
const auto& row : cohorts_by_id_.held_rows()) {
19453 if (row.second.opened.empty())
continue;
19454 if (sole_cohort !=
nullptr)
return false;
19455 sole_cohort = &row.second;
19457 if (sole_cohort ==
nullptr || sole_cohort->opened.size() != 1
19458 || sole_cohort->live_units_by_origin.size() != 1) {
19461 const auto origin = sole_cohort->opened.front();
19462 const auto live_units = sole_cohort->live_units_by_origin.find(origin.incarnation);
19463 const auto placement = placement_.find(origin.incarnation);
19464 if (live_units == sole_cohort->live_units_by_origin.end()
19465 || !(live_units->second > 1.0)
19466 || placement == placement_.end()
19469 || finite_positive(
placement->second.exit_levels.limit)
19470 || finite_positive(
placement->second.exit_levels.stop)
19471 || finite_positive(
placement->second.exit_levels.trail_offset)
19472 ||
placement->second.placement_script_open_ms
19473 != position_open_script_bar_) {
19477 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue;
19478 return std::isfinite(lot_value) && lot_value < 1.0;
19481bool PineExecutionAdapter::submit_slipped_pooc_opening_money_call(
19482 const Bar& bar,
const NativeDecisionContext& context) {
19487 && (!source_margin_call_enabled_ || !modeled_input()
19488 || !config_.process_orders_on_close || config_.slippage <= 0
19489 || config_.commission_value != 0.0))) {
19492 if (!slipped_pooc_opening_money_scope(bar, context)
19493 || position_open_script_bar_ == context.script_bar_open_ms
19494 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
19498 const double exact_value = position.signed_units * bar.open
19499 * staged_.syminfo.pointvalue;
19500 const double equity = require_host().native_marked_equity(bar.open);
19501 const double rounded_value = source_money_round(exact_value);
19502 if (!std::isfinite(exact_value) || !std::isfinite(equity)
19503 || equity < exact_value || !(equity < rounded_value)) {
19507 return submit_margin_call_units(
19508 bar.open, context, std::min(1.0, position.signed_units),
false);
19511bool PineExecutionAdapter::schedule_tv_money_long_margin_before_trail(
19512 const Bar& bar,
const NativeDecisionContext& context) {
19513 const auto grid = staged_.quantity_grid;
19518 && (!source_margin_call_enabled_ || config_.calc_on_order_fills || !modeled_input()
19519 || std::abs(config_.margin_long - 100.0) > 1e-12
19520 || config_.commission_value != 0.0 || config_.slippage != 0
19521 || config_.pyramiding < 0 || config_.pyramiding > 1
19522 || !grid || !(*grid > 0.0) || *grid >= 1.0
19523 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
19524 || !staged_.account_fx_effective_from_ms.empty()
19525 ||
cap.active() || risk_.max_intraday_loss > 0.0
19526 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
19527 || last_margin_call_script_bar_ == context.script_bar_open_ms))) {
19531 if (!source_margin_call_enabled_ || config_.calc_on_order_fills || !modeled_input()
19532 || position.signed_units <= 1.0 || position.lot_count != 1
19533 || std::abs(config_.margin_long - 100.0) > 1e-12
19534 || config_.commission_value != 0.0 || config_.slippage != 0
19535 || config_.pyramiding < 0 || config_.pyramiding > 1
19536 || !grid || !(*grid > 0.0) || *grid >= 1.0
19537 || std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12
19538 || active_staged_fx(context.sub_bar_open_ms) != 1.0
19539 || !staged_.account_fx_effective_from_ms.empty()
19540 ||
cap.active() || risk_.max_intraday_loss > 0.0
19541 || risk_.max_drawdown > 0.0 || risk_.max_cons_loss_days > 0
19542 || last_margin_call_script_bar_ == context.script_bar_open_ms) {
19545 const double lot_value = *grid * bar.close * staged_.syminfo.pointvalue
19546 * active_staged_fx(context.sub_bar_open_ms);
19547 if (position_open_priced_
19548 && (!std::isfinite(lot_value) || lot_value >= 1.0)) {
19552 const PlacementSnapshot* owned_trail =
nullptr;
19553 std::uint64_t owned_trail_incarnation = 0;
19554 for (
const auto& handle : live_handles_) {
19555 const auto found = placement_.find(handle.incarnation);
19556 if (found == placement_.end())
continue;
19557 const auto& candidate = found->second;
19562 if (
exit && !candidate.from_entry.empty()
19563 && cohort_exposure_for(candidate.from_entry) == 0.0) {
19567 || owned_trail !=
nullptr) {
19570 owned_trail = &candidate;
19571 owned_trail_incarnation = handle.incarnation;
19574 const bool full = !std::isfinite(owned_trail->requested_qty)
19575 && (!std::isfinite(owned_trail->qty_percent)
19576 || owned_trail->qty_percent >= 100.0);
19577 const bool relative = std::isfinite(owned_trail->exit_levels.trail_points)
19578 && !std::isfinite(owned_trail->exit_levels.trail_price)
19579 && std::isfinite(owned_trail->exit_levels.trail_offset)
19580 && owned_trail->exit_levels.trail_offset > 0.0;
19581 if (!full || !relative || !owned_trail->oca_name.empty())
return false;
19584 const bool high_first = source_path_uses_high_first(bar);
19585 const double path[] = {bar.open, high_first ? bar.high : bar.low,
19586 high_first ? bar.low : bar.high, bar.close};
19587 double fire_price = kNaN;
19588 int fire_point = -1;
19589 constexpr double kArithmeticGuard = 1e-7;
19590 for (
int point = 0; point != 4; ++point) {
19591 const double price = path[point];
19592 if (!finite_positive(
price))
continue;
19593 const double exact_value = position.signed_units *
price
19594 * staged_.syminfo.pointvalue;
19595 const double equity = require_host().native_marked_equity(
price);
19596 const double rounded_value = source_money_round(exact_value);
19597 if (std::isfinite(exact_value) && std::isfinite(equity)
19598 && equity + kArithmeticGuard >= exact_value
19599 && equity + kArithmeticGuard < rounded_value) {
19600 fire_price =
price;
19601 fire_point = point;
19605 if (!finite_positive(fire_price))
return false;
19612 if (fire_point == 3 && !config_.process_orders_on_close)
return false;
19613 if (config_.process_orders_on_close && owned_trail) {
19619 const double tick = staged_.syminfo.mintick;
19620 double activation = owned_trail->trail_activation_level;
19621 if (!finite_positive(activation) && finite_positive(tick)) {
19622 activation = require_host().position_avg_price()
19623 + source_trail_points_ticks(
19624 owned_trail->exit_levels.trail_points, tick) * tick;
19627 owned_trail->exit_levels.trail_offset) * tick;
19628 if (!finite_positive(activation) || !std::isfinite(offset))
return false;
19629 const auto state = trail_state_at_open_.find(owned_trail_incarnation);
19630 bool armed = state != trail_state_at_open_.end() && state->second.activated
19631 && finite_positive(state->second.best_price);
19632 double best = armed ? state->second.best_price : kNaN;
19633 int fill_point = -1;
19634 for (
int point = 0; point != 4 && fill_point < 0; ++point) {
19635 const double price = path[point];
19636 if (!finite_positive(
price))
continue;
19638 if (
price >= activation) {
19644 if (offset > 0.0 ?
price <= best - offset :
price < best) {
19645 fill_point = point;
19647 best = std::max(best,
price);
19650 if (fill_point < 0 || !(fire_point < fill_point))
return false;
19653 native_order::Request
request;
19654 request.intent = native_order::Reduce{native_order::ExplicitUnits{
19655 std::min(1.0, position.signed_units)}};
19657 request.comment =
"Margin call";
19658 request.trigger = fire_price <= bar.open
19664 snapshot.requested_qty = std::min(1.0, position.signed_units);
19665 snapshot.sizing = sizing_snapshot();
19666 return static_cast<bool>(submit_or_replace(
19667 std::move(request), std::move(snapshot),
false,
19668 "__tv_money_margin_path__"));
19677 return !finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop)
19678 && !finite_positive(row.exit_levels.trail_points)
19679 && !finite_positive(row.exit_levels.trail_price)
19680 && !finite_positive(row.exit_levels.trail_offset);
19686 && row.projection_position_side
19692 return add_entry_row(a) && add_entry_row(b) && a.is_long ==
b.is_long
19693 && a.placement_script_open_ms ==
b.placement_script_open_ms;
19703std::optional<std::int64_t> PineExecutionAdapter::flat_sibling_placement(
19704 std::size_t min_lots)
const {
19706 if (position.signed_units == 0.0 || position.lot_count < min_lots)
return std::nullopt;
19707 std::optional<std::int64_t> placed;
19708 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
19709 for (
const auto& [incarnation, units] :
cohort.second.live_units_by_origin) {
19710 if (!(units > 0.0))
continue;
19711 const auto found = placement_.find(incarnation);
19712 if (found == placement_.end())
return std::nullopt;
19713 const auto& lot = found->second;
19715 || lot.is_long != (position.signed_units > 0.0)
19716 || lot.projection_position_side
19718 || (placed && *placed != lot.placement_script_open_ms)) {
19719 return std::nullopt;
19721 placed = lot.placement_script_open_ms;
19731 std::int32_t interval_index,
19734 || source.projection_position_side !=
static_cast<std::int32_t
>(
PositionSide::FLAT)) {
19738 if (position.signed_units == 0.0 || (position.signed_units > 0.0) != source.is_long
19739 || position_open_bar_index_ != interval_index || position_open_phase_ != phase) {
19742 const auto placed = flat_sibling_placement(1);
19743 return placed && *placed == source.placement_script_open_ms;
19750bool PineExecutionAdapter::flat_sibling_fill_follows(
19751 const PlacementSnapshot& filled,
const native_order::RequestHandle& filled_handle)
const {
19753 || filled.projection_position_side !=
static_cast<std::int32_t
>(
PositionSide::FLAT)) {
19757 if (config_.pyramiding > 0
19758 && position.lot_count >=
static_cast<std::size_t
>(config_.pyramiding)) {
19761 return std::any_of(live_handles_.begin(), live_handles_.end(),
19762 [&](
const native_order::RequestHandle& handle) {
19763 if (handle == filled_handle) return false;
19764 const auto found = placement_.find(handle.incarnation);
19765 if (found == placement_.end()) return false;
19766 const auto& row = found->second;
19767 return row.family == PineOrderFamily::Entry && row.opening
19768 && row.is_long == filled.is_long
19769 && row.projection_position_side
19770 == static_cast<std::int32_t>(PositionSide::FLAT)
19771 && row.placement_script_open_ms == filled.placement_script_open_ms
19772 && market_entry_row(row);
19780bool PineExecutionAdapter::add_sibling_fill_follows(
19781 const PlacementSnapshot& filled,
const native_order::RequestHandle& filled_handle)
const {
19782 if (!add_entry_row(filled))
return false;
19784 if (config_.pyramiding > 0
19785 && position.lot_count >=
static_cast<std::size_t
>(config_.pyramiding)) {
19788 return std::any_of(live_handles_.begin(), live_handles_.end(),
19789 [&](
const native_order::RequestHandle& handle) {
19790 if (handle == filled_handle) return false;
19791 const auto found = placement_.find(handle.incarnation);
19792 return found != placement_.end() && add_siblings(filled, found->second);
19798std::size_t PineExecutionAdapter::add_sibling_lots(std::int64_t placed)
const {
19800 if (position.signed_units == 0.0)
return 0;
19801 std::size_t count = 0;
19802 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
19803 for (
const auto& [incarnation, units] :
cohort.second.live_units_by_origin) {
19804 if (!(units > 0.0))
continue;
19805 const auto found = placement_.find(incarnation);
19806 if (found == placement_.end())
continue;
19807 const auto& lot = found->second;
19808 if (add_entry_row(lot) && lot.is_long == (position.signed_units > 0.0)
19809 && lot.placement_script_open_ms == placed) {
19830bool PineExecutionAdapter::whole_unit_follow_up_due(
double called_units,
double mark)
const {
19831 if (!staged_.quantity_grid || *staged_.quantity_grid != 1.0)
return false;
19833 if (!(called_units >= 4.0) || !(called_units < held))
return false;
19834 const auto lots = require_host().native_open_lots(mark);
19835 return !lots.empty() && std::abs(std::abs(lots.front().signed_units) - 1.0) < 1e-9;
19845bool PineExecutionAdapter::margin_follow_up_scope() const noexcept {
19846 if (!(config_.slippage > 0) || !staged_.quantity_grid
19847 || !(*staged_.quantity_grid > 0.0)) {
19851 if (config_.commission_value != 0.0 && !percent)
return false;
19852 return *staged_.quantity_grid == 1.0 || (config_.commission_value > 0.0 && percent);
19872double PineExecutionAdapter::margin_follow_up_units(
19873 double called_units,
double fill, std::int64_t sub_bar_open_ms)
const {
19875 if (!money.valid || !staged_.quantity_grid || !(*staged_.quantity_grid > 0.0)
19876 || !(called_units > internal::kQtyEpsilon)) {
19879 const double fx = active_staged_fx(sub_bar_open_ms);
19880 const double step = config_.slippage * staged_.syminfo.mintick
19881 * staged_.syminfo.pointvalue * fx;
19882 const double shortfall = money.required - money.equity;
19883 const bool commissioned = config_.commission_value > 0.0
19885 if (!commissioned)
return shortfall > 2.0 * step ? std::min(1.0, money.held) : 0.0;
19889 const double exit_fees = (called_units + 1.0) * fill * staged_.syminfo.pointvalue * fx
19890 * config_.commission_value / 100.0;
19891 if (!(shortfall - exit_fees > 2.0 * step * called_units))
return 0.0;
19892 const double grid = *staged_.quantity_grid;
19893 const double minimum = std::floor(
19894 source_money_round(shortfall) / money.unit_margin / grid) * grid;
19895 double units = std::floor(4.0 * minimum / grid + 1e-6) * grid;
19896 if (!(units > internal::kQtyEpsilon)) units = 1.0;
19897 units = std::min(units, money.held);
19898 return units > internal::kQtyEpsilon && std::isfinite(units) ? units : 0.0;
19921void PineExecutionAdapter::follow_margin_call(
19922 double called_units,
double fill,
double current,
19923 const NativeDecisionContext& context) {
19924 if (!(called_units > internal::kQtyEpsilon) || !margin_follow_up_scope())
return;
19925 if (config_.commission_value == 0.0 && std::abs(called_units - 1.0) > 1e-9)
return;
19926 const Bar& bar = policy_script_bar_;
19927 const bool high_first = source_path_uses_high_first(bar);
19928 const double tick = staged_.syminfo.mintick;
19929 const double guard = tick * 1e-6;
19930 auto phase = context.coordinate.path_phase;
19931 double at = current;
19932 double called = called_units;
19933 while (finite_positive(fill)) {
19935 if (position.signed_units == 0.0)
return;
19936 const bool short_book = position.signed_units < 0.0;
19937 const double units = margin_follow_up_units(called, fill, context.sub_bar_open_ms);
19938 if (!(units > 0.0))
return;
19939 double next = next_source_path_waypoint(bar, phase, at, high_first);
19940 auto next_phase = NativePathPhase::None;
19941 if (phase == NativePathPhase::Open) {
19942 next_phase = high_first ? NativePathPhase::High : NativePathPhase::Low;
19943 }
else if (std::isfinite(next)) {
19944 next_phase = next == bar.high ? NativePathPhase::High : NativePathPhase::Low;
19945 }
else if (phase == NativePathPhase::High || phase == NativePathPhase::Low) {
19947 next_phase = NativePathPhase::Close;
19949 if (!finite_positive(next) || next_phase == NativePathPhase::None)
return;
19950 const double next_mark = nearest_tick(next, tick);
19951 if (short_book ? next_mark >= fill - guard : next_mark <= fill + guard)
return;
19956 if (next_phase == NativePathPhase::Close && close_point_margin_scope()) {
19957 close_margin_follow_up_bar_ = context.script_bar_open_ms;
19958 close_margin_follow_up_units_ = units;
19961 if (!submit_margin_call_units(next, context, units,
true))
return;
19965 phase = next_phase;
19973bool PineExecutionAdapter::lagged_short_scope() const noexcept {
19975 && source_margin_call_enabled_ && staged_.quantity_grid
19976 && *staged_.quantity_grid > 0.0 && *staged_.quantity_grid < 1.0
19977 && staged_.account_fx_effective_from_ms.empty()
19978 && (config_.commission_value == 0.0
19998double PineExecutionAdapter::lagged_short_follow_up_units(
19999 const std::vector<std::pair<double, double>>& taken,
double equity_before,
20000 double held_before,
double fill, std::int64_t sub_bar_open_ms)
const {
20001 if (!lagged_short_scope() || taken.empty() || !finite_positive(fill)
20002 || !std::isfinite(equity_before) || !finite_positive(held_before)
20003 || !finite_positive(config_.margin_short)) {
20006 const double grid = *staged_.quantity_grid;
20007 const double fx = active_staged_fx(sub_bar_open_ms);
20008 const double point = staged_.syminfo.pointvalue * fx;
20009 const double unit_margin = fill * point * (config_.margin_short / 100.0);
20010 const double fee = config_.commission_value / 100.0;
20011 if (!finite_positive(unit_margin))
return 0.0;
20012 const double lot_value = grid * fill * point;
20014 double booked_fees = 0.0;
20015 double last_short = 0.0;
20016 for (
const auto& [units,
entry] : taken) {
20017 if (!(units > 0.0))
continue;
20019 const double remaining = held_before - out;
20020 if (!(remaining > internal::kQtyEpsilon))
break;
20021 const double exact = remaining * unit_margin;
20022 const double required = std::isfinite(lot_value) && lot_value < 1.0
20023 ? source_money_round(exact) : exact;
20026 const double equity = equity_before - booked_fees + (fill -
entry) * units * point;
20027 const double deficit = required - equity;
20028 if (deficit > 0.0) last_short = deficit;
20029 booked_fees += units * fill * point * fee;
20031 if (!(last_short > 0.0) || !std::isfinite(last_short))
return 0.0;
20032 const double minimum = std::floor(source_money_round(last_short) / unit_margin / grid) * grid;
20033 double units = std::floor(4.0 * minimum / grid + 1e-6) * grid;
20034 units = std::min(units, held_before - out);
20035 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units))
return 0.0;
20052bool PineExecutionAdapter::close_sized_open_call_scope(
bool short_book,
bool several_lots)
const {
20053 const double margin = short_book ? config_.margin_short : config_.margin_long;
20054 if (!config_.process_orders_on_close || config_.calc_on_order_fills
20055 || !(config_.slippage > 0) || !source_margin_call_enabled_
20057 || !(config_.commission_value > 0.0) || std::abs(margin - 100.0) > 1e-12
20058 || !staged_.quantity_grid || !(*staged_.quantity_grid > 0.0)
20059 || *staged_.quantity_grid > 1.0 || staged_.syminfo.pointvalue != 1.0
20060 || staged_.account_fx != 1.0 || !staged_.account_fx_effective_from_ms.empty()
20061 || !finite_positive(staged_.syminfo.mintick)) {
20065 if (position.signed_units == 0.0 || (position.signed_units < 0.0) != short_book)
return false;
20069 return position.lot_count == 1
20070 || (several_lots && position.lot_count > 1 && *staged_.quantity_grid < 1.0
20084double PineExecutionAdapter::close_sized_open_units(
20085 double close_mark,
double print, std::int64_t sub_bar_open_ms)
const {
20087 if (!money.valid)
return 0.0;
20101double PineExecutionAdapter::open_print_follow_up_units(
20102 double held_before,
double equity_before,
double entry,
double called,
double fill,
20103 bool short_book, std::int64_t sub_bar_open_ms)
const {
20104 if (!staged_.quantity_grid || !(*staged_.quantity_grid > 0.0) || !finite_positive(fill)
20105 || !finite_positive(
entry) || !std::isfinite(equity_before)
20106 || !(called > internal::kQtyEpsilon)) {
20109 const double remaining = held_before - called;
20110 if (!(remaining > internal::kQtyEpsilon))
return 0.0;
20111 const double grid = *staged_.quantity_grid;
20112 const double point = staged_.syminfo.pointvalue * active_staged_fx(sub_bar_open_ms);
20113 const double margin = (short_book ? config_.margin_short : config_.margin_long) / 100.0;
20114 const double unit_margin = fill * point * margin;
20115 if (!finite_positive(unit_margin))
return 0.0;
20116 const double exact = remaining * unit_margin;
20117 const double lot_value = grid * fill * point;
20118 const double required = std::isfinite(lot_value) && lot_value < 1.0
20119 ? source_money_round(exact) : exact;
20121 const double side = short_book ? -1.0 : 1.0;
20122 const double equity = equity_before - side * (fill -
entry) * called * point;
20123 const double deficit = required - equity;
20124 if (!(deficit > 0.0) || !std::isfinite(deficit))
return 0.0;
20125 const double minimum = std::floor(source_money_round(deficit) / unit_margin / grid) * grid;
20126 double units = std::floor(4.0 * minimum / grid + 1e-6) * grid;
20127 if (!(units > internal::kQtyEpsilon) && exact - equity < unit_margin)
20128 units = std::min(1.0, remaining);
20129 units = std::min(units, remaining);
20130 return units > internal::kQtyEpsilon && std::isfinite(units) ? units : 0.0;
20147double PineExecutionAdapter::open_print_lot_follow_up_units(
20148 const std::vector<std::pair<double, double>>& taken,
double equity_before,
20149 double held_before,
double fill,
bool short_book, std::int64_t sub_bar_open_ms)
const {
20150 if (!staged_.quantity_grid || !(*staged_.quantity_grid > 0.0) || taken.empty()
20151 || !finite_positive(fill) || !std::isfinite(equity_before)
20152 || !finite_positive(held_before)) {
20155 const double grid = *staged_.quantity_grid;
20156 const double point = staged_.syminfo.pointvalue * active_staged_fx(sub_bar_open_ms);
20157 const double margin = (short_book ? config_.margin_short : config_.margin_long) / 100.0;
20158 const double unit_margin = fill * point * margin;
20159 if (!finite_positive(unit_margin))
return 0.0;
20160 const double fee = config_.commission_value / 100.0;
20161 const double side = short_book ? -1.0 : 1.0;
20162 const double lot_value = grid * fill * point;
20164 double booked_fees = 0.0;
20165 double last_short = 0.0;
20166 double last_exact = 0.0;
20167 double last_equity = 0.0;
20168 double last_remaining = 0.0;
20169 for (
const auto& [units,
entry] : taken) {
20170 if (!(units > 0.0))
continue;
20172 const double remaining = held_before - out;
20173 if (!(remaining > internal::kQtyEpsilon))
break;
20174 const double exact = remaining * unit_margin;
20175 const double required = std::isfinite(lot_value) && lot_value < 1.0
20176 ? source_money_round(exact) : exact;
20177 const double equity = equity_before - booked_fees - side * (fill -
entry) * units * point;
20178 const double deficit = required - equity;
20179 if (deficit > 0.0) {
20180 last_short = deficit;
20181 last_exact = exact;
20182 last_equity = equity;
20183 last_remaining = remaining;
20185 booked_fees += units * fill * point * fee;
20187 if (!(last_short > 0.0) || !std::isfinite(last_short))
return 0.0;
20188 const double minimum = std::floor(source_money_round(last_short) / unit_margin / grid) * grid;
20189 double units = std::floor(4.0 * minimum / grid + 1e-6) * grid;
20190 if (!(units > internal::kQtyEpsilon) && last_exact - last_equity < unit_margin)
20191 units = std::min(1.0, last_remaining);
20192 units = std::min(units, held_before - out);
20193 return units > internal::kQtyEpsilon && std::isfinite(units) ? units : 0.0;
20211bool PineExecutionAdapter::run_close_sized_open_calls(
20212 double units,
double opening_mark,
const NativeDecisionContext& context) {
20214 if (position.signed_units == 0.0 || !(units > 0.0))
return false;
20215 const bool short_book = position.signed_units < 0.0;
20222 double held = 0.0, equity = kNaN, entry = kNaN;
20223 std::vector<std::pair<double, double>> lots;
20225 const auto before_call = [&]() {
20229 const auto lots = require_host().native_open_lots(print);
20230 if (lots.size() == 1) {
20231 b.entry = lots.front().entry_price;
20232 }
else if (
PF_RARE(rules.lot_by_lot_open_follow_up)) {
20233 for (
const auto& lot : lots)
20234 b.lots.emplace_back(std::abs(lot.signed_units), lot.entry_price);
20238 const auto called_since = [&](
const Before&
b) {
20241 open_call_sequence_ =
true;
20242 Before last = before_call();
20243 if (!submit_margin_call_units(opening_mark, context, units)) {
20244 open_call_sequence_ =
false;
20247 double last_called = called_since(last);
20248 bool rechecked =
false;
20264 const Before before = before_call();
20265 if (submit_margin_call_units(opening_mark, context, again)) {
20267 last_called = called_since(before);
20270 }
else if (booked.valid && booked.required > booked.equity) {
20271 open_call_sequence_ =
false;
20279 if (
PF_RARE(rechecked && !last.lots.empty())) {
20280 open_call_sequence_ =
false;
20285 if (last.lots.empty()) {
20286 more = open_print_follow_up_units(
20287 last.held, last.equity, last.entry, last_called, print, short_book,
20288 context.sub_bar_open_ms);
20290 std::vector<std::pair<double, double>> taken;
20291 double left = last_called;
20292 for (
const auto& [size,
entry] : last.lots) {
20293 if (!(left > internal::kQtyEpsilon))
break;
20294 const double take = std::min(left, size);
20295 taken.emplace_back(take,
entry);
20298 more = open_print_lot_follow_up_units(taken, last.equity, last.held, print,
20299 short_book, context.sub_bar_open_ms);
20311 if (rechecked && short_book &&
PF_RARE(rules.short_point_drops_owed)
20312 && short_path_points_scope(context)) {
20313 owed_follow_up_bar_ = context.script_bar_open_ms;
20314 owed_follow_up_point_ = 1;
20315 owed_follow_up_units_ = more;
20317 (void)submit_margin_call_units(opening_mark, context, more);
20321 open_call_sequence_ =
false;
20332double PineExecutionAdapter::call_short_with_lagged_follow_up(
20333 double mark,
double units,
const NativeDecisionContext& context,
bool owed) {
20335 if (!(before.signed_units < 0.0)) {
20336 (void)submit_margin_call_units(mark, context, units,
true);
20341 const bool whole =
PF_RARE(whole_share_lag_scope())
20342 && (owed ? opening.chained_follow_up : opening.whole_share_lagged_short);
20343 const bool lagged = (whole || lagged_short_scope()) && finite_positive(fill);
20346 const auto lots = lagged ? require_host().native_open_lots(fill) : std::vector<NativeOpenLot>{};
20347 if (!submit_margin_call_units(mark, context, units,
true))
return 0.0;
20348 if (!lagged || !money.valid)
return 0.0;
20349 const double held_before = std::abs(before.signed_units);
20350 const double called = held_before - std::abs(
detail::run_position(require_host()).signed_units);
20351 std::vector<std::pair<double, double>> taken;
20352 double left = called;
20353 for (
const auto& lot : lots) {
20354 if (!(left > internal::kQtyEpsilon))
break;
20355 const double size = std::abs(lot.signed_units);
20356 const double take = std::min(left, size);
20357 taken.emplace_back(take, lot.entry_price);
20360 return whole ? whole_share_lag_units(taken, money.equity, held_before, fill,
20361 context.sub_bar_open_ms)
20362 : lagged_short_follow_up_units(taken, money.equity, held_before, fill,
20363 context.sub_bar_open_ms);
20369bool PineExecutionAdapter::whole_share_lag_scope() const noexcept {
20371 && source_margin_call_enabled_ && staged_.quantity_grid
20372 && *staged_.quantity_grid == 1.0 && staged_.account_fx_effective_from_ms.empty()
20373 && (config_.commission_value == 0.0
20386double PineExecutionAdapter::whole_share_lag_units(
20387 const std::vector<std::pair<double, double>>& taken,
double equity_before,
20388 double held_before,
double fill, std::int64_t sub_bar_open_ms)
const {
20389 if (taken.empty() || !finite_positive(fill) || !std::isfinite(equity_before)
20390 || !finite_positive(held_before) || !finite_positive(config_.margin_short)) {
20393 const double point = staged_.syminfo.pointvalue * active_staged_fx(sub_bar_open_ms);
20394 const double unit_margin = fill * point * (config_.margin_short / 100.0);
20395 const double fee = config_.commission_value / 100.0;
20396 if (!finite_positive(unit_margin))
return 0.0;
20397 const double lot_value = fill * point;
20399 double booked_fees = 0.0;
20400 double last_short = 0.0;
20401 double last_remaining = 0.0;
20402 double last_equity = 0.0;
20403 for (
const auto& [units,
entry] : taken) {
20404 if (!(units > 0.0))
continue;
20406 const double remaining = held_before - out;
20407 if (!(remaining > internal::kQtyEpsilon))
break;
20408 const double exact = remaining * unit_margin;
20409 const double required = std::isfinite(lot_value) && lot_value < 1.0
20410 ? source_money_round(exact) : exact;
20411 const double equity = equity_before - booked_fees + (fill -
entry) * units * point;
20412 const double deficit = required - equity;
20413 if (deficit > 0.0) {
20414 last_short = deficit;
20415 last_remaining = remaining;
20416 last_equity = equity;
20418 booked_fees += units * fill * point * fee;
20420 if (!(last_short > 0.0) || !std::isfinite(last_short))
return 0.0;
20421 const double minimum = std::floor(source_money_round(last_short) / unit_margin);
20422 double units = std::floor(4.0 * minimum + 1e-6);
20423 if (!(units > internal::kQtyEpsilon) && last_remaining * unit_margin - last_equity < unit_margin)
20425 units = std::min(units, held_before - out);
20426 if (!(units > internal::kQtyEpsilon) || !std::isfinite(units))
return 0.0;
20443bool PineExecutionAdapter::open_call_owing_follow_up(
20444 double opening_mark,
const NativeDecisionContext& context) {
20447 const bool whole = whole_share_lag_scope() && opening.whole_share_lagged_short;
20448 if (!
PF_RARE(opening.short_point_drops_owed) || !(before.signed_units < 0.0)
20449 || !(whole || lagged_short_scope()) || !short_path_points_scope(context)
20450 || !close_sized_open_call_scope(
true)) {
20451 return submit_margin_call_slice(opening_mark, context);
20455 const auto lots = require_host().native_open_lots(fill);
20456 open_call_sequence_ =
true;
20457 const bool called = submit_margin_call_slice(opening_mark, context);
20458 open_call_sequence_ =
false;
20459 if (!called || !money.valid || !finite_positive(fill))
return called;
20460 const double held_before = std::abs(before.signed_units);
20461 const double taken_units = held_before - std::abs(
detail::run_position(require_host()).signed_units);
20464 std::vector<std::pair<double, double>> taken;
20465 double left = taken_units;
20466 for (
const auto& lot : lots) {
20467 if (!(left > internal::kQtyEpsilon))
break;
20468 const double take = std::min(left, std::abs(lot.signed_units));
20469 taken.emplace_back(take, lot.entry_price);
20472 const double more = whole
20473 ? whole_share_lag_units(taken, money.equity, held_before, fill, context.sub_bar_open_ms)
20474 : lagged_short_follow_up_units(taken, money.equity, held_before, fill,
20475 context.sub_bar_open_ms);
20477 owed_follow_up_bar_ = context.script_bar_open_ms;
20478 owed_follow_up_point_ = 1;
20479 owed_follow_up_units_ = more;
20488bool PineExecutionAdapter::short_path_points_scope(
const NativeDecisionContext& context)
const {
20490 || config_.calc_on_order_fills || !source_margin_call_enabled_
20491 || (config_.commission_value == 0.0 && config_.slippage == 0)
20492 || !staged_.account_fx_effective_from_ms.empty()) {
20496 || position_open_script_bar_ == std::numeric_limits<std::int64_t>::min()
20497 || position_open_script_bar_ == context.script_bar_open_ms
20498 || !pending_bracket_legs_.empty() || !pending_coof_requests_.empty()
20499 || !delayed_market_orders_.empty() || !pending_entries_.empty()) {
20503 return !std::any_of(live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
20504 const auto found = placement_.find(handle.incarnation);
20505 return found == placement_.end()
20506 || (found->second.family != PineOrderFamily::Margin && !found->second.void_issue);
20513void PineExecutionAdapter::execute_owed_short_follow_up(
20514 int point,
double price,
const NativeDecisionContext& context) {
20515 if (owed_follow_up_bar_ == std::numeric_limits<std::int64_t>::min())
return;
20516 const bool due = owed_follow_up_bar_ == context.script_bar_open_ms
20517 && owed_follow_up_point_ == point;
20520 if (owed_follow_up_bar_ != context.script_bar_open_ms || owed_follow_up_point_ < point) {
20521 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
20522 owed_follow_up_point_ = -1;
20523 owed_follow_up_units_ = 0.0;
20527 const double units = owed_follow_up_units_;
20528 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
20529 owed_follow_up_point_ = -1;
20530 owed_follow_up_units_ = 0.0;
20532 if (!(position.signed_units < 0.0) || !(units > 0.0) || !finite_positive(
price))
return;
20533 const double mark = nearest_tick(
price, staged_.syminfo.mintick);
20541 if (here.valid && here.required > here.equity)
return;
20543 const double more = call_short_with_lagged_follow_up(
20544 mark, std::min(units, std::abs(position.signed_units)), context,
true);
20545 if (more > 0.0 && point < 3) {
20546 owed_follow_up_bar_ = context.script_bar_open_ms;
20547 owed_follow_up_point_ = point + 1;
20548 owed_follow_up_units_ = more;
20557void PineExecutionAdapter::hold_owed_close_follow_up(
20558 double close,
const NativeDecisionContext& context) {
20559 if (owed_follow_up_bar_ == std::numeric_limits<std::int64_t>::min())
return;
20560 const auto drop = [&]() {
20561 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
20562 owed_follow_up_point_ = -1;
20563 owed_follow_up_units_ = 0.0;
20565 if (owed_follow_up_bar_ != context.script_bar_open_ms || owed_follow_up_point_ != 3) {
20567 if (owed_follow_up_bar_ != context.script_bar_open_ms || owed_follow_up_point_ < 3) drop();
20571 || !(owed_follow_up_units_ > 0.0) || !finite_positive(
close)) {
20577 if (here.valid && here.required > here.equity) drop();
20594void PineExecutionAdapter::book_owed_close_follow_up(
20595 const Bar& bar,
const NativeDecisionContext& context) {
20596 if (owed_follow_up_bar_ != context.script_bar_open_ms || owed_follow_up_point_ != 3)
return;
20597 const double owed_units = owed_follow_up_units_;
20598 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
20599 owed_follow_up_point_ = -1;
20600 owed_follow_up_units_ = 0.0;
20602 if (!(held.signed_units < 0.0) || !(owed_units > 0.0) || !source_margin_call_enabled_
20603 || !finite_positive(bar.close)) {
20607 auto closes = same_bar_close_alls(context);
20608 closes.erase(std::remove_if(closes.begin(), closes.end(), [](
const auto&
close) {
20609 return close.second.crosses_zero;
20611 const double units = std::min(owed_units, std::abs(held.signed_units));
20612 if (!submit_margin_call_units(nearest_tick(bar.close, staged_.syminfo.mintick), context,
20617 record_close_call_after_script(std::abs(held.signed_units),
20618 std::abs(held.signed_units) - left, context);
20619 size_close_alls_at_placement(closes, held.signed_units);
20632void PineExecutionAdapter::walk_short_path_points(
20633 const Bar& bar,
const NativeDecisionContext& context) {
20634 const bool high_first = source_path_uses_high_first(bar);
20635 const double path[] = {high_first ? bar.high : bar.low, high_first ? bar.low : bar.high,
20637 for (
int index = 0; index < 3; ++index) {
20638 const int point = index + 1;
20639 const double price = path[index];
20640 if (!finite_positive(
price))
continue;
20642 hold_owed_close_follow_up(
price, context);
20644 execute_owed_short_follow_up(point,
price, context);
20646 if (!(position.signed_units < 0.0))
return;
20650 std::abs(position.signed_units));
20651 if (!(units > 0.0))
continue;
20652 const double more = call_short_with_lagged_follow_up(money.mark, units, context);
20654 owed_follow_up_bar_ = context.script_bar_open_ms;
20655 owed_follow_up_point_ = point + 1;
20656 owed_follow_up_units_ = more;
20670void PineExecutionAdapter::schedule_lagged_short_follow_up(
20671 const native_order::ExecutionAppliedEvent& event,
const NativeDecisionContext& context) {
20672 const auto phase = context.coordinate.path_phase;
20673 if (phase != NativePathPhase::High && phase != NativePathPhase::Low)
return;
20677 if (config_.process_orders_on_close || !close_point_margin_scope() || !lagged_short_scope()
20678 || margin_follow_up_scope()
20679 || !policy_script_bar_valid_ || policy_script_bar_.timestamp != context.script_bar_open_ms
20680 || event.closed_trade_count == 0 || !finite_positive(event.resolved_price)) {
20684 if (!(position.signed_units < 0.0))
return;
20685 const auto&
host = require_host();
20686 const double fill =
event.resolved_price;
20687 const double fx = active_staged_fx(context.sub_bar_open_ms);
20688 const double point = staged_.syminfo.pointvalue * fx;
20689 const double fee = config_.commission_value / 100.0;
20690 std::vector<std::pair<double, double>> taken;
20691 double called = 0.0;
20693 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
20694 const auto index =
event.first_trade_index + i;
20695 if (index >=
static_cast<std::size_t
>(
host.trade_count()))
return;
20696 const Trade& trade =
host.get_trade(
static_cast<int>(index));
20697 if (trade.is_long)
return;
20698 taken.emplace_back(trade.qty, trade.entry_price);
20699 called += trade.qty;
20700 fees += trade.qty * fill * point * fee;
20703 if (!after.valid)
return;
20704 const double more = lagged_short_follow_up_units(
20705 taken, after.equity + fees, std::abs(position.signed_units) + called, fill,
20706 context.sub_bar_open_ms);
20707 if (!(more > 0.0))
return;
20708 const Bar& bar = policy_script_bar_;
20709 const bool high_first = source_path_uses_high_first(bar);
20710 const bool first_extreme = high_first ? phase == NativePathPhase::High
20711 : phase == NativePathPhase::Low;
20712 if (!first_extreme) {
20713 owed_follow_up_bar_ = context.script_bar_open_ms;
20714 owed_follow_up_point_ = 3;
20715 owed_follow_up_units_ = more;
20719 const double next = high_first ? bar.low : bar.high;
20720 const double from = high_first ? bar.high : bar.low;
20721 if (!finite_positive(next) || !finite_positive(from))
return;
20722 native_order::Request
request;
20723 request.intent = native_order::Reduce{native_order::ExplicitUnits{
20724 std::min(more, std::abs(position.signed_units))}};
20726 request.comment =
"Margin call";
20735 snapshot.waypoint_margin_call =
true;
20736 snapshot.sizing = sizing_snapshot();
20737 (void)submit_or_replace(std::move(request), std::move(snapshot),
false,
20738 "__margin_follow_up__");
20754bool PineExecutionAdapter::pending_veto_point_first(
20755 const Bar& bar,
const NativeDecisionContext& context,
double opening_mark) {
20757 || !close_point_margin_scope() || !(config_.slippage > 0) || !source_margin_call_enabled_
20758 || !finite_positive(staged_.syminfo.mintick) || !finite_positive(bar.open)) {
20762 if (!(position.signed_units < 0.0))
return false;
20763 const double held = std::abs(position.signed_units);
20765 if (!at_open.valid || !(at_open.required > at_open.equity)
20769 const double tick = staged_.syminfo.mintick;
20770 const bool high_first = source_path_uses_high_first(bar);
20772 const double points[] = {nearest_tick(bar.open, tick),
20773 nearest_tick(high_first ? bar.high : bar.low, tick),
20774 nearest_tick(high_first ? bar.low : bar.high, tick)};
20775 if (!finite_positive(points[1]) || !finite_positive(points[2]))
return false;
20778 std::optional<std::pair<native_order::RequestHandle, PlacementSnapshot>> touched;
20779 int touched_point = 3;
20780 for (
const auto& handle : live_handles_) {
20781 const auto found = placement_.find(handle.incarnation);
20782 if (found == placement_.end())
continue;
20783 const auto& row = found->second;
20785 || row.legs.dormant() || row.void_issue
20787 || finite_positive(row.exit_levels.trail_offset)
20788 || finite_positive(row.exit_levels.trail_points)
20789 || finite_positive(row.exit_levels.trail_price)
20790 || (std::isfinite(row.requested_qty) && std::abs(row.requested_qty) < held - 1e-9)
20791 || (std::isfinite(row.qty_percent) && row.qty_percent < 100.0 - 1e-9)) {
20794 const double owned = row.from_entry.empty() ? held
20795 : from_entry_units(row.from_entry).value_or(0.0);
20796 if (!(owned >= held - 1e-9))
continue;
20798 const double level = stop ? row.exit_levels.stop : row.exit_levels.limit;
20799 if (!finite_positive(level))
continue;
20801 if (stop ? points[0] >= level : points[0] <= level)
continue;
20803 for (
int segment = 1; segment < 3 && point == 3; ++segment) {
20804 if (stop ? points[segment - 1] < level && points[segment] >= level
20805 : points[segment - 1] > level && points[segment] <= level) {
20809 if (point == 3)
continue;
20810 if (point < touched_point) {
20811 touched.emplace(handle, row);
20812 touched_point = point;
20813 }
else if (point == touched_point) {
20817 if (!touched)
return false;
20821 if (!pending_bracket_legs_.empty() || !pending_coof_requests_.empty()
20822 || !delayed_market_orders_.empty() || !pending_entries_.empty()
20823 || !pending_same_bar_commands_.empty()) {
20826 const auto& exit_row = touched->second;
20827 for (
const auto& handle : live_handles_) {
20828 const auto found = placement_.find(handle.incarnation);
20829 if (found == placement_.end())
return false;
20830 const auto& row = found->second;
20833 && row.source_id == exit_row.source_id && row.from_entry == exit_row.from_entry;
20837 if (touched_point == 2) {
20841 const double first = points[touched_point];
20845 if (!(units > 0.0))
return false;
20846 if (!submit_margin_call_units(money.mark, context, units,
true))
return false;
20848 if (!(left.signed_units < 0.0))
return true;
20849 const auto& [handle, row] = *touched;
20850 if (std::find(live_handles_.begin(), live_handles_.end(), handle) == live_handles_.end())
20852 cancel_bracket_siblings(handle);
20853 native_order::Request
request;
20855 request.label = row.source_id;
20856 request.comment = row.comment;
20857 request.trigger = native_order::Market{};
20858 PlacementSnapshot immediate = row;
20860 immediate.forced_execution_price = nearest_tick(
20861 nearest_tick(first, tick) + (stop_close ? config_.slippage * tick : 0.0), tick);
20862 immediate.projection_predecessor = handle.incarnation;
20863 immediate.projection_predecessor_exit =
true;
20864 const auto accepted = submit_or_replace(
20865 std::move(request), std::move(immediate),
false,
20866 row.source_id +
"\x1f" + row.from_entry + std::to_string(
static_cast<int>(row.family)));
20867 if (accepted) execute_or_withdraw_close(*accepted, row.void_issue);
20873void PineExecutionAdapter::record_close_call_after_script(
20874 double book_before,
double called,
const NativeDecisionContext& context) {
20879 const bool reversal_waits = position.signed_units != 0.0
20880 && any_live_row(live_handles_, placement_, [&](
const PlacementSnapshot& row) {
20882 && row.placement_script_open_ms == context.script_bar_open_ms
20883 && row.is_long != (position.signed_units > 0.0);
20885 if (!reversal_waits)
return;
20886 if (close_call_after_script_bar_ != context.script_bar_open_ms) {
20887 close_call_after_script_bar_ = context.script_bar_open_ms;
20888 close_call_after_script_book_ = book_before;
20889 close_call_after_script_units_ = 0.0;
20891 close_call_after_script_units_ += called;
20894bool PineExecutionAdapter::schedule_priced_opening_margin(
20895 const Bar& bar,
const NativeDecisionContext& context,
double fill) {
20897 if (position.signed_units == 0.0 || !source_margin_call_enabled_)
return false;
20898 const bool buy = position.signed_units < 0.0;
20899 const double mark = next_source_path_waypoint(
20900 bar, context.coordinate.path_phase, fill, source_path_uses_high_first(bar));
20901 if (!finite_positive(mark))
return false;
20904 if (!(units > 0.0))
return false;
20905 native_order::Request
request;
20906 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
20908 request.comment =
"Margin call";
20909 request.trigger = (buy ? mark > fill : mark < fill)
20917 snapshot.waypoint_margin_call =
true;
20918 snapshot.sizing = sizing_snapshot();
20919 return submit_or_replace(std::move(request), std::move(snapshot),
false,
20920 "__margin_priced_opening__").has_value();
20923bool PineExecutionAdapter::schedule_margin_call_path(
20924 const Bar& bar,
const NativeDecisionContext& context) {
20926 if (position.signed_units == 0.0)
return false;
20927 if (config_.process_orders_on_close) {
20943 const bool resting_order = std::any_of(
20944 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
20945 const auto found = placement_.find(handle.incarnation);
20946 return found == placement_.end()
20947 || (found->second.family != PineOrderFamily::Margin
20948 && !found->second.void_issue);
20950 if (!config_.calc_on_order_fills && modeled_input()
20951 && position.signed_units < 0.0
20952 && (config_.commission_value != 0.0 || config_.slippage != 0)
20953 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()
20954 && position_open_script_bar_ != context.script_bar_open_ms
20955 && !resting_order && pending_bracket_legs_.empty()
20956 && pending_coof_requests_.empty() && delayed_market_orders_.empty()
20957 && pending_entries_.empty()) {
20969 const bool high_first = source_path_uses_high_first(bar);
20971 const Waypoint path[] = {
20972 {NativePathPhase::Open, bar.open},
20973 {high_first ? NativePathPhase::High : NativePathPhase::Low,
20974 high_first ? bar.high : bar.low},
20975 {high_first ? NativePathPhase::Low : NativePathPhase::High,
20976 high_first ? bar.low : bar.high},
20977 {NativePathPhase::Close, bar.close},
20980 for (
int index = 0; index < 4; ++index) {
20981 if (path[index].phase == context.coordinate.path_phase) {
20986 double adverse = kNaN;
20987 for (
int index = std::max(current, 0); index < 4; ++index) {
20988 if (!finite_positive(path[index].
price))
continue;
20989 if (!std::isfinite(adverse)
20990 || (position.signed_units > 0.0
20991 ? path[index].price < adverse : path[index].price > adverse)) {
20992 adverse = path[index].price;
20995 if (!finite_positive(adverse))
return false;
21000 bool close_alone = current == 1 || current == 2;
21001 close_alone = close_alone && close_point_margin_scope() && finite_positive(bar.close);
21002 for (
int index = std::max(current, 0); close_alone && index < 3; ++index) {
21003 if (!finite_positive(path[index].
price))
continue;
21004 close_alone = position.signed_units > 0.0 ? path[index].price > bar.close
21005 : path[index].price < bar.close;
21007 if (close_alone) close_margin_point_ = context.coordinate.ordinal;
21019 withdraw_waypoint_margin_calls();
21020 if (close_point_margin_scope()) {
21021 if (rest_waypoint_margin_call(bar, current <= 0 ? 1 : current, 3, context)) {
21022 kernel_margin_path_point_ = context.coordinate.ordinal;
21023 close_margin_point_ = context.coordinate.ordinal;
21024 waypoint_chain_bar_ = context.script_bar_open_ms;
21035 kernel_margin_path_point_ = context.coordinate.ordinal;
21044double PineExecutionAdapter::source_margin_one_unit(
const SourceMarginMoney& money)
const {
21045 if (!money.valid || !(money.required > money.equity) || !staged_.quantity_grid
21046 || !(*staged_.quantity_grid <= 1.0)) {
21049 const double raw = (money.required - money.equity) / money.unit_margin;
21050 if (!(raw > internal::kQtyEpsilon) || !(raw < 1.0))
return 0.0;
21051 const double floor_basis = source_money_round(money.required - money.equity)
21052 / money.unit_margin;
21053 if (std::floor(floor_basis / *staged_.quantity_grid) * *staged_.quantity_grid > 0.0)
21055 const double candidate = std::min(1.0, money.held);
21056 const double rounded = floor_quantity_grid(candidate, staged_.quantity_grid);
21057 const double guard = std::max(1e-12, std::abs(candidate) * 1e-12);
21058 return candidate >= money.held - guard || std::abs(rounded - candidate) <= guard
21065bool PineExecutionAdapter::source_margin_unit_restores(
21066 const SourceMarginMoney& money,
double fill)
const {
21067 return money.valid && finite_positive(fill)
21068 && money.exact_required * fill / money.mark - money.equity < money.unit_margin;
21073bool PineExecutionAdapter::close_point_margin_scope() const noexcept {
21074 return !config_.process_orders_on_close && !config_.calc_on_order_fills
21075 && !coof_recalc_active_ && !bar_magnifier_ && modeled_input();
21095bool PineExecutionAdapter::close_point_margin_call(
21096 const Bar& bar,
const NativeDecisionContext& context,
bool cancelled) {
21097 const bool follow_up = close_margin_follow_up_bar_ == context.script_bar_open_ms;
21098 const double follow_up_units = close_margin_follow_up_units_;
21099 close_margin_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
21100 close_margin_follow_up_units_ = 0.0;
21105 if (
PF_RARE(owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min())) {
21106 const bool owed = owed_follow_up_bar_ == context.script_bar_open_ms
21107 && owed_follow_up_point_ == 3;
21108 const double owed_units = owed_follow_up_units_;
21109 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
21110 owed_follow_up_point_ = -1;
21111 owed_follow_up_units_ = 0.0;
21113 if (owed && held.signed_units < 0.0 && owed_units > 0.0 && source_margin_call_enabled_) {
21114 auto closes = same_bar_close_alls(context);
21115 const double units = std::min(owed_units, std::abs(held.signed_units));
21116 if (submit_margin_call_units(nearest_tick(bar.close, staged_.syminfo.mintick),
21117 context, units,
true)) {
21119 record_close_call_after_script(std::abs(held.signed_units),
21120 std::abs(held.signed_units) - left, context);
21121 size_close_alls_at_placement(closes, held.signed_units);
21126 if (position.signed_units == 0.0 || !source_margin_call_enabled_)
return false;
21129 if (position.signed_units > 0.0 && std::abs(config_.margin_long - 100.0) < 1e-12)
21133 const double one_unit = source_margin_one_unit(money);
21135 std::abs(position.signed_units));
21136 if (units > 0.0 && !cancelled)
return book_close_point_call(money.mark, units, context,
false);
21137 if (one_unit > 0.0)
return book_close_point_call(money.mark, one_unit, context,
true);
21138 if (units > 0.0)
return book_close_point_call(money.mark, units, context,
true);
21141 if (follow_up && follow_up_units > 0.0)
21142 return book_close_point_call(money.mark, follow_up_units, context, cancelled);
21150bool PineExecutionAdapter::book_close_point_call(
21151 double mark,
double units,
const NativeDecisionContext& context,
bool queued,
21152 double reference_mark) {
21154 units = std::min(units, std::abs(position.signed_units));
21155 if (!(units > 0.0))
return false;
21157 auto closes = same_bar_close_alls(context);
21158 if (!submit_margin_call_units(mark, context, units,
true))
return false;
21161 record_close_call_after_script(std::abs(position.signed_units),
21162 std::abs(position.signed_units) - left, context);
21164 size_close_alls_at_placement(closes, position.signed_units);
21165 close_point_follow_up(mark, units, context);
21168 const bool buy = position.signed_units < 0.0;
21169 close_margin_open_bar_ = context.script_bar_open_ms;
21170 native_order::Request
request;
21173 request.comment =
"Margin call";
21180 snapshot.sizing = sizing_snapshot();
21181 snapshot.follow_up_fill = reference_mark;
21182 return submit_or_replace(std::move(request), std::move(snapshot),
false,
21191bool PineExecutionAdapter::rest_waypoint_margin_call(
21192 const Bar& bar,
int from,
int to,
const NativeDecisionContext& context) {
21194 if (position.signed_units == 0.0)
return false;
21201 if (position.signed_units > 0.0 || !(config_.slippage > 0) || !staged_.quantity_grid
21202 || *staged_.quantity_grid != 1.0) {
21205 const bool high_first = source_path_uses_high_first(bar);
21206 const double path[] = {bar.open, high_first ? bar.high : bar.low,
21207 high_first ? bar.low : bar.high, bar.close};
21208 const bool buy = position.signed_units < 0.0;
21209 for (
int index = std::max(from, 1); index < std::min(to, 3); ++index) {
21210 const double price = path[index];
21211 if (!finite_positive(
price) || !finite_positive(path[index - 1]))
continue;
21212 const double units = std::min(
21214 std::abs(position.signed_units));
21215 if (!(units > 0.0))
continue;
21216 const bool falling =
price < path[index - 1];
21217 native_order::Request
request;
21218 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
21220 request.comment =
"Margin call";
21221 request.trigger = falling == buy
21230 snapshot.waypoint_margin_call =
true;
21231 snapshot.sizing = sizing_snapshot();
21232 return submit_or_replace(std::move(request), std::move(snapshot),
false,
21233 "__margin_waypoint__").has_value();
21239void PineExecutionAdapter::withdraw_waypoint_margin_calls() {
21241 std::vector<native_order::RequestHandle>
stale;
21242 for (
const auto& handle : live_handles_) {
21243 const auto found = placement_.find(handle.incarnation);
21244 if (found != placement_.end() && found->second.waypoint_margin_call)
21245 stale.push_back(handle);
21247 for (
const auto& handle :
stale) {
21248 if (require_host().
cancel(handle).status == native_order::CancelStatus::Cancelled)
21254std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>>
21255PineExecutionAdapter::same_bar_close_alls(
const NativeDecisionContext& context)
const {
21256 std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>> closes;
21258 for (
const auto& handle : live_handles_) {
21259 const auto found = placement_.find(handle.incarnation);
21261 && found->second.placement_script_open_ms == context.script_bar_open_ms) {
21262 closes.emplace_back(handle, found->second);
21271void PineExecutionAdapter::size_close_alls_at_placement(
21272 std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>>& closes,
21274 for (
auto&
close : closes) {
21275 if (std::find(live_handles_.begin(), live_handles_.end(),
close.first)
21276 == live_handles_.end()) {
21279 native_order::Request
request;
21283 close.second.crosses_zero =
true;
21284 (void)submit_or_replace(std::move(request), std::move(
close.second),
false,
21285 "__pine_close_all");
21300void PineExecutionAdapter::close_point_follow_up(
21301 double mark,
double called_units,
const NativeDecisionContext& context) {
21302 if (!margin_follow_up_scope())
return;
21304 if (position.signed_units == 0.0)
return;
21305 const bool buy = position.signed_units < 0.0;
21307 const double units = margin_follow_up_units(called_units, fill, context.sub_bar_open_ms);
21308 if (!(units > 0.0))
return;
21309 (void)book_close_point_call(mark, units, context,
true, fill);
21321bool PineExecutionAdapter::close_point_margin_call_at_open(
21322 const Bar& bar,
const Bar& prior,
const NativeDecisionContext& context) {
21323 if (close_margin_open_bar_ == std::numeric_limits<std::int64_t>::min())
return false;
21324 const bool owed = close_margin_open_bar_ == prior.timestamp;
21325 close_margin_open_bar_ = std::numeric_limits<std::int64_t>::min();
21326 std::vector<native_order::RequestHandle> queued;
21327 bool behind_orders =
false;
21328 for (
const auto& handle : live_handles_) {
21329 const auto found = placement_.find(handle.incarnation);
21330 if (found == placement_.end() || found->second.placement_script_open_ms != prior.timestamp)
21333 queued.push_back(handle);
21334 }
else if (!finite_positive(found->second.exit_levels.limit)
21335 && !finite_positive(found->second.exit_levels.stop)
21336 && !finite_positive(found->second.exit_levels.trail_offset)
21337 && !finite_positive(found->second.exit_levels.trail_points)) {
21338 behind_orders =
true;
21341 const auto withdraw = [&](native_order::RequestHandle handle) {
21342 if (require_host().
cancel(handle).status == native_order::CancelStatus::Cancelled)
21346 if (!owed || position.signed_units == 0.0) {
21347 for (
const auto& handle : queued) withdraw(handle);
21355 const bool buy = position.signed_units < 0.0;
21357 const double open_mark = nearest_tick(bar.open, staged_.syminfo.mintick);
21358 const double guard = staged_.syminfo.mintick * 1e-6;
21360 std::vector<native_order::RequestHandle> kept;
21361 double units = 0.0;
21362 for (
const auto& handle : queued) {
21363 const auto found = placement_.find(handle.incarnation);
21364 if (found == placement_.end())
continue;
21365 const double followed = found->second.follow_up_fill;
21366 const bool taken = finite_positive(followed)
21367 ? (buy ? open_mark <= followed + guard : open_mark >= followed - guard)
21368 : (!(source_margin_one_unit(money) > 0.0)
21369 || source_margin_unit_restores(money, fill));
21374 kept.push_back(handle);
21375 if (finite_positive(found->second.requested_qty)) units += found->second.requested_qty;
21377 if (kept.empty())
return false;
21381 if (behind_orders)
return true;
21382 for (
const auto& handle : kept) withdraw(handle);
21383 if (!submit_margin_call_units(bar.open, context, units,
true))
return false;
21388 for (
int more = 0; more < 8 && margin_follow_up_scope(); ++more) {
21390 const double follow_up = margin_follow_up_units(units, fill, context.sub_bar_open_ms);
21391 if (!(follow_up > 0.0))
break;
21392 if (!submit_margin_call_units(bar.open, context, follow_up,
true))
break;
21398bool PineExecutionAdapter::declined_reversal_at_open(
const Bar& bar)
const {
21400 if (position.signed_units == 0.0)
return false;
21401 for (
const auto& handle : live_handles_) {
21402 const auto found = placement_.find(handle.incarnation);
21403 if (found == placement_.end())
continue;
21404 const auto& candidate = found->second;
21411 || candidate.is_long == (position.signed_units > 0.0)
21412 || !candidate.reverse_to || candidate.projection_after_close
21413 || std::isfinite(candidate.requested_qty)) {
21416 double units = candidate.sizing.frozen_units;
21417 if (!finite_positive(units)) units = config_.default_qty_value;
21418 const double fill = nearest_tick(bar.open, staged_.syminfo.mintick);
21419 const double margin = candidate.is_long ? config_.margin_long : config_.margin_short;
21420 const double required = units * fill * staged_.syminfo.pointvalue
21421 * active_staged_fx(bar.timestamp) * margin / 100.0;
21422 const double equity = candidate.sizing.equity;
21423 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
21424 if (finite_positive(units) && std::isfinite(required)
21425 && std::isfinite(equity) && required > equity + epsilon) {
21432void PineExecutionAdapter::defer_declined_reversal_exits_at_adverse(
21433 const Bar& bar,
const NativeDecisionContext&,
bool margin_scheduled) {
21435 if (position.signed_units == 0.0)
return;
21436 const double adverse = position.signed_units > 0.0 ? bar.low : bar.high;
21437 if (!finite_positive(adverse))
return;
21439 const PlacementSnapshot* declined_reversal =
nullptr;
21440 for (
const auto& handle : live_handles_) {
21441 const auto found = placement_.find(handle.incarnation);
21442 if (found == placement_.end())
continue;
21443 const auto& candidate = found->second;
21446 || candidate.is_long == (position.signed_units > 0.0)
21447 || !candidate.reverse_to || candidate.projection_after_close
21448 || std::isfinite(candidate.requested_qty)) {
21451 double units = candidate.sizing.frozen_units;
21452 if (!finite_positive(units)) units = config_.default_qty_value;
21453 const double fill = nearest_tick(bar.open, staged_.syminfo.mintick);
21454 const double margin = candidate.is_long ? config_.margin_long : config_.margin_short;
21455 const double required = units * fill * staged_.syminfo.pointvalue
21456 * active_staged_fx(bar.timestamp) * margin / 100.0;
21457 const double equity = candidate.sizing.equity;
21458 const double epsilon = std::max(1e-9, std::abs(equity) * 1e-12);
21459 if (finite_positive(units) && std::isfinite(required)
21460 && std::isfinite(equity) && required > equity + epsilon) {
21461 declined_reversal = &candidate;
21465 if (!declined_reversal)
return;
21473 std::vector<native_order::RequestHandle> dependent_closes;
21474 const double held = std::abs(position.signed_units);
21475 for (
const auto& handle : live_handles_) {
21476 const auto found = placement_.find(handle.incarnation);
21477 if (found == placement_.end())
continue;
21478 const auto&
snapshot = found->second;
21479 const bool full = std::isfinite(
snapshot.projection_remaining_qty)
21480 ?
snapshot.projection_remaining_qty >= held - 1e-10
21481 : (std::isfinite(
snapshot.requested_qty)
21482 ? std::abs(
snapshot.requested_qty) >= held - 1e-10
21483 : std::isfinite(
snapshot.qty_percent)
21484 &&
snapshot.qty_percent >= 100.0 - 1e-9);
21486 &&
snapshot.command_ordinal > declined_reversal->command_ordinal
21487 &&
snapshot.projection_created_bar
21488 == declined_reversal->projection_created_bar) {
21489 dependent_closes.push_back(handle);
21492 for (
const auto& handle : dependent_closes) {
21493 const auto result = require_host().cancel(handle);
21494 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
21497 struct DeferredStop {
21498 native_order::RequestHandle handle;
21501 std::vector<DeferredStop> stops;
21502 for (
const auto& handle : live_handles_) {
21503 const auto found = placement_.find(handle.incarnation);
21504 if (found == placement_.end())
continue;
21505 const auto&
snapshot = found->second;
21508 || !finite_positive(
snapshot.exit_levels.stop)
21509 || std::isfinite(
snapshot.requested_qty)
21510 || !(
snapshot.qty_percent >= 100.0 - 1e-9)) {
21513 const bool adverse_reaches = position.signed_units > 0.0
21514 ? adverse <=
snapshot.exit_levels.stop
21515 : adverse >=
snapshot.exit_levels.stop;
21519 const bool through_at_open = position.signed_units > 0.0
21520 ? bar.open <=
snapshot.exit_levels.stop
21521 : bar.open >=
snapshot.exit_levels.stop;
21522 if (adverse_reaches && !through_at_open) stops.push_back({handle,
snapshot});
21524 for (
auto& deferred : stops) {
21525 if (!margin_scheduled) {
21526 const auto result = require_host().cancel(deferred.handle);
21527 if (result.status == native_order::CancelStatus::Cancelled)
21528 retire(deferred.handle);
21531 native_order::Request
request;
21532 request.intent = native_order::HostSized{
21533 native_order::HostSizedKind::Close, std::nullopt};
21534 request.label = deferred.snapshot.source_id;
21535 request.comment = deferred.snapshot.comment;
21536 request.trigger = native_order::Stop{adverse};
21537 request.owner = owner_for_close(deferred.snapshot.from_entry,
true);
21538 const std::string group_name = deferred.snapshot.oca_name.empty()
21539 ? deferred.snapshot.source_id +
"\x1f" + deferred.snapshot.from_entry
21540 : deferred.snapshot.oca_name;
21541 request.group = group_for(group_name, 1);
21542 deferred.snapshot.forced_execution_price = adverse;
21543 const SourceId replacement_key = deferred.snapshot.source_id +
"\x1f"
21544 + deferred.snapshot.from_entry
21546 const auto accepted = submit_or_replace(
21547 std::move(request), std::move(deferred.snapshot),
false,
21550 bracket_family(key_for(
21551 placement_.at(accepted->incarnation).source_id,
21552 placement_.at(accepted->incarnation).from_entry)).push_back(*accepted);
21557bool PineExecutionAdapter::submit_intraday_loss_close(
21558 double mark_price,
const NativeDecisionContext& context,
bool execute_current) {
21559 if (!intraday_loss_breached(mark_price)
21563 native_order::Request
request;
21568 if (!execute_current)
request.trigger = native_order::Stop{mark_price};
21571 snapshot.source_id =
"__intraday_loss__";
21573 snapshot.sizing = sizing_snapshot();
21574 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
21575 "__intraday_loss_close__");
21576 if (!accepted)
return false;
21577 if (execute_current) {
21578 const auto result = require_host().execute_current(
21579 {*accepted, NativeCurrentPriceRule::NearestTick});
21580 if (
const auto* applied = std::get_if<native_order::ExecutionAppliedEvent>(&result);
21581 applied && applied->closed_units > 0.0) {
21582 risk_.intraday_block_day =
chart_day_key(context.sub_bar_open_ms);
21583 risk_.intraday_cancel_pending =
true;
21589void PineExecutionAdapter::schedule_intraday_loss_path(
21590 const Bar& bar,
const NativeDecisionContext& context) {
21592 if (position.signed_units == 0.0)
return;
21593 const double adverse = position.signed_units > 0.0 ? bar.low : bar.high;
21594 if (!finite_positive(adverse) || adverse == bar.open)
return;
21595 if (position.signed_units > 0.0 ? !(adverse < bar.open) : !(adverse > bar.open))
21597 (void)submit_intraday_loss_close(adverse, context,
false);
21600void PineExecutionAdapter::execute_due_cap_close(
const NativeDecisionContext& context) {
21601 const auto due =
cap.due_cause();
21602 if (!due || context.coordinate.interval_index <= due->trigger_bar
21606 native_order::Request
request;
21608 request.comment =
"Close Position (Max number of filled orders in one day)";
21611 snapshot.source_id =
"__intraday_cap_close__";
21613 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
21614 "__intraday_cap_close__");
21616 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
21617 cap.after_immediate_close_attempt();
21621void PineExecutionAdapter::execute_cap_close_now(
const compat::pine::CloseNow& close) {
21623 cap.after_immediate_close_attempt();
21626 native_order::Request
request;
21631 snapshot.source_id =
"__intraday_cap_close__";
21634 snapshot.forced_execution_price = nearest_tick(
21635 close.price + (closing_long ? -1.0 : 1.0) * config_.slippage * staged_.syminfo.mintick,
21636 staged_.syminfo.mintick);
21637 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
21638 "__intraday_cap_close__");
21640 (void)require_host().execute_current({*accepted, NativeCurrentPriceRule::NearestTick});
21642 cap.after_immediate_close_attempt();
21645void PineExecutionAdapter::observe_intraday_cap(
21646 const native_order::ExecutionAppliedEvent& event,
21648 if (!
cap.active())
return;
21650 ||
snapshot.source_id ==
"__intraday_cap_close__")
21652 const auto clock = cap_clock(context);
21653 const auto calculation = cap_calculation(context);
21656 const bool full =
event.closed_units > 0.0
21658 const bool observe_close =
cap.direct_close_routing(calculation, full)
21660 const bool ordinary_full_close =
full && calculation.process_on_close
21661 && !calculation.calc_on_fills && !calculation.coof_scheduler
21662 && !calculation.magnifier && !calculation.stream_warmup
21663 && calculation.stream_idle && calculation.fifo;
21664 if (observe_close || ordinary_full_close) {
21666 if (event.closed_trade_count > 0
21667 && event.first_trade_index
21668 <
static_cast<std::size_t
>(require_host().trade_count())) {
21669 before = require_host().get_trade(
21670 static_cast<int>(event.first_trade_index)).is_long
21673 std::vector<compat::pine::ContinuationCandidate> candidates;
21674 std::optional<native_order::RequestHandle> continuation;
21675 std::optional<PlacementSnapshot> continuation_snapshot;
21676 std::size_t continuation_index = 0;
21677 std::uint64_t continuation_sequence =
21678 std::numeric_limits<std::uint64_t>::max();
21679 for (
const auto& handle : live_handles_) {
21680 if (handle == event.handle())
continue;
21681 const auto found = placement_.find(handle.incarnation);
21682 if (found == placement_.end())
continue;
21683 const auto& candidate = found->second;
21685 && !finite_positive(candidate.exit_levels.limit)
21686 && !finite_positive(candidate.exit_levels.stop)
21687 && !finite_positive(candidate.exit_levels.trail_offset);
21688 candidates.push_back({
21693 candidate.projection_created_bar, candidate.is_long,
21694 static_cast<std::int64_t
>(candidate.command_sequence),
21695 handle.incarnation});
21697 && candidate.projection_created_bar == calculation.bar
21698 && candidate.command_sequence < continuation_sequence) {
21699 continuation = handle;
21700 continuation_snapshot = candidate;
21701 continuation_index = candidates.size() - 1U;
21702 continuation_sequence = candidate.command_sequence;
21705 double continuation_units = continuation_snapshot
21706 ? continuation_snapshot->frozen_market_own_units : kNaN;
21707 if (continuation_snapshot && !finite_positive(continuation_units)) {
21708 if (finite_positive(continuation_snapshot->requested_qty)) {
21709 continuation_units = std::abs(continuation_snapshot->requested_qty);
21710 }
else if (config_.default_qty_type ==
static_cast<int>(
QtyType::FIXED)) {
21711 continuation_units = config_.default_qty_value;
21713 continuation_units = continuation_snapshot->sizing.frozen_units;
21716 if (candidates.size() != 1U) continuation.reset();
21717 if (candidates.size() == 1U && continuation && continuation_snapshot
21718 && finite_positive(continuation_units)) {
21719 const auto old = *continuation;
21720 const auto cancelled = require_host().cancel(old);
21721 if (
cancelled.status == native_order::CancelStatus::Cancelled) retire(old);
21722 native_order::Request
request;
21724 continuation_snapshot->is_long
21725 ? continuation_units : -continuation_units};
21726 request.label = continuation_snapshot->source_id;
21727 request.comment = continuation_snapshot->comment;
21728 request.trigger = native_order::Market{};
21729 continuation_snapshot->projection_predecessor = old.incarnation;
21730 continuation_snapshot->projection_predecessor_market =
true;
21735 continuation_snapshot->market_admission = {};
21736 const auto accepted = submit_or_replace(
21737 std::move(request), *continuation_snapshot,
true,
21738 continuation_snapshot->source_id);
21740 continuation = *accepted;
21741 candidates[continuation_index].incarnation = accepted->incarnation;
21743 continuation.reset();
21744 candidates.erase(candidates.begin()
21745 +
static_cast<std::ptrdiff_t
>(continuation_index));
21748 if (observe_close) {
21755 std::vector<compat::pine::ContinuationCandidate> inheritors;
21756 for (
const auto& candidate : candidates) {
21757 if (
static_cast<std::uint64_t
>(candidate.created_seq)
21759 inheritors.push_back(candidate);
21762 cap.committed_close(clock, calculation, before, event.ordinal, inheritors);
21764 cap_latest_fill_ =
event.ordinal;
21765 if (continuation) {
21766 (void)require_host().execute_current(
21767 {*continuation, NativeCurrentPriceRule::NearestTick});
21775 const auto attempt = cap_attempt(snapshot, event.handle().incarnation, &event);
21776 const auto origin =
cap.origin(clock, calculation, event.handle().incarnation, cap_latest_fill_);
21777 const auto admission =
cap.pre_dispatch(clock, calculation, attempt, cap_latest_fill_);
21779 cap.decline(event.handle().incarnation);
21782 const bool primary_fill_applied =
event.closed_units > 0.0
21783 ||
event.opened_units != 0.0 ||
event.closed_trade_count > 0
21784 ||
event.opened_lot_incarnation != 0
21785 ||
event.cycle_before !=
event.cycle_after;
21790 Bar prices = policy_script_bar_valid_ ? policy_script_bar_ : coof_script_bar_;
21791 if (
const auto* pine_host = pine_view_of(&require_host())) {
21792 if (
const auto broker = pine_host->scheduler_.broker_bar(*pine_host, context))
21795 const auto decision =
cap.post_dispatch(admission, calculation, attempt,
21799 current_position_cycle_,
21800 {((context.coordinate.path_phase == NativePathPhase::None || context.coordinate.path_phase == NativePathPhase::Close)
21801 && config_.process_orders_on_close)
21802 ? prices.close : event.resolved_price,
21803 prices.open, prices.high, prices.low});
21804 if (
const auto* now = std::get_if<compat::pine::CloseNow>(&decision)) {
21805 execute_cap_close_now(*now);
21807 cap_latest_fill_ =
event.ordinal;
21810void PineExecutionAdapter::observe_intraday_cap_noop(
21811 bool is_long,
const NativeDecisionContext& context) {
21812 if (!
cap.active())
return;
21816 snapshot.projection_position_side = is_long
21820 snapshot.source_sequence = source_sequence_;
21821 const auto clock = cap_clock(context);
21822 const auto calculation = cap_calculation(context);
21823 const auto attempt = cap_attempt(snapshot, 0);
21824 const auto origin =
cap.origin(clock, calculation, 0, cap_latest_fill_);
21825 const auto admission =
cap.pre_dispatch(clock, calculation, attempt, cap_latest_fill_);
21832 Bar prices = policy_script_bar_valid_ ? policy_script_bar_ : coof_script_bar_;
21833 if (
const auto* pine_host = pine_view_of(&require_host())) {
21834 if (
const auto broker = pine_host->scheduler_.broker_bar(*pine_host, context))
21837 const auto decision =
cap.post_dispatch(admission, calculation, attempt,
21841 current_position_cycle_,
21842 {context.coordinate.path_phase == NativePathPhase::None ? prices.close
21843 : detail::callback_point(require_host())->price,
21844 prices.open, prices.high, prices.low});
21845 if (
const auto* now = std::get_if<compat::pine::CloseNow>(&decision)) {
21846 execute_cap_close_now(*now);
21851 cap.source_batch_end();
21856void PineExecutionAdapter::record_market_review(
21858 const std::vector<native_order::RequestHandle>& handles) {
21859 std::vector<native_order::RequestHandle> selected;
21860 selected.reserve(handles.size());
21861 for (
const auto& handle : handles) {
21862 const auto found = placement_.find(handle.incarnation);
21863 if (found == placement_.end())
continue;
21864 const auto& draft = found->second.market_admission;
21865 if (!draft.observation() || draft.review())
continue;
21866 const bool belongs = checkpoint == admission::Checkpoint::TerminalGross
21867 || (checkpoint == admission::Checkpoint::DefaultGross
21869 || (checkpoint == admission::Checkpoint::ExplicitPair
21871 if (belongs) selected.push_back(handle);
21873 if (selected.empty())
return;
21877 review.
receipt = {allocation.sequence(), checkpoint, bar, 0};
21878 review.
open_price = policy_script_bar_valid_ ? policy_script_bar_.
open : kNaN;
21885 for (
const auto& handle : selected) {
21886 auto found = placement_.find(handle.incarnation);
21887 if (found == placement_.end())
continue;
21888 auto& snapshot = found->second;
21889 const auto& origin = snapshot.market_admission.observation();
21890 if (!origin)
continue;
21891 admission::BookObservation book;
21892 book.incarnation = handle.incarnation;
21893 book.priority =
static_cast<std::int64_t
>(snapshot.source_sequence);
21894 book.bar = snapshot.projection_created_bar;
21896 book.placement_side = snapshot.projection_position_side;
21897 book.buy = snapshot.is_long;
21898 book.id = snapshot.source_id;
21899 book.oca_name = snapshot.oca_name;
21900 book.oca_type = snapshot.oca_type;
21901 book.birth = snapshot.birth;
21902 book.prices = {snapshot.exit_levels.limit, snapshot.exit_levels.stop,
21903 snapshot.exit_levels.trail_points,
21904 snapshot.exit_levels.trail_price,
21905 snapshot.exit_levels.trail_offset};
21906 book.draft =
snapshot.market_admission;
21907 review.
book.push_back(book);
21909 snapshot.market_admission.reviewed(
21910 {allocation.sequence(), checkpoint, bar, origin->command});
21916void PineExecutionAdapter::refresh_pending_sizing_after_margin(
21917 const native_order::ExecutionAppliedEvent& event,
21918 const NativeDecisionContext& context) {
21919 const double mark = policy_script_bar_valid_
21920 ? policy_script_bar_.close :
event.resolved_price;
21921 PineSizingSnapshot marked;
21922 mark_sizing_equity(marked, nearest_tick(mark, staged_.syminfo.mintick));
21923 const double active_fx = active_staged_fx(context.sub_bar_open_ms);
21924 const std::uint64_t cause_fill =
21926 for (
const auto& handle : live_handles_) {
21927 auto found = placement_.find(handle.incarnation);
21928 if (found == placement_.end())
continue;
21931 || !
snapshot.market_admission.observation()) {
21934 const admission::SizingObservation before{
21937 const double affordability_before =
snapshot.projection_affordability_equity;
21938 bool revised =
false;
21939 const bool market_entry =
snapshot.opening
21942 && !finite_positive(
snapshot.exit_levels.limit)
21943 && !finite_positive(
snapshot.exit_levels.stop);
21944 if (market_entry && std::isfinite(
snapshot.sizing.frozen_units)) {
21945 snapshot.sizing.equity = marked.equity;
21946 snapshot.sizing.strategy_equity = marked.strategy_equity;
21952 && std::isfinite(
snapshot.projection_affordability_equity)) {
21953 snapshot.projection_affordability_equity =
21954 require_host().native_marked_equity(mark);
21957 if (!revised || cause_fill == 0)
continue;
21959 const auto& origin =
snapshot.market_admission.observation();
21961 admission::SizingEvent sizing;
21966 sizing.receipt = {allocation.sequence(), cause_fill,
21968 sizing.incarnation = handle.incarnation;
21969 sizing.before = before;
21973 sizing.affordability_equity_before = affordability_before;
21974 sizing.affordability_equity_after =
21975 snapshot.projection_affordability_equity;
21976 snapshot.market_admission.sizing_revised(sizing.receipt);
21981void PineExecutionAdapter::apply_open_market_admission(
21982 const NativeDecisionContext& context) {
21984 const int source_bar = context.coordinate.interval_index - 1;
21986 native_order::RequestHandle handle;
21987 const PlacementSnapshot*
snapshot =
nullptr;
21989 std::vector<Candidate> market;
21990 bool foreign_live_order =
false;
21993 const auto commands_on_bar = [&]() {
21994 std::size_t count = 0;
21998 [&](std::uint64_t,
const PlacementSnapshot& snapshot) {
21999 return snapshot.projection_created_bar == source_bar
22003 for (
const auto& row : placement_) {
22004 const auto&
snapshot = row.second;
22005 if (
snapshot.projection_created_bar != source_bar
22012 for (
const auto& delayed : delayed_market_orders_) {
22013 if (delayed.snapshot.projection_created_bar == source_bar)
22018 for (
const auto& handle : live_handles_) {
22019 const auto found = placement_.find(handle.incarnation);
22020 if (found == placement_.end())
continue;
22021 const auto&
snapshot = found->second;
22022 const bool unpriced_entry =
snapshot.projection_created_bar == source_bar
22024 && !finite_positive(
snapshot.exit_levels.limit)
22025 && !finite_positive(
snapshot.exit_levels.stop)
22026 && !finite_positive(
snapshot.exit_levels.trail_points)
22027 && !finite_positive(
snapshot.exit_levels.trail_price)
22028 && !finite_positive(
snapshot.exit_levels.trail_offset)
22031 if (unpriced_entry) {
22032 market.push_back({handle, &
snapshot});
22035 const bool same_bar_unpriced_close =
snapshot.projection_created_bar == source_bar
22038 && !finite_positive(
snapshot.exit_levels.limit)
22039 && !finite_positive(
snapshot.exit_levels.stop)
22040 && !finite_positive(
snapshot.exit_levels.trail_points)
22041 && !finite_positive(
snapshot.exit_levels.trail_price)
22042 && !finite_positive(
snapshot.exit_levels.trail_offset)
22045 if (!same_bar_unpriced_close) foreign_live_order =
true;
22047 const bool family_s_command_order =
22049 && config_.pyramiding == 1 && config_.slippage == 0
22050 && config_.commission_value == 0.0;
22051 std::stable_sort(market.begin(), market.end(), [&](
const Candidate& left,
22052 const Candidate& right) {
22053 const std::uint64_t left_key = family_s_command_order
22054 ? left.snapshot->command_sequence : left.snapshot->source_sequence;
22055 const std::uint64_t right_key = family_s_command_order
22056 ? right.snapshot->command_sequence : right.snapshot->source_sequence;
22057 if (left_key != right_key) return left_key < right_key;
22058 if (left.snapshot->source_sequence != right.snapshot->source_sequence)
22059 return left.snapshot->source_sequence < right.snapshot->source_sequence;
22060 return left.handle.incarnation < right.handle.incarnation;
22063 std::vector<native_order::RequestHandle> cancellations;
22064 const auto cancel_later = [&](
const native_order::RequestHandle& handle) {
22065 if (std::find(cancellations.begin(), cancellations.end(), handle)
22066 == cancellations.end()) {
22067 cancellations.push_back(handle);
22071 if (market.size() == 2 && !foreign_live_order && commands_on_bar() == 2) {
22072 const auto& first = *market[0].snapshot;
22073 const auto& second = *market[1].snapshot;
22074 if (first.projection_predecessor == 0 && second.projection_predecessor == 0) {
22075 const bool default_pair =
22078 && first.source_id != second.source_id
22079 && first.is_long != second.is_long;
22080 if (default_pair) {
22084 const double first_units = first.projection_over_pyramiding
22085 ? 0.0 : first.sizing.frozen_units;
22086 const double first_margin = first.is_long
22087 ? config_.margin_long : config_.margin_short;
22088 const double second_margin = second.is_long
22089 ? config_.margin_long : config_.margin_short;
22090 const double required = first_units * first.sizing.price
22091 * staged_.syminfo.pointvalue * first.sizing.fx
22092 * first_margin / 100.0
22093 + second.sizing.frozen_units * second.sizing.price
22094 * staged_.syminfo.pointvalue * second.sizing.fx
22095 * second_margin / 100.0;
22096 const double equity = std::min(first.sizing.equity, second.sizing.equity);
22097 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
22098 if (std::isfinite(required) && std::isfinite(equity)
22099 && required > equity + guard) {
22100 cancel_later(market[1].handle);
22102 }
else if (began_flat && first.source_id != second.source_id
22103 && first.is_long != second.is_long && config_.pyramiding == 2
22104 && finite_positive(first.requested_qty)
22105 && finite_positive(second.requested_qty)
22106 && first.qty_type < 0 && second.qty_type < 0
22107 && first.oca_name.empty() && second.oca_name.empty()) {
22108 const double required = (first.requested_qty + second.requested_qty)
22109 * first.sizing.price * staged_.syminfo.pointvalue * first.sizing.fx;
22110 if (std::isfinite(required) && std::isfinite(first.sizing.equity)
22111 && required > first.sizing.equity) {
22112 cancel_later(market[1].handle);
22131 const auto dropped_at_placement = [&](
const PlacementSnapshot& row) {
22132 return !foreign_live_order
22133 && !config_.process_orders_on_close && !config_.calc_on_order_fills
22135 && config_.default_qty_value <= 100.0
22136 && row.deferred_cohort && !std::isfinite(row.requested_qty)
22137 && !row.projection_after_close
22138 && close_all_pending_script_bar_ != row.placement_script_open_ms
22139 && held != 0.0 && (held > 0.0) == row.is_long
22140 && finite_positive(row.sizing.frozen_units) && finite_positive(row.sizing.mark)
22141 && add_margin_exceeds_equity(std::abs(held), row.sizing,
22142 row.is_long ? config_.margin_long : config_.margin_short,
22143 staged_.syminfo.mintick, staged_.syminfo.pointvalue)
22144 && std::none_of(delayed_market_orders_.begin(), delayed_market_orders_.end(),
22145 [&](
const DelayedMarketOrder& delayed) {
22146 return delayed.snapshot.projection_created_bar == source_bar;
22149 for (std::size_t index = 0; index < market.size(); ++index) {
22150 if (!market[index].
snapshot->projection_over_pyramiding)
continue;
22151 bool earlier_opposite =
false;
22152 for (std::size_t prior = 0; prior < index; ++prior) {
22153 if (market[prior].
snapshot->is_long != market[index].snapshot->is_long) {
22154 earlier_opposite =
true;
22158 if (!earlier_opposite || dropped_at_placement(*market[index].snapshot))
22159 cancel_later(market[index].handle);
22161 std::vector<native_order::RequestHandle> review_handles;
22162 review_handles.reserve(market.size());
22163 for (
const auto& candidate : market) review_handles.push_back(candidate.handle);
22164 record_market_review(admission::Checkpoint::DefaultGross,
22166 record_market_review(admission::Checkpoint::ExplicitPair,
22168 for (
const auto& handle : cancellations) {
22169 const auto result = require_host().cancel(handle);
22170 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
22174void PineExecutionAdapter::defer_open_marketable_sells(
const Bar& bar) {
22175 if (config_.calc_on_order_fills || !modeled_input())
22178 const bool high_first = source_path_uses_high_first(bar);
22182 const double fill_point_price = config_.process_orders_on_close ? bar.close : bar.open;
22184 native_order::RequestHandle handle;
22185 const PlacementSnapshot*
snapshot =
nullptr;
22186 double path_position = 0.0;
22187 bool open_marketable =
false;
22188 bool touched =
false;
22190 std::vector<Candidate> buys;
22191 std::vector<Candidate> sells;
22192 for (
const auto& handle : live_handles_) {
22193 const auto found = placement_.find(handle.incarnation);
22194 if (found == placement_.end())
continue;
22195 const auto&
snapshot = found->second;
22197 || !finite_positive(
snapshot.exit_levels.stop)
22198 || finite_positive(
snapshot.exit_levels.limit)
22199 || finite_positive(
snapshot.exit_levels.trail_points)
22200 || finite_positive(
snapshot.exit_levels.trail_price)
22201 || finite_positive(
snapshot.exit_levels.trail_offset)) {
22205 row.handle = handle;
22207 row.open_marketable = pure_stop_entry_marketable_at(snapshot, fill_point_price);
22210 &row.path_position);
22211 if (
snapshot.is_long) buys.push_back(row);
22212 else sells.push_back(row);
22214 if (buys.empty() || sells.empty())
return;
22215 std::uint64_t min_open_buy_incarnation = std::numeric_limits<std::uint64_t>::max();
22216 bool open_buy =
false;
22217 for (
const auto& buy : buys) {
22218 if (!buy.open_marketable)
continue;
22220 min_open_buy_incarnation = std::min(min_open_buy_incarnation, buy.handle.incarnation);
22222 if (!open_buy)
return;
22223 std::vector<Candidate> deferred;
22224 bool deferred_open_sell =
false;
22225 for (
const auto& sell : sells) {
22226 if (sell.open_marketable && sell.handle.incarnation < min_open_buy_incarnation) {
22227 deferred.push_back(sell);
22228 deferred_open_sell =
true;
22231 if (!deferred_open_sell)
return;
22232 for (
const auto& sell : sells) {
22233 if (sell.open_marketable)
continue;
22236 if (!sell.touched)
continue;
22237 deferred.push_back(sell);
22239 std::stable_sort(deferred.begin(), deferred.end(),
22240 [](
const Candidate& left,
const Candidate& right) {
22241 return left.handle.incarnation < right.handle.incarnation;
22243 deferred.erase(std::unique(deferred.begin(), deferred.end(),
22244 [](
const Candidate& left,
const Candidate& right) {
22245 return left.handle.incarnation == right.handle.incarnation;
22248 for (
const auto& sell : deferred) {
22249 DeferredOpenMarketableSell row;
22250 row.snapshot = *sell.snapshot;
22251 row.replacement_key = sell.snapshot->source_id;
22252 row.fill_price = sell.open_marketable ? fill_point_price : sell.snapshot->exit_levels.stop;
22253 row.path_position = sell.path_position;
22254 row.open_marketable = sell.open_marketable;
22255 const auto result = require_host().cancel(sell.handle);
22256 if (result.status != native_order::CancelStatus::Cancelled)
continue;
22257 retire(sell.handle);
22258 deferred_open_marketable_sells_.push_back(std::move(row));
22262void PineExecutionAdapter::admit_deferred_open_marketable_sells() {
22263 require_cohort_ticket();
22264 auto queued = std::move(deferred_open_marketable_sells_);
22265 deferred_open_marketable_sells_.clear();
22266 std::stable_sort(queued.begin(), queued.end(),
22267 [](
const DeferredOpenMarketableSell& left,
22268 const DeferredOpenMarketableSell& right) {
22269 if (left.open_marketable != right.open_marketable)
22270 return !left.open_marketable && right.open_marketable;
22271 if (left.path_position != right.path_position)
22272 return left.path_position < right.path_position;
22273 return left.snapshot.command_sequence < right.snapshot.command_sequence;
22275 for (
auto& row : queued) {
22276 native_order::Request
request;
22277 if (row.snapshot.deferred_cohort || !std::isfinite(row.snapshot.requested_qty)) {
22278 request.intent = native_order::HostSized{
22279 native_order::HostSizedKind::Open, native_order::Side::Short};
22283 request.label = row.snapshot.source_id;
22284 request.comment = row.snapshot.comment;
22285 request.trigger = native_order::Market{};
22286 request.group = group_for(row.snapshot.oca_name, row.snapshot.oca_type);
22287 row.snapshot.forced_execution_price = row.fill_price;
22290 row.snapshot.projection_after_close =
true;
22291 row.snapshot.cancellation = {};
22292 row.snapshot.market_admission = {};
22298 const bool throttled_reopen =
22300 && entry_openings_this_interval_ > 0;
22301 if (throttled_reopen) {
22302 request.trigger = native_order::Stop{row.snapshot.exit_levels.stop};
22303 row.snapshot.forced_execution_price = kNaN;
22304 row.snapshot.projection_after_close =
false;
22305 (void)submit_or_replace(std::move(request), std::move(row.snapshot),
true, key);
22308 const auto accepted = submit_or_replace(
22309 std::move(request), std::move(row.snapshot),
true, key);
22311 (void)require_host().execute_current(
22312 {*accepted, NativeCurrentPriceRule::NearestTick});
22317void PineExecutionAdapter::apply_reversal_gap_bracket_policy(
22318 const Bar& bar,
const NativeDecisionContext& context,
bool defer_trails) {
22320 if (physical.signed_units == 0.0)
return;
22321 const int source_bar = context.coordinate.interval_index - 1;
22322 bool opposite_market =
false;
22323 bool gap_decline =
false;
22324 for (
const auto& handle : live_handles_) {
22325 const auto found = placement_.find(handle.incarnation);
22326 if (found == placement_.end())
continue;
22327 const auto&
entry = found->second;
22328 const bool opposite = (physical.signed_units > 0.0) !=
entry.is_long;
22330 ||
entry.projection_created_bar != source_bar
22331 || std::isfinite(
entry.requested_qty)
22332 || finite_positive(
entry.exit_levels.limit)
22333 || finite_positive(
entry.exit_levels.stop)
22334 || config_.default_qty_type
22336 || config_.default_qty_value > 100.0
22337 || !finite_positive(
entry.sizing.frozen_units)
22338 || !finite_positive(
entry.sizing.equity)
22339 || !finite_positive(
entry.sizing.price)
22340 || !finite_positive(
entry.sizing.fx)) {
22343 const double margin =
entry.is_long ? config_.margin_long : config_.margin_short;
22344 if (!finite_positive(margin) || !finite_positive(staged_.syminfo.pointvalue))
22346 const double notional_per_price =
entry.sizing.frozen_units
22347 * staged_.syminfo.pointvalue *
entry.sizing.fx;
22348 if (!finite_positive(notional_per_price))
continue;
22354 const bool money_scope = staged_.quantity_grid
22355 && *staged_.quantity_grid > 0.0 && *staged_.quantity_grid < 1.0
22356 && staged_.syminfo.pointvalue == 1.0 &&
entry.sizing.fx == 1.0
22357 && staged_.account_fx_effective_from_ms.empty()
22358 && config_.commission_value == 0.0 && config_.slippage == 0
22359 && !config_.process_orders_on_close && !config_.calc_on_order_fills
22361 double affordable_price = kNaN;
22363 const double rounded_cost = source_money_round(
22364 notional_per_price *
entry.sizing.price);
22365 affordable_price = source_money_round(
22366 source_money_round(
entry.sizing.equity) / notional_per_price);
22367 if (
entry.sizing.equity + 1e-9 < rounded_cost
22368 || (std::isfinite(affordable_price)
22369 && affordable_price <
entry.sizing.price)) {
22373 opposite_market =
true;
22374 const double fill = source_bar_fill_tick(bar.open, staged_.syminfo.mintick)
22375 + (
entry.is_long ? 1.0 : -1.0)
22376 * config_.slippage * staged_.syminfo.mintick;
22377 const double required = notional_per_price * fill * margin / 100.0;
22378 const double guard = std::max(1e-9, std::abs(
entry.sizing.equity) * 1e-12);
22379 const bool price_band_admitted = money_scope
22380 && std::isfinite(affordable_price) && affordable_price >= fill;
22381 if (std::isfinite(required)
22382 && required >
entry.sizing.equity + guard
22383 && !price_band_admitted) {
22384 gap_decline =
true;
22388 if (!opposite_market)
return;
22390 const double held_margin = physical.signed_units > 0.0
22391 ? config_.margin_long : config_.margin_short;
22392 const double open_fill = source_bar_fill_tick(bar.open, staged_.syminfo.mintick);
22393 const double open_fx = active_staged_fx(bar.timestamp);
22394 const double held_required = std::abs(physical.signed_units) * open_fill
22395 * staged_.syminfo.pointvalue * open_fx * held_margin / 100.0;
22396 const double held_equity = require_host().native_marked_equity(open_fill);
22397 const bool opening_margin_slice = source_margin_call_enabled_
22398 && finite_positive(held_margin) && std::isfinite(held_required)
22399 && std::isfinite(held_equity) && held_required > held_equity;
22401 struct RetiredLeg {
22402 native_order::RequestHandle handle;
22403 std::optional<PendingBracketLeg> delayed_leg;
22404 std::optional<PlacementSnapshot> margin_revival;
22405 bool release_at_open =
false;
22407 std::vector<RetiredLeg> retired_legs;
22408 for (
const auto& handle : live_handles_) {
22409 const auto found = placement_.find(handle.incarnation);
22410 if (found == placement_.end())
continue;
22411 const auto& leg = found->second;
22414 double trail_activation = leg.exit_levels.trail_price;
22415 if (!finite_positive(trail_activation)
22416 && finite_positive(leg.exit_levels.trail_points)
22417 && finite_positive(staged_.syminfo.mintick)) {
22418 trail_activation = require_host().position_avg_price()
22419 + (physical.signed_units > 0.0 ? 1.0 : -1.0)
22420 * leg.exit_levels.trail_points * staged_.syminfo.mintick;
22421 trail_activation = directional_tick(
22422 trail_activation, staged_.syminfo.mintick,
22423 physical.signed_units > 0.0);
22426 && !std::isfinite(leg.exit_levels.trail_offset)
22427 && finite_positive(trail_activation);
22428 const bool exit_is_buy = physical.signed_units < 0.0;
22430 ? leg.exit_levels.stop : leg.exit_levels.limit;
22431 const bool gapped_priced_leg = stop_or_limit && finite_positive(priced_level)
22432 && (physical.signed_units > 0.0
22434 ? bar.open <= priced_level : bar.open >= priced_level)
22436 ? bar.open >= priced_level : bar.open <= priced_level));
22437 const bool reorder_priced_leg = gapped_priced_leg
22438 && (!gap_decline || opening_margin_slice);
22443 const bool retire_priced_leg = gapped_priced_leg
22444 && (gap_decline || reorder_priced_leg);
22445 const bool retire_trail = gap_decline && omitted_offset_trail;
22446 if ((!retire_priced_leg && !retire_trail)
22447 || (!leg.from_entry.empty()
22448 && !(cohort_exposure_for(leg.from_entry) > 0.0))) {
22451 std::optional<PendingBracketLeg> delayed;
22452 std::optional<PlacementSnapshot> margin_revival;
22453 bool release_at_open =
false;
22454 if (reorder_priced_leg) {
22455 native_order::Request
request;
22456 request.intent = native_order::HostSized{
22457 native_order::HostSizedKind::Close, std::nullopt};
22458 request.label = leg.source_id;
22459 request.comment = leg.comment;
22463 const double deferred_level = physical.signed_units > 0.0
22464 ? bar.low : bar.high;
22465 request.trigger = native_order::Stop{source_trigger_threshold(
22466 deferred_level, staged_.syminfo.mintick, exit_is_buy,
false)};
22467 request.owner = owner_for_close(leg.from_entry,
true);
22468 const std::string group_name = leg.oca_name.empty()
22469 ? leg.source_id +
"\x1f" + leg.from_entry : leg.oca_name;
22470 request.group = group_for(group_name, 1);
22471 const std::string replacement_key = leg.source_id +
"\x1f"
22472 + leg.from_entry + std::to_string(
static_cast<int>(leg.family));
22473 PlacementSnapshot reordered = leg;
22474 reordered.forced_execution_price = open_fill;
22475 delayed.emplace(PendingBracketLeg{
22476 std::move(request), std::move(reordered), replacement_key,
22477 key_for(leg.source_id, leg.from_entry)});
22478 release_at_open =
true;
22479 }
else if (retire_trail) {
22480 const bool long_position = physical.signed_units > 0.0;
22481 const bool activated_at_open = long_position
22482 ? bar.open >= trail_activation
22483 : bar.open <= trail_activation;
22484 if (!activated_at_open) {
22485 native_order::Request
request;
22486 request.intent = native_order::HostSized{
22487 native_order::HostSizedKind::Close, std::nullopt};
22488 request.label = leg.source_id;
22489 request.comment = leg.comment;
22490 request.trigger = native_order::Limit{
22491 directional_tick(trail_activation,
22492 staged_.syminfo.mintick,
22494 request.owner = owner_for_close(leg.from_entry,
true);
22495 const std::string group_name = leg.oca_name.empty()
22496 ? leg.source_id +
"\x1f" + leg.from_entry : leg.oca_name;
22497 request.group = group_for(group_name, 1);
22498 const std::string replacement_key = leg.source_id +
"\x1f"
22501 delayed.emplace(PendingBracketLeg{
22502 std::move(request), leg, replacement_key,
22503 key_for(leg.source_id, leg.from_entry)});
22507 && !reorder_priced_leg) {
22508 margin_revival = leg;
22510 retired_legs.push_back({handle, std::move(delayed),
22511 std::move(margin_revival), release_at_open});
22513 for (
auto& retired : retired_legs) {
22514 const auto result = require_host().cancel(retired.handle);
22515 if (result.status != native_order::CancelStatus::Cancelled)
continue;
22516 if (
const auto found = placement_.find(retired.handle.incarnation);
22517 found != placement_.end()) {
22518 found->second.cancellation = {
22520 retired.handle.incarnation,
22521 static_cast<std::int64_t
>(found->second.source_sequence),
22522 retired.handle.incarnation, found->second.placement_cycle,
22523 found->second.legs.revision(), found->second.requested_qty, kNaN};
22525 retire(retired.handle);
22526 if (retired.margin_revival) {
22527 pending_margin_revivals_.push_back({
22528 std::move(*retired.margin_revival),
22529 context.coordinate.interval_index});
22531 if (retired.delayed_leg) {
22532 if (retired.release_at_open) {
22533 auto reordered = std::move(*retired.delayed_leg);
22534 const auto accepted = submit_or_replace(
22535 std::move(reordered.request), std::move(reordered.snapshot),
22536 false, reordered.replacement_key);
22538 bracket_family(reordered.family_key).push_back(*accepted);
22539 }
else if (defer_trails) {
22540 auto delayed = std::move(*retired.delayed_leg);
22541 delayed_market_orders_.push_back({
22542 std::move(delayed.request), std::move(delayed.snapshot),
22543 std::move(delayed.replacement_key), broker_open_epoch_ + 1U});
22545 pending_write(pending_bracket_legs_).push_back(std::move(*retired.delayed_leg));
22551void PineExecutionAdapter::reaccept_gapped_bracket_behind_same_id_add(
22552 const Bar& bar,
const NativeDecisionContext& context) {
22565 if (config_.process_orders_on_close || !modeled_input() || coof_recalc_active_
22566 || context.sub_index != 0) {
22570 if (physical.signed_units == 0.0)
return;
22571 const bool long_position = physical.signed_units > 0.0;
22577 const auto pure_market_add = [&](
const PlacementSnapshot&
add,
22582 && (from_entry.empty() ||
add.source_id == from_entry)
22583 &&
add.is_long == long_position
22584 && !finite_positive(
add.exit_levels.limit)
22585 && !finite_positive(
add.exit_levels.stop)
22586 && !finite_positive(
add.exit_levels.trail_points)
22587 && !finite_positive(
add.exit_levels.trail_price)
22588 && !finite_positive(
add.exit_levels.trail_offset)
22589 && !
add.birth.from_fill() && !
add.birth.at_terminal_fill();
22591 std::vector<native_order::RequestHandle> gapped;
22592 for (
const auto& handle : live_handles_) {
22593 const auto found = placement_.find(handle.incarnation);
22594 if (found == placement_.end())
continue;
22595 const auto& leg = found->second;
22596 const bool global_leg = leg.from_entry.empty() && fill_time_global_exit(leg);
22600 if (config_.calc_on_order_fills && !global_leg)
continue;
22603 || (leg.from_entry.empty() && !global_leg) || !leg.deferred_cohort
22604 || std::isfinite(leg.requested_qty)
22605 || (!global_leg && std::isfinite(leg.qty_percent)
22606 && leg.qty_percent < 100.0 - 1e-9)
22607 || finite_positive(leg.exit_levels.trail_points)
22608 || finite_positive(leg.exit_levels.trail_price)
22609 || finite_positive(leg.exit_levels.trail_offset)
22610 || (!global_leg && !(cohort_exposure_for(leg.from_entry) > 0.0))) {
22613 int exit_priority = 0;
22615 && (long_position ? bar.open <= leg.exit_levels.stop
22616 : bar.open >= leg.exit_levels.stop)) {
22617 exit_priority = long_position ? 2 : 1;
22619 && finite_positive(leg.exit_levels.limit)
22620 && (long_position ? bar.open >= leg.exit_levels.limit
22621 : bar.open <= leg.exit_levels.limit)) {
22624 const int add_priority = long_position ? 1 : 2;
22625 if (exit_priority == 0 || exit_priority < add_priority)
continue;
22626 const bool add_waits = std::any_of(live_handles_.begin(), live_handles_.end(),
22627 [&](
const native_order::RequestHandle& live) {
22628 if (live.incarnation < handle.incarnation) return false;
22629 const auto add = placement_.find(live.incarnation);
22630 return add != placement_.end()
22631 && pure_market_add(add->second, leg.from_entry);
22633 if (add_waits) gapped.push_back(handle);
22635 const bool exit_is_buy = !long_position;
22636 const double tick = staged_.syminfo.mintick;
22637 for (
const auto& handle : gapped) {
22638 const auto found = placement_.find(handle.incarnation);
22639 if (found == placement_.end())
continue;
22640 PlacementSnapshot leg = found->second;
22641 const auto result = require_host().cancel(handle);
22642 if (result.status != native_order::CancelStatus::Cancelled)
continue;
22643 found->second.cancellation = {
22645 static_cast<std::int64_t
>(leg.source_sequence), handle.incarnation,
22646 leg.placement_cycle, leg.legs.revision(), leg.requested_qty, kNaN};
22648 native_order::Request
request;
22649 request.intent = native_order::HostSized{
22650 native_order::HostSizedKind::Close, std::nullopt};
22651 request.label = leg.source_id;
22652 request.comment = leg.comment;
22654 request.trigger = native_order::Stop{source_trigger_threshold(
22655 leg.exit_levels.stop, tick, exit_is_buy,
false)};
22657 const double snapped = nearest_tick(leg.exit_levels.limit, tick);
22658 request.trigger = native_order::Limit{
22659 !finite_positive(tick) || snapped == leg.exit_levels.limit
22660 ? leg.exit_levels.limit
22661 : source_trigger_threshold(leg.exit_levels.limit, tick,
22662 exit_is_buy,
true)};
22665 leg.forced_execution_price = source_bar_fill_tick(bar.open, tick);
22667 request.owner = owner_for_close(leg.from_entry,
true);
22668 const std::string group_name = leg.oca_name.empty()
22669 ? leg.source_id +
"\x1f" + leg.from_entry : leg.oca_name;
22670 request.group = group_for(group_name, 1,
static_cast<std::int64_t
>(leg.family));
22671 leg.cancellation = {};
22672 const std::uint64_t family_key = key_for(leg.source_id, leg.from_entry);
22673 const SourceId replacement_key = leg.source_id +
"\x1f" + leg.from_entry
22674 + std::to_string(
static_cast<int>(leg.family));
22675 const auto accepted = submit_or_replace(
22676 std::move(request), std::move(leg),
false, replacement_key);
22677 if (accepted) bracket_family(family_key).push_back(*accepted);
22681void PineExecutionAdapter::order_open_marketable_limit_entries(
22682 const Bar& bar,
const NativeDecisionContext& context) {
22693 if (config_.process_orders_on_close || config_.calc_on_order_fills
22694 || !modeled_input() || coof_recalc_active_ || context.sub_index != 0) {
22697 const double open_fill = source_bar_fill_tick(bar.open, staged_.syminfo.mintick);
22698 const auto marketable = [&](
const PlacementSnapshot& row) {
22700 && finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop)
22701 && !finite_positive(row.exit_levels.trail_points)
22702 && !finite_positive(row.exit_levels.trail_price)
22703 && !finite_positive(row.exit_levels.trail_offset)
22704 && open_fill <= row.exit_levels.limit;
22706 if (!has_live_family(
PineOrderFamily::Entry) || !any_live_row(live_handles_, placement_, marketable))
return;
22712 const auto market_entry = [](
const PlacementSnapshot& row,
bool is_long) {
22714 && !finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop);
22717 && any_live_row(live_handles_, placement_,
22718 [&](
const PlacementSnapshot& row) {
return market_entry(row,
true); })
22719 && any_live_row(live_handles_, placement_,
22720 [&](
const PlacementSnapshot& row) {
return market_entry(row,
false); })) {
22723 auto&
host = require_host();
22724 struct Marketable {
22725 native_order::RequestHandle handle;
22726 native_order::Request
request;
22727 double level = 0.0;
22728 std::uint64_t priority = 0;
22730 std::vector<Marketable> limits;
22731 std::uint64_t last_market = 0;
22732 for (
const auto& working :
host.native_working_requests()) {
22733 const auto& definition = *working.definition;
22734 const std::uint64_t
priority = definition.priority != 0
22735 ? definition.priority : definition.handle.incarnation;
22736 if (std::holds_alternative<native_order::Market>(definition.request.trigger)) {
22737 last_market = std::max(last_market,
priority);
22740 const auto*
limit = std::get_if<native_order::Limit>(&definition.request.trigger);
22741 if (!limit || open_fill >
limit->price)
continue;
22742 const auto found = placement_.find(definition.handle.incarnation);
22743 if (found == placement_.end() || !marketable(found->second))
continue;
22744 limits.push_back({definition.handle, definition.request,
limit->price,
priority});
22746 std::sort(limits.begin(), limits.end(), [](
const auto& left,
const auto& right) {
22747 return left.priority < right.priority;
22749 bool in_order =
true;
22750 for (std::size_t i = 0; i < limits.size(); ++i) {
22751 if (limits[i].
priority < last_market
22752 || (i > 0 && limits[i].level < limits[i - 1].level)) {
22756 if (in_order)
return;
22757 std::stable_sort(limits.begin(), limits.end(), [](
const auto& left,
const auto& right) {
22758 return left.level < right.level;
22763 native_order::ReplaceOptions reprice;
22764 reprice.keep_handle =
true;
22765 for (
const auto& row : limits) {
22766 const auto result =
host.replace(row.handle, row.request, reprice);
22767 if (result.status != native_order::ReplaceStatus::Replaced)
continue;
22768 if (
const auto found = placement_.find(row.handle.incarnation);
22769 found != placement_.end()) {
22770 found->second.forced_execution_price = open_fill;
22775void PineExecutionAdapter::apply_terminal_explicit_market_policy(
22776 const NativeDecisionContext& context) {
22777 apply_terminal_explicit_market_policy(context,
true);
22783void PineExecutionAdapter::apply_terminal_explicit_market_policy(
22784 const NativeDecisionContext& context,
bool execute_now) {
22785 if (!config_.process_orders_on_close
22786 || config_.pyramiding != 0 || !modeled_input()) {
22790 native_order::RequestHandle handle;
22792 std::uint64_t priority = 0;
22794 std::vector<Candidate> candidates;
22795 for (
const auto& handle : live_handles_) {
22796 const auto found = placement_.find(handle.incarnation);
22797 if (found == placement_.end())
continue;
22798 const auto& row = found->second;
22801 || !finite_positive(row.requested_qty)
22802 || finite_positive(row.exit_levels.limit)
22803 || finite_positive(row.exit_levels.stop)
22804 || !row.oca_name.empty() || row.oca_type != 0
22805 || row.birth.from_fill() || row.birth.at_terminal_fill()
22812#if PINEFORGE_PLACEMENT_AUDIT
22816 const PlacementSnapshot* origin = &row;
22817 std::unordered_set<std::uint64_t> seen;
22819 while (origin->projection_predecessor != 0
22820 && seen.insert(origin->projection_predecessor).second) {
22821 const auto prior = placement_.find(origin->projection_predecessor);
22822 if (prior != placement_.end()) {
22823 origin = &prior->second;
22826 const PlacementSnapshot* stone =
nullptr;
22828 for (
const auto&
entry : *stones)
22829 if (
entry.first == origin->projection_predecessor) stone =
entry.second;
22834 if (origin->source_sequence != row.chain_origin_sequence)
22838 candidates.push_back({handle, row, row.chain_origin_sequence});
22840 if (candidates.size() < 2)
return;
22841 std::stable_sort(candidates.begin(), candidates.end(),
22842 [](
const Candidate& left,
const Candidate& right) {
22843 if (left.priority != right.priority) return left.priority < right.priority;
22844 return left.snapshot.source_sequence < right.snapshot.source_sequence;
22846 std::vector<native_order::RequestHandle> terminal_review_handles;
22847 terminal_review_handles.reserve(candidates.size());
22848 for (
const auto& candidate : candidates)
22849 terminal_review_handles.push_back(candidate.handle);
22851 const auto& first = candidates[0].snapshot;
22852 const auto& second = candidates[1].snapshot;
22859 const bool clean_pair = candidates.size() == 2
22860 && config_.margin_long == 100.0 && config_.margin_short == 100.0
22861 && entry_attempt_bar_ == context.coordinate.interval_index
22862 && entry_attempts_on_bar_ == 2
22863 && first.projection_predecessor == 0 && second.projection_predecessor == 0
22864 && first.recreated_after_named_cancelled_entry_incarnation == 0
22865 && second.recreated_after_named_cancelled_entry_incarnation == 0
22866 && first.source_id != second.source_id && first.is_long != second.is_long
22870 const double required = (first.requested_qty + second.requested_qty)
22871 * second.sizing.price * staged_.syminfo.pointvalue * second.sizing.fx;
22872 const double equity = std::min(first.sizing.equity, second.sizing.equity);
22873 const double guard = std::max(1e-9, std::abs(equity) * 1e-12);
22874 if (std::isfinite(required) && std::isfinite(equity)
22875 && required > equity + guard) {
22876 const auto result = require_host().cancel(candidates[1].handle);
22877 if (result.status == native_order::CancelStatus::Cancelled)
22878 retire(candidates[1].handle);
22879 candidates.resize(1);
22882 record_market_review(admission::Checkpoint::TerminalGross,
22884 terminal_review_handles);
22890 for (
const auto& candidate : candidates) {
22891 const auto result = require_host().cancel(candidate.handle);
22892 if (result.status == native_order::CancelStatus::Cancelled)
22893 retire(candidate.handle);
22900 const bool buy_first = candidates.size() == 2 && !candidates[0].snapshot.is_long
22901 && candidates[1].snapshot.is_long
22903 if (buy_first) std::swap(candidates[0], candidates[1]);
22904 int simulated_sign = 0;
22905 for (
auto& candidate : candidates) {
22906 native_order::Request
request;
22907 const int requested_sign = candidate.snapshot.is_long ? 1 : -1;
22908 const double signed_units = requested_sign * candidate.snapshot.requested_qty;
22910 const double pending_sell = candidate.snapshot.is_long
22911 ? candidates[1].snapshot.requested_qty : 0.0;
22913 candidate.snapshot.reverse_to =
false;
22914 }
else if (simulated_sign != 0 && simulated_sign != requested_sign) {
22915 request.intent = native_order::ReverseTo{signed_units};
22916 candidate.snapshot.reverse_to =
true;
22919 candidate.snapshot.reverse_to =
false;
22921 request.label = candidate.snapshot.source_id;
22922 request.comment = candidate.snapshot.comment;
22923 const SourceId replacement_key = candidate.snapshot.source_id;
22924 candidate.snapshot.market_admission = {};
22925 const auto accepted = submit_or_replace(
22926 std::move(request), std::move(candidate.snapshot),
true,
22929 simulated_sign = requested_sign;
22931 (void)require_host().execute_current(
22932 {*accepted, NativeCurrentPriceRule::NearestTick});
22943 if (!finite_positive(row.exit_levels.stop))
return false;
22946 && std::isnan(row.exit_levels.trail_points)
22947 && std::isnan(row.exit_levels.trail_price)
22948 && std::isnan(row.exit_levels.trail_offset);
22956 const Bar prior_policy_bar = policy_script_bar_;
22957 const bool prior_policy_bar_valid = policy_script_bar_valid_;
22962 erase_retired_rows(context);
22963 release_closed_cohort_origins();
22964 retire_origin_prefixes();
22965 restore_carried_trail_retunes();
22966 trail_state_at_open_.clear();
22967 unrepeated_margin_calls_.clear();
22969 if (
PF_RARE(open_exit_first_bar_ != std::numeric_limits<std::int64_t>::min())) {
22970 open_exit_first_bar_ = std::numeric_limits<std::int64_t>::min();
22971 open_exit_first_origin_ = 0;
22972 open_exit_first_id_.clear();
22973 open_exit_first_fill_ = 0.0;
22975 bool rested_entry_stop =
false;
22976 if (has_live_trail_policy())
for (
const auto& handle : live_handles_) {
22977 const auto placement = placement_.find(handle.incarnation);
22978 if (placement == placement_.end())
continue;
22979 if (rested_stop_family(placement->second)) {
22980 rested_entry_stop = rested_entry_stop
22981 || (finite_positive(placement->second.forced_execution_price)
22982 && require_host().trail_state(handle).has_value());
22986 if (
const auto state = require_host().trail_state(handle))
22987 trail_state_at_open_.emplace(handle.incarnation, *state);
22993 if (rested_entry_stop) {
22994 auto& host = require_host();
22997 for (
const auto& working : host.native_working_requests()) {
22998 const auto& definition = *working.definition;
22999 if (!std::holds_alternative<native_order::Trail>(definition.request.trigger))
23001 const auto found = placement_.find(definition.handle.incarnation);
23002 if (found == placement_.end() || !rested_stop_family(found->second))
continue;
23005 const auto result = host.replace(definition.handle, plain, keep);
23006 if (result.status == native_order::ReplaceStatus::Replaced)
23007 found->second.forced_execution_price = kNaN;
23015 if (config_.process_orders_on_close && !config_.calc_on_order_fills
23016 && config_.pyramiding > 0 && margins_disabled()
23018 withdraw_unplaced_cap_adds(context);
23022 entry_openings_this_interval_ = 0;
23026 ++broker_open_epoch_;
23037 if (!skip_quiet(QuietHook::DelayedRelease, delayed_market_orders_.empty()))
23039 activate_short_seed_plan_at_open(context);
23044 if (!skip_quiet(QuietHook::L4cPriority,
23046 || !config_.process_orders_on_close || config_.calc_on_order_fills
23047 || coof_recalc_active_ || live_handles_.size() < 2)) {
23048 update_l4c_priority();
23059 apply_open_market_admission(context);
23064 const double sell_fill_point = config_.process_orders_on_close ? bar.
close : bar.
open;
23065 const auto marketable_stop_entry = [&](
bool is_long) {
23067 return row.
is_long == is_long
23068 && pure_stop_entry_marketable_at(row, sell_fill_point);
23071 if (!skip_quiet(QuietHook::OpenMarketableSells,
23072 config_.calc_on_order_fills || !modeled_input()
23073 || !marketable_stop_entry(
true) || !marketable_stop_entry(
false))) {
23074 defer_open_marketable_sells(bar);
23079 if (!skip_quiet(QuietHook::GappedBracketReaccept,
23084 reaccept_gapped_bracket_behind_same_id_add(bar, context);
23086 order_open_marketable_limit_entries(bar, context);
23087 pending_write(source_shadow_pending_).clear();
23088 coof_script_bar_ = bar;
23089 coof_script_bar_valid_ =
true;
23090 policy_script_bar_ = bar;
23091 policy_script_bar_valid_ =
true;
23093 && !trail_state_at_open_.empty()) {
23094 const double open_tick = source_bar_fill_tick(bar.
open, staged_.syminfo.mintick);
23097 for (
const auto& working : require_host().native_working_requests()) {
23098 const auto& definition = *working.definition;
23099 const auto carried = trail_state_at_open_.find(definition.handle.incarnation);
23100 const auto found = placement_.find(definition.handle.incarnation);
23101 if (carried == trail_state_at_open_.end() || !carried->second.activated
23102 || found == placement_.end()
23103 || !std::holds_alternative<native_order::Trail>(definition.request.trigger)
23104 || !(found->second.exit_levels.trail_offset >= 1.0)) {
23108 if (held_units == 0.0)
continue;
23109 const bool is_buy = held_units < 0.0;
23110 const double level = carried->second.current_level;
23111 const bool raw_reached = is_buy ? bar.
open >= level : bar.
open <= level;
23112 const bool print_reached = is_buy ? open_tick >= level : open_tick <= level;
23113 if (raw_reached || !print_reached)
continue;
23116 const auto replaced = require_host().replace(definition.handle, request, keep);
23117 if (replaced.status != native_order::ReplaceStatus::Replaced)
continue;
23118 found->second.forced_execution_price = open_tick
23119 + (is_buy ? 1.0 : -1.0) * config_.slippage * staged_.syminfo.mintick;
23120 (void)require_host().execute_current(
23121 {definition.handle, NativeCurrentPriceRule::NearestTick});
23124 retune_carried_trails_for_tick_reach(bar, context);
23129 last_bar_dual_entry_path_ = 0;
23141 std::size_t touched_stop_entries = 0;
23143 const auto found = placement_.find(handle.incarnation);
23144 if (found != placement_.end() && touched_stop_entry(found->second))
23145 ++touched_stop_entries;
23147 if (!skip_quiet(QuietHook::DualEntryPath, touched_stop_entries < 2)
23148 && !config_.calc_on_order_fills &&
detail::run_position(require_host()).signed_units == 0.0) {
23149 std::vector<const PlacementSnapshot*> stops;
23150 for (
const auto& handle : live_handles_) {
23151 const auto found = placement_.find(handle.incarnation);
23152 if (found == placement_.end())
continue;
23153 const auto& snapshot = found->second;
23155 || !finite_positive(snapshot.exit_levels.stop)
23156 || std::isfinite(snapshot.exit_levels.limit)
23157 || finite_positive(snapshot.exit_levels.trail_offset)) {
23160 stops.push_back(&snapshot);
23162 if (stops.size() == 2 && stops[0]->is_long != stops[1]->is_long) {
23163 const auto* long_stop = stops[0]->is_long ? stops[0] : stops[1];
23164 const auto* short_stop = stops[0]->is_long ? stops[1] : stops[0];
23165 const bool long_touched = bar.
high >= long_stop->exit_levels.stop;
23166 const bool short_touched = bar.
low <= short_stop->exit_levels.stop;
23167 if (long_touched && short_touched) {
23170 last_bar_dual_entry_path_ = source_path_uses_high_first(bar) ? 1 : 2;
23175 if (!skip_quiet(QuietHook::CoofTail, pending_coof_requests_.empty()))
23176 flush_coof_tail(
false,
true);
23182 !any_live_row(live_handles_, placement_, opening_reversal_entry))) {
23183 suspend_coof_declined_reversal_at_open(bar, context);
23186 close_all_pending_script_bar_ = std::numeric_limits<std::int64_t>::min();
23190 if (day_ledger_.intraday_loss_day != day_ledger_.current_day) {
23191 day_ledger_.intraday_loss_day = day_ledger_.current_day;
23192 day_ledger_.intraday_start_equity = require_host().native_marked_equity(bar.
open);
23194 day_ledger_.intraday_realized = 0.0;
23197 if (!skip_quiet(QuietHook::DueCapClose, !
cap.due_cause().has_value()))
23198 execute_due_cap_close(context);
23199 apply_fx_open_margin_slice(bar, context);
23200 (void)submit_slipped_pooc_opening_money_call(bar, context);
23201 const bool close_call_queued = prior_policy_bar_valid
23202 && close_point_margin_call_at_open(bar, prior_policy_bar, context);
23205 if (
PF_RARE(close_call_after_script_bar_ != std::numeric_limits<std::int64_t>::min())
23206 && (!prior_policy_bar_valid || close_call_after_script_bar_ != prior_policy_bar.
timestamp)) {
23207 close_call_after_script_bar_ = std::numeric_limits<std::int64_t>::min();
23208 close_call_after_script_book_ = 0.0;
23209 close_call_after_script_units_ = 0.0;
23213 if (
PF_RARE(owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min())) {
23214 if (prior_policy_bar_valid && owed_follow_up_bar_ == prior_policy_bar.
timestamp
23215 && owed_follow_up_point_ == 4 && short_path_points_scope(context)) {
23217 owed_follow_up_point_ = 0;
23218 execute_owed_short_follow_up(0, bar.
open, context);
23220 owed_follow_up_bar_ = std::numeric_limits<std::int64_t>::min();
23221 owed_follow_up_point_ = -1;
23222 owed_follow_up_units_ = 0.0;
23226 const bool long_full_margin = opening_position.signed_units > 0.0
23227 && std::abs(config_.margin_long - 100.0) < 1e-12;
23237 const bool pooc_opened_at_prior_close = config_.process_orders_on_close
23238 && !config_.calc_on_order_fills
23240 && prior_policy_bar_valid && position_open_phase_ == NativePathPhase::Close
23241 && position_open_script_bar_ == prior_policy_bar.
timestamp;
23246 const bool pooc_adds_at_prior_close = config_.process_orders_on_close
23247 && !config_.calc_on_order_fills
23249 && prior_policy_bar_valid && !pooc_opened_at_prior_close
23250 && finite_positive(prior_policy_bar.
close)
23251 && add_sibling_lots(prior_policy_bar.
timestamp) >= 2;
23252 if (long_full_margin && (pooc_opened_at_prior_close || pooc_adds_at_prior_close)) {
23253 const double opening_mark = nearest_tick(bar.
open, staged_.syminfo.mintick);
23258 const double close_sized = config_.commission_value != 0.0 && pooc_opened_at_prior_close
23260 && !pooc_adds_at_prior_close && finite_positive(prior_policy_bar.
close)
23262 ? close_sized_open_units(nearest_tick(prior_policy_bar.
close, staged_.syminfo.mintick),
23266 if (close_sized > 0.0) {
23267 (void)run_close_sized_open_calls(close_sized, opening_mark, context);
23268 }
else if (config_.commission_value != 0.0 && pooc_opened_at_prior_close) {
23269 (void)submit_margin_call_slice(opening_mark, context);
23270 }
else if ((pooc_adds_at_prior_close || flat_sibling_placement(2))
23271 && finite_positive(prior_policy_bar.
close)) {
23275 const double open_shortfall = at_open.valid && at_open.required > at_open.equity
23276 ? (at_open.required - at_open.equity) / at_open.unit_margin : 0.0;
23277 const bool at_close = at_close_units > 0.0 && at_close_units >= open_shortfall;
23278 const bool follow_up = whole_unit_follow_up_due(
23280 const double held_before = opening_position.signed_units;
23281 const auto lots_before = pooc_adds_at_prior_close
23282 ? require_host().native_open_lots(opening_mark) : std::vector<NativeOpenLot>{};
23283 const bool called = at_close
23284 ? submit_margin_call_units(opening_mark, context, at_close_units)
23285 : submit_margin_call_slice(opening_mark, context);
23286 if (called && follow_up) (void)submit_margin_call_units(opening_mark, context, 1.0);
23289 if (called && pooc_adds_at_prior_close) {
23290 const double taken = held_before
23295 if (more > 0.0) (void)submit_margin_call_units(opening_mark, context, more);
23302 if (
PF_RARE(long_full_margin && slipped_long_margin_scope())) {
23303 const double opening_mark = nearest_tick(bar.
open, staged_.syminfo.mintick);
23304 const double called = slipped_long_margin_units(opening_mark);
23305 if (called > 0.0) (void)submit_margin_call_units(opening_mark, context, called);
23307 bool marketable_limit_at_open =
false;
23308 if (long_full_margin) {
23309 for (
const auto& handle : live_handles_) {
23310 const auto found = placement_.find(handle.incarnation);
23311 if (found == placement_.end())
continue;
23312 const auto& candidate = found->second;
23316 if (
exit && std::isfinite(candidate.exit_levels.limit)
23317 && bar.
open >= candidate.exit_levels.limit) {
23318 marketable_limit_at_open =
true;
23323 if (long_full_margin && !marketable_limit_at_open
23327 (void)submit_tv_money_long_margin_call(open_only, context);
23329 (void)schedule_tv_money_long_margin_before_trail(bar, context);
23334 if (!long_full_margin && staged_.account_fx_effective_from_ms.empty()
23335 && !skip_quiet(QuietHook::OpenMarginCheckpoints, opening_position.signed_units == 0.0)) {
23336 const double held_at_open = std::abs(opening_position.signed_units);
23337 const bool held_long_at_open = opening_position.signed_units > 0.0;
23350 && pinned_lot_grid();
23353 const double open_print = point_order ? nearest_tick(bar.
open, staged_.syminfo.mintick)
23355 bool opposite_entry_waits =
false;
23356 bool whole_market_close_waits =
false;
23358 const bool opposite_entry_pending = point_order && any_live_row(live_handles_, placement_,
23367 bool protective_whole_waits =
false;
23368 double protective_partial_closing = 0.0;
23369 for (
const auto& handle : live_handles_) {
23370 const auto found = placement_.find(handle.incarnation);
23371 if (found == placement_.end())
continue;
23372 const auto& pending = found->second;
23373 if (pending.opening
23377 && pending.is_long != held_long_at_open
23378 && !(point_order && pending.close_first_entry)) {
23379 opposite_entry_waits =
true;
23387 const bool protective_exit = point_order && !opposite_entry_pending
23390 && !pending.legs.dormant() && !pending.void_issue
23393 || (!finite_positive(pending.exit_levels.trail_offset)
23394 && !finite_positive(pending.exit_levels.trail_points)
23395 && !finite_positive(pending.exit_levels.trail_price)))
23396 && finite_positive(open_print)
23398 && finite_positive(pending.exit_levels.stop)
23399 && (held_long_at_open ? open_print <= pending.exit_levels.stop
23400 : open_print >= pending.exit_levels.stop))
23402 && finite_positive(pending.exit_levels.limit)
23403 && (held_long_at_open ? open_print >= pending.exit_levels.limit
23404 : open_print <= pending.exit_levels.limit)));
23408 const double exit_owned = !protective_exit ? 0.0
23409 : from_entry_units(pending.from_entry).value_or(held_at_open);
23410 if (
PF_RARE(protective_exit && exit_owned > internal::kQtyEpsilon)) {
23411 const double closing = std::isfinite(pending.projection_remaining_qty)
23412 ? pending.projection_remaining_qty
23413 : (std::isfinite(pending.requested_qty)
23414 ? std::abs(pending.requested_qty)
23415 : (!std::isfinite(pending.qty_percent)
23416 || pending.qty_percent >= 100.0 - 1e-9
23417 ? held_at_open : 0.0));
23418 if (exit_owned >= held_at_open - 1e-10 && closing >= held_at_open - 1e-10)
23419 protective_whole_waits =
true;
23421 protective_partial_closing += std::min(closing, exit_owned);
23426 || finite_positive(pending.exit_levels.limit)
23427 || finite_positive(pending.exit_levels.stop)
23428 || finite_positive(pending.exit_levels.trail_offset)) {
23431 const double closing = std::isfinite(pending.projection_remaining_qty)
23432 ? pending.projection_remaining_qty
23433 : (std::isfinite(pending.requested_qty)
23434 ? std::abs(pending.requested_qty)
23435 : (std::isfinite(pending.qty_percent)
23436 && pending.qty_percent >= 100.0 - 1e-9
23437 ? held_at_open : 0.0));
23438 const double owned = pending.from_entry.empty()
23439 ? held_at_open : cohort_exposure_for(pending.from_entry);
23441 || (owned >= held_at_open - 1e-10
23442 && closing >= held_at_open - 1e-10)) {
23443 whole_market_close_waits =
true;
23450 whole_market_close_waits = (whole_market_close_waits
23451 && !opposite_entry_waits) || close_call_queued;
23452 const double opening_mark = nearest_tick(bar.
open, staged_.syminfo.mintick);
23463 double close_margined_units = 0.0;
23470 bool close_sized_calls =
false;
23471 if (config_.process_orders_on_close && !config_.calc_on_order_fills
23472 && opening_position.signed_units < 0.0 && prior_policy_bar_valid
23474 && ((position_open_phase_ == NativePathPhase::Close
23475 && position_open_script_bar_ == prior_policy_bar.
timestamp)
23476 || pooc_adds_at_prior_close)
23477 && finite_positive(prior_policy_bar.
close)
23480 const bool schedule = !pooc_adds_at_prior_close
23481 &&
PF_RARE(close_sized_open_call_scope(
true,
true));
23482 const double at_close_units = schedule
23483 ? close_sized_open_units(nearest_tick(prior_policy_bar.
close, staged_.syminfo.mintick),
23489 const double open_shortfall = at_open.valid && at_open.required > at_open.equity
23490 ? (at_open.required - at_open.equity) / at_open.unit_margin : 0.0;
23491 if (at_close_units > 0.0
23492 && ((schedule && opening_rules.close_call_gate_at_print)
23493 || at_close_units >= open_shortfall)) {
23494 close_margined_units = at_close_units;
23495 close_sized_calls = schedule
23496 && (opening_rules.booked_open_recheck || opening_rules.open_print_follow_up);
23512 bool open_exit_fills_first =
false;
23513 if (!(close_margined_units > 0.0)) {
23514 if (protective_whole_waits && !opposite_entry_waits) whole_market_close_waits =
true;
23515 if (
PF_RARE(protective_partial_closing > internal::kQtyEpsilon
23516 && !whole_market_close_waits)) {
23518 const double remainder = held_at_open - protective_partial_closing;
23519 open_exit_fills_first = !(at_open.valid && remainder > internal::kQtyEpsilon
23520 && remainder * at_open.unit_margin > at_open.equity);
23526 const bool opening_follow_up = !whole_market_close_waits && close_margined_units > 0.0
23527 && flat_sibling_placement(2)
23528 && whole_unit_follow_up_due(close_margined_units, opening_mark);
23529 const bool opening_margin_applied =
23530 !whole_market_close_waits && !open_exit_fills_first
23531 && (close_margined_units > 0.0
23532 ? (close_sized_calls
23533 ? run_close_sized_open_calls(close_margined_units, opening_mark, context)
23534 : submit_margin_call_units(opening_mark, context, close_margined_units))
23535 : open_call_owing_follow_up(opening_mark, context));
23536 if (opening_margin_applied && opening_follow_up) {
23537 (void)submit_margin_call_units(opening_mark, context, 1.0);
23538 }
else if (opening_margin_applied && close_margined_units > 0.0 && !close_sized_calls
23554 if (margin_follow_up_scope()
23556 const double follow_up = margin_follow_up_units(
23558 if (follow_up > 0.0)
23559 (void)submit_margin_call_units(opening_mark, context, follow_up,
true, fill);
23568 const bool declined_reversal = !skip_quiet(QuietHook::DeclinedReversalAtOpen,
23569 !any_live_row(live_handles_, placement_, opening_reversal_entry))
23570 && declined_reversal_at_open(bar);
23573 if (!opening_margin_applied && !whole_market_close_waits && !open_exit_fills_first
23574 && !declined_reversal &&
PF_RARE(!held_long_at_open
23576 (void)pending_veto_point_first(bar, context, opening_mark);
23578 bool margin_scheduled =
false;
23579 if (!opening_margin_applied
23580 && (!whole_market_close_waits || declined_reversal)) {
23581 margin_scheduled = schedule_margin_call_path(bar, context);
23583 if (declined_reversal && !opening_margin_applied) {
23584 defer_declined_reversal_exits_at_adverse(
23585 bar, context, margin_scheduled);
23590 const bool no_intraday_loss_rule = !(risk_.max_intraday_loss > 0.0);
23591 if (!skip_quiet(QuietHook::IntradayLossClose, no_intraday_loss_rule))
23592 (void)submit_intraday_loss_close(bar.
open, context,
true);
23593 if (!skip_quiet(QuietHook::IntradayLossPath, no_intraday_loss_rule))
23594 schedule_intraday_loss_path(bar, context);
23597 if (!skip_quiet(QuietHook::PreopenMarginSlice,
23598 !source_margin_call_enabled_
23603 schedule_preopen_margin_slice(bar, context);
23614 (void)submit_margin_call_slice(tick.
close, context.
decision);
23617void PineExecutionAdapter::rearm_throttled_reopens() {
23618 auto queued = std::move(throttled_reopen_rearm_);
23619 throttled_reopen_rearm_.clear();
23621 for (
auto& snapshot : queued) {
23623 if (snapshot.deferred_cohort || !std::isfinite(snapshot.requested_qty)) {
23625 native_order::HostSizedKind::Open,
23626 snapshot.is_long ? native_order::Side::Long : native_order::Side::Short};
23628 const double units = std::abs(snapshot.requested_qty);
23635 const bool same_dir = physical.signed_units != 0.0
23636 && ((physical.signed_units > 0.0) ==
snapshot.is_long);
23637 const bool already_touched = policy_script_bar_valid_
23638 && finite_positive(
snapshot.exit_levels.stop)
23640 ? policy_script_bar_.high >=
snapshot.exit_levels.stop
23641 : policy_script_bar_.low <=
snapshot.exit_levels.stop);
23645 snapshot.forced_execution_price = (same_dir && already_touched)
23646 ? (finite_positive(staged_.syminfo.mintick)
23647 ? directional_tick(
snapshot.exit_levels.stop, staged_.syminfo.mintick,
snapshot.is_long)
23650 snapshot.projection_after_close =
false;
23654 const auto accepted = submit_or_replace(
23655 std::move(request), std::move(snapshot),
true, key);
23656 if (accepted && same_dir && already_touched) {
23657 (void)require_host().execute_current(
23658 {*accepted, NativeCurrentPriceRule::NearestTick});
23663void PineExecutionAdapter::flush_pooc_marketable_limit_entry_fills(
23664 const Bar& bar,
const NativeDecisionContext& context) {
23698 if (!config_.process_orders_on_close || !modeled_input() || coof_recalc_active_)
return;
23699 if (fill_pooc_close_pair(bar.close, context))
return;
23700 fill_pooc_close_entries(bar.close, context,
false);
23703void PineExecutionAdapter::fill_pooc_close_entries(
23704 double raw_close,
const NativeDecisionContext& context,
bool after_close) {
23706 if (!finite_positive(raw_close))
return;
23709 const bool close_follows = !after_close && std::any_of(
23710 live_handles_.begin(), live_handles_.end(),
23711 [&](
const native_order::RequestHandle& handle) {
23712 const auto found = placement_.find(handle.incarnation);
23713 return found != placement_.end()
23714 && (found->second.family == PineOrderFamily::Close
23715 || found->second.family == PineOrderFamily::CloseAll)
23716 && found->second.projection_created_bar == projection_bar_index(context);
23718 const double tick = staged_.syminfo.mintick;
23719 const double close_tick = source_bar_fill_tick(raw_close, tick);
23720 std::vector<std::pair<native_order::RequestHandle, PlacementSnapshot>> marketable;
23721 for (
const auto& handle : live_handles_) {
23722 const auto found = placement_.find(handle.incarnation);
23723 if (found == placement_.end())
continue;
23724 const auto& row = found->second;
23726 if (row.birth.from_fill())
continue;
23728 const bool pure_limit = finite_positive(row.exit_levels.limit)
23729 && !std::isfinite(row.exit_levels.stop);
23730 const bool pure_stop = finite_positive(row.exit_levels.stop)
23731 && !std::isfinite(row.exit_levels.limit);
23732 const bool stop_limit = finite_positive(row.exit_levels.stop)
23733 && finite_positive(row.exit_levels.limit);
23734 if (!pure_limit && !pure_stop && !stop_limit)
continue;
23735 const bool placed_flat = row.projection_position_side
23737 const bool placed_against = row.projection_position_side
23740 const bool reversing_stop = placed_against && pure_stop
23741 && !config_.calc_on_order_fills && (after_close || !close_follows)
23742 && (book_before == 0.0 || (book_before > 0.0) != row.is_long);
23747 const bool reversing_limit = placed_against && pure_limit
23748 && !config_.calc_on_order_fills && (after_close || !close_follows)
23749 && (book_before == 0.0 || (book_before > 0.0) != row.is_long);
23750 const bool placed_with = row.projection_position_side
23753 const bool pyramid_add = placed_with && !after_close && book_before != 0.0
23754 && (book_before > 0.0) == row.is_long;
23755 if (placed_flat ? (after_close || book_before != 0.0)
23756 : !(reversing_stop || reversing_limit || pyramid_add)) {
23759 if (row.direction_gate || row.paired_flat_market_candidate)
continue;
23760 if (row.terms_priced_reverse && !(reversing_stop || reversing_limit))
continue;
23761 const bool at_close = finite_positive(close_tick) && (pure_limit
23762 ? (row.is_long ? close_tick <= row.exit_levels.limit
23763 : close_tick >= row.exit_levels.limit)
23764 : (row.is_long ? close_tick >= row.exit_levels.stop
23765 : close_tick <= row.exit_levels.stop));
23766 if (stop_limit && !(row.is_long ? close_tick <= row.exit_levels.limit
23767 : close_tick >= row.exit_levels.limit)) {
23768 if (at_close) (void)require_host().activate_current_stop_limit(handle);
23771 if (at_close) marketable.emplace_back(handle, row);
23773 if (marketable.size() > 1) {
23774 marketable.erase(std::remove_if(marketable.begin(), marketable.end(),
23775 [](
const std::pair<native_order::RequestHandle, PlacementSnapshot>& candidate) {
23776 return finite_positive(candidate.second.exit_levels.stop);
23777 }), marketable.end());
23779 for (
auto& candidate : marketable) {
23783 const auto& row = candidate.second;
23786 const bool host_sized = row.deferred_cohort || row.terms_priced_reverse
23789 if (!host_sized && !finite_positive(row.requested_qty))
continue;
23790 native_order::Request
request;
23793 native_order::HostSizedKind::Open,
23794 row.is_long ? native_order::Side::Long : native_order::Side::Short}}
23796 row.is_long ? row.requested_qty : -row.requested_qty}};
23797 request.label = row.source_id;
23798 request.comment = row.comment;
23799 request.trigger = native_order::Market{};
23800 request.group = group_for(row.oca_name, row.oca_type);
23801 PlacementSnapshot immediate = row;
23802 immediate.cancellation = {};
23803 immediate.market_admission = {};
23807 const bool stop_entry = finite_positive(row.exit_levels.stop)
23808 && !std::isfinite(row.exit_levels.limit);
23809 immediate.forced_execution_price = stop_entry && config_.slippage != 0
23810 ? nearest_tick(close_tick + (row.is_long ? 1.0 : -1.0)
23811 * config_.slippage * tick, tick)
23813 const auto accepted = submit_or_replace(
23814 std::move(request), std::move(immediate),
true, row.source_id);
23820 auto& placed = placement_.at(accepted->incarnation);
23821 placed.projection_position_side = row.projection_position_side; invalidate_retirement();
23822 placed.projection_tv_carry_qty = row.projection_tv_carry_qty;
23823 (void)require_host().execute_current(
23824 {*accepted, NativeCurrentPriceRule::NearestTick});
23829void PineExecutionAdapter::flush_pooc_marketable_exit_fills(
23830 const Bar& bar,
const NativeDecisionContext& context) {
23836 if (!config_.process_orders_on_close || !modeled_input() || coof_recalc_active_)
return;
23837 if (pooc_close_market_pending(context))
return;
23838 if (pooc_close_entry_immediate_pending(context,
nullptr))
return;
23839 fill_pooc_close_exits(bar.close, context);
23848bool PineExecutionAdapter::pooc_close_entry_immediate_pending(
23849 const NativeDecisionContext& context,
23850 const native_order::RequestHandle* except)
const {
23852 for (
const auto& handle : live_handles_) {
23853 if (except && handle == *except)
continue;
23854 const auto found = placement_.find(handle.incarnation);
23855 if (found == placement_.end())
continue;
23856 const auto& row = found->second;
23858 && row.projection_created_bar ==
bar_index
23859 && std::isfinite(row.forced_execution_price)
23860 && (std::isfinite(row.exit_levels.limit) || std::isfinite(row.exit_levels.stop))) {
23867bool PineExecutionAdapter::pooc_close_market_pending(
23868 const NativeDecisionContext& context)
const {
23872 return any_live_row(live_handles_, placement_, [&](
const PlacementSnapshot& row) {
23873 if (row.projection_created_bar !=
bar_index || row.birth.from_fill())
return false;
23877 && !std::isfinite(row.exit_levels.limit) && !std::isfinite(row.exit_levels.stop);
23881bool PineExecutionAdapter::fill_time_global_exit(
const PlacementSnapshot& source)
const {
23891 if (!source.from_entry.empty() || std::isfinite(source.requested_qty))
return false;
23896 if (!(std::isfinite(source.projection_remaining_qty) && source.projection_remaining_qty > 0.0))
23898 const auto other_exit = [&](
const PlacementSnapshot& row) {
23901 && (row.source_id != source.source_id || !row.from_entry.empty());
23903 if (any_live_row(live_handles_, placement_, other_exit))
return false;
23904 for (
const auto& leg : pending_bracket_legs_)
23905 if (other_exit(leg.snapshot))
return false;
23928bool PineExecutionAdapter::per_entry_exit_scope()
const {
23930 || config_.calc_on_order_fills || config_.process_orders_on_close
23931 || config_.close_entries_rule_any || !finite_positive(staged_.syminfo.mintick)
23932 || !modeled_input()) {
23936 return !native.spec || native.spec->intrabar.is_none();
23944PineExecutionAdapter::per_entry_open_entries(
bool long_side)
const {
23945 detail::ExitScratchRows<PerEntryExitChild> entries(*
this);
23947 [&](
const NativeOpenLot& lot) {
23948 const auto row = placement_.find(lot.entry_incarnation);
23949 if (row == placement_.end() || !row->second.opening
23951 || (lot.signed_units > 0.0) != long_side || !finite_positive(lot.entry_price)
23952 || !(std::abs(lot.signed_units) > 0.0)) {
23956 entries.push_back({lot.entry_incarnation, lot.entry_price, std::abs(lot.signed_units),
23964 const SourceId& exit_id, std::uint64_t origin)
const {
23965 detail::ExitScratchRows<native_order::RequestHandle> legs(*
this);
23966 for (
const auto& handle : live_handles_) {
23967 const auto row = placement_.find(handle.incarnation);
23968 if (row != placement_.end() && row->second.per_entry_origin != 0
23969 && row->second.source_id == exit_id
23970 && (origin == 0 || row->second.per_entry_origin == origin)) {
23971 legs.push_back(handle);
23980void PineExecutionAdapter::cancel_working_legs(
23982 if (legs.empty())
return;
23983 detail::ExitScratchIndex working(legs.context());
23985 [&](
const NativeWorkingRequest& request) {
23986 working.put(
request.definition->handle.incarnation, 0);
23989 for (
const auto& handle : legs) {
23990 const bool still_working =
23992 if (!still_working)
continue;
23993 const auto result = require_host().cancel(handle);
23994 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
24000bool PineExecutionAdapter::per_entry_exit_call(
24001 const SourceId& exit_id,
const SourceId& from_entry,
double limit,
double stop,
24002 double trail_points,
double trail_offset,
double trail_price,
double qty_percent,
24003 const std::string& comment,
double qty,
const std::string& oca_name,
24004 double profit_ticks,
double loss_ticks, std::uint64_t command_sequence) {
24005 const auto tracked = [&] {
24006 return std::find_if(per_entry_exits_.begin(), per_entry_exits_.end(),
24007 [&](
const PerEntryExit& row) { return row.exit_id == exit_id; });
24009 const auto absent_or_positive = [](
double value) {
24010 return std::isnan(value) || finite_positive(value);
24012 const bool relative_only = from_entry.empty() && std::isnan(limit) && std::isnan(stop)
24013 && std::isnan(trail_points) && std::isnan(trail_offset) && std::isnan(trail_price)
24015 && (std::isnan(qty_percent) || qty_percent >= 100.0 - internal::kFullPercentEps)
24016 && oca_name.empty() && absent_or_positive(profit_ticks)
24017 && absent_or_positive(loss_ticks)
24018 && (finite_positive(profit_ticks) || finite_positive(loss_ticks));
24019 auto record = tracked();
24020 if (record == per_entry_exits_.end() && !relative_only)
return false;
24022 const bool long_side = physical.signed_units > 0.0;
24023 const bool in_scope = relative_only && physical.signed_units != 0.0
24024 && per_entry_exit_scope();
24025 if (record != per_entry_exits_.end()
24026 && (!in_scope || record->cycle != current_position_cycle_
24027 || record->long_side != long_side)) {
24030 end_per_entry_exit(
static_cast<std::size_t
>(record - per_entry_exits_.begin()));
24031 record = per_entry_exits_.end();
24033 if (!in_scope)
return false;
24034 const auto same = [](
double left,
double right) {
24035 return same_double_bits(left, right) || (std::isnan(left) && std::isnan(right));
24037 if (record == per_entry_exits_.end()) {
24038 auto entries = per_entry_open_entries(long_side);
24039 if (entries.size() < 2)
return false;
24042 detail::ExitScratchRows<native_order::RequestHandle> whole(*
this);
24043 for (
const auto& handle : live_handles_) {
24044 const auto row = placement_.find(handle.incarnation);
24045 if (row == placement_.end())
continue;
24046 const auto& leg = row->second;
24050 && leg.source_id == exit_id && leg.from_entry.empty()) {
24051 whole.push_back(handle);
24054 cancel_working_legs(whole);
24055 if (
const auto family = bracket_families_.find(key_for(exit_id, from_entry));
24056 family != bracket_families_.end()) {
24057 family->second.remove_selected(whole);
24058 if (family->second.empty()) bracket_families_.erase(family);
24060 PerEntryExit added;
24062 added.cycle = current_position_cycle_;
24063 added.long_side = long_side;
24064 added.profit_ticks = profit_ticks;
24065 added.loss_ticks = loss_ticks;
24066 added.comment = comment;
24067 for (
const auto&
entry : entries) added.children.push_back(
entry);
24068 per_entry_exits_.push_back(std::move(added));
24069 const PerEntryExit&
exit = per_entry_exits_.back();
24070 for (
const auto& child :
exit.children)
24071 submit_per_entry_child(
exit, child, command_sequence);
24077 const bool same_definition = same(record->profit_ticks, profit_ticks)
24078 && same(record->loss_ticks, loss_ticks) && record->comment == comment;
24079 record->profit_ticks = profit_ticks;
24080 record->loss_ticks = loss_ticks;
24081 record->comment = comment;
24082 for (
const auto&
entry : per_entry_open_entries(long_side)) {
24083 const bool known = std::any_of(record->children.begin(), record->children.end(),
24084 [&](
const PerEntryExitChild& child) { return child.origin == entry.origin; });
24085 if (!known) record->children.push_back(
entry);
24087 const std::size_t index =
static_cast<std::size_t
>(record - per_entry_exits_.begin());
24088 for (std::size_t position = 0; position < per_entry_exits_[index].children.size();
24090 const PerEntryExitChild child = per_entry_exits_[index].children[position];
24091 if (child.consumed)
continue;
24092 const auto legs = per_entry_child_legs(exit_id, child.origin);
24093 if (!legs.empty() && same_definition)
continue;
24094 cancel_working_legs(legs);
24095 submit_per_entry_child(per_entry_exits_[index], child, command_sequence);
24103void PineExecutionAdapter::submit_per_entry_child(
const PerEntryExit& exit,
24104 const PerEntryExitChild& child,
24105 std::uint64_t command_sequence) {
24106 const double tick = staged_.syminfo.mintick;
24107 const double side =
exit.long_side ? 1.0 : -1.0;
24108 const bool exit_is_buy = !
exit.long_side;
24109 const double limit = finite_positive(
exit.profit_ticks)
24110 ? source_level_on_price_grid(child.fill_price + side *
exit.profit_ticks * tick, tick)
24112 const double stop = finite_positive(
exit.loss_ticks)
24113 ? source_level_on_price_grid(child.fill_price - side *
exit.loss_ticks * tick, tick)
24115 const std::string origin_key = std::to_string(child.origin);
24116 const SourceId group_name =
exit.exit_id +
"\x1f\x1f" + origin_key;
24117 const OrderBirth birth = capture_order_birth();
24118 const PineSizingSnapshot sizing = sizing_snapshot();
24119 const auto family_key = key_for(
exit.exit_id,
SourceId{});
24121 native_order::Request
request;
24122 request.intent = native_order::HostSized{native_order::HostSizedKind::Close, std::nullopt};
24126 request.owner = native_order::Independent{};
24127 request.group = group_for(group_name, 1,
static_cast<std::int64_t
>(family));
24135 snapshot.command_sequence = command_sequence;
24140 snapshot.placement_cycle = current_position_cycle_;
24141 snapshot.projection_remaining_qty = child.units;
24142 snapshot.per_entry_origin = child.origin;
24143 snapshot.per_entry_units = child.units;
24144 initialize_l4c_policy(snapshot, {});
24145 const SourceId replacement_key =
exit.exit_id +
"\x1f\x1f"
24146 + std::to_string(
static_cast<int>(family)) +
"\x1f" + origin_key;
24147 const auto accepted = submit_or_replace(std::move(request), std::move(snapshot),
false,
24150 auto& roster = bracket_family(family_key);
24151 if (!roster.holds(*accepted)) roster.push_back(*accepted);
24154 if (std::isfinite(limit)) {
24156 native_order::Limit{exit_limit_trigger(limit, tick, exit_is_buy)});
24158 if (std::isfinite(stop)) {
24160 native_order::Stop{source_trigger_threshold(stop, tick, exit_is_buy,
false)});
24168void PineExecutionAdapter::per_entry_exits_at_entry_fill(
24169 const PlacementSnapshot& opening,
const native_order::ExecutionAppliedEvent& event) {
24171 || !(std::abs(event.opened_units) > 0.0) || event.closed_units > 0.0) {
24175 const bool long_side = physical.signed_units > 0.0;
24176 if (physical.signed_units == 0.0 || opening.is_long != long_side)
return;
24178 return std::any_of(per_entry_exits_.begin(), per_entry_exits_.end(),
24179 [&](
const PerEntryExit& row) { return row.exit_id == exit_id; });
24183 detail::ExitScratchRows<SourceId> standing(*
this);
24184 for (
const auto& handle : live_handles_) {
24185 const auto row = placement_.find(handle.incarnation);
24186 if (row == placement_.end())
continue;
24187 const auto& leg = row->second;
24189 || !leg.from_entry.empty() || leg.per_entry_origin != 0
24190 || !(finite_positive(leg.exit_levels.profit_ticks)
24191 || finite_positive(leg.exit_levels.loss_ticks))
24192 || tracked(leg.source_id)
24193 || standing.contains(leg.source_id)) {
24196 standing.push_back(leg.source_id);
24198 if (per_entry_exits_.empty() && standing.empty())
return;
24199 if (!per_entry_exit_scope())
return;
24200 const PerEntryExitChild added{
event.handle().incarnation,
event.resolved_price,
24201 std::abs(event.opened_units),
false};
24202 for (std::size_t index = 0; index < per_entry_exits_.size(); ++index) {
24203 auto&
exit = per_entry_exits_[index];
24204 if (
exit.cycle != current_position_cycle_ ||
exit.long_side != long_side
24205 || std::any_of(
exit.children.begin(),
exit.children.end(),
24206 [&](
const PerEntryExitChild& child) { return child.origin == added.origin; })) {
24209 exit.children.push_back(added);
24210 submit_per_entry_child(per_entry_exits_[index], added, 0);
24212 if (standing.empty())
return;
24213 const auto held_side =
static_cast<std::int32_t
>(
24215 const double tick = staged_.syminfo.mintick;
24216 const double side = long_side ? 1.0 : -1.0;
24217 for (
const auto& exit_id : standing) {
24218 const auto staged = [&](
const PlacementSnapshot& row) {
24219 return row.source_id ==
exit_id && row.from_entry.empty();
24221 bool uniform = std::none_of(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
24222 [&](
const PendingBracketLeg& row) { return staged(row.snapshot); })
24223 && std::none_of(delayed_market_orders_.begin(), delayed_market_orders_.end(),
24224 [&](
const DelayedMarketOrder& row) { return staged(row.snapshot); })
24225 && std::none_of(pending_coof_requests_.begin(), pending_coof_requests_.end(),
24226 [&](
const PendingCoofRequest& row) { return staged(row.snapshot); });
24227 detail::ExitScratchRows<native_order::RequestHandle> legs(*
this);
24228 const PlacementSnapshot* first =
nullptr;
24229 for (
const auto& handle : live_handles_) {
24230 const auto row = placement_.find(handle.incarnation);
24231 if (row == placement_.end() || !staged(row->second))
continue;
24232 const auto& leg = row->second;
24236 if (!exit_leg)
continue;
24237 if (!first) first = ⋚
24239 && leg.deferred_cohort && std::isnan(leg.requested_qty)
24240 && (std::isnan(leg.qty_percent)
24241 || leg.qty_percent >= 100.0 - internal::kFullPercentEps)
24242 && leg.oca_name.empty() && std::isnan(leg.exit_levels.trail_points)
24243 && std::isnan(leg.exit_levels.trail_offset)
24244 && std::isnan(leg.exit_levels.trail_price)
24245 && leg.placement_cycle == current_position_cycle_
24246 && leg.projection_position_side == held_side
24247 && same_double_bits(leg.exit_levels.profit_ticks, first->exit_levels.profit_ticks)
24248 && same_double_bits(leg.exit_levels.loss_ticks, first->exit_levels.loss_ticks)
24249 && leg.comment == first->comment;
24250 legs.push_back(handle);
24252 if (!uniform || !first)
continue;
24255 const auto entries = per_entry_open_entries(long_side);
24256 PerEntryExitChild prior{};
24257 std::size_t prior_count = 0;
24258 for (
const auto&
entry : entries) {
24259 if (
entry.origin == added.origin)
continue;
24263 if (prior_count != 1)
continue;
24264 const double profit_ticks = first->exit_levels.profit_ticks;
24265 const double loss_ticks = first->exit_levels.loss_ticks;
24266 const auto own_level = [&](
const PlacementSnapshot& leg) {
24268 return finite_positive(profit_ticks) && same_double_bits(leg.exit_levels.limit,
24269 source_level_on_price_grid(prior.fill_price + side * profit_ticks * tick, tick));
24271 return finite_positive(loss_ticks) && same_double_bits(leg.exit_levels.stop,
24272 source_level_on_price_grid(prior.fill_price - side * loss_ticks * tick, tick));
24275 for (
const auto& handle : legs) {
24276 const auto row = placement_.find(handle.incarnation);
24277 own = own && row != placement_.end() && own_level(row->second);
24279 if (!own)
continue;
24281 exit.exit_id = first->source_id;
24282 exit.cycle = current_position_cycle_;
24283 exit.long_side = long_side;
24284 exit.profit_ticks = profit_ticks;
24285 exit.loss_ticks = loss_ticks;
24286 exit.comment = first->comment;
24287 exit.children = {prior, added};
24288 for (
const auto& handle : legs) {
24289 if (
const auto row = placement_.find(handle.incarnation); row != placement_.end()) {
24290 row->second.per_entry_origin = prior.origin; invalidate_retirement();
24291 row->second.per_entry_units = prior.units;
24294 per_entry_exits_.push_back(std::move(
exit));
24295 submit_per_entry_child(per_entry_exits_.back(), added, 0);
24301void PineExecutionAdapter::per_entry_exits_after_fill(
24302 const std::optional<PlacementSnapshot>& placement,
24303 const native_order::ExecutionAppliedEvent& event) {
24304 if (per_entry_exits_.empty())
return;
24305 if (placement &&
placement->per_entry_origin != 0 && event.closed_units > 0.0) {
24306 for (
auto&
exit : per_entry_exits_) {
24308 for (
auto& child :
exit.children)
24309 if (child.origin ==
placement->per_entry_origin) child.consumed =
true;
24313 for (std::size_t index = per_entry_exits_.size(); index-- > 0;) {
24314 const auto&
exit = per_entry_exits_[index];
24315 if (physical.signed_units == 0.0 ||
exit.cycle != current_position_cycle_
24316 || (physical.signed_units > 0.0) !=
exit.long_side) {
24317 end_per_entry_exit(index);
24335void PineExecutionAdapter::order_per_entry_point_ties() {
24338 if (physical.signed_units == 0.0)
return;
24339 const bool long_side = physical.signed_units > 0.0;
24340 const auto exit_leg = [&](
const PlacementSnapshot& row) {
24343 && row.from_entry.empty() && std::isnan(row.requested_qty) && row.oca_name.empty()
24344 && (finite_positive(row.exit_levels.profit_ticks)
24345 || finite_positive(row.exit_levels.loss_ticks))
24346 && std::isnan(row.exit_levels.trail_points)
24347 && std::isnan(row.exit_levels.trail_offset)
24348 && std::isnan(row.exit_levels.trail_price);
24350 const auto add = [&](
const PlacementSnapshot& row) {
24353 if (!any_live_row(live_handles_, placement_, exit_leg)
24354 || !any_live_row(live_handles_, placement_, add) || !per_entry_exit_scope()) {
24357 auto&
host = require_host();
24359 std::uint64_t incarnation = 0;
24362 std::uint64_t priority = 0;
24364 detail::ExitScratchRows<Ranked> rows(*
this);
24366 bool supported =
true;
24368 const auto& definition = *working.definition;
24369 const auto found = placement_.find(definition.handle.incarnation);
24370 if (found == placement_.end()) return true;
24371 const bool leg = exit_leg(found->second);
24372 if (!leg && !add(found->second)) return true;
24373 const auto& trigger = definition.request.trigger;
24374 int order_class = 0;
24375 if (std::holds_alternative<native_order::Limit>(trigger)) {
24377 }
else if (!std::holds_alternative<native_order::Market>(trigger)
24378 && !std::holds_alternative<native_order::Stop>(trigger)) {
24382 if (std::holds_alternative<native_order::Sized>(definition.request.intent)) {
24386 const bool buy = leg ? !long_side : long_side;
24387 rows.push_back({definition.handle.incarnation,
24388 versions.request(rows.scope(), definition.request),
24389 order_class * 2 + (buy ? 0 : 1),
24390 definition.priority != 0 ? definition.priority
24391 : definition.handle.incarnation});
24394 if (!supported)
return;
24397 [](
const Ranked& row) {
return row.priority; },
24398 [&](
const Ranked& row, std::size_t) { by_priority.push_back(row);
return true; });
24399 native_order::ReplaceOptions reprice;
24400 reprice.keep_handle =
true;
24401 std::size_t index = 0;
24402 bool unchanged_prefix =
true;
24403 by_priority.ordered(
24404 [](
const Ranked& row) {
return static_cast<std::uint64_t
>(row.rank); },
24405 [&](
const Ranked& row, std::size_t) {
24406 unchanged_prefix = unchanged_prefix
24407 && by_priority[index].incarnation == row.incarnation;
24409 if (!unchanged_prefix) {
24410 const native_order::RequestHandle handle{rows.context().run, row.incarnation};
24411 (void)
host.replace(handle, versions.request(row.request), reprice);
24417void PineExecutionAdapter::end_per_entry_exit(std::size_t index) {
24419 per_entry_exits_.erase(per_entry_exits_.begin() +
static_cast<std::ptrdiff_t
>(index));
24420 const auto legs = per_entry_child_legs(exit_id, 0);
24421 if (legs.empty())
return;
24422 cancel_working_legs(legs);
24423 if (
const auto family = bracket_families_.find(key_for(exit_id,
SourceId{}));
24424 family != bracket_families_.end()) {
24425 family->second.remove_selected(legs);
24426 if (family->second.empty()) bracket_families_.erase(family);
24430void PineExecutionAdapter::fill_pooc_close_exits(
24431 double raw_close,
const NativeDecisionContext& context) {
24449 if (physical.signed_units == 0.0 || !finite_positive(raw_close))
return;
24450 const bool closing_long = physical.signed_units > 0.0;
24451 const auto held_side =
static_cast<std::int32_t
>(
24453 const double tick = staged_.syminfo.mintick;
24454 const double close_tick = source_bar_fill_tick(raw_close, tick);
24456 std::uint64_t stop = 0;
24457 std::uint64_t stop_snapshot = 0;
24458 std::uint64_t
limit = 0;
24459 std::uint64_t limit_snapshot = 0;
24461 detail::ExitScratchRows<Group> groups(*
this);
24462 detail::ExitScratchIndex keys(groups.context());
24464 for (
const auto& handle : live_handles_) {
24465 const auto found = placement_.find(handle.incarnation);
24466 if (found == placement_.end())
continue;
24467 const auto& row = found->second;
24470 if (row.birth.from_fill())
continue;
24476 if (!row.from_entry.empty()
24477 ? !(cohort_exposure_for(row.from_entry) > 0.0)
24478 : (row.projection_position_side != held_side
24479 && row.projection_position_side
24485 if (!row.from_entry.empty()) {
24486 const auto cohort = cohorts_by_id_.find(row.from_entry);
24487 if (cohort != cohorts_by_id_.end() && !
cohort->second.opened.empty()) {
24488 const auto parent = placement_.find(
cohort->second.opened.back().incarnation);
24489 if (parent != placement_.end()
24490 && parent->second.projection_created_bar == row.projection_created_bar
24491 && parent->second.command_sequence > row.command_sequence) {
24497 const double level = stop_leg ? row.exit_levels.stop : row.exit_levels.limit;
24498 if (!finite_positive(level))
continue;
24499 const detail::ExitScratchPairText
key(row.source_id, row.from_entry);
24500 auto index = keys.find(key);
24502 index = groups.size();
24503 keys.put(key, index);
24504 groups.push_back(Group{});
24506 Group group = groups[index];
24508 if (group.stop == 0) {
24509 group.stop = handle.incarnation;
24510 group.stop_snapshot = versions.placement(groups.scope(), row);
24512 }
else if (group.limit == 0) {
24513 group.limit = handle.incarnation;
24514 group.limit_snapshot = versions.placement(groups.scope(), row);
24516 groups.set(index, group);
24518 for (
const auto& group : groups) {
24521 if (live_signed == 0.0 || (live_signed > 0.0) != closing_long)
break;
24522 const double stop_level = group.stop != 0
24523 ? versions.placement(group.stop_snapshot).exit_levels.stop : kNaN;
24524 const double limit_level = group.limit != 0
24525 ? versions.placement(group.limit_snapshot).exit_levels.limit : kNaN;
24526 const bool fill_stop = group.stop != 0
24527 && (closing_long ? close_tick <= stop_level : close_tick >= stop_level);
24528 const bool fill_limit = group.limit != 0
24529 && (closing_long ? close_tick >= limit_level : close_tick <= limit_level);
24530 if (!fill_stop && !fill_limit)
continue;
24531 const native_order::RequestHandle selected{
24532 groups.context().run, fill_stop ? group.stop : group.limit};
24533 if (!selected.incarnation)
continue;
24534 const auto row = versions.placement(
24535 fill_stop ? group.stop_snapshot : group.limit_snapshot);
24536 const double live_held_units = std::abs(live_signed);
24537 double units = std::isfinite(row.projection_remaining_qty)
24538 ? std::max(0.0, row.projection_remaining_qty)
24539 : (std::isfinite(row.requested_qty) ? std::abs(row.requested_qty) : 0.0);
24540 if ((!std::isfinite(row.projection_remaining_qty) && !std::isfinite(row.requested_qty))
24541 || fill_time_global_exit(row)) {
24546 units = quantize_close_units(
24547 row.from_entry.empty() ? live_held_units : cohort_exposure_for(row.from_entry),
24548 std::isfinite(row.qty_percent) ? row.qty_percent : 100.0);
24550 if (std::isfinite(row.qty_percent)
24551 && row.qty_percent < 100.0 - internal::kFullPercentEps
24552 && finite_positive(row.projection_remaining_qty)) {
24553 units = std::max(units, row.projection_remaining_qty);
24556 if (!(units > 0.0))
continue;
24565 const bool covers_live_book = live_held_units > 0.0
24566 && units >= live_held_units - internal::kQtyEpsilon;
24567 cancel_bracket_siblings(selected);
24568 native_order::Request
request;
24569 request.intent = covers_live_book
24572 native_order::Reduce{native_order::ExplicitUnits{units}}};
24573 request.label = row.source_id;
24574 request.comment = row.comment;
24575 request.trigger = native_order::Market{};
24576 PlacementSnapshot immediate = row;
24582 immediate.forced_execution_price = nearest_tick(
24583 raw_close + (stop_close ? (closing_long ? -1.0 : 1.0) : 0.0)
24584 * config_.slippage * tick,
24586 immediate.projection_predecessor = selected.incarnation;
24587 immediate.projection_predecessor_exit =
true;
24588 const auto accepted = submit_or_replace(
24589 std::move(request), std::move(immediate),
false,
24590 row.source_id +
"\x1f" + row.from_entry
24591 + std::to_string(
static_cast<int>(row.family)));
24592 if (accepted) execute_or_withdraw_close(*accepted, row.void_issue);
24617void PineExecutionAdapter::retune_carried_trails_for_tick_reach(
24618 const Bar& bar,
const NativeDecisionContext& context) {
24620 || trail_state_at_open_.empty() || !modeled_input() || config_.calc_on_order_fills
24621 || context.driver_statistics.intrabar_path_enabled) {
24624 const double tick = staged_.syminfo.mintick;
24625 if (!finite_positive(tick) || !finite_positive(bar.open) || !finite_positive(bar.high)
24626 || !finite_positive(bar.low) || !finite_positive(bar.close)) {
24630 if (held_units == 0.0)
return;
24631 const bool is_buy = held_units < 0.0;
24632 const bool high_first = source_path_uses_high_first(bar);
24633 const double path[3] = {high_first ? bar.high : bar.low,
24634 high_first ? bar.low : bar.high, bar.close};
24635 const auto improves = [&](
double best,
double price) {
24638 for (
const auto& working : require_host().native_working_requests()) {
24639 const auto& definition = *working.definition;
24640 const auto* trail = std::get_if<native_order::Trail>(&definition.request.trigger);
24641 const auto carried = trail_state_at_open_.find(definition.handle.incarnation);
24642 const auto found = placement_.find(definition.handle.incarnation);
24643 if (!trail || carried == trail_state_at_open_.end() || !carried->second.activated
24645 || !(found->second.exit_levels.trail_offset >= 1.0)
24646 || !std::isfinite(found->second.retained_trail_best)
24647 || !std::holds_alternative<native_order::TrailTrack>(working.trigger_state)) {
24650 const double offset = trail->offset;
24651 double best = carried->second.best_price;
24655 == carried->second.current_level)) {
24658 const auto reached = [&](
double price,
double level,
bool on_tick) {
24659 const double threshold =
on_tick
24660 ? source_trigger_threshold(level, tick, is_buy,
false) : level;
24661 return is_buy ?
price >= threshold :
price <= threshold;
24666 if (reached(bar.open, carried_level,
true) || reached(bar.open, carried_level,
false))
24668 if (improves(best, bar.open)) best = bar.open;
24669 double bests[3] = {kNaN, kNaN, kNaN};
24670 int raw_point = -1;
24671 int tick_point = -1;
24672 for (
int i = 0; i < 3; ++i) {
24675 if (raw_point < 0 && reached(path[i], level,
false)) raw_point = i;
24676 if (tick_point < 0 && reached(path[i], level,
true)) tick_point = i;
24677 if (improves(best, path[i])) best = path[i];
24681 if (tick_point < 0 || (raw_point >= 0 && raw_point <= tick_point))
continue;
24683 const auto hits = [&](
double distance,
int i) {
24686 const auto fires_first_at = [&](
double distance) {
24687 for (
int i = 0; i < 3; ++i)
24688 if (hits(distance, i))
return i;
24692 double retuned = is_buy ? path[tick_point] - bests[tick_point]
24693 : bests[tick_point] - path[tick_point];
24694 for (
int step = 0; step < 64 && retuned > 0.0 && !hits(retuned, tick_point); ++step)
24695 retuned = std::nextafter(retuned, 0.0);
24696 for (
int step = 0; step < 64; ++step) {
24697 const double wider = std::nextafter(retuned, std::numeric_limits<double>::infinity());
24698 if (!hits(wider, tick_point))
break;
24701 if (!(retuned > 0.0) || fires_first_at(retuned) != tick_point)
continue;
24702 native_order::Request
request = definition.request;
24703 auto retuned_trail = *trail;
24704 retuned_trail.offset = retuned;
24705 retuned_trail.ticks.reset();
24706 request.trigger = retuned_trail;
24707 native_order::ReplaceOptions keep;
24708 keep.retain_trigger_state =
true;
24709 keep.keep_handle =
true;
24711 const auto replaced = require_host().replace(definition.handle, request, keep);
24712 if (replaced.status != native_order::ReplaceStatus::Replaced)
continue;
24713 carried_trail_retunes_.push_back({definition.handle, offset});
24719void PineExecutionAdapter::restore_carried_trail_retunes() {
24720 if (carried_trail_retunes_.empty())
return;
24721 auto retunes = std::move(carried_trail_retunes_);
24722 carried_trail_retunes_.clear();
24723 for (
const auto& [handle, offset] : retunes) {
24724 for (
const auto& working : require_host().native_working_requests()) {
24725 const auto& definition = *working.definition;
24726 if (!(definition.handle == handle))
continue;
24727 const auto* trail = std::get_if<native_order::Trail>(&definition.request.trigger);
24728 if (!trail || trail->offset == offset)
break;
24729 native_order::Request
request = definition.request;
24730 auto restored = *trail;
24731 restored.offset = offset;
24732 restored.ticks.reset();
24734 native_order::ReplaceOptions keep;
24735 keep.retain_trigger_state =
true;
24736 keep.keep_handle =
true;
24738 (void)require_host().replace(definition.handle, request, keep);
24746 restore_carried_trail_retunes();
24759 if (host_ ==
nullptr || !modeled_input() || !config_.process_orders_on_close
24760 || config_.calc_on_order_fills
24761 || (config_.commission_value == 0.0 && config_.slippage == 0)
24762 || !finite_positive(bar.
high)) {
24765 if (
PF_RARE(short_path_points_scope(context))) {
24766 walk_short_path_points(bar, context);
24770 && position_open_script_bar_ != std::numeric_limits<std::int64_t>::min()
24772 (void)submit_margin_call_slice(bar.
high, context);
24778 on_bar_close_policy(bar, context);
24782 finish_admission_bar();
24786void PineExecutionAdapter::finish_admission_bar() {
24787 fold_admission_events();
24789 admission_events_digest_, admission_events_last_);
24792void PineExecutionAdapter::on_bar_close_policy(
24798 const bool bound = host_ !=
nullptr;
24799 if (bound) fold_source_money();
24802 close_first_script_bar_ = std::numeric_limits<std::int64_t>::min();
24803 close_first_side_ = 0;
24807 if (
PF_RARE(owed_follow_up_bar_ != std::numeric_limits<std::int64_t>::min()) && bound
24809 book_owed_close_follow_up(bar, context);
24819 && modeled_input() && !coof_recalc_active_;
24821 && modeled_input() && !coof_recalc_active_;
24823 if (!skip_quiet(QuietHook::PoocLimitEntryFills, bound && (!entry_close_fill_pass
24824 || !any_live_row(live_handles_, placement_, [&](
const PlacementSnapshot& row) {
24826 && row.projection_created_bar ==
bar_index;
24828 flush_pooc_marketable_limit_entry_fills(bar, context);
24830 if (!
skip_quiet(QuietHook::PoocExitFills, bound && (!close_fill_pass
24831 || !any_live_row(live_handles_, placement_, [&](
const PlacementSnapshot& row) {
24834 && row.projection_created_bar ==
bar_index;
24836 flush_pooc_marketable_exit_fills(bar, context);
24839 if (!
skip_quiet(QuietHook::DeferredSellAdmission,
24840 bound && deferred_open_marketable_sells_.empty())) {
24841 admit_deferred_open_marketable_sells();
24844 if (!
skip_quiet(QuietHook::ThrottledReopens, bound && throttled_reopen_rearm_.empty()))
24845 rearm_throttled_reopens();
24850 last_bar_dual_entry_path_ = 0;
24855 if (bound) order_per_entry_point_ties();
24859 std::size_t explicit_entries = 0;
24860 if (config_.process_orders_on_close && config_.pyramiding == 0 && modeled_input()) {
24861 for (
const auto& handle : live_handles_) {
24862 const auto found = placement_.find(handle.incarnation);
24863 if (found != placement_.end() && found->second.opening
24865 && found->second.projection_created_bar ==
bar_index
24866 && finite_positive(found->second.requested_qty)) {
24867 ++explicit_entries;
24871 if (!
skip_quiet(QuietHook::TerminalExplicitMarket, bound && explicit_entries < 2))
24872 apply_terminal_explicit_market_policy(context);
24873 update_risk_state(bar.
close);
24874 if (!modeled_input())
return;
24875 withdraw_waypoint_margin_calls();
24876 waypoint_chain_bar_ = std::numeric_limits<std::int64_t>::min();
24879 close_margin_cancelled_bar_ = std::numeric_limits<std::int64_t>::min();
24880 if (close_point_margin_scope() && close_point_margin_call(bar, context, cancelled))
24889 auto closes = same_bar_close_alls(context);
24892 if (submit_tv_money_long_margin_call(bar, context, &fired)) {
24893 if (fired == 3 && (!config_.process_orders_on_close
24895 size_close_alls_at_placement(closes, placed);
24896 if ((fired == 1 || fired == 2) && !closes.empty())
24897 (void)close_all_at_next_point(bar, fired, closes, context);
24906 if (
skip_quiet(QuietHook::CloseMarginCheckpoints,
24908 && !(!config_.process_orders_on_close && config_.margin_short == 100.0
24910 && config_.commission_value > 0.0))) {
24921 const bool non_pooc_commissioned_short = !config_.process_orders_on_close
24922 && position.signed_units < 0.0
24923 && config_.margin_short == 100.0
24925 && config_.commission_value > 0.0;
24926 if (non_pooc_commissioned_short && finite_positive(bar.
high)) {
24927 const double adverse = nearest_tick(bar.
high, staged_.syminfo.mintick);
24928 (void)submit_margin_call_slice(adverse, context);
24940bool exit_leg_marketable_at(
const PineExitLevels& legs,
bool short_book,
double open_tick)
noexcept {
24941 if (std::isfinite(legs.stop) && (short_book ? open_tick >= legs.stop : open_tick <= legs.stop))
24943 return std::isfinite(legs.limit)
24944 && (short_book ? open_tick <= legs.limit : open_tick >= legs.limit);
24959bool PineExecutionAdapter::opening_released_leg(
24961 const SourceId&
id,
bool short_book,
const NativeDecisionContext& context)
const {
24962 if (row.bracket_origin.incarnation == opening)
return true;
24963 if (row.bracket_origin.incarnation != 0)
return false;
24964 if (row.from_entry.empty())
24965 return leg > opening
24967 return row.from_entry ==
id
24969 && row.projection_position_side
24989bool PineExecutionAdapter::priced_exit_precedes_opening(
24990 bool short_book, std::uint64_t opening,
const SourceId& opening_id,
24992 bool* at_open)
const {
24993 const double open_tick = opening != 0
24994 ? nearest_tick(policy_script_bar_.open, staged_.syminfo.mintick) : kNaN;
24995 bool marketable_at_open =
false;
24996 for (
const auto& handle : live_handles_) {
24997 const auto found = placement_.find(handle.incarnation);
24998 if (found == placement_.end())
continue;
24999 const auto& row = found->second;
25004 if (filled && row.command_sequence == filled->command_sequence
25005 && row.bracket_origin == filled->bracket_origin) {
25010 if (row.void_global_issue)
continue;
25011 if (row.projection_created_bar < 0
25020 if (!row.from_entry.empty()
25021 && !from_entry_filled_this_cycle(row.from_entry)) {
25025 && opening_released_leg(row, handle.incarnation, opening, opening_id, short_book,
25027 marketable_at_open = marketable_at_open
25028 || exit_leg_marketable_at(row.exit_levels, short_book, open_tick);
25031 if (filled || !short_book)
continue;
25032 if ((std::isfinite(row.exit_levels.stop)
25033 && policy_script_bar_.high >= row.exit_levels.stop)
25034 || (std::isfinite(row.exit_levels.limit)
25035 && policy_script_bar_.low <= row.exit_levels.limit)) {
25036 if (at_open) *at_open =
false;
25040 if (at_open) *at_open = marketable_at_open;
25041 return marketable_at_open;
25049void PineExecutionAdapter::run_opening_checkpoint(
25050 double fill,
bool market,
bool sibling_book,
bool adds_book_last,
25051 const NativeDecisionContext& context) {
25053 const double opening_exact_required = std::abs(opened_position.signed_units) * fill
25054 * staged_.syminfo.pointvalue * active_staged_fx(context.sub_bar_open_ms);
25055 const double opening_equity = require_host().native_marked_equity(fill);
25056 const bool defer_slipped_pooc_rounding =
25057 slipped_pooc_opening_money_scope(policy_script_bar_, context)
25058 && std::isfinite(opening_exact_required)
25059 && std::isfinite(opening_equity)
25060 && opening_equity >= opening_exact_required
25061 && opening_equity < source_money_round(opening_exact_required);
25071 const bool slipped_market_short = opened_position.signed_units < 0.0
25072 && config_.slippage != 0 && !config_.process_orders_on_close && market;
25073 const bool cash_contract_market_opening = config_.commission_value > 0.0
25075 && !config_.process_orders_on_close && market;
25076 if (!defer_slipped_pooc_rounding && cash_contract_market_opening) {
25077 const double print = source_bar_fill_tick(
25078 fill - (opened_position.signed_units > 0.0 ? 1.0 : -1.0)
25079 * config_.slippage * staged_.syminfo.mintick,
25080 staged_.syminfo.mintick);
25083 (void)submit_margin_call_units(print, context, units,
true);
25084 }
else if (!defer_slipped_pooc_rounding && slipped_market_short) {
25085 const double print = source_bar_fill_tick(
25086 fill + config_.slippage * staged_.syminfo.mintick,
25087 staged_.syminfo.mintick);
25089 if (submit_margin_call_slice(print, context)) {
25090 follow_margin_call(
25094 }
else if (!defer_slipped_pooc_rounding) {
25097 const bool follow_up = sibling_book
25099 fill, context.sub_bar_open_ms),
true), fill);
25112 const bool adds_book = adds_book_last && opened_position.signed_units > 0.0;
25113 const double print = source_bar_fill_tick(
25114 fill - config_.slippage * staged_.syminfo.mintick,
25115 staged_.syminfo.mintick);
25116 const auto print_money = adds_book
25118 const auto lots_before = adds_book
25119 ? require_host().native_open_lots(print) : std::vector<NativeOpenLot>{};
25120 const bool called = adds_book && config_.slippage != 0
25121 ? submit_margin_call_slice(print, context)
25122 : submit_margin_call_slice(fill, context, true);
25123 const double next = next_source_path_waypoint(
25124 policy_script_bar_, context.coordinate.path_phase, fill,
25125 source_path_uses_high_first(policy_script_bar_));
25126 if (called && follow_up && finite_positive(next))
25127 (void)submit_margin_call_units(next, context, 1.0,
true);
25128 if (called && adds_book && finite_positive(next)) {
25129 const double taken = opened_position.signed_units
25133 context.sub_bar_open_ms);
25134 if (more > 0.0) (void)submit_margin_call_units(next, context, more,
true);
25141 retired_row_scratch_.roster_pass_due =
true;
25142 fold_source_money();
25144 if constexpr (PINEFORGE_POSITION_TAIL_DIAGNOSTICS::enabled) {
25145 if (position_tail_summary_) {
25146 auto& work = position_tail_summary_->work;
25147 const auto before = work.update_rows;
25148 ++work.reissue_updates;
25149 observe_position_tail_rows();
25150 work.reissue_rows += work.update_rows - before;
25152 observe_position_tail_rows();
25155 observe_position_tail_rows();
25160 std::optional<PlacementSnapshot> placement_snapshot;
25162 placement != placement_.end()) {
25163 placement_snapshot = placement->second;
25166 == native_order::RequestOrigin::KernelLiquidation) {
25178 adopted.
sizing = sizing_snapshot();
25179 placement_.try_emplace(event.
handle().
incarnation, adopted); invalidate_retirement();
25180 placement_snapshot = adopted;
25185 note_exit_rank_key(placement_snapshot->source_id);
25188 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
25189 const std::size_t index =
event.first_trade_index + i;
25192 const bool from_bracket =
25196 if (
auto* pine_host = pine_view_of(&require_host())) {
25197 pine_host->adapter_label_bracket_trades(event, from_bracket);
25200 if (placement_snapshot && placement_snapshot->opening) {
25202 for (
const auto& handle : live_handles_) {
25203 if (handle == event.
handle())
continue;
25204 const auto pending = placement_.find(handle.incarnation);
25205 if (pending != placement_.end()
25206 && pending->second.paired_reversal_parent == event.
handle()) {
25210 for (
const auto& handle : paired_closes) {
25211 const auto result = require_host().cancel(handle);
25212 if (result.status != native_order::CancelStatus::Cancelled)
continue;
25213 if (
const auto pending = placement_.find(handle.incarnation);
25214 pending != placement_.end()) {
25215 pending->second.cancellation = {
25217 event.handle().incarnation,
25218 static_cast<std::int64_t
>(placement_snapshot->source_sequence),
25219 handle.incarnation, pending->second.placement_cycle,
25220 pending->second.legs.revision(),
25221 pending->second.requested_qty, kNaN};
25232 placement_snapshot->projection_position_side);
25234 for (
const auto& handle : live_handles_) {
25235 if (handle == event.
handle())
continue;
25236 const auto found = placement_.find(handle.incarnation);
25237 if (found == placement_.end())
continue;
25238 const auto& pending = found->second;
25241 || pending.projection_position_side
25242 !=
static_cast<std::int32_t
>(closed_side)) {
25245 const bool resting_limit =
25247 && finite_positive(pending.exit_levels.limit)
25248 && !finite_positive(pending.exit_levels.stop);
25253 const bool resting_stop =
25255 && config_.process_orders_on_close && !config_.calc_on_order_fills
25256 && margins_disabled()
25258 && finite_positive(pending.exit_levels.stop);
25259 const bool coqueued_within_cap =
25260 pending.projection_created_bar
25261 == placement_snapshot->projection_created_bar
25262 && (!pending.projection_over_pyramiding
25263 || (placement_snapshot->close_batch_calls != 0
25264 && (finite_positive(pending.exit_levels.limit)
25265 || finite_positive(pending.exit_levels.stop))));
25266 const bool preserved_stop =
25268 && pending.preserved_by_close_all ==
event.handle()
25269 && pending.preserved_close_all_bar
25270 == placement_snapshot->projection_created_bar;
25271 const bool frozen_over_cap_transaction = pending.frozen_market_instruction
25272 && pending.projection_over_pyramiding;
25273 if (!resting_limit && !resting_stop && !coqueued_within_cap && !preserved_stop
25274 && !frozen_over_cap_transaction) {
25278 for (
const auto& handle : stale_entries) {
25279 const auto result = require_host().cancel(handle);
25280 if (result.status != native_order::CancelStatus::Cancelled)
continue;
25281 if (
const auto found = placement_.find(handle.incarnation);
25282 found != placement_.end()) {
25283 found->second.cancellation = {
25285 event.handle().incarnation,
25286 static_cast<std::int64_t
>(placement_snapshot->source_sequence),
25287 handle.incarnation, found->second.placement_cycle,
25288 found->second.legs.revision(), found->second.requested_qty, kNaN};
25293 if (placement_snapshot
25307 retire_in_position_exits_at_flat(
true,
25309 &*placement_snapshot);
25312 std::size_t remaining = 0;
25313 for (std::size_t index = 0; index < pending_entries_.size(); ++index) {
25314 auto&
entry = pending_entries_[index];
25315 if (
entry.snapshot.paired_reversal_parent == event.
handle()) {
25316 after_close.
push_back(versions.pending(after_close.
scope(), std::as_const(
entry.request),
25317 std::as_const(
entry.snapshot),
25318 std::as_const(
entry.replacement_key)));
25320 if (remaining != index) pending_write(pending_entries_[remaining]) = std::move(
entry);
25324 pending_write(pending_entries_).resize(remaining);
25325 for (
const auto version : after_close) {
25326 PendingEntry
entry;
25327 std::as_const(versions).pending(version,
entry.request,
entry.snapshot,
entry.replacement_key);
25328 entry.snapshot.paired_reversal_parent = {};
25329 entry.snapshot.market_admission = {};
25335 const bool priced_stop = finite_positive(
entry.snapshot.exit_levels.stop)
25336 && !finite_positive(
entry.snapshot.exit_levels.limit)
25337 && !finite_positive(
entry.snapshot.exit_levels.trail_points)
25338 && !finite_positive(
entry.snapshot.exit_levels.trail_price)
25339 && !finite_positive(
entry.snapshot.exit_levels.trail_offset);
25346 const bool calculation_close = placement_snapshot->immediately
25348 const bool marketable_now = !calculation_close
25350 || pure_stop_entry_marketable_at(
25352 if (marketable_now) {
25353 entry.snapshot.forced_execution_price =
event.resolved_price;
25361 placement_snapshot->projection_position_side);
25362 if (!priced_stop && config_.slippage != 0
25365 entry.snapshot.forced_execution_price =
25366 nearest_tick(event.
raw_price, staged_.syminfo.mintick)
25367 + (
entry.snapshot.is_long ? 1.0 : -1.0) * config_.slippage
25368 * staged_.syminfo.mintick;
25372 const bool self_touched = priced_stop && policy_script_bar_valid_
25373 && (
entry.snapshot.is_long
25374 ? policy_script_bar_.high >=
entry.snapshot.exit_levels.stop
25375 : policy_script_bar_.low <=
entry.snapshot.exit_levels.stop);
25376 if (priced_stop && !marketable_now && self_touched) {
25377 std::uint64_t prior_seq = std::numeric_limits<std::uint64_t>::max();
25378 for (
const auto& handle : live_handles_) {
25379 const auto found = placement_.find(handle.incarnation);
25380 if (found == placement_.end())
continue;
25381 const auto& prior = found->second;
25383 || prior.is_long !=
entry.snapshot.is_long
25384 || !finite_positive(prior.exit_levels.stop)
25385 || finite_positive(prior.exit_levels.limit)
25386 || prior.command_sequence >=
entry.snapshot.command_sequence) {
25390 prior.projection_position_side);
25395 const bool prior_touched = prior.is_long
25396 ? policy_script_bar_.high >= prior.exit_levels.stop
25397 : policy_script_bar_.low <= prior.exit_levels.stop;
25398 if (!prior_touched || prior.command_sequence >= prior_seq)
continue;
25399 prior_seq = prior.command_sequence;
25400 prior_flip_stop = handle;
25403 if (prior_flip_stop.incarnation != 0) {
25404 const double units = finite_positive(
entry.snapshot.requested_qty)
25405 ? std::abs(
entry.snapshot.requested_qty)
25406 : (finite_positive(
entry.snapshot.sizing.frozen_units)
25407 ?
entry.snapshot.sizing.frozen_units
25408 : std::abs(config_.default_qty_value));
25410 entry.snapshot.is_long ? units : -units};
25413 const auto accepted = submit_or_replace(
25414 std::move(
entry.request), std::move(
entry.snapshot),
true,
25415 entry.replacement_key);
25416 if (accepted && marketable_now && prior_flip_stop.incarnation == 0) {
25417 const auto outcome = require_host().execute_current(
25418 {*accepted, NativeCurrentPriceRule::NearestTick});
25423 bool preclose_intraday_loss =
false;
25434 && !intraday_loss_orders_blocked() && !day_open_fill
25435 && std::isfinite(day_ledger_.intraday_start_equity)) {
25436 double closed_pnl = 0.0;
25437 const auto& host = require_host();
25438 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
25439 const auto index =
event.first_trade_index + i;
25440 if (index <
static_cast<std::size_t
>(host.trade_count()))
25441 closed_pnl += host.get_trade(
static_cast<int>(index)).pnl;
25443 const double after = host.native_marked_equity(event.
resolved_price);
25444 const double before = after - closed_pnl;
25445 const double loss = day_ledger_.intraday_start_equity - before;
25446 const double threshold = risk_.max_intraday_loss_percent
25447 ? day_ledger_.intraday_start_equity * risk_.max_intraday_loss / 100.0
25448 : risk_.max_intraday_loss;
25449 if (std::isfinite(risk_.max_intraday_loss) && !std::isfinite(threshold)) {
25450 engine_command_value_invalid_or_count(
"risk.threshold",
25451 EngineValueStage::risk, EngineValueReason::numeric_overflow);
25453 const double epsilon = 1e-9 * std::max(1.0, std::abs(threshold));
25454 preclose_intraday_loss = std::isfinite(before) && threshold > 0.0
25455 && loss > 0.0 && loss + epsilon >= threshold;
25457 last_applied_ordinal_ =
event.ordinal;
25460 entry_openings_this_interval_ += 1;
25461 if (!throttled_reopen_rearm_.empty())
25462 rearm_throttled_reopens();
25465 const int next_sign = live_position > 0.0 ? 1 : (live_position < 0.0 ? -1 : 0);
25466 const bool flipped_position = current_position_sign_ != 0 && next_sign != 0
25467 && current_position_sign_ != next_sign;
25473 std::vector<SourceId> preserved_cohorts;
25474 if (placement_snapshot) preserved_cohorts.push_back(placement_snapshot->source_id);
25475 for (
const auto& handle : live_handles_) {
25476 const auto pending = placement_.find(handle.incarnation);
25477 if (pending != placement_.end()
25478 && pending->second.opening
25480 && pending->second.is_long == (live_position > 0.0)) {
25481 preserved_cohorts.push_back(pending->second.source_id);
25484 std::sort(preserved_cohorts.begin(), preserved_cohorts.end());
25489 cancel_reversal_exit_owners(preserved_cohorts, closed_cohorts);
25499 if (position_tail_summary_) position_tail_summary_->forget_origins();
25500 const auto held = cohorts_by_id_.held_rows();
25501 for (
auto at = held.begin(); at != held.end();) {
25502 const auto current = at++;
25503 auto& cohort = *current;
25504 assert(cohort_opened_slot_count_ >= cohort.second.opened.size());
25505 cohort_opened_slot_count_ -= cohort.second.opened.size();
25508 cohorts_by_id_.refresh_held(current);
25515 close_logical_units_.clear();
25516 close_ledger_records_.clear();
25517 pyramiding_records_.clear();
25518 exit_tombstones_.clear();
25520 if (next_sign != 0 && (current_position_sign_ == 0 || current_position_sign_ != next_sign)) {
25521 ++current_position_cycle_;
25522 position_tail_spill_.reset();
25524 begin_position_tail_summary();
25525 position_open_epoch_ = broker_open_epoch_;
25528 position_open_priced_ = placement_snapshot
25529 && (finite_positive(placement_snapshot->exit_levels.limit)
25530 || finite_positive(placement_snapshot->exit_levels.stop)
25533 current_position_sign_ = next_sign;
25534 per_entry_exits_after_fill(placement_snapshot, event);
25535 if (placement_snapshot
25541 && ((std::isfinite(placement_snapshot->requested_qty) && placement_snapshot->requested_qty > 0.0)
25542 || (std::isfinite(placement_snapshot->qty_percent) && placement_snapshot->qty_percent < 100.0 - 1e-9))) {
25553 && !placement_snapshot->from_entry.empty() && pinned_lot_grid()) {
25554 const std::uint64_t origin = entry_incarnation_of(placement_snapshot->from_entry);
25556 exit_tombstones_[placement_snapshot->source_id +
"\x1f"
25557 + placement_snapshot->from_entry] = origin;
25561 if (!partial_exit_sibling_live(*placement_snapshot, event.
handle())) {
25562 consumed_partial_exit_cycles_.insert_or_assign(
25563 placement_snapshot->source_id +
"\x1f" + placement_snapshot->from_entry,
25564 current_position_cycle_);
25568 && placement_snapshot->oca_type == 1 && !placement_snapshot->oca_name.empty()) {
25569 const bool fully_filled = !std::isfinite(placement_snapshot->requested_qty)
25570 ||
event.filled_working >= placement_snapshot->requested_qty;
25571 if (fully_filled) {
25573 for (
const auto& handle : live_handles_) {
25574 if (handle == event.
handle())
continue;
25575 const auto peer = placement_.find(handle.incarnation);
25576 if (peer != placement_.end()
25579 && peer->second.oca_type == 1
25580 && peer->second.oca_name == placement_snapshot->oca_name) {
25584 for (
const auto& sibling : siblings) {
25585 const auto result = require_host().cancel(sibling);
25586 if (result.status == native_order::CancelStatus::Cancelled) retire(sibling);
25599 if (
PF_RARE(placement_snapshot && placement_snapshot->crosses_zero
25600 && !placement_snapshot->opening)
25603 && std::abs(event.
opened_units) > 0.0 && live_position > 0.0
25604 && config_.process_orders_on_close
25607 ?
"__close__" + placement_snapshot->source_id : placement_snapshot->source_id;
25608 credit_close_ledger(lot, std::abs(event.
opened_units));
25611 if (placement_snapshot && placement_snapshot->opening
25613 withdraw_void_exits(placement_snapshot->source_id);
25617 settle_void_global_exits(!finite_positive(placement_snapshot->exit_levels.limit)
25618 && !finite_positive(placement_snapshot->exit_levels.stop));
25619 bind_pending_exits_to_fill(placement_snapshot->source_id);
25626 for (
const auto& handle : live_handles_) {
25627 auto child = placement_.find(handle.incarnation);
25628 if (child == placement_.end())
continue;
25629 auto& snapshot = child->second;
25633 if (!
exit || snapshot.from_entry != placement_snapshot->source_id
25634 || snapshot.bracket_origin.incarnation != 0
25635 || !std::isfinite(snapshot.requested_qty)) {
25638 const auto replacement = [&](std::uint64_t origin) {
25639 return snapshot.source_id +
"\x1f" + snapshot.from_entry +
"\x1f"
25640 + std::to_string(
static_cast<int>(snapshot.family)) +
"\x1f"
25641 + std::to_string(origin);
25643 const auto old_key = checked_live_key(replacement(0));
25644 const auto old = live_by_source_key_.find(old_key);
25645 if (old != live_by_source_key_.end() && old->second == handle)
25646 live_by_source_key_.erase(old);
25647 snapshot.bracket_origin =
event.handle(); invalidate_retirement();
25651 const auto cohort = require_cohort(placement_snapshot->source_id);
25652 auto& facts = cohort->second;
25653 facts.cycle =
event.cycle_after;
25654 if (std::find(facts.opened.begin(), facts.opened.end(), event.
handle())
25655 == facts.opened.end()) {
25656 facts.opened.push_back(event.
handle());
25657 ++cohort_opened_slot_count_;
25658 cohorts_by_id_.refresh_held(cohort);
25660 facts.live_units_by_origin[
event.handle().incarnation]
25662 cohorts_by_id_.refresh_held(cohort);
25663 if (position_tail_summary_)
25664 position_tail_summary_->open_origin(
25667 if (position_tail_summary_)
25668 position_tail_summary_->live_cohort(&*cohort,
true);
25670 credit_close_ledger(placement_snapshot->source_id, std::abs(event.
opened_units));
25671 credit_pyramiding_record(placement_snapshot->source_id, event.
handle().
incarnation,
25673 record_opening_fee(*placement_snapshot, event);
25674 materialize_pending_bracket_legs(event);
25676 placement_snapshot->projection_position_side);
25677 const bool consumed_deferred_carry =
25678 finite_positive(placement_snapshot->projection_tv_carry_qty)
25681 if (consumed_deferred_carry) {
25682 for (
const auto& handle : live_handles_) {
25683 if (handle == event.
handle())
continue;
25684 const auto sibling = placement_.find(handle.incarnation);
25685 if (sibling == placement_.end())
continue;
25686 auto& candidate = sibling->second;
25687 if (!candidate.opening
25688 || candidate.source_id == placement_snapshot->source_id
25689 || candidate.projection_position_side
25690 != placement_snapshot->projection_position_side
25691 || candidate.projection_created_bar
25692 > placement_snapshot->projection_created_bar) {
25695 candidate.projection_tv_carry_qty = 0.0;
25698 materialize_relative_exits(*placement_snapshot, event);
25699 per_entry_exits_at_entry_fill(*placement_snapshot, event);
25700 const bool true_paired_transaction =
25701 placement_snapshot->paired_flat_market_candidate
25702 && finite_positive(placement_snapshot->paired_flat_market_own_qty)
25703 && finite_positive(placement_snapshot->paired_flat_market_transaction_qty)
25704 && placement_snapshot->paired_flat_market_transaction_qty
25705 > placement_snapshot->paired_flat_market_own_qty + 1e-10;
25706 if (!true_paired_transaction) {
25707 reconcile_deferred_exit_reservations(
25708 placement_snapshot->source_id,
25709 cohort_exposure_for(placement_snapshot->source_id));
25716 return row.bracket_origin.incarnation != 0
25717 && row.bracket_origin !=
event.handle();
25719 const bool partial_prearmed_parent = std::isfinite(
25721 double smallest = kNaN;
25722 for (
const auto& handle : live_handles_) {
25723 const auto pending = placement_.find(handle.incarnation);
25724 if (pending == placement_.end())
continue;
25725 const auto& row = pending->second;
25728 && row.from_entry == placement_snapshot->source_id
25729 && row.projection_created_bar
25730 == placement_snapshot->projection_created_bar
25731 && !carried_origin_leg(row)
25732 && std::isfinite(row.requested_qty)) {
25733 smallest = std::isfinite(smallest)
25734 ? std::min(smallest, row.requested_qty)
25735 : row.requested_qty;
25741 for (
const auto& handle : live_handles_) {
25742 const auto pending = placement_.find(handle.incarnation);
25743 if (pending == placement_.end())
continue;
25744 const auto& row = pending->second;
25747 && row.from_entry == placement_snapshot->source_id
25748 && row.projection_created_bar
25749 == placement_snapshot->projection_created_bar
25750 && !carried_origin_leg(row)
25751 && std::isfinite(row.requested_qty)
25752 && row.requested_qty < std::abs(event.
opened_units)) {
25758 const bool multiple_prearmed_parents = std::any_of(
25759 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
25760 if (handle == event.handle()) return false;
25761 const auto pending = placement_.find(handle.incarnation);
25762 return pending != placement_.end() && pending->second.opening
25763 && pending->second.family == PineOrderFamily::Entry
25764 && pending->second.projection_created_bar
25765 == placement_snapshot->projection_created_bar;
25771 const bool long_position =
event.opened_units > 0.0;
25772 const bool stop_marketable = finite_positive(row.exit_levels.stop)
25773 && (long_position ?
event.resolved_price <= row.exit_levels.stop
25774 :
event.resolved_price >= row.exit_levels.stop);
25775 const bool limit_marketable = finite_positive(row.exit_levels.limit)
25776 && (long_position ?
event.resolved_price >= row.exit_levels.limit
25777 :
event.resolved_price <= row.exit_levels.limit);
25778 return stop_marketable || limit_marketable;
25785 if (partial_prearmed_parent || multiple_prearmed_parents) {
25787 for (
const auto& handle : live_handles_) {
25788 const auto pending = placement_.find(handle.incarnation);
25789 if (pending == placement_.end())
continue;
25790 const auto& row = pending->second;
25791 const bool matches_parent = row.from_entry.empty()
25792 || row.from_entry == placement_snapshot->source_id;
25795 && matches_parent && row.per_entry_origin == 0
25796 && row.projection_created_bar
25797 == placement_snapshot->projection_created_bar
25798 && !carried_origin_leg(row)
25799 && (partial_prearmed_parent || marketable_at_fill(row))) {
25803 for (
const auto& handle : delayed_legs) {
25804 const auto pending = placement_.find(handle.incarnation);
25805 if (pending == placement_.end())
continue;
25809 native_order::HostSizedKind::Close, std::nullopt};
25811 request.comment = row.
comment;
25812 request.owner = owner_for_close(row.
from_entry,
true);
25813 const bool exit_is_buy =
event.opened_units < 0.0;
25817 exit_is_buy,
false)};
25821 exit_is_buy,
true)};
25823 const bool explicit_origin = std::isfinite(row.
requested_qty)
25825 const std::string origin_suffix = explicit_origin
25827 const std::string group_name = row.
oca_name.empty()
25830 request.group = group_for(group_name, 1);
25831 const std::string replacement_key = row.
source_id +
"\x1f"
25833 + std::to_string(
static_cast<int>(row.
family)) + origin_suffix;
25834 const auto cancelled = require_host().cancel(handle);
25835 if (cancelled.status != native_order::CancelStatus::Cancelled)
25838 delayed_market_orders_.push_back({
25839 std::move(request), row, replacement_key,
25840 broker_open_epoch_ + 1U});
25843 if (!partial_prearmed_parent) {
25844 std::optional<PlacementSnapshot> rearm;
25845 for (
const auto& handle : live_handles_) {
25846 const auto pending = placement_.find(handle.incarnation);
25847 if (pending == placement_.end())
continue;
25848 const auto& row = pending->second;
25849 const bool matches_parent = row.from_entry.empty()
25850 || row.from_entry == placement_snapshot->source_id;
25855 || !matches_parent || row.per_entry_origin != 0
25856 || row.projection_created_bar
25857 != placement_snapshot->projection_created_bar
25858 || carried_origin_leg(row)) {
25865 if (row.from_entry.empty() && event.
closed_units > 0.0
25866 && row.projection_position_side
25872 const bool long_position =
event.opened_units > 0.0;
25873 const bool stop_marketable = finite_positive(row.exit_levels.stop)
25874 && (long_position ?
event.resolved_price <= row.exit_levels.stop
25875 :
event.resolved_price >= row.exit_levels.stop);
25876 const bool limit_marketable = finite_positive(row.exit_levels.limit)
25877 && (long_position ?
event.resolved_price >= row.exit_levels.limit
25878 :
event.resolved_price <= row.exit_levels.limit);
25879 const bool equal_limit = finite_positive(row.exit_levels.limit)
25880 &&
event.resolved_price == row.exit_levels.limit;
25881 if ((!stop_marketable && !limit_marketable) || equal_limit) {
25894 const bool long_position =
event.opened_units > 0.0;
25895 const bool stop_wrong_side = multiple_prearmed_parents
25896 && finite_positive(rearm->exit_levels.stop)
25897 && (long_position ?
event.resolved_price <= rearm->exit_levels.stop
25898 :
event.resolved_price >= rearm->exit_levels.stop);
25899 const bool limit_wrong_side = multiple_prearmed_parents
25900 && finite_positive(rearm->exit_levels.limit)
25901 && (long_position ?
event.resolved_price >= rearm->exit_levels.limit
25902 :
event.resolved_price <= rearm->exit_levels.limit)
25904 exit(rearm->source_id, rearm->from_entry,
25905 limit_wrong_side ? kNaN : rearm->exit_levels.limit,
25906 stop_wrong_side ? kNaN : rearm->exit_levels.stop,
25907 rearm->exit_levels.trail_points,
25908 rearm->exit_levels.trail_offset,
25909 rearm->exit_levels.trail_price,
25910 rearm->qty_percent, rearm->comment,
25911 rearm->requested_qty, rearm->oca_name,
25912 rearm->exit_levels.profit_ticks,
25913 rearm->exit_levels.loss_ticks);
25917 && config_.default_qty_type
25919 && config_.default_qty_value < 100.0) {
25925 for (
const auto& handle : live_handles_) {
25926 const auto pending = placement_.find(handle.incarnation);
25927 if (pending != placement_.end()
25929 && pending->second.from_entry == placement_snapshot->source_id
25930 && pending->second.projection_created_bar
25931 == placement_snapshot->projection_created_bar) {
25935 for (
const auto& handle : prearmed_trails) {
25936 const auto result = require_host().cancel(handle);
25937 if (result.status == native_order::CancelStatus::Cancelled)
25945 for (
const auto& handle : live_handles_) handles.
push_back(handle);
25947 for (
const auto& handle : handles) {
25948 const auto pending = placement_.find(handle.incarnation);
25949 if (pending == placement_.end())
continue;
25950 auto& candidate = pending->second;
25954 if (!
exit)
continue;
25955 if (candidate.from_entry == placement_snapshot->source_id) {
25956 if (candidate.legs.target().incarnation
25957 && candidate.legs.target().owner != current_position_cycle_) {
25960 context.
sub_count > 1 ? exit_legs::Domain::MagnifierFillRecalc
25961 : exit_legs::Domain::FillRecalc,
25962 exit_legs::Phase::Observation};
25964 candidate.legs.revision(), cause,
25966 (void)candidate.legs.apply(candidate.legs.target(),
bind); invalidate_retirement();
25968 initialize_l4c_policy(candidate, handle);
25970 const bool bound_preexit_add = placement_snapshot
25971 && placement_snapshot->opening &&
event.opened_units > 0.0
25972 && placement_snapshot->command_sequence < candidate.command_sequence;
25973 const bool selected_growth_source = placement_snapshot
25974 && placement_snapshot->reservation_growth_source.reservation_owner()
25975 && *placement_snapshot->reservation_growth_source.reservation_owner()
25976 == handle.incarnation;
25977 if (candidate.reservation_expansion.capture() && handle != event.
handle()
25978 && selected_growth_source
25979 && (candidate.reservation_expansion.population_open() || bound_preexit_add)) {
25980 if (bound_preexit_add && std::isfinite(candidate.projection_remaining_qty)) {
25981 candidate.projection_remaining_qty += std::abs(event.
opened_units);
25983 if (candidate.reservation_expansion.population_open()) {
25984 candidate.pooc_global_full_exit_dynamic_qty =
false;
25985 candidate.pooc_global_full_exit_tracks_bound_adds =
false;
25987 candidate.pooc_global_full_exit_bound_add = bound_preexit_add;
25992 if (placement_snapshot && placement_snapshot->opening
25993 && placement_snapshot->reverse_to && event.
closed_units > 0.0
25995 purge_brackets_after_applied_reversal(*placement_snapshot);
26006 if (placement_snapshot && placement_snapshot->opening
26007 && placement_snapshot->reverse_to && event.
closed_units > 0.0
26009 const auto reversed_side =
static_cast<std::int32_t
>(
26012 for (
const auto& handle : live_handles_) {
26013 const auto found = placement_.find(handle.incarnation);
26014 if (found == placement_.end())
continue;
26015 const auto& row = found->second;
26021 if (
exit && row.from_entry.empty() && row.projection_position_side == reversed_side
26022 && row.placement_cycle == current_position_cycle_ - 1) {
26023 reversed_global_exits.
push_back(handle);
26026 cancel_working_legs(reversed_global_exits);
26036 && row.from_entry.empty() && row.projection_position_side == reversed_side
26037 && row.placement_cycle == current_position_cycle_ - 1;
26039 delayed_market_orders_.erase(std::remove_if(
26040 delayed_market_orders_.begin(), delayed_market_orders_.end(),
26041 [&](
const auto&
order) { return reversed_global_leg(order.snapshot); }),
26042 delayed_market_orders_.end());
26043 pending_write(pending_bracket_legs_).erase(std::remove_if(
26044 pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
26045 [&](
const PendingBracketLeg& leg) { return reversed_global_leg(leg.snapshot); }),
26046 pending_bracket_legs_.end());
26050 refresh_pending_sizing_after_margin(event, context);
26058 std::uint64_t sole_live_entry_incarnation = 0;
26059 std::size_t live_entry_count = 0;
26060 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
26061 for (
const auto& opening : cohort.second.opened) {
26062 const auto units = cohort.second.live_units_by_origin.find(
26063 opening.incarnation);
26064 if (units == cohort.second.live_units_by_origin.end()
26065 || !(units->second > 0.0)) {
26068 sole_live_entry_incarnation = opening.incarnation;
26069 ++live_entry_count;
26072 const std::uint64_t source_fill_sequence =
26074 if (live_entry_count == 1U && sole_live_entry_incarnation != 0
26075 && physical.signed_units > 0.0 && physical.lot_count == 1U
26077 && config_.default_qty_type
26079 && std::abs(config_.default_qty_value - 100.0) < 1e-12
26081 && config_.commission_value == 0.0 && config_.slippage == 0
26082 && source_fill_sequence != 0) {
26084 signal_close_mc_position_cycle_ = current_position_cycle_;
26085 signal_close_mc_entry_incarnation_ = sole_live_entry_incarnation;
26086 signal_close_mc_fill_seq_ = source_fill_sequence;
26087 signal_close_mc_remaining_qty_ = std::abs(physical.signed_units);
26088 signal_close_mc_before_qty_ = signal_close_mc_remaining_qty_
26091 std::uint64_t sole_entry_incarnation = 0;
26092 int live_entry_origins = 0;
26093 if (physical.signed_units != 0.0 && physical.lot_count == 1U) {
26094 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
26095 for (
const auto& origin : cohort.second.opened) {
26096 const auto units = cohort.second.live_units_by_origin.find(
26097 origin.incarnation);
26098 if (units == cohort.second.live_units_by_origin.end()
26099 || !(units->second > 0.0)) {
26102 const auto opening = placement_.find(origin.incarnation);
26103 if (opening == placement_.end()
26104 || !opening->second.opening
26105 || opening->second.is_long != (physical.signed_units > 0.0)) {
26108 sole_entry_incarnation = origin.incarnation;
26109 ++live_entry_origins;
26113 if (live_entry_origins != 1) sole_entry_incarnation = 0;
26115 last_margin_call_event_ordinal_ =
event.ordinal;
26116 last_margin_call_entry_incarnation_ = sole_entry_incarnation;
26117 last_margin_call_position_cycle_ = current_position_cycle_;
26118 last_margin_call_at_script_close_ = policy_script_bar_valid_
26121 == nearest_tick(policy_script_bar_.close, staged_.syminfo.mintick);
26122 last_margin_call_closed_units_ =
event.closed_units;
26123 last_margin_call_remaining_units_ = std::abs(physical.signed_units);
26125 const auto* pine_host = pine_view_of(&require_host());
26126 const bool magnifier = pine_host
26127 && pine_host->scheduler_.bar_magnifier_enabled();
26128 const bool ordinary_margin_receipt = !config_.process_orders_on_close
26129 && !config_.calc_on_order_fills && !coof_recalc_active_
26131 && last_margin_call_at_script_close_;
26132 for (
const auto& handle : live_handles_) {
26133 const auto found = placement_.find(handle.incarnation);
26134 if (found == placement_.end())
continue;
26135 auto& candidate = found->second;
26136 const double close_surplus = candidate.projection_tv_carry_qty
26137 - std::abs(physical.signed_units);
26139 && candidate.affordability_close_only && !candidate.is_long
26140 && candidate.rounded_signal_cost_close_only
26141 && candidate.deferred_cohort
26142 && ordinary_margin_receipt
26143 && !std::isfinite(candidate.requested_qty)
26144 && !finite_positive(candidate.exit_levels.limit)
26145 && !finite_positive(candidate.exit_levels.stop)
26146 && !candidate.projection_after_close
26147 && candidate.projection_created_bar
26149 && candidate.projection_position_side
26151 && candidate.placement_cycle == current_position_cycle_
26152 && physical.signed_units > 0.0 && physical.lot_count == 1U
26153 && sole_entry_incarnation != 0
26154 && std::isfinite(close_surplus) && std::abs(close_surplus - 1.0) < 1e-6
26156 if (exact_margin_receipt) {
26157 candidate.affordability_keep_mc_close_surplus =
true;
26158 candidate.signal_close_mc_bar = candidate.projection_created_bar;
26159 candidate.signal_close_mc_entry_incarnation = sole_entry_incarnation;
26160 candidate.signal_close_mc_fill_seq = pine_host
26161 ? pine_host->adapter_broker_fill_event_sequence() : 0;
26162 candidate.signal_close_mc_remaining_qty =
26163 std::abs(physical.signed_units);
26164 if (live_entry_count == 1U && sole_live_entry_incarnation != 0) {
26165 candidate.signal_close_mc_bar = candidate.projection_created_bar;
26166 candidate.signal_close_mc_entry_incarnation =
26167 sole_live_entry_incarnation;
26168 candidate.signal_close_mc_fill_seq = source_fill_sequence;
26169 candidate.signal_close_mc_remaining_qty =
26170 std::abs(physical.signed_units);
26174 revive_brackets_after_margin(event, context);
26177 const SourceId* fee_source =
nullptr;
26178 if (placement_snapshot && !placement_snapshot->from_entry.empty())
26179 fee_source = &placement_snapshot->from_entry;
26180 consume_opening_fees(event, fee_source);
26182 const bool current_debit_observed =
26183 current_debited_applied_ordinals_.erase(event.
ordinal) != 0;
26185 consume_closed_trade_rows(event,
26186 placement_snapshot ? &*placement_snapshot :
nullptr);
26191 reconcile_exit_reservations_after_margin();
26199 if (placement_snapshot && placement_snapshot->opening) {
26200 const double physical_exposure =
26202 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
26203 const auto&
id = cohort.first;
26204 const double exposure = cohort_exposure_for(cohort.second);
26205 const bool paired_source_transaction =
26206 placement_snapshot->paired_flat_market_candidate
26207 && finite_positive(
26208 placement_snapshot->paired_flat_market_own_qty)
26209 && finite_positive(
26210 placement_snapshot->paired_flat_market_transaction_qty)
26211 && placement_snapshot->paired_flat_market_transaction_qty
26212 > placement_snapshot->paired_flat_market_own_qty + 1e-10;
26213 const double source_own = paired_source_transaction
26214 ? placement_snapshot->paired_flat_market_own_qty
26215 : physical_exposure;
26216 const bool intermediate_paired_gross = config_.pyramiding == 2
26217 && source_own > 0.0 && exposure > source_own + 1e-10;
26218 if (exposure > 0.0 && !intermediate_paired_gross)
26219 reconcile_deferred_exit_reservations(
id, exposure);
26229 if (physical_exposure > 0.0) {
26230 struct GlobalExit {
26231 std::uint64_t key = 0;
26232 std::uint64_t rank = 0;
26234 bool reserved =
false;
26235 double units = 0.0;
26240 std::uint64_t member_ordinal = 0;
26241 for (
const auto& handle : live_handles_) {
26242 const auto found = placement_.find(handle.incarnation);
26243 if (found == placement_.end())
continue;
26244 const auto& row = found->second;
26248 if (!
exit || !row.from_entry.empty())
continue;
26249 const bool reserved = std::isfinite(row.projection_remaining_qty);
26250 const bool full = !std::isfinite(row.requested_qty)
26251 && (!std::isfinite(row.qty_percent)
26252 || row.qty_percent >= 100.0 - internal::kFullPercentEps);
26255 const double units = reserved ? std::max(0.0, row.projection_remaining_qty)
26256 : std::isfinite(row.requested_qty) ? std::abs(row.requested_qty)
26257 : physical_exposure * (std::isfinite(row.qty_percent)
26258 ? std::clamp(row.qty_percent, 0.0, 100.0) : 100.0) / 100.0;
26259 const auto key = key_for(row.source_id, row.from_entry);
26260 const auto rank = exit_queue_rank(placement_, row);
26261 auto index = families.
find(key);
26263 index = armed.
size();
26264 families.
put(key, index);
26265 armed.
push_back({key, rank, full, reserved, units});
26267 GlobalExit family = armed[index];
26268 family.rank = std::min(family.rank, rank);
26269 family.full = family.full && full;
26270 family.reserved = family.reserved || reserved;
26271 family.units = std::max(family.units, units);
26272 armed.
set(index, family);
26274 members.
put(key, handle.incarnation, member_ordinal++);
26276 if (armed.
size() > 1) {
26278 bool behind_full =
false;
26279 bool partial_ahead_of_full =
false;
26280 double ahead = 0.0;
26282 [](
const GlobalExit& family) {
return family.rank; },
26283 [&](
const GlobalExit& family, std::size_t) {
26285 if (!family.reserved) {
26286 members.
each(family.key, [&](std::uint64_t incarnation) {
26287 behind.push_back({behind.context().run, incarnation});
26293 if (!family.full) {
26294 ahead += family.units;
26299 if (family.reserved)
return true;
26300 behind_full =
true;
26301 if (ahead > internal::kQtyEpsilon) partial_ahead_of_full =
true;
26304 if (partial_ahead_of_full) {
26305 reconcile_deferred_exit_reservations({}, physical_exposure);
26307 for (
const auto& handle : behind) {
26308 const auto result = require_host().cancel(handle);
26309 if (result.status == native_order::CancelStatus::Cancelled)
26317 && event.closed_units > 0.0) {
26318 const auto held = cohorts_by_id_.held_rows();
26319 for (
auto at = held.begin(); at != held.end();) {
26320 const auto current = at++;
26322 cohorts_by_id_.refresh_held(current);
26324 if (position_tail_summary_) position_tail_summary_->forget_origins();
26327 && event.closed_units > 0.0
26330 auto revival = std::find_if(
26331 pending_margin_revivals_.begin(), pending_margin_revivals_.end(),
26332 [&](
const PendingMarginRevival& pending) {
26333 const auto& row = pending.snapshot;
26334 const bool same_side = row.projection_position_side
26335 == static_cast<std::int32_t>(PositionSide::LONG)
26336 ? physical.signed_units > 0.0 : physical.signed_units < 0.0;
26337 const bool exposed = !row.from_entry.empty()
26338 && cohort_exposure_for(row.from_entry) > 0.0;
26339 return same_side && exposed;
26341 if (revival != pending_margin_revivals_.end()) {
26342 PlacementSnapshot snapshot = std::move(revival->snapshot);
26343 pending_margin_revivals_.erase(revival);
26344 const bool reached = physical.signed_units > 0.0
26345 ?
event.resolved_price <= snapshot.exit_levels.stop
26346 :
event.resolved_price >= snapshot.exit_levels.stop;
26347 native_order::Request request;
26348 request.intent = native_order::Reduce{
26349 native_order::ExplicitUnits{std::abs(physical.signed_units)}};
26353 const bool exit_is_buy = physical.signed_units < 0.0;
26354 request.trigger = native_order::Stop{source_trigger_threshold(
26355 snapshot.exit_levels.stop, staged_.syminfo.mintick,
26356 exit_is_buy,
false)};
26359 snapshot.restored_after_margin =
true;
26360 snapshot.requested_qty = std::abs(physical.signed_units);
26362 snapshot.projection_remaining_qty = kNaN;
26363 snapshot.fixed_exit_reservation =
false;
26364 snapshot.forced_execution_price = reached
26365 ?
event.resolved_price : kNaN;
26370 +
snapshot.from_entry +
"\x1fmargin-revival";
26371 const auto accepted = submit_or_replace(
26372 std::move(request), std::move(snapshot),
false, replacement_key);
26374 bracket_family(family_key).push_back(*accepted);
26376 (void)require_host().execute_current(
26377 {*accepted, NativeCurrentPriceRule::NearestTick});
26388 if (exit) exit_owners.push_back(candidate.from_entry);
26390 for (
const auto& handle : live_handles_) {
26391 const auto found = placement_.find(handle.incarnation);
26392 if (found != placement_.end()) collect_owner(found->second);
26394 for (
const auto& pending : pending_bracket_legs_) collect_owner(
pending.snapshot);
26395 for (
const auto& pending : pending_coof_requests_) collect_owner(
pending.snapshot);
26396 for (
const auto& shadow : source_shadow_pending_) collect_owner(shadow.snapshot);
26398 if (placement_snapshot && placement_snapshot->opening
26399 && placement_snapshot->source_id == owner) {
26402 for (
const auto& handle : live_handles_) {
26403 if (event.terminal && handle == event.handle())
continue;
26404 const auto found = placement_.find(handle.incarnation);
26405 if (found != placement_.end() && found->second.opening
26407 && found->second.source_id == owner) {
26411 return std::any_of(pending_entries_.begin(), pending_entries_.end(),
26412 [&](
const PendingEntry& entry) {
26413 return entry.snapshot.opening
26414 && entry.snapshot.source_id == owner;
26417 exit_owners.ordered_text(
26422 if (!pending_parent(owner)) cancel_exit_orders_for_full_close(owner);
26431 retire_in_position_exits_at_flat(
true,
26434 position_tail_spill_.reset();
26435 if (position_tail_summary_)
26436 position_tail_summary_->invalidate(PositionTailGuard::Flat,
26438 position_open_script_bar_ = std::numeric_limits<std::int64_t>::min();
26439 position_open_bar_index_ = -1;
26440 position_open_phase_ = NativePathPhase::None;
26441 position_open_priced_ =
false;
26442 open_entry_fees_.clear();
26443 close_logical_units_.clear();
26444 close_ledger_records_.clear();
26445 pyramiding_records_.clear();
26446 exit_tombstones_.clear();
26448 pending_margin_revivals_.clear();
26449 const auto held = cohorts_by_id_.held_rows();
26450 for (
auto at = held.begin(); at != held.end();) {
26451 const auto current = at++;
26452 auto&
cohort = *current;
26453 if (!
cohort.second.opened.empty()) ended_sources.push_back(
cohort.first);
26454 assert(cohort_opened_slot_count_ >=
cohort.second.opened.size());
26455 cohort_opened_slot_count_ -=
cohort.second.opened.size();
26458 cohorts_by_id_.refresh_held(current);
26461 for (
const auto& handle : live_handles_) handles.push_back(handle);
26463 for (
const auto& handle : handles) {
26464 const auto found = placement_.find(handle.incarnation);
26465 if (found == placement_.end())
continue;
26466 const auto family = found->second.family;
26484 if (!found->second.from_entry.empty()
26485 && !ended_sources.contains(found->second.from_entry)) {
26488 (void)require_host().cancel(handle);
26492 bracket_shadowed_openings_.clear();
26494 if (short_seed_.final_short.incarnation != 0 && event.handle() == short_seed_.final_short
26495 && config_.default_qty_type !=
static_cast<int>(
QtyType::FIXED)) {
26496 short_seed_.report_swap_pending =
true;
26498 update_l4c_lifecycle(event, context);
26499 if (event.terminal) {
26500 const auto filled = placement_.find(event.handle().incarnation);
26502 if (filled != placement_.end() && event.closed_units > 0.0
26503 && unbound_limit_without_revival(filled->second))
26504 filled->second.filled_exit =
true, invalidate_retirement();
26505 retire(event.handle());
26507 if (event.ordinal != day_ledger_.observed_applied_ordinal) {
26508 day_ledger_.observed_applied_ordinal =
event.ordinal;
26509 const auto day = chart_day_key(context.sub_bar_open_ms);
26510 if (event.closed_trade_count > 0) {
26511 const auto&
host = require_host();
26512 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
26513 const auto index =
event.first_trade_index + i;
26514 if (index >=
static_cast<std::size_t
>(
host.trade_count()))
continue;
26515 const double pnl =
host.get_trade(
static_cast<int>(index)).pnl;
26516 day_ledger_.intraday_realized += pnl;
26517 if (pnl < 0.0 && day != day_ledger_.last_loss_day) {
26518 day_ledger_.last_loss_day = day;
26519 if (day_ledger_.consecutive_loss_days
26520 == std::numeric_limits<int>::max()) {
26522 "closed trade counter exhausted");
26524 ++day_ledger_.consecutive_loss_days;
26525 }
else if (pnl > 0.0) {
26526 day_ledger_.consecutive_loss_days = 0;
26533 if (event.closed_units > 0.0 && !flipped_position
26535 observe_close_ledger(event, placement_snapshot ? &*placement_snapshot :
nullptr);
26537 if (placement_snapshot) {
26538 observe_intraday_cap(event, *placement_snapshot, context);
26540 && event.closed_units > 0.0) {
26541 last_margin_call_script_bar_ = context.script_bar_open_ms;
26543 const bool one_x_long = after_margin.signed_units > 0.0
26544 && std::abs(config_.margin_long - 100.0) < 1e-12;
26545 if (context.coordinate.path_phase == NativePathPhase::Open
26546 && policy_script_bar_valid_
26547 && policy_script_bar_.timestamp == context.script_bar_open_ms
26548 && after_margin.signed_units != 0.0 && !one_x_long) {
26559 const auto unrepeated =
PF_RARE(!unrepeated_margin_calls_.empty())
26560 ? std::find(unrepeated_margin_calls_.begin(), unrepeated_margin_calls_.end(),
26561 event.handle().incarnation)
26562 : unrepeated_margin_calls_.
end();
26563 const bool sequence_call = unrepeated != unrepeated_margin_calls_.end();
26564 if (sequence_call) unrepeated_margin_calls_.erase(unrepeated);
26565 const bool recheck_at_fill = config_.process_orders_on_close
26566 && !config_.calc_on_order_fills && config_.slippage != 0
26567 && after_margin.signed_units < 0.0
26568 && position_open_phase_ == NativePathPhase::Close
26569 && position_open_script_bar_ != context.script_bar_open_ms
26570 && finite_positive(event.resolved_price) && !sequence_call;
26576 if (!recheck_at_fill || !submit_margin_call_slice(
26577 event.resolved_price, context,
false, event.resolved_price))
26578 (
void)schedule_margin_call_path(policy_script_bar_, context);
26582 if (
PF_RARE(after_margin.signed_units < 0.0
26584 schedule_lagged_short_follow_up(event, context);
26587 && event.closed_units > 0.0) {
26588 risk_.intraday_block_day = chart_day_key(context.sub_bar_open_ms);
26589 risk_.intraday_cancel_pending =
true;
26592 if (preclose_intraday_loss) {
26593 risk_.intraday_block_day = chart_day_key(context.sub_bar_open_ms);
26594 risk_.intraday_cancel_pending =
true;
26596 native_order::Request
request;
26601 snapshot.source_id =
"__intraday_loss__";
26603 snapshot.sizing = sizing_snapshot();
26604 const auto accepted = submit_or_replace(
26605 std::move(request), std::move(snapshot),
false,
26606 "__intraday_loss_close__");
26608 (void)require_host().execute_current(
26609 {*accepted, NativeCurrentPriceRule::NearestTick});
26613 if (risk_.intraday_cancel_pending) {
26614 risk_.intraday_cancel_pending =
false;
26617 if (placement_snapshot && placement_snapshot->opening
26618 && std::abs(event.opened_units) > 0.0 && policy_script_bar_valid_) {
26625 const double opening_margin = opened_position.signed_units < 0.0
26626 ? config_.margin_short : config_.margin_long;
26627 const bool full_margin_opening = opened_position.signed_units != 0.0
26628 && std::abs(opening_margin - 100.0) < 1e-12;
26629 const bool preopen_margin_already_scheduled = std::any_of(
26630 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
26631 const auto pending = placement_.find(handle.incarnation);
26632 return pending != placement_.end()
26633 && pending->second.family == PineOrderFamily::Margin
26634 && pending->second.from_entry == placement_snapshot->source_id
26635 && pending->second.source_id.rfind(
"__margin_preopen__", 0) == 0;
26639 const auto dual_stop_peer = [&](std::uint64_t incarnation,
const PlacementSnapshot& peer) {
26640 return incarnation !=
event.handle().incarnation && peer.opening
26642 && peer.projection_position_side
26644 && peer.projection_created_bar
26645 == placement_snapshot->projection_created_bar
26646 && peer.is_long != placement_snapshot->is_long
26647 && finite_positive(peer.exit_levels.stop)
26648 && !finite_positive(peer.exit_levels.limit);
26650 const bool dual_stop_candidate = last_bar_dual_entry_path_ != 0
26651 && placement_snapshot->projection_position_side
26653 && finite_positive(placement_snapshot->exit_levels.stop)
26654 && !finite_positive(placement_snapshot->exit_levels.limit);
26655 if (dual_stop_candidate)
26657 const bool flat_dual_stop_member = dual_stop_candidate
26658 && std::any_of(placement_.begin(), placement_.end(),
26659 [&](
const auto& row) { return dual_stop_peer(row.first, row.second); });
26664 const bool stable_opening_fx = staged_.account_fx_effective_from_ms.empty()
26665 || placement_snapshot->sizing.fx
26666 == active_staged_fx(context.sub_bar_open_ms);
26672 const bool sibling_book = opened_position.lot_count >= 2
26673 && flat_sibling_book(*placement_snapshot, context.coordinate.interval_index,
26674 context.coordinate.path_phase);
26675 const bool sibling_fill_follows = sibling_book
26676 || opened_position.lot_count == 1
26677 ? flat_sibling_fill_follows(*placement_snapshot, event.handle()) : false;
26684 const bool placed_add = !sibling_book && add_entry_row(*placement_snapshot);
26685 const bool add_fill_follows = placed_add
26686 && add_sibling_fill_follows(*placement_snapshot, event.handle());
26687 const std::size_t filled_adds = placed_add && !add_fill_follows
26688 ? add_sibling_lots(placement_snapshot->placement_script_open_ms) : 0;
26689 const bool pooc_add_book = placed_add && config_.process_orders_on_close
26690 && !config_.calc_on_order_fills
26691 && !context.driver_statistics.intrabar_path_enabled
26692 && context.coordinate.path_phase == NativePathPhase::Close
26693 && (add_fill_follows || filled_adds >= 2);
26697 const bool adds_book_last = placed_add && !config_.process_orders_on_close
26698 && filled_adds >= 2;
26699 const bool zero_fee_true_flat_default = !sibling_book
26701 && std::abs(config_.default_qty_value - 100.0) < 1e-12
26702 && config_.commission_value == 0.0
26703 && !std::isfinite(placement_snapshot->requested_qty)
26704 && placement_snapshot->projection_position_side
26706 && !placement_snapshot->projection_after_close
26707 && std::holds_alternative<native_order::Market>(event.request().trigger)
26708 && placement_snapshot->sizing.price ==
event.resolved_price;
26709 if ((full_margin_opening || placement_snapshot->has_full_entry_bracket)
26710 && !preopen_margin_already_scheduled && stable_opening_fx) {
26711 const bool long_full_margin = opened_position.signed_units > 0.0
26712 && std::abs(config_.margin_long - 100.0) < 1e-12;
26713 const bool prearmed_entry_bar_margin = std::any_of(
26714 live_handles_.begin(), live_handles_.end(), [&](
const auto& handle) {
26715 const auto pending = placement_.find(handle.incarnation);
26716 return pending != placement_.end()
26717 && pending->second.family == PineOrderFamily::Margin
26718 && pending->second.from_entry == placement_snapshot->source_id;
26733 && context.coordinate.path_phase == NativePathPhase::Open
26734 && context.sub_index == 0
26735 && policy_script_bar_.timestamp == context.script_bar_open_ms
26736 && std::holds_alternative<native_order::Market>(event.request().trigger)
26737 && !config_.process_orders_on_close && !config_.calc_on_order_fills
26738 &&
event.closed_units == 0.0 && opened_position.lot_count == 1
26739 && !sibling_book && !placed_add;
26740 bool exit_first_at_open =
false;
26741 const bool short_preempted_by_priced_exit = priced_exit_precedes_opening(
26742 opened_position.signed_units < 0.0,
26743 open_market_fill ? event.handle().incarnation : 0, placement_snapshot->source_id,
26744 context,
nullptr, &exit_first_at_open);
26753 const bool called_at_next_open = config_.process_orders_on_close
26754 && !config_.calc_on_order_fills
26755 && (config_.commission_value != 0.0 || sibling_book)
26756 && !context.driver_statistics.intrabar_path_enabled
26757 && context.coordinate.path_phase == NativePathPhase::Close
26758 && position_open_phase_ == NativePathPhase::Close
26759 && position_open_script_bar_ == context.script_bar_open_ms;
26760 const bool stopped_flat_reversal = opened_position.signed_units < 0.0
26761 && std::abs(config_.margin_short - 100.0) < 1e-12 &&
event.closed_units == 0.0
26762 && placement_snapshot->reverse_to
26763 && std::holds_alternative<native_order::Stop>(event.request().trigger)
26764 && context.coordinate.path_phase != NativePathPhase::Open;
26765 const bool priced_opening_waypoint = (stopped_flat_reversal
26766 || (long_full_margin && !std::isfinite(placement_snapshot->requested_qty)
26767 && context.coordinate.path_phase != NativePathPhase::Open))
26768 && position_open_priced_
26770 && std::holds_alternative<native_order::Stop>(event.request().trigger)
26771 && !sibling_book && !placed_add
26772 && !config_.process_orders_on_close && !config_.calc_on_order_fills
26773 && modeled_input() && !bar_magnifier_;
26781 const bool slipped_long_called_at_next_open = config_.process_orders_on_close
26782 && long_full_margin && slipped_long_margin_scope()
26783 && context.coordinate.path_phase == NativePathPhase::Close
26784 && position_open_script_bar_ == context.script_bar_open_ms
26785 && slipped_long_unit_shortfall(nearest_tick(policy_script_bar_.close,
26786 staged_.syminfo.mintick));
26787 const bool opening_check_due = !zero_fee_true_flat_default && !called_at_next_open
26788 && !priced_opening_waypoint && !slipped_long_called_at_next_open
26789 && !sibling_fill_follows && !add_fill_follows && !pooc_add_book
26790 && !flat_dual_stop_member && !prearmed_entry_bar_margin
26791 && !(long_full_margin && !sibling_book
26792 && std::abs(staged_.syminfo.pointvalue - 1.0) > 1e-12);
26793 if (opening_check_due && !short_preempted_by_priced_exit
26794 && !same_point_pair_fill_follows(*placement_snapshot, event.handle(),
26795 event.resolved_price)) {
26796 run_opening_checkpoint(
26797 event.resolved_price,
26798 std::holds_alternative<native_order::Market>(event.request().trigger),
26799 sibling_book, adds_book_last, context);
26800 }
else if (opening_check_due && exit_first_at_open
26801 && !same_point_pair_fill_follows(*placement_snapshot, event.handle(),
26802 event.resolved_price)) {
26806 open_exit_first_bar_ = context.script_bar_open_ms;
26807 open_exit_first_origin_ =
event.handle().incarnation;
26808 open_exit_first_id_ = placement_snapshot->source_id;
26809 open_exit_first_fill_ =
event.resolved_price;
26811 const bool terminal_pooc_open = config_.process_orders_on_close
26812 && position_open_phase_ == NativePathPhase::Close;
26813 if (!terminal_pooc_open && !flat_dual_stop_member
26814 && !prearmed_entry_bar_margin) {
26815 if (priced_opening_waypoint) {
26816 (void)schedule_priced_opening_margin(
26817 policy_script_bar_, context, event.resolved_price);
26818 }
else if (long_full_margin) {
26819 (void)schedule_tv_money_long_margin_before_trail(
26820 policy_script_bar_, context);
26822 schedule_margin_call_path(policy_script_bar_, context);
26825 }
else if (!full_margin_opening && !placement_snapshot->has_full_entry_bracket
26826 && !preopen_margin_already_scheduled && stable_opening_fx
26827 && leveraged_entry_bar_checked()
26828 && !preopen_slice_class(*placement_snapshot, context)) {
26857 && placed_add && !add_fill_follows && !config_.process_orders_on_close
26858 && !config_.calc_on_order_fills && config_.slippage == 0
26860 && config_.commission_value > 0.0
26861 && context.coordinate.path_phase == NativePathPhase::Open
26862 && std::holds_alternative<native_order::Market>(event.request().trigger)
26863 && policy_script_bar_.timestamp == context.script_bar_open_ms
26864 && staged_.account_fx_effective_from_ms.empty()
26865 && finite_positive(policy_script_bar_.open)) {
26866 (void)submit_margin_call_slice(
26867 nearest_tick(policy_script_bar_.open, staged_.syminfo.mintick), context);
26869 (void)schedule_margin_call_path(policy_script_bar_, context);
26871 schedule_intraday_loss_path(policy_script_bar_, context);
26874 && context.coordinate.path_phase == NativePathPhase::Open
26876 && policy_script_bar_valid_ && !config_.process_orders_on_close
26877 && staged_.account_fx_effective_from_ms.empty()) {
26883 for (
const auto& handle : live_handles_) {
26884 const auto pending = placement_.find(handle.incarnation);
26885 if (pending != placement_.end()
26887 stale_margin_requests.push_back(handle);
26890 for (
const auto& handle : stale_margin_requests) {
26891 const auto cancelled = require_host().cancel(handle);
26892 if (
cancelled.status == native_order::CancelStatus::Cancelled)
26895 (void)schedule_margin_call_path(policy_script_bar_, context);
26905 if (
PF_RARE(open_exit_first_bar_ != std::numeric_limits<std::int64_t>::min())
26906 && placement_snapshot && event.closed_units > 0.0
26910 const bool short_book = after_exit.signed_units < 0.0;
26911 const bool owed = after_exit.signed_units != 0.0
26912 && open_exit_first_bar_ == context.script_bar_open_ms
26913 && policy_script_bar_.timestamp == context.script_bar_open_ms;
26914 const bool released_leg_at_open = owed
26915 && opening_released_leg(*placement_snapshot, event.handle().incarnation,
26916 open_exit_first_origin_,
26917 open_exit_first_id_, short_book, context)
26918 && exit_leg_marketable_at(placement_snapshot->exit_levels, short_book,
26919 nearest_tick(policy_script_bar_.open,
26920 staged_.syminfo.mintick));
26921 if (!owed || (released_leg_at_open
26922 && !priced_exit_precedes_opening(short_book, open_exit_first_origin_,
26923 open_exit_first_id_, context,
26924 &*placement_snapshot,
nullptr))) {
26925 const double fill = open_exit_first_fill_;
26926 open_exit_first_bar_ = std::numeric_limits<std::int64_t>::min();
26927 open_exit_first_origin_ = 0;
26928 open_exit_first_id_.clear();
26929 open_exit_first_fill_ = 0.0;
26932 NativeDecisionContext at_open = context;
26933 at_open.coordinate.path_phase = NativePathPhase::Open;
26934 run_opening_checkpoint(fill,
true,
false,
false, at_open);
26938 if (placement_snapshot && event.closed_units > 0.0
26942 && policy_script_bar_valid_
26943 && policy_script_bar_.timestamp == context.script_bar_open_ms
26944 && !config_.process_orders_on_close && !config_.calc_on_order_fills
26945 && staged_.account_fx_effective_from_ms.empty()) {
26955 const bool one_x_long = after_exit.signed_units > 0.0
26956 && std::abs(config_.margin_long - 100.0) < 1e-12;
26958 for (
const auto& handle : live_handles_) {
26959 const auto pending = placement_.find(handle.incarnation);
26960 if (pending != placement_.end()
26962 stale_margin_requests.push_back(handle);
26965 if (!one_x_long && !stale_margin_requests.empty()) {
26966 for (
const auto& handle : stale_margin_requests) {
26967 const auto cancelled = require_host().cancel(handle);
26968 if (
cancelled.status == native_order::CancelStatus::Cancelled)
26971 if (after_exit.signed_units != 0.0)
26972 (void)schedule_margin_call_path(policy_script_bar_, context);
26973 }
else if (!one_x_long) {
26982 kernel_margin_resize_point_ = context.coordinate.ordinal;
26983 if (after_exit.signed_units != 0.0)
26984 (void)schedule_margin_call_path(policy_script_bar_, context);
26987 apply_fx_opening_margin_slice(event, context);
26988 refresh_pending_view();
26994 if (placement_snapshot && event.closed_units > 0.0
26998 && !placement_snapshot->from_entry.empty()
27000 && current_position_cycle_ > 0) {
27001 auto* pine = pine_view_of(&require_host());
27002 const auto cohort = cohorts_by_id_.find(placement_snapshot->from_entry);
27003 if (pine && cohort != cohorts_by_id_.end()) {
27008 const int ratio = state.spec && !state.spec->timeframe_undetected
27009 ?
tf_ratio(state.spec->input_tf, state.spec->script_tf) : 1;
27010 const int bar = pine->scheduler_.bar_magnifier_enabled() || ratio > 1 || ratio == -1
27011 ? pine->scheduler_.source_bar_index_for(context)
27012 : context.coordinate.interval_index;
27016 for (
const auto& opening :
cohort->second.opened)
27017 opened.put(opening.incarnation, 0);
27018 double units = 0.0;
27019 for (
const auto& lot : pine->pyramid_entries_) {
27020 if (!market_pyramid_add(lot.entry_incarnation)
27021 || lot.entry_bar_index != bar
27022 || lot.entry_id != placement_snapshot->from_entry
27023 || !(lot.qty > internal::kQtyEpsilon)) {
27028 adds.push_back({adds.context().run, lot.entry_incarnation});
27029 selected_adds.put(lot.entry_incarnation, 0);
27036 bool drained_older =
false;
27037 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
27038 const std::size_t row =
event.first_trade_index + i;
27039 drained_older = drained_older || (row < pine->trades_.size()
27040 && pine->trades_[row].entry_id == placement_snapshot->from_entry
27041 && selected_adds.find(pine->trades_[row].entry_incarnation)
27044 if (!adds.empty() && units > 0.0 && drained_older) {
27045 native_order::Request
request;
27046 request.intent = native_order::Reduce{native_order::ExplicitUnits{units}};
27047 request.label = placement_snapshot->source_id;
27048 request.comment = placement_snapshot->comment;
27049 request.trigger = native_order::Market{};
27051 adds.context().run, current_position_cycle_, adds.size(), &adds,
27052 [](
void* opaque, std::uint64_t index, native_order::RequestHandle& result) {
27053 const auto& rows = *static_cast<const detail::ExitScratchRows<
27054 native_order::RequestHandle>*>(opaque);
27055 if (index >= rows.size()) return detail::ScratchStatus::not_found;
27057 result = rows[index];
27058 return detail::ScratchStatus::ok;
27060 return error.status();
27063 request.group = native_order::NoGroup{};
27066 cover.immediately =
true;
27067 cover.deferred_cohort =
false;
27068 cover.forced_execution_price =
event.resolved_price;
27069 const auto accepted = submit_or_replace(
27070 std::move(request), std::move(cover),
false,
27071 placement_snapshot->source_id +
"\x1f" + placement_snapshot->from_entry
27072 +
"\x1f" +
"cover");
27074 (void)require_host().execute_current(
27075 {*accepted, NativeCurrentPriceRule::NearestTick});
27083 if (placement_snapshot && event.closed_units > 0.0
27086 && placement_snapshot->projection_created_bar == projection_bar_index(context)
27087 && config_.process_orders_on_close && !config_.calc_on_order_fills
27088 && modeled_input() && !coof_recalc_active_
27089 && event.cursor.point.path_phase == NativePathPhase::Close) {
27090 fill_pooc_close_entries(event.raw_price, context,
true);
27098 const bool close_pass_entry_filled = placement_snapshot
27100 && context.coordinate.provenance == NativePriceProvenance::Calculation
27101 && std::isfinite(placement_snapshot->forced_execution_price)
27102 && (std::isfinite(placement_snapshot->exit_levels.limit)
27103 || std::isfinite(placement_snapshot->exit_levels.stop))
27104 && !pooc_close_entry_immediate_pending(context, &event.handle());
27105 if (placement_snapshot && config_.process_orders_on_close
27106 && modeled_input() && !coof_recalc_active_
27107 && (context.coordinate.provenance == NativePriceProvenance::AfterCalculationClose
27108 || close_pass_entry_filled)
27109 && placement_snapshot->projection_created_bar == projection_bar_index(context)
27110 && (placement_snapshot->opening
27114 && !pooc_close_market_pending(context)) {
27115 fill_pooc_close_exits(event.raw_price, context);
27119 if (placement_snapshot && placement_snapshot->waypoint_margin_call
27120 && event.closed_units > 0.0 && policy_script_bar_valid_
27121 && waypoint_chain_bar_ == context.script_bar_open_ms
27123 const bool high_first = source_path_uses_high_first(policy_script_bar_);
27124 const auto phase = context.coordinate.path_phase;
27125 const int filled_at = phase == NativePathPhase::Open ? 0
27126 : phase == NativePathPhase::Close ? 3
27127 : ((phase == NativePathPhase::High) == high_first ? 1 : 2);
27128 const double print = filled_at == 0 ? policy_script_bar_.open
27129 : filled_at == 3 ? policy_script_bar_.close
27130 : phase == NativePathPhase::High ? policy_script_bar_.high : policy_script_bar_.low;
27131 withdraw_waypoint_margin_calls();
27133 follow_margin_call(event.closed_units, event.resolved_price, print, context);
27135 (void)rest_waypoint_margin_call(policy_script_bar_, filled_at + 1, 3, context);
27139 if (event.definition
27140 && event.definition->origin == native_order::RequestOrigin::KernelLiquidation
27141 && event.closed_units > 0.0) {
27142 follow_margin_call(event.closed_units, event.resolved_price, event.raw_price, context);
27150 if (!short_seed_.active)
return 0;
27151 if (handle == short_seed_.long_entry)
return 1;
27152 if (handle == short_seed_.materialize_long)
return 2;
27153 if (handle == short_seed_.final_short)
return 3;
27158 const bool relabel = intraday_loss_relabel_ordinals_.erase(ordinal) != 0;
27160 if (relabel && position_tail_summary_)
27166std::vector<PineExecutionAdapter::FixturePendingSnapshot>
27168 std::vector<FixturePendingSnapshot> rows;
27169 rows.reserve(live_handles_.size() + pending_entries_.size()
27170 + pending_bracket_legs_.size()
27171 + pending_same_bar_commands_.size()
27172 + pending_coof_requests_.size()
27173 + source_shadow_pending_.size());
27176 std::uint64_t next_incarnation = std::max<std::uint64_t>(1, placement_.high_water() + 1);
27177 struct StagedParentProjection {
27179 std::uint64_t command_sequence = 0;
27180 std::uint64_t incarnation = 0;
27182 std::vector<StagedParentProjection> staged_parents;
27183 for (
const auto& handle : live_handles_) {
27184 const auto found = placement_.find(handle.incarnation);
27185 if (found != placement_.end()) rows.push_back({handle.incarnation, found->second,
false});
27187 for (
const auto& pending : pending_entries_) {
27188 const std::uint64_t incarnation = next_incarnation++;
27189 rows.push_back({incarnation, pending.snapshot,
true});
27190 staged_parents.push_back({pending.snapshot.source_id,
27191 pending.snapshot.command_sequence,
27194 std::vector<std::uint64_t> pending_bracket_families;
27195 for (
const auto& pending : pending_bracket_legs_) {
27196 if (std::find(pending_bracket_families.begin(), pending_bracket_families.end(),
27197 pending.family_key) != pending_bracket_families.end()) {
27200 pending_bracket_families.push_back(pending.family_key);
27201 std::uint64_t incarnation = 0;
27202 if (pending.snapshot.defer_until_post_parent_calculation
27203 && pending.snapshot.legs.target().incarnation != 0) {
27204 incarnation = pending.snapshot.legs.target().incarnation;
27206 const auto parent = std::find_if(
27207 staged_parents.begin(), staged_parents.end(),
27208 [&](
const StagedParentProjection& row) {
27209 return row.id == pending.snapshot.from_entry
27210 && pending.snapshot.command_sequence >= row.command_sequence;
27212 if (parent != staged_parents.end()) {
27213 incarnation = parent->incarnation
27214 + (pending.snapshot.command_sequence - parent->command_sequence);
27215 next_incarnation = std::max(next_incarnation, incarnation + 1);
27217 incarnation = next_incarnation++;
27220 rows.push_back({incarnation, pending.snapshot,
true});
27222 for (
const auto& pending : pending_same_bar_commands_)
27223 rows.push_back({0, pending.snapshot,
true});
27224 for (
const auto& pending : pending_coof_requests_)
27225 rows.push_back({0, pending.snapshot,
true});
27226 for (
const auto& shadow : source_shadow_pending_)
27227 rows.push_back({0, shadow.snapshot,
true});
27232 const SourceId&
id)
const noexcept {
27233 const auto found = close_logical_units_.find(
id);
27234 return found == close_logical_units_.end() ? 0.0 : found->second;
27237std::vector<PineExecutionAdapter::FixtureCloseCallsite>
27239 std::vector<FixtureCloseCallsite> result;
27240 result.reserve(close_batch_callsites_.size());
27241 for (
const auto& row : close_batch_callsites_) {
27242 const auto& site = row.second;
27243 result.push_back({site.token, site.active, site.target, site.calls,
27244 site.id, site.comment, site.queue_sequence});
27251 if (index < 0 || index >=
static_cast<int>(live_handles_.size()))
return nullptr;
27252 const auto handle = live_handles_[
static_cast<std::size_t
>(index)];
27253 const auto found = placement_.find(handle.incarnation);
27254 return found == placement_.end() ? nullptr : &found->second.cancellation;
27259 pending_write(pending_entries_).erase(std::remove_if(
27260 pending_entries_.begin(), pending_entries_.end(),
27261 [&](
const PendingEntry& pending) {
27262 return pending.snapshot.family == PineOrderFamily::Entry
27263 && pending.snapshot.source_id == id;
27264 }), pending_entries_.end());
27265 std::vector<native_order::RequestHandle> matches;
27266 for (
const auto& handle : live_handles_) {
27267 const auto found = placement_.find(handle.incarnation);
27268 if (found != placement_.end()
27270 && found->second.source_id ==
id) {
27271 matches.push_back(handle);
27274 for (
const auto& handle : matches) {
27275 const auto result = require_host().cancel(handle);
27276 if (result.status == native_order::CancelStatus::Cancelled) retire(handle);
27278 named_entry_cancel_tokens_.erase(
id);
27284 risk_.max_drawdown = value;
27285 if (percent) risk_.max_drawdown_percent =
true;
27288 risk_.max_intraday_loss = value;
27289 if (percent) risk_.max_intraday_loss_percent =
true;
27294 snapshot.direction = risk_.direction;
27295 snapshot.max_cons_loss_days = risk_.max_cons_loss_days;
27296 snapshot.max_drawdown = risk_.max_drawdown;
27297 snapshot.max_drawdown_percent = risk_.max_drawdown_percent;
27298 snapshot.max_intraday_loss = risk_.max_intraday_loss;
27299 snapshot.max_intraday_loss_percent = risk_.max_intraday_loss_percent;
27300 snapshot.max_position_size = risk_.max_position_size;
27301 snapshot.cap_attachment =
cap.attachment();
27302 snapshot.cap_configuration =
cap.configuration();
27303 snapshot.cap_declared =
cap.declared();
27308 risk_.direction = snapshot.direction;
27309 risk_.max_cons_loss_days = snapshot.max_cons_loss_days;
27310 risk_.max_drawdown = snapshot.max_drawdown;
27311 risk_.max_drawdown_percent = snapshot.max_drawdown_percent;
27312 risk_.max_intraday_loss = snapshot.max_intraday_loss;
27313 risk_.max_intraday_loss_percent = snapshot.max_intraday_loss_percent;
27314 risk_.max_position_size = snapshot.max_position_size;
27315 cap.restore_replay_configuration(snapshot.cap_attachment, snapshot.cap_configuration,
27316 snapshot.cap_declared);
27319 source_margin_call_enabled_ = enabled;
27322 market_pyramid_adds_.insert(incarnation);
27328 auto result = std::move(first_open_newborns_);
27329 first_open_newborns_.clear();
27333void PineExecutionAdapter::refresh_pending_view() noexcept {
27334 ++pending_projection_version_;
27335 projected_pending_valid_ =
false;
27336 std::vector<int>{}.swap(projected_pending_indexes_);
27337 std::vector<int>{}.swap(projected_pending_next_);
27340int PineExecutionAdapter::projected_raw_pending_size() const noexcept {
27341 const std::size_t total = live_handles_.size()
27342 + pending_same_bar_commands_.size() + pending_entries_.size()
27343 + pending_bracket_legs_.size() + pending_coof_requests_.size()
27344 + source_shadow_pending_.size();
27345 return total >
static_cast<std::size_t
>(std::numeric_limits<int>::max())
27346 ? std::numeric_limits<int>::max() : static_cast<int>(total);
27349bool PineExecutionAdapter::projected_raw_pending_at(
27351 native_order::RequestHandle& handle)
const noexcept {
27352 ++projected_raw_pending_calls_;
snapshot =
nullptr;
27354 if (index < 0)
return false;
27355 std::size_t offset =
static_cast<std::size_t
>(index);
27356 if (offset < live_handles_.size()) {
27357 handle = live_handles_[offset];
27358 const auto found = placement_.find(handle.incarnation);
27359 if (found == placement_.end())
return false;
27363 offset -= live_handles_.size();
27364 const auto locate = [&](
const auto& rows,
auto read) {
27365 if (offset >= rows.size()) {
27366 offset -= rows.size();
27372 if (locate(pending_same_bar_commands_,
27374 return row.snapshot;
27376 if (locate(pending_entries_,
27378 return row.snapshot;
27380 if (locate(pending_bracket_legs_,
27382 return row.snapshot;
27384 if (locate(pending_coof_requests_,
27386 return row.snapshot;
27388 return locate(source_shadow_pending_,
27390 return row.snapshot;
27394bool PineExecutionAdapter::same_projected_order(
27402 if (!is_exit(left.family) || !is_exit(right.family))
return false;
27403 return left.source_id == right.source_id
27404 && left.from_entry == right.from_entry
27405 && left.bracket_origin == right.bracket_origin;
27408bool PineExecutionAdapter::ensure_projected_pending_index() const try {
27409 const auto placements_version = placement_.pending_observer_version_;
27410 if (projected_pending_valid_
27411 && projected_pending_version_ == pending_projection_version_
27412 && projected_placement_version_ == placements_version)
return true;
27414 std::string_view source, from, session;
27415 std::uint64_t run, origin;
27416 bool operator==(
const Key& other)
const noexcept {
27417 return source == other.source && from == other.from
27418 && session == other.session && run == other.run
27419 && origin == other.origin;
27423 std::size_t operator()(
const Key& key)
const noexcept {
27424 std::size_t hash = 0;
27425 const auto add = [&](std::size_t
value) {
27426 hash ^=
value + 0x9e3779b9U + (hash << 6U) + (hash >> 2U);
27428 const std::hash<std::string_view>
text;
27430 add(std::hash<std::uint64_t>{}(
key.run));
27431 add(std::hash<std::uint64_t>{}(
key.origin));
27435 const int raw_count = projected_raw_pending_size();
27436 std::vector<int> indexes;
27437 std::vector<int> next(
static_cast<std::size_t
>(raw_count), -1);
27438 std::unordered_map<Key, int, Hash> exits;
27439 indexes.reserve(
static_cast<std::size_t
>(raw_count));
27440 exits.reserve(
static_cast<std::size_t
>(raw_count));
27442 for (
int raw = 0; raw < raw_count; ++raw) {
27443 const PlacementSnapshot* row =
nullptr;
27444 native_order::RequestHandle handle;
27445 if (!projected_raw_pending_at(raw, row, handle) || !row)
continue;
27450 const auto& origin = row->bracket_origin;
27451 const Key
key{row->source_id, row->from_entry, std::string_view(origin.run.session_key.string()),
27452 origin.run.run_number, origin.incarnation};
27453 auto inserted = exits.emplace(key, raw);
27454 if (!inserted.second) {
27455 next[
static_cast<std::size_t
>(inserted.first->second)] = raw;
27456 inserted.first->second = raw;
27460 indexes.push_back(raw);
27463 projected_pending_indexes_.swap(indexes);
27464 projected_pending_next_.swap(next);
27465 projected_pending_version_ = pending_projection_version_;
27466 projected_placement_version_ = placements_version;
27467 projected_pending_valid_ =
true;
return true;
27468}
catch (
const std::bad_alloc&) { projected_pending_valid_ =
false;
return false; }
27471 return owner_ ? owner_->projected_pending_size() : 0;
27475 int* close_only,
int* partition)
const noexcept {
27476 if (!owner_ || !qty || !close_only || !partition)
return -1;
27479 if (!owner_->projected_pending_at(index, row, handle) || !row)
return -1;
27480 const auto& snapshot = *row;
27484 if (!snapshot.opening)
return 1;
27487 const bool opposite = physical.signed_units != 0.0
27488 && ((physical.signed_units > 0.0) != snapshot.is_long);
27489 bool kernel_close_only =
false;
27490 bool sized =
false;
27491 if (snapshot.frozen_market_instruction
27492 && finite_positive(snapshot.frozen_market_transaction_units)) {
27494 *qty = std::max(0.0, snapshot.frozen_market_transaction_units
27495 - std::abs(physical.signed_units));
27497 kernel_close_only = !(*qty > 1e-10);
27499 }
else if (owner_->fixed_unmargined_market_batch_scope()) {
27501 *qty = snapshot.frozen_market_transaction_units;
27504 }
else if (physical.signed_units != 0.0
27505 && ((physical.signed_units > 0.0) == snapshot.is_long)
27506 && snapshot.projection_over_pyramiding) {
27507 *qty = snapshot.frozen_market_transaction_units;
27510 }
else if (physical.signed_units == 0.0
27511 && finite_positive(snapshot.frozen_market_own_units)
27512 && snapshot.frozen_market_transaction_units
27513 > snapshot.frozen_market_own_units + 1e-10) {
27514 *qty = snapshot.frozen_market_transaction_units;
27520 && !std::isnan(snapshot.exit_levels.stop)
27521 && std::isnan(snapshot.exit_levels.limit)
27522 && std::isnan(snapshot.requested_qty)
27523 && owner_->config_.default_qty_type
27525 && finite_positive(snapshot.sizing.frozen_units);
27526 const bool default_stop = default_stop_shape
27527 && finite_positive(fill_price)
27528 && snapshot.projection_position_side
27530 const bool unpriced_market = std::isnan(snapshot.exit_levels.limit)
27531 && std::isnan(snapshot.exit_levels.stop)
27532 && std::isnan(snapshot.exit_levels.trail_offset)
27533 && std::isnan(snapshot.exit_levels.trail_price);
27536 && !std::isnan(snapshot.exit_levels.limit);
27537 double sized_price = fill_price;
27538 if (!limit_route && std::isfinite(sized_price)) {
27539 sized_price += (snapshot.is_long ? 1.0 : -1.0)
27540 * owner_->config_.slippage * owner_->staged_.syminfo.mintick;
27545 if (!sized && finite_positive(snapshot.requested_qty)) {
27549 ? owner_->typed_entry_units(snapshot, sized_price,
27550 owner_->sizing_snapshot().fx, handle).value_or(0.0)
27551 : snapshot.requested_qty;
27554 }
else if (!sized && default_stop) {
27555 *qty = snapshot.sizing.frozen_units;
27558 }
else if (!sized && !default_stop_shape && unpriced_market
27559 && finite_positive(snapshot.sizing.frozen_units)) {
27560 *qty = snapshot.sizing.frozen_units;
27565 const int type = owner_->config_.default_qty_type;
27573 sizing.
price = sized_price;
27574 sizing.
mark = sized_price;
27575 if (finite_positive(sized_price)) {
27576 owner_->mark_sizing_equity(sizing, sized_price);
27581 *qty = owner_->default_sizing_units(sizing);
27583 *qty = floor_quantity_grid(owner_->config_.default_qty_value,
27584 owner_->staged_.quantity_grid);
27591 && snapshot.projection_position_side
27592 != (physical.signed_units > 0.0
27595 && !snapshot.projection_predecessor_market;
27596 *close_only = (snapshot.affordability_close_only || prior_cycle_close_only
27597 || kernel_close_only) ? 1 : 0;
27602 if (!owner_)
return -1;
27605 if (!owner_->projected_pending_at(index, snapshot, handle) || !snapshot)
return -1;
27608 return owner_->projected_level_resolved(*snapshot);
27612 double* trail_activation)
const noexcept {
27613 if (!owner_ || !stop || !limit || !trail_activation)
return -1;
27616 if (!owner_->projected_pending_at(index, row, handle) || !row)
return -1;
27617 const auto& snapshot = *row;
27618 const double tick = owner_->staged_.syminfo.mintick;
27620 const bool long_side = physical.signed_units > 0.0;
27621 const double entry = owner_->require_host().position_avg_price();
27622 *stop = snapshot.exit_levels.stop;
27623 *limit = snapshot.exit_levels.limit;
27624 double trail_points = snapshot.exit_levels.trail_points;
27625 double trail_price = snapshot.exit_levels.trail_price;
27626 double profit_ticks = snapshot.exit_levels.profit_ticks;
27627 double loss_ticks = snapshot.exit_levels.loss_ticks;
27631 for (
int raw = owner_->projected_pending_next(index); raw >= 0; raw = owner_->projected_raw_pending_next(raw)) {
27634 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
27635 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
27653 const bool position_live = physical.signed_units != 0.0 && std::isfinite(entry);
27654 const bool resolved = position_live && owner_->projected_level_resolved(snapshot) == 1;
27655 const double direction = long_side ? 1.0 : -1.0;
27660 if (std::isnan(*limit) && !std::isnan(profit_ticks)) {
27661 *limit = source_level_on_price_grid(
27662 entry + direction * profit_ticks * tick, tick);
27664 if (std::isnan(*stop) && !std::isnan(loss_ticks)) {
27665 *stop = source_level_on_price_grid(
27666 entry - direction * loss_ticks * tick, tick);
27669 *trail_activation = kNaN;
27670 if (!std::isnan(trail_points)) {
27672 const double ticks = source_trail_points_ticks(trail_points, tick);
27674 entry + direction * ticks * tick, tick);
27677 *trail_activation = trail_price;
27683 if (!owner_ || !out)
return -1;
27686 if (!owner_->projected_pending_at(index, row, handle) || !row)
return -1;
27689 std::memset(out, 0,
sizeof(*out));
27690 out->struct_version = 1;
27691 out->size =
static_cast<std::uint32_t
>(
sizeof(*out));
27693 copy_pending_prefixed_string(
"__close__", snapshot.source_id,
27694 out->id, &out->id_truncated,
27697 copy_pending_string(snapshot.source_id, out->id,
27698 &out->id_truncated, &out->id_hash64);
27700 copy_pending_string(snapshot.from_entry, out->from_entry, &out->from_entry_truncated,
27701 &out->from_entry_hash64);
27702 copy_pending_string(snapshot.oca_name, out->oca_name, &out->oca_name_truncated,
27703 &out->oca_name_hash64);
27704 copy_pending_string(snapshot.comment, out->comment, &out->comment_truncated,
27705 &out->comment_hash64);
27706 out->type = mirror_order_type(snapshot.family);
27707 out->is_long = snapshot.is_long ? 1U : 0U;
27708 out->limit_price = snapshot.exit_levels.limit;
27709 out->stop_price = snapshot.exit_levels.stop;
27710 out->trail_points = snapshot.exit_levels.trail_points;
27711 out->trail_price = snapshot.exit_levels.trail_price;
27712 out->trail_offset = snapshot.exit_levels.trail_offset;
27713 out->profit_ticks = snapshot.exit_levels.profit_ticks;
27714 out->loss_ticks = snapshot.exit_levels.loss_ticks;
27718 for (
int raw = owner_->projected_pending_next(index); raw >= 0; raw = owner_->projected_raw_pending_next(raw)) {
27721 if (!owner_->projected_raw_pending_at(raw, sibling, sibling_handle)
27722 || !sibling || !PineExecutionAdapter::same_projected_order(snapshot, *sibling)) {
27742 ? snapshot.requested_qty
27743 : (std::isfinite(snapshot.projection_remaining_qty)
27744 ? snapshot.projection_remaining_qty : snapshot.requested_qty);
27745 out->qty_type = snapshot.qty_type;
27746 out->qty_percent = snapshot.qty_percent;
27747 out->oca_type = snapshot.oca_type;
27748 out->created_bar = snapshot.projection_created_bar;
27749 out->created_seq =
static_cast<std::int64_t
>(snapshot.source_sequence);
27751 out->created_by_same_id_replacement = snapshot.projection_predecessor != 0
27753 out->replaced_default_market_incarnation = snapshot.projection_predecessor_market
27754 ? snapshot.projection_predecessor : 0;
27755 out->declined_by_replaced_short_market = snapshot.cancellation.cause
27757 out->replaced_exit_order_incarnation = snapshot.projection_predecessor_exit
27758 ? snapshot.projection_predecessor : 0;
27759 out->recreated_after_named_cancelled_entry_incarnation =
27760 snapshot.recreated_after_named_cancelled_entry_incarnation;
27761 out->named_cancel_surviving_exit_incarnation = snapshot.named_cancel_surviving_exit_incarnation;
27762 out->stop_limit_activated = snapshot.stop_limit_activated ? 1U : 0U;
27763 out->coof_suppress_stop_on_entry_bar = snapshot.exit_activation.holds_stop() ? 1U : 0U;
27764 out->coof_suppress_limit_on_entry_bar = snapshot.exit_activation.holds_limit() ? 1U : 0U;
27765 out->created_during_coof_recalc = snapshot.birth.from_fill() ? 1U : 0U;
27766 out->coof_born_at_close_recalc = snapshot.birth.at_terminal_fill() ? 1U : 0U;
27768 out->coof_cascade_seg_i = snapshot.coof_cascade_seg_i;
27769 out->coof_cascade_inflight_fires = snapshot.coof_cascade_inflight_fires ? 1U : 0U;
27770 out->created_position_side = snapshot.projection_position_side;
27771 out->created_position_cycle_seq = snapshot.placement_cycle;
27772 out->created_after_position_close_in_bar = snapshot.projection_after_close ? 1U : 0U;
27773 out->over_pyramiding_cap_at_placement = snapshot.projection_over_pyramiding ? 1U : 0U;
27774 out->same_id_stop_deferred_close_all_bar = snapshot.cancellation.cause
27776 out->same_id_stop_deferred_close_all_incarnation = snapshot.cancellation.cause
27778 out->reverses_same_bar_market_from_flat =
27779 snapshot.projection_opposite_market_predecessor ? 1U : 0U;
27780 out->paired_flat_market_candidate = snapshot.paired_flat_market_candidate ? 1U : 0U;
27781 out->paired_flat_market_own_qty = snapshot.paired_flat_market_own_qty;
27782 out->paired_flat_market_signal_close = snapshot.paired_flat_market_signal_close;
27783 out->paired_flat_market_signal_equity = snapshot.paired_flat_market_signal_equity;
27784 out->paired_flat_market_signal_margin_pct = snapshot.paired_flat_market_signal_margin_pct;
27785 out->paired_flat_market_signal_pointvalue = snapshot.paired_flat_market_signal_pointvalue;
27786 out->paired_flat_market_signal_fx = snapshot.paired_flat_market_signal_fx;
27787 out->paired_flat_market_peer_seq = snapshot.paired_flat_market_peer_seq;
27788 out->paired_flat_market_transaction_qty = snapshot.paired_flat_market_transaction_qty;
27789 out->default_flat_market_gross_candidate = snapshot.paired_flat_market_candidate
27790 && !std::isfinite(snapshot.requested_qty) ? 1U : 0U;
27791 out->tv_carry_qty = snapshot.projection_tv_carry_qty;
27792 out->frozen_default_qty = snapshot.sizing.frozen_units;
27793 out->default_stop_placement_qty = snapshot.sizing.frozen_units;
27794 out->default_stop_placement_equity = snapshot.projection_default_stop_equity;
27795 out->default_stop_placement_signal_close = snapshot.projection_default_stop_signal_close;
27796 out->default_stop_sizing_price = snapshot.sizing.price;
27797 out->sizing_equity = snapshot.sizing.equity;
27798 out->sizing_price = snapshot.sizing.price;
27799 out->sizing_fx = snapshot.sizing.fx;
27800 out->sizing_mark = snapshot.sizing.mark;
27801 out->opening_affordability_exemption_candidate = snapshot.opening
27802 && !std::isfinite(snapshot.requested_qty)
27803 && snapshot.projection_position_side ==
static_cast<std::int32_t
>(
PositionSide::FLAT) ? 1U : 0U;
27804 out->explicit_flat_admission_candidate = snapshot.opening
27805 && std::isfinite(snapshot.requested_qty)
27806 && snapshot.projection_position_side ==
static_cast<std::int32_t
>(
PositionSide::FLAT) ? 1U : 0U;
27807 out->explicit_placement_equity = snapshot.projection_explicit_equity;
27808 out->explicit_slipped_signal_close = snapshot.projection_explicit_signal_close;
27809 out->affordability_placement_equity = snapshot.projection_affordability_equity;
27810 out->affordability_signal_price = snapshot.projection_affordability_signal_price;
27811 out->affordability_held_qty = snapshot.projection_affordability_held_qty;
27812 out->affordability_close_only = snapshot.affordability_close_only ? 1U : 0U;
27813 out->rounded_signal_cost_close_only = snapshot.rounded_signal_cost_close_only ? 1U : 0U;
27819 out->market_admission_observation_present = snapshot.opening ? 1U : 0U;
27820 out->market_admission_observation_original_sizing_present =
27821 snapshot.opening ? 1U : 0U;
27822 out->market_admission_observation_requested_quantity = snapshot.requested_qty;
27823 out->market_admission_observation_quantity_type = snapshot.qty_type;
27824 out->market_admission_observation_buy = snapshot.is_long ? 1U : 0U;
27825 out->market_admission_observation_prices_limit = snapshot.exit_levels.limit;
27826 out->market_admission_observation_prices_stop = snapshot.exit_levels.stop;
27827 copy_pending_string(snapshot.source_id,
27828 out->market_admission_observation_id,
27829 &out->market_admission_observation_id_truncated,
27830 &out->market_admission_observation_id_hash64);
27831 copy_pending_string(snapshot.oca_name,
27832 out->market_admission_observation_oca_name,
27833 &out->market_admission_observation_oca_name_truncated,
27834 &out->market_admission_observation_oca_name_hash64);
27835 out->market_admission_observation_placement_equity = snapshot.sizing.equity;
27836 out->market_admission_observation_signal_close = snapshot.sizing.price;
27837 out->market_admission_observation_original_sizing_quantity =
27838 snapshot.requested_qty;
27839 out->market_admission_observation_original_sizing_equity = snapshot.sizing.equity;
27840 out->market_admission_observation_original_sizing_price = snapshot.sizing.price;
27841 out->market_admission_observation_original_sizing_mark = snapshot.sizing.mark;
27842 out->market_admission_observation_original_sizing_fx = snapshot.sizing.fx;
27843 out->signal_close_mc_bar = snapshot.signal_close_mc_bar;
27844 out->signal_close_mc_entry_incarnation = snapshot.signal_close_mc_entry_incarnation;
27845 out->signal_close_mc_fill_seq = snapshot.signal_close_mc_fill_seq;
27846 out->signal_close_mc_remaining_qty = snapshot.signal_close_mc_remaining_qty;
27847 out->requested_partial = (!snapshot.opening && std::isfinite(snapshot.qty_percent)
27848 && snapshot.qty_percent < 100.0) || (!snapshot.opening
27849 && std::isfinite(snapshot.requested_qty)) ? 1U : 0U;
27850 out->full_percent_exit_request = !snapshot.opening && !std::isfinite(snapshot.requested_qty)
27851 && (!std::isfinite(snapshot.qty_percent) || snapshot.qty_percent == 100.0) ? 1U : 0U;
27852 out->pooc_global_full_exit_dynamic_qty = snapshot.pooc_global_full_exit_dynamic_qty ? 1U : 0U;
27853 out->pooc_global_full_exit_tracks_bound_adds = snapshot.pooc_global_full_exit_tracks_bound_adds ? 1U : 0U;
27854 out->pooc_global_full_exit_bound_add = snapshot.pooc_global_full_exit_bound_add ? 1U : 0U;
27855 out->created_while_in_position = !snapshot.opening
27856 && snapshot.projection_position_side !=
static_cast<std::int32_t
>(
PositionSide::FLAT)
27858 out->sbmt_member = snapshot.frozen_market_instruction ? 1U : 0U;
27859 out->sbmt_own_qty = snapshot.frozen_market_instruction
27860 ? snapshot.frozen_market_own_units : kNaN;
27861 out->sbmt_tx_qty = snapshot.frozen_market_instruction
27862 ? snapshot.frozen_market_transaction_units : kNaN;
27863 out->sbmt_kept_over_cap = snapshot.frozen_market_instruction
27864 && snapshot.projection_over_pyramiding ? 1U : 0U;
27865 out->sbmt_close_qty = snapshot.frozen_market_targeted_close
27866 ? snapshot.requested_qty : kNaN;
27867 out->sbmt_close_buy = snapshot.frozen_market_targeted_close
27870 out->suppress_as_declined_reversal_close = snapshot.cancellation.cause
27872 out->dormant_bracket = snapshot.legs.dormant() ? 1U : 0U;
27873 out->dormant_reissue_pending = snapshot.legs.pending_replacement() ? 1U : 0U;
27874 out->dormant_original_stop_price = snapshot.legs.original_stop();
27875 out->dormant_hold_bar = snapshot.legs.hold_bar();
27876 out->dormant_reversal_kill_bar = snapshot.legs.excluded_bar();
27877 out->dormant_trail_best = snapshot.legs.trail_best();
27878 out->dormant_trail_best_start = snapshot.legs.trail_prefix();
27879 out->dormant_trail_leg_dead = snapshot.legs.retired(exit_legs::Leg::Trail) ? 1U : 0U;
27880 out->suppressed_close_consumed_ledger_qty = snapshot.cancellation.close_claim_consumed;
27881 out->suppressed_close_retired_ledger_qty = snapshot.cancellation.close_claim_retired;
27882 out->short_seed_collision_role = owner_->short_seed_.active ? owner_->short_seed_collision_role_v1(handle) : 0;
27883 out->replaced_order_incarnation = snapshot.projection_predecessor;
27884 out->birth_timestamp = snapshot.birth.timestamp();
27885 out->birth_cause =
static_cast<std::int32_t
>(snapshot.birth.cause());
27886 out->birth_bar = snapshot.birth.bar();
27887 out->birth_cursor_domain =
static_cast<std::int32_t
>(snapshot.birth.cursor().domain());
27888 out->birth_cursor_position =
static_cast<std::int32_t
>(snapshot.birth.cursor().position());
27889 out->birth_cursor_index = snapshot.birth.cursor().index();
27890 out->birth_cursor_count = snapshot.birth.cursor().count();
27891 out->birth_cursor_price = snapshot.birth.cursor_price();
27892 out->birth_first_fill = snapshot.birth.first_fill();
27893 out->birth_last_fill = snapshot.birth.last_fill();
27894 out->birth_evaluation_ordinal = snapshot.birth.evaluation_ordinal();
27895 out->pine_birth_reach =
static_cast<std::int32_t
>(snapshot.birth_reach);
27896 out->pine_frozen_market_instruction_kind = snapshot.frozen_market_instruction ? 1U : 0U;
27897 out->pine_frozen_market_instruction_own_units = snapshot.frozen_market_own_units;
27898 out->pine_frozen_market_instruction_transaction_units =
27899 snapshot.frozen_market_transaction_units;
27900 copy_pending_string(snapshot.frozen_market_targeted_close
27901 ? std::string_view(snapshot.source_id) : std::string_view{},
27902 out->pine_frozen_market_instruction_target_id,
27903 &out->pine_frozen_market_instruction_target_id_truncated,
27904 &out->pine_frozen_market_instruction_target_id_hash64);
27905 const bool explicit_units = std::isfinite(snapshot.requested_qty);
27906 const bool percentage = !explicit_units && std::isfinite(snapshot.qty_percent);
27907 out->quantity_intent_kind = explicit_units ? 2U : (percentage ? 3U : 1U);
27908 out->quantity_intent_units = explicit_units ? snapshot.requested_qty : 0.0;
27909 out->quantity_intent_numerator = percentage ? snapshot.qty_percent : 0.0;
27910 out->quantity_intent_denominator = percentage ? 100.0 : 0.0;
27911 const double exposure = snapshot.from_entry.empty() ? 0.0
27912 : owner_->cohort_exposure_for(snapshot.from_entry);
27913 out->quantity_reservation_present = snapshot.deferred_cohort && exposure > 0.0 ? 1U : 0U;
27914 out->quantity_reservation_units = out->quantity_reservation_present ? exposure : 0.0;
27915 out->quantity_reservation_basis_units = out->quantity_reservation_present ? exposure : 0.0;
27916 out->leg_activation_present = snapshot.leg_activation.bounds() ? 1U : 0U;
27917 out->leg_activation_owner_cycle = snapshot.leg_activation.bounds()
27918 ? snapshot.leg_activation.bounds()->position_cycle : 0;
27919 out->leg_activation_stop_first_bar = snapshot.leg_activation.bounds()
27920 ? snapshot.leg_activation.bounds()->stop_first_bar : 0;
27921 out->leg_activation_limit_first_bar = snapshot.leg_activation.bounds()
27922 ? snapshot.leg_activation.bounds()->limit_first_bar : 0;
27923 const auto& activation = snapshot.exit_activation.evidence();
27924 out->pine_exit_activation_present = activation ? 1U : 0U;
27925 out->pine_exit_activation_owner_cycle_at_birth = activation ? activation->position_cycle : 0;
27926 out->pine_exit_activation_entry_bar_at_birth = activation ? activation->entry_bar : 0;
27927 out->pine_exit_activation_direction_at_birth = activation ? activation->direction : 0;
27928 out->pine_exit_activation_cursor_price_at_birth = activation ? activation->cursor_price : 0.0;
27929 out->pine_exit_activation_stop_level_at_birth = activation ? activation->stop_level : 0.0;
27930 out->pine_exit_activation_limit_level_at_birth = activation ? activation->limit_level : 0.0;
27931 out->pine_exit_activation_limit_continuation_present = activation
27932 && activation->limit_continuation ? 1U : 0U;
27933 out->pine_exit_activation_limit_continuation_cause = activation
27934 && activation->limit_continuation
27935 ?
static_cast<std::int32_t
>(activation->limit_continuation->cause) : 0;
27936 out->pine_exit_activation_limit_continuation_fill = activation
27937 && activation->limit_continuation
27938 ? activation->limit_continuation->observed_fill_sequence : 0;
27939 const auto& expansion = snapshot.reservation_expansion.capture();
27940 out->reservation_expansion_present = expansion ? 1U : 0U;
27941 out->reservation_expansion_position_cycle = expansion ? expansion->position_cycle : 0;
27942 out->reservation_expansion_side = expansion ?
static_cast<std::int32_t
>(expansion->side) : 0;
27943 out->reservation_expansion_first_later_admission_present = expansion
27944 && expansion->first_later_admission ? 1U : 0U;
27945 out->reservation_expansion_first_later_admission = expansion
27946 && expansion->first_later_admission ? *expansion->first_later_admission : 0;
27947 out->reservation_growth_source_present = snapshot.reservation_growth_source.reservation_owner()
27949 out->reservation_growth_source_reservation_owner = snapshot.reservation_growth_source.reservation_owner()
27950 ? *snapshot.reservation_growth_source.reservation_owner() : 0;
27952 const auto target = snapshot.legs.target();
27953 const auto& definition = snapshot.legs.current_definition();
27954 out->legs_target_incarnation = target.incarnation;
27955 out->legs_target_owner = target.owner;
27956 out->legs_revision = snapshot.legs.revision();
27957 out->legs_definition_incarnation = definition.incarnation();
27958 out->legs_definition_revision = definition.revision();
27959 out->legs_definition_value_present = definition.has_value() ? 1U : 0U;
27960 out->legs_definition_limit_price = definition.has_value() ? definition.prices().limit_price : kNaN;
27961 out->legs_definition_stop_price = definition.has_value() ? definition.prices().stop_price : kNaN;
27962 out->legs_definition_trail_points = definition.has_value() ? definition.prices().trail_points : kNaN;
27963 out->legs_definition_trail_price = definition.has_value() ? definition.prices().trail_price : kNaN;
27964 out->legs_definition_trail_offset = definition.has_value() ? definition.prices().trail_offset : kNaN;
27965 out->legs_definition_profit_ticks = definition.has_value() ? definition.prices().profit_ticks : kNaN;
27966 out->legs_definition_loss_ticks = definition.has_value() ? definition.prices().loss_ticks : kNaN;
27967 const auto& retirements = snapshot.legs.retirements();
27968 const auto copy_retirement = [&](std::size_t
number, std::uint64_t& generation,
27969 std::uint8_t&
present, std::uint64_t& receipt_generation,
27970 std::uint64_t& event, std::int64_t& bar,
27971 std::uint32_t& domain, std::uint32_t& phase) {
27973 generation = snapshot.legs.generation(leg);
27974 const auto& receipt = retirements[
number];
27976 receipt_generation = receipt ? receipt->generation : 0;
27977 event = receipt ? receipt->cause.event : 0;
27978 bar = receipt ? receipt->cause.bar : 0;
27979 domain = receipt ?
static_cast<std::uint32_t
>(receipt->cause.domain) : 0U;
27980 phase = receipt ?
static_cast<std::uint32_t
>(receipt->cause.phase) : 0U;
27982 copy_retirement(0, out->legs_generation0, out->legs_retirement0_present,
27983 out->legs_retirement0_generation, out->legs_retirement0_cause_event,
27984 out->legs_retirement0_cause_bar, out->legs_retirement0_cause_domain,
27985 out->legs_retirement0_cause_phase);
27986 copy_retirement(1, out->legs_generation1, out->legs_retirement1_present,
27987 out->legs_retirement1_generation, out->legs_retirement1_cause_event,
27988 out->legs_retirement1_cause_bar, out->legs_retirement1_cause_domain,
27989 out->legs_retirement1_cause_phase);
27990 copy_retirement(2, out->legs_generation2, out->legs_retirement2_present,
27991 out->legs_retirement2_generation, out->legs_retirement2_cause_event,
27992 out->legs_retirement2_cause_bar, out->legs_retirement2_cause_domain,
27993 out->legs_retirement2_cause_phase);
27994 const auto& suspension = snapshot.legs.suspension();
27995 out->legs_suspension_present = suspension ? 1U : 0U;
27996 out->legs_suspension_cause_event = suspension ? suspension->cause.event : 0;
27997 out->legs_suspension_cause_bar = suspension ? suspension->cause.bar : 0;
27998 out->legs_suspension_cause_domain = suspension
27999 ?
static_cast<std::uint32_t
>(suspension->cause.domain) : 0U;
28000 out->legs_suspension_cause_phase = suspension
28001 ?
static_cast<std::uint32_t
>(suspension->cause.phase) : 0U;
28002 out->legs_suspension_legs_count = suspension
28003 ?
static_cast<std::uint32_t
>(suspension->legs.size()) : 0U;
28004 const auto suspended_leg = [&](std::size_t
number) -> std::uint32_t {
28005 return suspension && suspension->legs.size() >
number
28006 ?
static_cast<std::uint32_t
>(suspension->legs[
number]) : UINT32_MAX;
28008 out->legs_suspension_legs_item0 = suspended_leg(0);
28009 out->legs_suspension_legs_item1 = suspended_leg(1);
28010 out->legs_suspension_legs_item2 = suspended_leg(2);
28011 out->legs_suspension_hold_present = suspension && suspension->hold ? 1U : 0U;
28012 if (suspension && suspension->hold) {
28013 const auto& hold = *suspension->hold;
28014 out->legs_suspension_hold_requested_event = hold.requested.event;
28015 out->legs_suspension_hold_requested_bar = hold.requested.bar;
28016 out->legs_suspension_hold_requested_domain =
static_cast<std::uint32_t
>(hold.requested.domain);
28017 out->legs_suspension_hold_requested_phase =
static_cast<std::uint32_t
>(hold.requested.phase);
28018 out->legs_suspension_hold_target_incarnation = hold.target.incarnation;
28019 out->legs_suspension_hold_target_owner = hold.target.owner;
28020 out->legs_suspension_hold_revision = hold.revision;
28022 out->legs_suspension_revival_definition_limit_price = kNaN;
28023 out->legs_suspension_revival_definition_stop_price = kNaN;
28024 out->legs_suspension_revival_definition_trail_points = kNaN;
28025 out->legs_suspension_revival_definition_trail_price = kNaN;
28026 out->legs_suspension_revival_definition_trail_offset = kNaN;
28027 out->legs_suspension_revival_definition_profit_ticks = kNaN;
28028 out->legs_suspension_revival_definition_loss_ticks = kNaN;
28029 const auto* revival = suspension && suspension->revival_definition
28030 ? &*suspension->revival_definition :
nullptr;
28031 out->legs_suspension_revival_definition_present = revival ? 1U : 0U;
28033 out->legs_suspension_revival_definition_incarnation = revival->incarnation();
28034 out->legs_suspension_revival_definition_revision = revival->revision();
28035 out->legs_suspension_revival_definition_value_present = revival->has_value() ? 1U : 0U;
28036 if (revival->has_value()) {
28037 const auto& prices = revival->prices();
28038 out->legs_suspension_revival_definition_limit_price = prices.limit_price;
28039 out->legs_suspension_revival_definition_stop_price = prices.stop_price;
28040 out->legs_suspension_revival_definition_trail_points = prices.trail_points;
28041 out->legs_suspension_revival_definition_trail_price = prices.trail_price;
28042 out->legs_suspension_revival_definition_trail_offset = prices.trail_offset;
28043 out->legs_suspension_revival_definition_profit_ticks = prices.profit_ticks;
28044 out->legs_suspension_revival_definition_loss_ticks = prices.loss_ticks;
28047 out->legs_suspension_replacement_revival_definition_limit_price = kNaN;
28048 out->legs_suspension_replacement_revival_definition_stop_price = kNaN;
28049 out->legs_suspension_replacement_revival_definition_trail_points = kNaN;
28050 out->legs_suspension_replacement_revival_definition_trail_price = kNaN;
28051 out->legs_suspension_replacement_revival_definition_trail_offset = kNaN;
28052 out->legs_suspension_replacement_revival_definition_profit_ticks = kNaN;
28053 out->legs_suspension_replacement_revival_definition_loss_ticks = kNaN;
28054 const auto* replacement = suspension && suspension->replacement
28055 ? &*suspension->replacement :
nullptr;
28056 out->legs_suspension_replacement_present = replacement ? 1U : 0U;
28058 out->legs_suspension_replacement_queue_predecessor = replacement->queue_predecessor;
28059 const auto& definition = replacement->revival_definition;
28060 out->legs_suspension_replacement_revival_definition_incarnation = definition.incarnation();
28061 out->legs_suspension_replacement_revival_definition_revision = definition.revision();
28062 out->legs_suspension_replacement_revival_definition_value_present =
28063 definition.has_value() ? 1U : 0U;
28064 if (definition.has_value()) {
28065 const auto& prices = definition.prices();
28066 out->legs_suspension_replacement_revival_definition_limit_price = prices.limit_price;
28067 out->legs_suspension_replacement_revival_definition_stop_price = prices.stop_price;
28068 out->legs_suspension_replacement_revival_definition_trail_points = prices.trail_points;
28069 out->legs_suspension_replacement_revival_definition_trail_price = prices.trail_price;
28070 out->legs_suspension_replacement_revival_definition_trail_offset = prices.trail_offset;
28071 out->legs_suspension_replacement_revival_definition_profit_ticks = prices.profit_ticks;
28072 out->legs_suspension_replacement_revival_definition_loss_ticks = prices.loss_ticks;
28074 const auto& release = replacement->release;
28075 out->legs_suspension_replacement_release_requested_event = release.requested.event;
28076 out->legs_suspension_replacement_release_requested_bar = release.requested.bar;
28077 out->legs_suspension_replacement_release_requested_domain =
28078 static_cast<std::uint32_t
>(release.requested.domain);
28079 out->legs_suspension_replacement_release_requested_phase =
28080 static_cast<std::uint32_t
>(release.requested.phase);
28081 out->legs_suspension_replacement_release_target_incarnation = release.target.incarnation;
28082 out->legs_suspension_replacement_release_target_owner = release.target.owner;
28083 out->legs_suspension_replacement_release_revision = release.revision;
28085 out->legs_suspension_window_present = suspension && suspension->window ? 1U : 0U;
28086 out->legs_suspension_window_best = kNaN;
28087 out->legs_suspension_window_prefix = kNaN;
28088 if (suspension && suspension->window) {
28089 const auto& window = *suspension->window;
28090 out->legs_suspension_window_excluded_event = window.excluded.event;
28091 out->legs_suspension_window_excluded_bar = window.excluded.bar;
28092 out->legs_suspension_window_excluded_domain =
static_cast<std::uint32_t
>(window.excluded.domain);
28093 out->legs_suspension_window_excluded_phase =
static_cast<std::uint32_t
>(window.excluded.phase);
28094 out->legs_suspension_window_best = window.best;
28095 out->legs_suspension_window_prefix = window.prefix;
28097 const auto& last = snapshot.legs.last_action();
28098 out->legs_last_present = last ? 1U : 0U;
28099 out->legs_last_suspend_legs_item0 = UINT32_MAX;
28100 out->legs_last_suspend_legs_item1 = UINT32_MAX;
28101 out->legs_last_suspend_legs_item2 = UINT32_MAX;
28102 out->legs_last_suspend_window_best = kNaN;
28103 out->legs_last_suspend_window_prefix = kNaN;
28104 out->legs_last_suspend_retire_item0 = UINT32_MAX;
28105 out->legs_last_suspend_retire_item1 = UINT32_MAX;
28106 out->legs_last_suspend_retire_item2 = UINT32_MAX;
28107 out->legs_last_stage_revival_definition_limit_price = kNaN;
28108 out->legs_last_stage_revival_definition_stop_price = kNaN;
28109 out->legs_last_stage_revival_definition_trail_points = kNaN;
28110 out->legs_last_stage_revival_definition_trail_price = kNaN;
28111 out->legs_last_stage_revival_definition_trail_offset = kNaN;
28112 out->legs_last_stage_revival_definition_profit_ticks = kNaN;
28113 out->legs_last_stage_revival_definition_loss_ticks = kNaN;
28114 out->legs_last_restore_legs_item0 = UINT32_MAX;
28115 out->legs_last_restore_legs_item1 = UINT32_MAX;
28116 out->legs_last_restore_legs_item2 = UINT32_MAX;
28117 out->legs_last_observe_high = kNaN;
28118 out->legs_last_observe_low = kNaN;
28119 out->legs_last_cancel_legs_item0 = UINT32_MAX;
28120 out->legs_last_cancel_legs_item1 = UINT32_MAX;
28121 out->legs_last_cancel_legs_item2 = UINT32_MAX;
28123 out->legs_last_target_incarnation = last->target.incarnation;
28124 out->legs_last_target_owner = last->target.owner;
28125 out->legs_last_expected_revision = last->expected_revision;
28126 out->legs_last_cause_event = last->cause.event;
28127 out->legs_last_cause_bar = last->cause.bar;
28128 out->legs_last_cause_domain =
static_cast<std::uint32_t
>(last->cause.domain);
28129 out->legs_last_cause_phase =
static_cast<std::uint32_t
>(last->cause.phase);
28130 out->legs_last_operation =
static_cast<std::uint32_t
>(last->operation.index());
28131 if (
const auto* bind = std::get_if<exit_legs::BindOwner>(&last->operation)) {
28132 out->legs_last_bind_owner = bind->owner;
28133 }
else if (
const auto* suspended = std::get_if<exit_legs::Suspend>(&last->operation)) {
28134 const auto copy_leg = [](
const std::vector<exit_legs::Leg>& legs,
28135 std::size_t index) -> std::uint32_t {
28136 return index < legs.size() ?
static_cast<std::uint32_t
>(legs[index])
28139 out->legs_last_suspend_legs_count =
28140 static_cast<std::uint32_t
>(suspended->legs.size());
28141 out->legs_last_suspend_legs_item0 = copy_leg(suspended->legs, 0);
28142 out->legs_last_suspend_legs_item1 = copy_leg(suspended->legs, 1);
28143 out->legs_last_suspend_legs_item2 = copy_leg(suspended->legs, 2);
28144 out->legs_last_suspend_hold_present = suspended->hold ? 1U : 0U;
28145 if (suspended->hold) {
28146 const auto& hold = *suspended->hold;
28147 out->legs_last_suspend_hold_requested_event = hold.requested.event;
28148 out->legs_last_suspend_hold_requested_bar = hold.requested.bar;
28149 out->legs_last_suspend_hold_requested_domain =
28150 static_cast<std::uint32_t
>(hold.requested.domain);
28151 out->legs_last_suspend_hold_requested_phase =
28152 static_cast<std::uint32_t
>(hold.requested.phase);
28153 out->legs_last_suspend_hold_target_incarnation = hold.target.incarnation;
28154 out->legs_last_suspend_hold_target_owner = hold.target.owner;
28155 out->legs_last_suspend_hold_revision = hold.revision;
28157 out->legs_last_suspend_window_present = suspended->window ? 1U : 0U;
28158 if (suspended->window) {
28159 const auto& window = *suspended->window;
28160 out->legs_last_suspend_window_excluded_event = window.excluded.event;
28161 out->legs_last_suspend_window_excluded_bar = window.excluded.bar;
28162 out->legs_last_suspend_window_excluded_domain =
28163 static_cast<std::uint32_t
>(window.excluded.domain);
28164 out->legs_last_suspend_window_excluded_phase =
28165 static_cast<std::uint32_t
>(window.excluded.phase);
28166 out->legs_last_suspend_window_best = window.best;
28167 out->legs_last_suspend_window_prefix = window.prefix;
28169 out->legs_last_suspend_retire_count =
28170 static_cast<std::uint32_t
>(suspended->retire.size());
28171 out->legs_last_suspend_retire_item0 = copy_leg(suspended->retire, 0);
28172 out->legs_last_suspend_retire_item1 = copy_leg(suspended->retire, 1);
28173 out->legs_last_suspend_retire_item2 = copy_leg(suspended->retire, 2);
28174 }
else if (
const auto* stage =
28175 std::get_if<exit_legs::StageReplacement>(&last->operation)) {
28176 const auto& relation = stage->relation;
28177 out->legs_last_stage_queue_predecessor = relation.queue_predecessor;
28178 const auto& definition = relation.revival_definition;
28179 out->legs_last_stage_revival_definition_incarnation = definition.incarnation();
28180 out->legs_last_stage_revival_definition_revision = definition.revision();
28181 out->legs_last_stage_revival_definition_value_present =
28182 definition.has_value() ? 1U : 0U;
28183 if (definition.has_value()) {
28184 const auto& prices = definition.prices();
28185 out->legs_last_stage_revival_definition_limit_price = prices.limit_price;
28186 out->legs_last_stage_revival_definition_stop_price = prices.stop_price;
28187 out->legs_last_stage_revival_definition_trail_points = prices.trail_points;
28188 out->legs_last_stage_revival_definition_trail_price = prices.trail_price;
28189 out->legs_last_stage_revival_definition_trail_offset = prices.trail_offset;
28190 out->legs_last_stage_revival_definition_profit_ticks = prices.profit_ticks;
28191 out->legs_last_stage_revival_definition_loss_ticks = prices.loss_ticks;
28193 const auto& release = relation.release;
28194 out->legs_last_stage_release_requested_event = release.requested.event;
28195 out->legs_last_stage_release_requested_bar = release.requested.bar;
28196 out->legs_last_stage_release_requested_domain =
28197 static_cast<std::uint32_t
>(release.requested.domain);
28198 out->legs_last_stage_release_requested_phase =
28199 static_cast<std::uint32_t
>(release.requested.phase);
28200 out->legs_last_stage_release_target_incarnation = release.target.incarnation;
28201 out->legs_last_stage_release_target_owner = release.target.owner;
28202 out->legs_last_stage_release_revision = release.revision;
28203 }
else if (
const auto* restore = std::get_if<exit_legs::Restore>(&last->operation)) {
28204 out->legs_last_restore_legs_count =
28205 static_cast<std::uint32_t
>(restore->legs.size());
28206 const auto copy_leg = [&](std::size_t index) -> std::uint32_t {
28207 return index < restore->legs.size()
28208 ?
static_cast<std::uint32_t
>(restore->legs[index]) : UINT32_MAX;
28210 out->legs_last_restore_legs_item0 = copy_leg(0);
28211 out->legs_last_restore_legs_item1 = copy_leg(1);
28212 out->legs_last_restore_legs_item2 = copy_leg(2);
28213 }
else if (
const auto* complete =
28214 std::get_if<exit_legs::CompleteBarrier>(&last->operation)) {
28215 out->legs_last_complete_completed_event = complete->completed.event;
28216 out->legs_last_complete_completed_bar = complete->completed.bar;
28217 out->legs_last_complete_completed_domain =
28218 static_cast<std::uint32_t
>(complete->completed.domain);
28219 out->legs_last_complete_completed_phase =
28220 static_cast<std::uint32_t
>(complete->completed.phase);
28221 out->legs_last_complete_requested_present = complete->requested ? 1U : 0U;
28222 if (complete->requested) {
28223 const auto& requested = *complete->requested;
28224 out->legs_last_complete_requested_requested_event = requested.requested.event;
28225 out->legs_last_complete_requested_requested_bar = requested.requested.bar;
28226 out->legs_last_complete_requested_requested_domain =
28227 static_cast<std::uint32_t
>(requested.requested.domain);
28228 out->legs_last_complete_requested_requested_phase =
28229 static_cast<std::uint32_t
>(requested.requested.phase);
28230 out->legs_last_complete_requested_target_incarnation =
28231 requested.target.incarnation;
28232 out->legs_last_complete_requested_target_owner = requested.target.owner;
28233 out->legs_last_complete_requested_revision = requested.revision;
28235 }
else if (
const auto* observe = std::get_if<exit_legs::Observe>(&last->operation)) {
28236 out->legs_last_observe_high = observe->high;
28237 out->legs_last_observe_low = observe->low;
28238 out->legs_last_observe_direction = observe->direction;
28239 out->legs_last_observe_fold =
static_cast<std::uint32_t
>(observe->fold);
28240 }
else if (
const auto* cancel = std::get_if<exit_legs::Cancel>(&last->operation)) {
28241 out->legs_last_cancel_legs_count =
28242 static_cast<std::uint32_t
>(cancel->legs.size());
28243 const auto copy_leg = [&](std::size_t index) -> std::uint32_t {
28244 return index < cancel->legs.size()
28245 ?
static_cast<std::uint32_t
>(cancel->legs[index]) : UINT32_MAX;
28247 out->legs_last_cancel_legs_item0 = copy_leg(0);
28248 out->legs_last_cancel_legs_item1 = copy_leg(1);
28249 out->legs_last_cancel_legs_item2 = copy_leg(2);
28252 const auto&
admission = snapshot.market_admission;
28253 const auto& observation =
admission.observation();
28254 if (observation) out->market_admission_observation_present = 1U;
28256 out->market_admission_observation_command = observation->command;
28257 out->market_admission_observation_kind =
static_cast<std::int64_t
>(observation->kind);
28258 out->market_admission_observation_birth_cause =
28259 static_cast<std::int64_t
>(observation->birth.cause());
28260 out->market_admission_observation_birth_bar = observation->birth.bar();
28261 out->market_admission_observation_birth_timestamp = observation->birth.timestamp();
28262 out->market_admission_observation_birth_cursor_domain =
28263 static_cast<std::int64_t
>(observation->birth.cursor().domain());
28264 out->market_admission_observation_birth_cursor_position =
28265 static_cast<std::int64_t
>(observation->birth.cursor().position());
28266 out->market_admission_observation_birth_cursor_index = observation->birth.cursor().index();
28267 out->market_admission_observation_birth_cursor_count = observation->birth.cursor().count();
28268 out->market_admission_observation_birth_cursor_price = observation->birth.cursor_price();
28269 out->market_admission_observation_birth_first_fill = observation->birth.first_fill();
28270 out->market_admission_observation_birth_last_fill = observation->birth.last_fill();
28271 out->market_admission_observation_birth_evaluation_ordinal =
28272 observation->birth.evaluation_ordinal();
28273 copy_pending_string(observation->id, out->market_admission_observation_id,
28274 &out->market_admission_observation_id_truncated,
28275 &out->market_admission_observation_id_hash64);
28276 out->market_admission_observation_requested_quantity = observation->requested_quantity;
28277 out->market_admission_observation_quantity_type = observation->quantity_type;
28278 out->market_admission_observation_buy = observation->buy ? 1U : 0U;
28279 out->market_admission_observation_prices_limit = observation->prices.limit;
28280 out->market_admission_observation_prices_stop = observation->prices.stop;
28281 copy_pending_string(observation->oca_name, out->market_admission_observation_oca_name,
28282 &out->market_admission_observation_oca_name_truncated,
28283 &out->market_admission_observation_oca_name_hash64);
28284 out->market_admission_observation_oca_type = observation->oca_type;
28285 const auto& configuration = observation->configuration;
28286 out->market_admission_observation_configuration_process_on_close =
28287 configuration.process_on_close ? 1U : 0U;
28288 out->market_admission_observation_configuration_calc_on_fills =
28289 configuration.calc_on_fills ? 1U : 0U;
28290 out->market_admission_observation_configuration_magnifier =
28291 configuration.magnifier ? 1U : 0U;
28292 out->market_admission_observation_configuration_fill_recalculation =
28293 configuration.fill_recalculation ? 1U : 0U;
28294 out->market_admission_observation_configuration_scheduler =
28295 configuration.scheduler ? 1U : 0U;
28296 out->market_admission_observation_configuration_slippage = configuration.slippage;
28297 out->market_admission_observation_configuration_pyramiding = configuration.pyramiding;
28298 out->market_admission_observation_configuration_default_quantity_type =
28299 configuration.default_quantity_type;
28300 out->market_admission_observation_configuration_default_quantity_value =
28301 configuration.default_quantity_value;
28302 out->market_admission_observation_configuration_long_margin = configuration.long_margin;
28303 out->market_admission_observation_configuration_short_margin = configuration.short_margin;
28304 out->market_admission_observation_configuration_commission_value =
28305 configuration.commission_value;
28306 out->market_admission_observation_configuration_commission_type =
28307 configuration.commission_type;
28308 out->market_admission_observation_configuration_pointvalue = configuration.pointvalue;
28309 out->market_admission_observation_configuration_fx = configuration.fx;
28310 out->market_admission_observation_configuration_quantity_step =
28311 configuration.quantity_step;
28312 out->market_admission_observation_configuration_mintick = configuration.mintick;
28313 out->market_admission_observation_configuration_risk_direction =
28314 configuration.risk_direction;
28315 out->market_admission_observation_configuration_loss_days_limit =
28316 configuration.loss_days_limit;
28317 out->market_admission_observation_configuration_drawdown_limit =
28318 configuration.drawdown_limit;
28319 out->market_admission_observation_configuration_intraday_loss_limit =
28320 configuration.intraday_loss_limit;
28321 out->market_admission_observation_configuration_position_limit =
28322 configuration.position_limit;
28323 out->market_admission_observation_configuration_fill_cap_active =
28324 configuration.fill_cap_active ? 1U : 0U;
28325 out->market_admission_observation_configuration_risk_halted =
28326 configuration.risk_halted ? 1U : 0U;
28327 out->market_admission_observation_bar = observation->bar;
28328 out->market_admission_observation_placement_side = observation->placement_side;
28329 out->market_admission_observation_placement_cycle = observation->placement_cycle;
28330 out->market_admission_observation_prior_close_quantity =
28331 observation->prior_close_quantity;
28332 out->market_admission_observation_held_quantity = observation->held_quantity;
28333 out->market_admission_observation_held_entries = observation->held_entries;
28334 out->market_admission_observation_realized_equity = observation->realized_equity;
28335 out->market_admission_observation_placement_equity = observation->placement_equity;
28336 out->market_admission_observation_signal_close = observation->signal_close;
28337 out->market_admission_observation_quantized_fixed_quantity =
28338 observation->quantized_fixed_quantity;
28339 out->market_admission_observation_original_sizing_present =
28340 observation->original_sizing ? 1U : 0U;
28341 if (observation->original_sizing) {
28342 out->market_admission_observation_original_sizing_quantity =
28343 observation->original_sizing->quantity;
28344 out->market_admission_observation_original_sizing_equity =
28345 observation->original_sizing->equity;
28346 out->market_admission_observation_original_sizing_price =
28347 observation->original_sizing->price;
28348 out->market_admission_observation_original_sizing_mark =
28349 observation->original_sizing->mark;
28350 out->market_admission_observation_original_sizing_fx =
28351 observation->original_sizing->fx;
28353 out->market_admission_observation_explicit_equity = observation->explicit_equity;
28354 out->market_admission_observation_explicit_price = observation->explicit_price;
28356 out->market_admission_review_present =
admission.review() ? 1U : 0U;
28358 out->market_admission_review_sequence =
admission.review()->sequence;
28359 out->market_admission_review_checkpoint =
28360 static_cast<std::int64_t
>(
admission.review()->checkpoint);
28361 out->market_admission_review_bar =
admission.review()->bar;
28362 out->market_admission_review_target_command =
admission.review()->target_command;
28364 out->market_admission_sizing_revision_present =
admission.sizing_revision() ? 1U : 0U;
28366 out->market_admission_sizing_revision_sequence =
admission.sizing_revision()->sequence;
28367 out->market_admission_sizing_revision_cause_fill =
28368 admission.sizing_revision()->cause_fill;
28369 out->market_admission_sizing_revision_bar =
admission.sizing_revision()->bar;
28370 out->market_admission_sizing_revision_target_command =
28371 admission.sizing_revision()->target_command;
28373 out->cancellation_cause =
static_cast<std::int32_t
>(snapshot.cancellation.cause);
28374 out->cancellation_state = snapshot.cancellation.state;
28375 out->cancellation_close_claim_release = snapshot.cancellation.close_claim_release;
28376 out->cancellation_source_incarnation = snapshot.cancellation.source_incarnation;
28377 out->cancellation_source_sequence = snapshot.cancellation.source_sequence;
28378 out->cancellation_target_incarnation = snapshot.cancellation.target_incarnation;
28379 out->cancellation_target_owner = snapshot.cancellation.target_owner;
28380 out->cancellation_target_revision = snapshot.cancellation.target_revision;
28381 out->cancellation_close_claim_consumed = snapshot.cancellation.close_claim_consumed;
28382 out->cancellation_close_claim_retired = snapshot.cancellation.close_claim_retired;
28387 if (!owner_)
return -1;
28390 if (!owner_->projected_pending_at(index, snapshot, handle) || !snapshot)
return -1;
28391 if (!owner_->short_seed_.active)
return 0;
28392 return owner_->short_seed_collision_role_v1(handle);
28396 return owner_ && owner_->host_ ? owner_->host_->trail_best_price() : kNaN;
28406double PineExecutionAdapter::pending_opposite_market_units(
28407 bool is_long, std::int64_t script_open_ms,
const SourceId&
id)
const {
28408 double units = 0.0;
28409 bool unsized =
false;
28422 if (measured && finite_positive(own)) units += own;
28423 else unsized =
true;
28425 for (
const auto& prior : pending_same_bar_commands_) {
28426 add(prior.snapshot, finite_positive(prior.snapshot.frozen_market_own_units)
28427 ? prior.snapshot.frozen_market_own_units : prior.snapshot.requested_qty);
28429 for (
const auto& prior : pending_entries_)
add(prior.snapshot, kNaN);
28430 for (
const auto& handle : live_handles_) {
28431 const auto prior = placement_.find(handle.incarnation);
28432 if (prior != placement_.end())
add(prior->second, prior->second.requested_qty);
28434 return unsized ? kNaN : units;
28443bool PineExecutionAdapter::same_point_pair_fill_follows(
28444 const PlacementSnapshot& filled,
const native_order::RequestHandle& filled_handle,
28445 double price)
const {
28448 || !finite_positive(filled.requested_qty) || !finite_positive(
price)
28449 || !same_point_pair_scope()) {
28452 return std::any_of(live_handles_.begin(), live_handles_.end(),
28453 [&](
const native_order::RequestHandle& handle) {
28454 if (handle == filled_handle) return false;
28455 const auto found = placement_.find(handle.incarnation);
28456 if (found == placement_.end()) return false;
28457 const auto& row = found->second;
28458 if (!row.opening || row.family != PineOrderFamily::Entry
28459 || row.is_long == filled.is_long
28460 || row.projection_created_bar != filled.projection_created_bar
28461 || row.projection_position_side
28462 != static_cast<std::int32_t>(PositionSide::FLAT)
28463 || !finite_positive(row.requested_qty) || !row.oca_name.empty()
28464 || row.birth.from_fill()
28465 || !(row.qty_type < 0 || row.qty_type == static_cast<int>(QtyType::FIXED))) {
28470 if (stop && limit)
return false;
28490bool PineExecutionAdapter::fill_pooc_close_pair(
28491 double raw_close,
const NativeDecisionContext& context) {
28492 const double tick = staged_.syminfo.mintick;
28493 const double close_tick = source_bar_fill_tick(raw_close, tick);
28494 if (config_.pyramiding != 0 || config_.slippage != 0 || config_.close_entries_rule_any
28495 || config_.commission_value != 0.0 || config_.margin_long != 100.0
28496 || config_.margin_short != 100.0 || bar_magnifier_
28498 || risk_.direction != 0 || risk_.max_cons_loss_days != 0
28499 || risk_.max_drawdown > 0.0 || risk_.max_intraday_loss > 0.0
28500 || risk_.max_position_size > 0.0 || risk_.halted ||
cap.active()
28501 || !finite_positive(close_tick)
28506 native_order::RequestHandle handle;
28507 PlacementSnapshot row;
28508 bool priced =
false;
28511 std::vector<Leg> legs;
28513 for (
const auto& handle : live_handles_) {
28514 const auto found = placement_.find(handle.incarnation);
28515 if (found == placement_.end())
continue;
28516 const auto& row = found->second;
28543 legs.push_back({handle, row, stop ||
limit,
28544 limit ? (row.is_long ? 4 : 5)
28545 : (stop ? (row.is_long ? 1 : 3) : (row.is_long ? 0 : 2))});
28547 if (legs.size() != 2 || legs[0].row.is_long == legs[1].row.is_long
28548 || legs[0].priced == legs[1].priced) {
28551 if (legs[1].row.
command_sequence < legs[0].row.command_sequence) std::swap(legs[0], legs[1]);
28554 const double pending = legs[0].priced ? 0.0 : legs[0].row.requested_qty;
28555 std::vector<std::pair<Leg*, double>>
order = {
28556 {&legs[0], legs[0].row.requested_qty},
28557 {&legs[1], legs[1].row.requested_qty + pending},
28559 if (legs[1].rank < legs[0].rank) std::swap(
order[0],
order[1]);
28562 const double equity = require_host().native_marked_equity(close_tick);
28563 const double required = (legs[1].row.requested_qty +
pending) * close_tick
28564 * staged_.syminfo.pointvalue * active_staged_fx(context.sub_bar_open_ms);
28565 if (!std::isfinite(required) || !std::isfinite(equity) || required > equity)
return false;
28566 bool filled =
false;
28567 for (
const auto& [leg, units] :
order) {
28568 native_order::Request
request;
28570 request.label = leg->row.source_id;
28571 request.comment = leg->row.comment;
28572 request.trigger = native_order::Market{};
28573 PlacementSnapshot immediate = leg->row;
28574 immediate.cancellation = {};
28575 immediate.market_admission = {};
28576 immediate.reverse_to =
false;
28577 immediate.terms_priced_reverse =
false;
28578 immediate.forced_execution_price = close_tick;
28579 const auto accepted = submit_or_replace(
28580 std::move(request), std::move(immediate),
true, leg->row.source_id);
28581 if (!accepted)
return filled;
28582 auto& placed = placement_.at(accepted->incarnation);
28583 placed.projection_position_side = leg->row.projection_position_side; invalidate_retirement();
28584 (void)require_host().execute_current(
28585 {*accepted, NativeCurrentPriceRule::NearestTick});
28596bool PineExecutionAdapter::same_point_two_leg_book(
28597 const SourceId&
id, std::int64_t script_open_ms)
const {
28598 std::size_t others = 0;
28599 bool foreign =
false;
28600 const auto count = [&](
const PlacementSnapshot& row) {
28607 else foreign =
true;
28609 for (
const auto& prior : pending_same_bar_commands_) count(prior.snapshot);
28610 for (
const auto& prior : pending_entries_) count(prior.snapshot);
28611 for (
const auto& handle : live_handles_) {
28612 const auto found = placement_.find(handle.incarnation);
28613 if (found != placement_.end()) count(found->second);
28615 return !foreign && others == 1;
28633void PineExecutionAdapter::order_same_point_entries() {
28634 if (!has_live_family(
PineOrderFamily::Entry) || !host_ || live_handles_.size() < 2 || !same_point_pair_scope())
return;
28635 auto&
host = require_host();
28638 if (!point || !finite_positive(point->price))
return;
28640 const double signal = point->price;
28642 native_order::RequestHandle handle;
28644 std::uint64_t queue = 0;
28646 std::vector<Candidate> candidates;
28647 bool pure_stops =
true;
28648 for (
const auto& handle : live_handles_) {
28649 const auto found = placement_.find(handle.incarnation);
28650 if (found == placement_.end())
continue;
28651 const auto& row = found->second;
28669 pure_stops = pure_stops && stop;
28670 candidates.push_back({handle,
limit ? (row.
is_long ? 4 : 5)
28671 : (stop ? (row.is_long ? 1 : 3)
28672 : (row.is_long ? 0 : 2)), 0});
28674 if (candidates.size() < 2 || pure_stops)
return;
28680 if (config_.pyramiding > 1) {
28681 if (source_batch_mutated_) return;
28682 for (const auto& candidate : candidates) {
28683 const auto found = placement_.find(candidate.handle.incarnation);
28684 if (found != placement_.end() && found->second.replaced_opening) return;
28686 const auto buys = [](
const Candidate& c) { return c.rank == 0 || c.rank == 1 || c.rank == 4; };
28687 const auto buy_count = std::count_if(candidates.begin(), candidates.end(), buys);
28688 const bool both_sides = buy_count != 0
28689 && buy_count !=
static_cast<std::ptrdiff_t
>(candidates.size());
28690 if (both_sides && candidates.size() > 2)
return;
28691 for (
const auto& handle : live_handles_) {
28692 const auto found = placement_.find(handle.incarnation);
28693 if (found == placement_.end() || !found->second.opening
28698 const bool candidate = std::any_of(candidates.begin(), candidates.end(),
28699 [&](
const Candidate& c) { return c.handle == handle; });
28700 if (!candidate)
return;
28703 const auto working =
host.native_working_requests();
28704 const auto definition = [&](
const native_order::RequestHandle& handle) {
28705 const auto row = std::find_if(working.begin(), working.end(),
28706 [&](
const NativeWorkingRequest& request) {
28707 return request.definition->handle == handle;
28709 return row == working.end() ? nullptr : row->definition.get();
28711 for (
auto& candidate : candidates) {
28712 const auto* live = definition(candidate.handle);
28714 candidate.queue = live->priority != 0 ? live->priority : live->handle.incarnation;
28716 std::stable_sort(candidates.begin(), candidates.end(),
28717 [](
const Candidate& left,
const Candidate& right) {
28718 if (left.rank != right.rank) return left.rank < right.rank;
28719 return left.queue < right.queue;
28721 std::size_t first = candidates.size();
28722 std::uint64_t highest = 0;
28723 for (std::size_t index = 0; index < candidates.size(); ++index) {
28724 if (candidates[index].queue < highest) {
28728 highest = candidates[index].queue;
28730 native_order::ReplaceOptions in_place;
28731 in_place.keep_handle =
true;
28732 for (std::size_t index = first; index < candidates.size(); ++index) {
28733 const auto& handle = candidates[index].handle;
28734 const auto* live = definition(handle);
28736 const native_order::Request
request = live->request;
28737 const auto result =
host.replace(handle, request, in_place);
28738 if (result.status != native_order::ReplaceStatus::Replaced)
return;
28741 requeue_live(handle);
28748void PineExecutionAdapter::preserve_position_tail_before_seal(
28749 std::size_t global_end,
const std::string& scratch_directory) {
28750 auto* pine = pine_view_of(host_);
28751 if (!pine || global_end < pine->trades_.base() || global_end > pine->trades_.size()) {
28753 "invalid Pine position-tail seal prefix");
28755 observe_position_tail_rows();
28757 position_open_script_bar_, scratch_directory,
28758 pine->cache_policy_for_run().position_tail_pages);
28763 return position_tail_summary_ ? position_tail_summary_->work :
PositionTailWork{};
28766void PineExecutionAdapter::observe_position_tail_rows() {
28767 if (!position_tail_summary_)
return;
28768 auto* pine = pine_view_of(host_);
28772 position_tail_summary_->invalidate(
28777 const auto& host = require_host();
28778 position_tail_summary_->observe(pine->trades_.base(), host.closed_trade_count(),
28779 [&](std::size_t index) -> Trade { return host.get_trade(static_cast<int>(index)); },
28783void PineExecutionAdapter::begin_position_tail_summary() {
28784 if (!position_tail_summary_)
28785 position_tail_summary_ = std::make_shared<detail::AdapterPositionTailSummary>();
28786 auto* pine = pine_view_of(host_);
28787 if (!pine || short_seed_.final_short.incarnation != 0) {
28788 position_tail_summary_->invalidate(
28795 const auto&
host = require_host();
28796 position_tail_summary_->begin_position(position_open_script_bar_, pine->trades_.base(),
28797 host.closed_trade_count(),
28798 [&](std::size_t index) -> Trade { return host.get_trade(static_cast<int>(index)); },
28801 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
28802 if (
cohort.second.live_units_by_origin.empty())
continue;
28803 position_tail_summary_->live_cohort(&cohort,
true);
28804 for (
const auto& origin :
cohort.second.live_units_by_origin)
28805 position_tail_summary_->open_origin(origin.first, is_long,
28816 return floor_quantity_grid(
adapter_.config_.default_qty_value,
28819 auto sizing =
adapter_.sizing_snapshot();
28820 sizing.price = fill_price;
28821 if (!std::isfinite(fill_price) || fill_price == 0.0)
return 0.0;
28822 if (fill_price > 0.0)
return adapter_.default_sizing_units(sizing);
28823 sizing.price = -fill_price;
28824 const auto sized =
adapter_.default_sizing_shape(sizing);
28825 if (!sized)
return 0.0;
28826 const auto units = native_sized_units(
28827 *sized, -fill_price, sizing.equity, sizing.fx);
28830 return units ? -
adapter_.default_sizing_lot_floor(*units) : 0.0;
28835#include "exit_payload_codec.inc"
28843 if (global < base_ || global >=
size()) {
28845 "origin roster read outside retained window");
28847 return items_[global - base_];
28851 std::vector<std::size_t>& out)
const {
28852 const auto found = positions_.find(handle.
incarnation);
28853 if (found == positions_.end())
return;
28854 for (
const auto position : found->second)
28855 if ((*
this)[position] == handle) out.push_back(position);
28859 if (global < base_ || global >
size()) {
28861 "origin roster retirement outside retained window");
28863 if (global == base_)
return;
28866 for (
auto at = positions_.begin(); at != positions_.end();) {
28867 auto& positions = at->second;
28868 const auto kept = std::lower_bound(positions.begin(), positions.end(), global);
28869 positions.erase(positions.begin(), kept);
28870 if (positions.empty()) at = positions_.erase(at);
28873 items_.erase(items_.begin(), items_.begin() +
static_cast<std::ptrdiff_t
>(global - base_));
28875 if (items_.empty()) {
28876 decltype(items_){}.swap(items_);
28877 decltype(positions_){}.swap(positions_);
28882 if (items_.capacity() / 4 > items_.size())
28883 decltype(items_)(items_).swap(items_);
28884 for (
auto& row : positions_) {
28885 auto& positions = row.second;
28886 if (positions.capacity() / 4 > positions.size())
28887 std::vector<std::size_t>(positions).swap(positions);
28889 if (positions_.bucket_count() / 4 > positions_.size())
28890 positions_.rehash(positions_.size());
28898void PineExecutionAdapter::retire_origin_prefixes() {
28899 RetirementObservation observation(*
this);
28900 auto& work = retirement_work_;
28908 if (retired_row_scratch_.origin_fronts_pinned)
return;
28911 if (std::all_of(cohorts_by_id_.
begin(), cohorts_by_id_.
end(), [
this](
const auto& row) {
28912 auto& counters = retirement_work_;
28913 ++counters.cohort_rows;
28914 ++counters.certificate_rows;
28915 const auto& members = row.second.origins.members();
28916 if (members.empty()) return true;
28917 ++counters.fronts_tested;
28918 const bool present = placement_.contains(members.front().incarnation);
28919 if (!present) ++counters.missing_fronts;
28921 })) { retired_row_scratch_.origin_fronts_pinned =
true;
return; }
28923 if (!consumer)
return;
28924 const auto& core = as_native_consumer(*consumer).request_core();
28925 std::vector<std::uint64_t> named;
28926 const auto name = [&](std::uint64_t incarnation) {
28927 if (incarnation != 0) named.push_back(incarnation);
28929 const auto snapshot_names = [&](
const PlacementSnapshot& row) {
28937 const auto request_names = [&](
const native_order::Request&
request) {
28938 if (
const auto* wait = std::get_if<native_order::WaitForApplied>(&
request.owner)) {
28939 name(wait->parent.incarnation);
28940 }
else if (
const auto* opening = std::get_if<native_order::BindOpening>(&
request.owner)) {
28941 name(opening->opening.incarnation);
28942 }
else if (
const auto* openings = std::get_if<native_order::BindOpenings>(&
request.owner)) {
28943 for (
const auto& opening : openings->openings) name(opening.incarnation);
28946 const auto live_names = [&](
const native_order::RequestHandle& handle) {
28947 name(handle.incarnation);
28948 const auto at = placement_.find(handle.incarnation);
28949 if (at != placement_.end()) snapshot_names(at->second);
28951 for (
const auto& handle : live_handles_) live_names(handle);
28952 for (
const auto& live : core.live()) {
28953 live_names(live.handle());
28954 request_names(live.definition->request);
28955 if (live.definition->root) name(live.definition->root->incarnation);
28957 for (
const auto& leg : pending_bracket_legs_) {
28958 snapshot_names(leg.snapshot); request_names(leg.request);
28960 for (
const auto&
entry : pending_entries_) {
28961 snapshot_names(
entry.snapshot); request_names(
entry.request);
28963 for (
const auto&
order : delayed_market_orders_) {
28964 snapshot_names(
order.snapshot); request_names(
order.request);
28966 for (
const auto& sell : deferred_open_marketable_sells_) snapshot_names(sell.snapshot);
28967 for (
const auto& row : throttled_reopen_rearm_) snapshot_names(row);
28968 for (
const auto& command : pending_same_bar_commands_) {
28969 snapshot_names(
command.snapshot); request_names(
command.request);
28971 for (
const auto& shadow : source_shadow_pending_) snapshot_names(shadow.snapshot);
28972 for (
const auto& pending : pending_coof_requests_) {
28973 snapshot_names(
pending.snapshot); request_names(
pending.request);
28975 for (
const auto& revival : pending_margin_revivals_) snapshot_names(revival.snapshot);
28976 for (
const auto& leg : anchored_relative_legs_) {
28977 name(leg.handle.incarnation); name(leg.parent.incarnation); request_names(leg.request);
28979 std::sort(named.begin(), named.end());
28980 named.erase(std::unique(named.begin(), named.end()), named.end());
28981 for (
auto& row : cohorts_by_id_) {
28982 ++work.cohort_rows;
28983 auto&
facts = row.second;
28984 auto& origins =
facts.origins;
28985 std::size_t through = origins.base();
28986 for (
const auto& origin : origins) {
28987 ++work.fronts_tested;
28988 const bool present = placement_.contains(origin.incarnation);
28990 || std::binary_search(named.begin(), named.end(), origin.incarnation)
28991 ||
facts.live_units_by_origin.find(origin.incarnation)
28992 !=
facts.live_units_by_origin.end()
28993 || std::find(
facts.opened.begin(),
facts.opened.end(), origin) !=
facts.opened.end()
28994 || core.cohort_contains(
facts.handle, origin)) {
28995 if (!
present && through == origins.base()) ++work.missing_fronts_pinned;
29000 const auto old_base = origins.base();
29001 origins.retire_before(through);
29002 work.origins_retired += through - old_base;
29003 if (origins.members().empty()) {
29004 if (
auto* index = adapter_lookup_index(consumer,
this, run_counter_,
false))
29005 index->cohort_sides.erase(
facts.handle.value);
29008 cohorts_by_id_.spill_implicitly();
29009 if (
auto* index = adapter_lookup_index(consumer,
this, run_counter_,
false)) {
29010 auto& sides = index->cohort_sides;
29011 if (sides.bucket_count() / 4 > sides.size()) sides.rehash(sides.size());
29018 result.
live = live_handles_.size();
29019 result.
pending = pending_bracket_legs_.size() + pending_entries_.size()
29020 + delayed_market_orders_.size() + deferred_open_marketable_sells_.size()
29021 + throttled_reopen_rearm_.size() + pending_same_bar_commands_.size()
29022 + source_shadow_pending_.size() + pending_coof_requests_.size()
29023 + pending_margin_revivals_.size() + anchored_relative_legs_.size();
29025 if (
const auto* wait = std::get_if<native_order::WaitForApplied>(&request.owner))
29026 result.
owners += wait->parent.incarnation != 0;
29027 else if (
const auto* opening = std::get_if<native_order::BindOpening>(&request.owner))
29028 result.
owners += opening->opening.incarnation != 0;
29029 else if (
const auto* openings = std::get_if<native_order::BindOpenings>(&request.owner))
29030 for (
const auto& opening : openings->openings)
29031 result.
owners += opening.incarnation != 0;
29033 for (
const auto& leg : pending_bracket_legs_) owners(leg.request);
29034 for (
const auto&
entry : pending_entries_) owners(
entry.request);
29035 for (
const auto&
order : delayed_market_orders_) owners(
order.request);
29036 for (
const auto& command : pending_same_bar_commands_) owners(command.request);
29037 for (
const auto& request : pending_coof_requests_) owners(request.request);
29038 for (
const auto& leg : anchored_relative_legs_) owners(leg.request);
29039 result.
opened = cohort_opened_slot_count_;
29041 result.
R = placement_.size() - result.
live;
29042 result.
C = cohorts_by_id_.size();
29043 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& row) {
29046 const bool live = std::any_of(live_handles_.begin(), live_handles_.end(),
29047 [&](
const auto& handle) { return handle.incarnation == incarnation; });
29061 result.base_ = count;
29062 result.digest_ =
digest;
29067PineExecutionAdapter::CohortFacts::retired_record() const noexcept {
29068 static_assert(
sizeof(Record) == 40,
"retired cohort scratch record");
29069 return {handle.value, cycle, origins.size(), origins.digest(), erased_opening_sides};
29072PineExecutionAdapter::CohortFacts
29073PineExecutionAdapter::CohortFacts::restore_record(
const Record& row,
bool ) {
29074 if (row.handle == 0 ||
static_cast<std::uint64_t
>(
29075 static_cast<std::size_t
>(row.origin_count)) != row.origin_count
29076 || row.erased_sides > 3) {
29080 facts.handle.value = row.handle;
29081 facts.cycle = row.cycle;
29082 facts.origins = OriginRoster::from_retired(
29083 static_cast<std::size_t
>(row.origin_count), row.origin_digest);
29084 facts.erased_opening_sides =
static_cast<std::uint8_t
>(row.erased_sides);
29088bool PineExecutionAdapter::CohortFacts::spillable() const noexcept {
29089 return origins.members().empty() && opened.empty() && live_units_by_origin.empty();
29092bool PineExecutionAdapter::CohortFacts::held() const noexcept {
29094 return !opened.empty() || !live_units_by_origin.empty();
29107 cohorts_by_id_.set_directory(directory);
29108 exit_rank_keys_.set_directory(directory);
29109 placement_.set_auxiliary_tables_directory(directory);
29110 consumed_partial_exit_cycles_.set_directory(directory);
29111 if (!position_tail_summary_)
29112 position_tail_summary_ = std::make_shared<detail::AdapterPositionTailSummary>();
29113 position_tail_summary_->set_directory(directory);
29124PineExecutionAdapter::require_cohort(
const SourceId&
id) {
29125 auto found = cohorts_by_id_.find(
id);
29126 if (found == cohorts_by_id_.end()) {
29128 "accepted opening has no source cohort");
29138namespace pineforge::source {
29141 erased_minima_.set_directory(directory); sequence_index_.reset();
29147 sequence_index_.reset();
29151 if (high_water_ != 0)
29154 "placement auxiliary fixture backend changed after use");
29155 erased_minima_.fixture_use_memory_reference(); sequence_index_.reset();
29159 return erased_minima_.disk_bytes();
29169 placement_.fixture_use_memory_auxiliary_tables();
29170 consumed_partial_exit_cycles_.fixture_use_memory_reference();
29174 return placement_.auxiliary_disk_bytes() + consumed_partial_exit_cycles_.disk_bytes();
29179#include "../scratch_table_binders.hpp"
29187 const std::shared_ptr<ScratchSlab>& slab) {
29189 if (adapter.cohorts_by_id_.
size() != 0 || adapter.exit_rank_keys_.
size() != 0)
29191 "tree scratch backend changed after use");
29197 const std::shared_ptr<ScratchSlab>& slab) {
29207void PineExecutionAdapter::require_cohort_ticket(
const SourceId* prospective)
const {
29208 cohorts_by_id_.require_inserts([&](
auto&& visit) {
29209 if (prospective) visit(*prospective);
29210 for (
const auto& row : pending_same_bar_commands_)
29211 if (row.opening) visit(row.snapshot.source_id);
29212 for (
const auto& row : pending_entries_) visit(row.snapshot.source_id);
29213 for (
const auto& row : pending_coof_requests_)
29214 if (row.opening) visit(row.snapshot.source_id);
29215 for (
const auto& row : delayed_market_orders_)
29216 if (row.snapshot.family == PineOrderFamily::Entry
29217 || row.snapshot.family == PineOrderFamily::Order)
29218 visit(row.snapshot.source_id);
29219 for (
const auto& row : deferred_open_marketable_sells_) visit(row.snapshot.source_id);
29223void PineExecutionAdapter::require_exit_rank_key(
const SourceId&
id)
const {
29226 if (exit_rank_keys_.size() > 26)
return;
29227 exit_rank_keys_.require_insert(
id);
29230void PineExecutionAdapter::require_tree_precommit(
const NativePrecommitView& view)
const {
29234 if (!view.account.would_open || view.inspected_opened_units == 0.0)
return;
29235 const auto row = placement_.find(view.target.incarnation);
29236 if (row != placement_.end()
29237 && (row->second.family == PineOrderFamily::Entry
29238 || row->second.family == PineOrderFamily::Order))
29239 require_exit_rank_key(row->second.source_id);
29244namespace pineforge::source {
29246std::uint64_t PineExecutionAdapter::checked_live_key(
const SourceId& spelling)
const {
29247 const auto key = key_for(spelling);
29248 const auto existing = live_by_source_key_.find(key);
29249 if (existing == live_by_source_key_.end())
return key;
29251 if (existing->second.identity.shape == detail::LiveSourceIdentity::Shape::Unverified)
29253 const auto previous = placement_.find(existing->second.incarnation);
29254 if (previous == placement_.end()) {
29255 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
29256 "Pine live source key has no placement");
29258 if (!existing->second.identity.matches(spelling, previous->second.source_id,
29259 previous->second.from_entry)) {
29260 throw coded<std::logic_error>(RunFailureCode::engine_invariant,
29261 {{
"reason",
"order_key_hash_collision"}},
29262 "Pine live source key collision");
29267detail::LiveSourceHandle PineExecutionAdapter::live_source_value(
29268 native_order::RequestHandle handle,
const SourceId& spelling)
const {
29269 const auto row = placement_.find(handle.incarnation);
29270 if (row == placement_.end()) {
29271 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
29272 "Pine live source key has no placement");
29274 const auto&
snapshot = row->second;
29276#ifdef PINEFORGE_TEST_SOURCE_KEY_MASK
29279 if (
value.identity.shape == detail::LiveSourceIdentity::Shape::Unverified) {
29280 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
29281 "Pine live source key spelling is not represented");
29289namespace pineforge::source {
29291#ifndef PINEFORGE_FULL_CLOSE_EXIT_ORACLE
29292#define PINEFORGE_FULL_CLOSE_EXIT_ORACLE() false
29294#ifndef PINEFORGE_FULL_CLOSE_EXIT_CALL
29295#define PINEFORGE_FULL_CLOSE_EXIT_CALL() ((void)0)
29298void PineExecutionAdapter::cancel_reversal_exit_owners(
29299 const std::vector<SourceId>& preserved,
29300 detail::ExitScratchRows<SourceId>& selection) {
29301 const auto exempt = [&](
const SourceId& id) {
29302 return std::binary_search(preserved.begin(), preserved.end(),
id);
29307 cohorts_by_id_.for_each_sorted_id([&](
const SourceId&
id) {
29308 if (!exempt(
id)) selection.push_back(
id);
29310 selection.ordered_text([&](
const detail::ExitScratchText&
id) {
29311 cancel_exit_orders_for_full_close(
id);
29319 std::uint64_t count = cohorts_by_id_.size();
29320 const SourceId* previous =
nullptr;
29321 for (
const auto&
id : preserved) {
29322 if (previous && *previous ==
id)
continue;
29324 if (cohorts_by_id_.contains(
id)) --count;
29326 if (count == 0)
return;
29331 std::vector<SourceId> owners;
29332 for (
const auto& row : pending_bracket_legs_) owners.push_back(row.snapshot.from_entry);
29333 for (
const auto& row : pending_coof_requests_) owners.push_back(row.snapshot.from_entry);
29334 for (
const auto& row : source_shadow_pending_) owners.push_back(row.snapshot.from_entry);
29335 for (
const auto& row : pending_relative_exits_) owners.push_back(row.from_entry);
29336 for (
const auto& row : anchored_relative_legs_) owners.push_back(row.from_entry);
29337 for (
const auto& handle : live_handles_) {
29338 const auto row = placement_.find(handle.incarnation);
29339 if (row == placement_.end())
continue;
29340 const auto family = row->second.family;
29341 if (family == PineOrderFamily::ExitLimit || family == PineOrderFamily::ExitStop
29342 || family == PineOrderFamily::ExitTrail) owners.push_back(row->second.from_entry);
29344 std::sort(owners.begin(), owners.end());
29345 owners.erase(std::unique(owners.begin(), owners.end()), owners.end());
29346 owners.erase(std::remove_if(owners.begin(), owners.end(), [&](
const SourceId&
id) {
29347 return exempt(id) || !cohorts_by_id_.contains(id);
29349 assert(owners.size() <= count);
29355 auto& context = selection.context();
29356 if (count > std::numeric_limits<std::uint64_t>::max() / 2)
29357 detail::exit_scratch_failure(detail::ScratchStatus::programming_error);
29358 detail::exit_scratch_edit(context, [&] {
29359 return context.store.advance_handles(2 * count);
29364 for (
auto family = bracket_families_.begin(); family != bracket_families_.end();) {
29365 if (family->second.empty()) family = bracket_families_.erase(family);
29372 const bool keep_pending_bound =
29373 detail::exit_binding_rule_switches().pending_bound_exit_survives_flat;
29374 cohorts_by_id_.for_each_sorted_id([&](
const SourceId&
id) {
29375 if (exempt(
id) || std::binary_search(owners.begin(), owners.end(),
id))
return;
29376 const auto matches = [&](
const PlacementSnapshot& row) {
29377 const bool exit = row.family == PineOrderFamily::ExitLimit
29378 || row.family == PineOrderFamily::ExitStop
29379 || row.family == PineOrderFamily::ExitTrail;
29380 return exit && !(keep_pending_bound && row.pending_bound_exit)
29381 && row.from_entry ==
id;
29383 assert(std::none_of(pending_bracket_legs_.begin(), pending_bracket_legs_.end(),
29384 [&](
const PendingBracketLeg& row) { return matches(row.snapshot); }));
29385 assert(std::none_of(pending_coof_requests_.begin(), pending_coof_requests_.end(),
29386 [&](
const PendingCoofRequest& row) { return matches(row.snapshot); }));
29387 assert(std::none_of(source_shadow_pending_.begin(), source_shadow_pending_.end(),
29388 [&](
const SourceShadowPending& row) { return matches(row.snapshot); }));
29389 assert(std::none_of(pending_relative_exits_.begin(), pending_relative_exits_.end(),
29390 [&](
const PendingRelativeExit& row) { return row.from_entry == id; }));
29392 assert(std::none_of(anchored_relative_legs_.begin(), anchored_relative_legs_.end(),
29393 [&](
const AnchoredRelativeLeg& row) { return row.from_entry == id; }));
29394 for (
const auto& handle : live_handles_) {
29395 const auto row = placement_.find(handle.incarnation);
29396 assert(row == placement_.end() || !matches(row->second)
29397 || coof_leg_reaches_exit(row->second));
29399 assert(std::none_of(bracket_families_.begin(), bracket_families_.end(),
29400 [](
const auto& family) { return family.second.empty(); }));
29404 for (
const auto&
id : owners) {
29406 cancel_exit_orders_for_full_close(
id);
29414 const auto skipped = count -
static_cast<std::uint64_t
>(owners.size());
29415 if (skipped > (std::numeric_limits<std::uint64_t>::max() - context.next_scope) / 2)
29416 detail::exit_scratch_failure(detail::ScratchStatus::programming_error);
29417 context.next_scope += 2 * skipped;
29423void PineExecutionAdapter::remember_live(
const native_order::RequestHandle& handle,
29424 PlacementSnapshot&& snapshot) {
29425 invalidate_retirement();
29426 const auto previous = placement_.find(handle.incarnation);
29427 const bool already_live = previous != placement_.end()
29428 && std::find(live_handles_.begin(), live_handles_.end(), handle) != live_handles_.end();
29429 if (already_live) count_live(previous->second,
false);
29430 if (previous != placement_.end() ||
snapshot.projection_predecessor != 0)
29431 retired_row_scratch_.roster_pass_due =
true;
29432 const auto inserted = placement_.try_emplace(handle.incarnation, std::move(snapshot));
29433 if (!inserted.second) placement_.replace(handle.incarnation, std::move(snapshot));
29434 if (!already_live) pending_write(live_handles_).push_back(handle);
29435 count_live(placement_.at(handle.incarnation),
true);
29436 assert_live_counts();
29439void PineExecutionAdapter::forget_live(
const native_order::RequestHandle& handle)
noexcept {
29440 const auto removed = std::remove(pending_write(live_handles_).begin(), live_handles_.end(), handle);
29441 if (removed != live_handles_.end()) {
29442 assert(std::distance(removed, live_handles_.end()) == 1);
29443 const auto row = placement_.find(handle.incarnation);
29444 assert(row != placement_.end());
29445 if (row != placement_.end()) count_live(row->second,
false);
29446 pending_write(live_handles_).erase(removed, live_handles_.end());
29448 assert_live_counts();
29451void PineExecutionAdapter::requeue_live(
const native_order::RequestHandle& handle) {
29453 assert(std::find(live_handles_.begin(), live_handles_.end(), handle) != live_handles_.end());
29454 forget_live(handle);
29455 pending_write(live_handles_).push_back(handle);
29456 count_live(placement_.at(handle.incarnation),
true);
29457 assert_live_counts();
29460void PineExecutionAdapter::clear_live() noexcept {
29461 pending_write(live_handles_).clear();
29462 for (
auto& count : retired_row_scratch_.live_families) count = 0;
29463 retired_row_scratch_.origin_fronts_pinned =
true;
29466void PineExecutionAdapter::erase_placement(std::uint64_t incarnation) {
29467 assert(std::none_of(live_handles_.begin(), live_handles_.end(),
29468 [&](
const auto& handle) { return handle.incarnation == incarnation; }));
29472 retired_row_scratch_.origin_fronts_pinned =
false;
29473 invalidate_retirement();
29474 if (placement_.erase(incarnation)) ++retirement_work_.rows_erased;
29477void PineExecutionAdapter::count_live(
const PlacementSnapshot& row,
bool adding)
noexcept {
29478 const auto change = [&](std::size_t slot) {
29479 auto& count = retired_row_scratch_.live_families[slot];
29480 if (adding) ++count;
29481 else { assert(count != 0); --count; }
29483 change(
static_cast<std::size_t
>(row.family));
29484 if (row.family == PineOrderFamily::Order
29485 && !finite_positive(row.exit_levels.limit) && !finite_positive(row.exit_levels.stop))
29486 change(RetiredRowScratch::market_order_slot);
29492 if (!row.opening || row.family == PineOrderFamily::ExitLimit
29493 || row.family == PineOrderFamily::ExitStop || row.family == PineOrderFamily::ExitTrail
29494 || row.reservation_expansion.capture().has_value())
29495 change(RetiredRowScratch::exit_policy_slot);
29498void PineExecutionAdapter::assert_live_counts() const noexcept {
29500 std::size_t actual[RetiredRowScratch::family_count + 2]{};
29501 for (
const auto& handle : live_handles_) {
29502 const auto found = placement_.find(handle.incarnation);
29503 assert(found != placement_.end());
29504 if (found == placement_.end())
continue;
29505 const auto& row = found->second;
29506 ++actual[
static_cast<std::size_t
>(row.family)];
29507 if (row.family == PineOrderFamily::Order
29508 && !finite_positive(row.exit_levels.limit)
29509 && !finite_positive(row.exit_levels.stop))
29510 ++actual[RetiredRowScratch::market_order_slot];
29511 if (!row.opening || row.family == PineOrderFamily::ExitLimit
29512 || row.family == PineOrderFamily::ExitStop || row.family == PineOrderFamily::ExitTrail
29513 || row.reservation_expansion.capture().has_value())
29514 ++actual[RetiredRowScratch::exit_policy_slot];
29516 for (std::size_t i = 0; i < RetiredRowScratch::family_count + 2; ++i)
29517 assert(actual[i] == retired_row_scratch_.live_families[i]);
29521bool PineExecutionAdapter::has_live_family(PineOrderFamily family)
const noexcept {
29522 assert_live_counts();
29523 return retired_row_scratch_.live_families[
static_cast<std::size_t
>(family)] != 0;
29526bool PineExecutionAdapter::has_live_exit_policy() const noexcept {
29527 assert_live_counts();
29528 return retired_row_scratch_.live_families[RetiredRowScratch::exit_policy_slot] != 0;
29531bool PineExecutionAdapter::has_live_trail_policy() const noexcept {
29532 assert_live_counts();
29533 const auto& counts = retired_row_scratch_.live_families;
29534 return counts[
static_cast<std::size_t
>(PineOrderFamily::Entry)] != 0
29535 || counts[
static_cast<std::size_t
>(PineOrderFamily::ExitStop)] != 0
29536 || counts[
static_cast<std::size_t
>(PineOrderFamily::ExitTrail)] != 0;
29540int PineExecutionAdapter::scan_projected_pending_size(
29541 const PineExecutionAdapter& owner)
noexcept {
29542 const int raw_count = owner.projected_raw_pending_size();
29544 for (
int index = 0; index < raw_count; ++index) {
29545 const PlacementSnapshot* candidate =
nullptr;
29546 native_order::RequestHandle handle;
29547 if (!owner.projected_raw_pending_at(index, candidate, handle) || !candidate)
continue;
29548 bool duplicate =
false;
29549 if (candidate->family == PineOrderFamily::ExitLimit
29550 || candidate->family == PineOrderFamily::ExitStop
29551 || candidate->family == PineOrderFamily::ExitTrail) {
29552 for (
int prior = 0; prior < index; ++prior) {
29553 const PlacementSnapshot* earlier =
nullptr;
29554 native_order::RequestHandle earlier_handle;
29555 if (owner.projected_raw_pending_at(prior, earlier, earlier_handle) && earlier
29556 && same_projected_order(*candidate, *earlier)) {
29562 if (!duplicate && count != std::numeric_limits<int>::max()) ++count;
29567bool PineExecutionAdapter::scan_projected_pending_at(
29568 const PineExecutionAdapter& owner,
int index,
const PlacementSnapshot*& snapshot,
29569 native_order::RequestHandle& handle,
int* raw_index)
noexcept {
29572 if (raw_index) *raw_index = -1;
29573 if (index < 0)
return false;
29574 const int raw_count = owner.projected_raw_pending_size();
29576 for (
int raw = 0; raw < raw_count; ++raw) {
29577 const PlacementSnapshot* candidate =
nullptr;
29578 native_order::RequestHandle candidate_handle;
29579 if (!owner.projected_raw_pending_at(raw, candidate, candidate_handle) || !candidate)
continue;
29580 bool duplicate =
false;
29581 if (candidate->family == PineOrderFamily::ExitLimit
29582 || candidate->family == PineOrderFamily::ExitStop
29583 || candidate->family == PineOrderFamily::ExitTrail) {
29584 for (
int prior = 0; prior < raw; ++prior) {
29585 const PlacementSnapshot* earlier =
nullptr;
29586 native_order::RequestHandle earlier_handle;
29587 if (owner.projected_raw_pending_at(prior, earlier, earlier_handle) && earlier
29588 && same_projected_order(*candidate, *earlier)) {
29594 if (duplicate)
continue;
29595 if (projected++ == index) {
29597 handle = candidate_handle;
29598 if (raw_index) *raw_index = raw;
29605int PineExecutionAdapter::projected_pending_size() const noexcept {
29606 if (!ensure_projected_pending_index())
return scan_projected_pending_size(*
this);
29607 const int count =
static_cast<int>(projected_pending_indexes_.size());
29609 if (projected_raw_pending_size() <= 256)
29610 assert(count == scan_projected_pending_size(*
this));
29615bool PineExecutionAdapter::projected_pending_at(
29616 int index,
const PlacementSnapshot*& snapshot,
29617 native_order::RequestHandle& handle)
const noexcept {
29620 bool found =
false;
29622 if (!ensure_projected_pending_index())
29623 return scan_projected_pending_at(*
this, index, snapshot, handle);
29624 if (
static_cast<std::size_t
>(index) < projected_pending_indexes_.size())
29625 found = projected_raw_pending_at(
29626 projected_pending_indexes_[
static_cast<std::size_t
>(index)], snapshot, handle);
29629 if (projected_raw_pending_size() <= 256) {
29630 const PlacementSnapshot* expected_snapshot =
nullptr;
29631 native_order::RequestHandle expected_handle;
29632 const bool expected_found = scan_projected_pending_at(
29633 *
this, index, expected_snapshot, expected_handle);
29634 assert(found == expected_found);
29635 assert(snapshot == expected_snapshot);
29636 assert(handle == expected_handle);
29642int PineExecutionAdapter::projected_pending_next(
int index)
const noexcept {
29643 if (!projected_pending_valid_) {
29644 const PlacementSnapshot*
snapshot =
nullptr;
29645 native_order::RequestHandle handle;
29647 if (!scan_projected_pending_at(*
this, index, snapshot, handle, &raw))
return -1;
29648 return projected_raw_pending_next(raw);
29650 return projected_raw_pending_next(
29651 projected_pending_indexes_[
static_cast<std::size_t
>(index)]);
29654int PineExecutionAdapter::projected_raw_pending_next(
int raw)
const noexcept {
29657 if (raw < 0)
return -1;
29658 if (!projected_pending_valid_)
29659 return raw < projected_raw_pending_size() - 1 ? raw + 1 : -1;
29660 return projected_pending_next_[
static_cast<std::size_t
>(raw)];
29663int PineExecutionAdapter::projected_level_resolved(
29664 const PlacementSnapshot& snapshot)
const noexcept {
29665 if (
snapshot.from_entry.empty())
return 1;
29666 const auto cohort = cohorts_by_id_.find_resident(
snapshot.from_entry);
29667 return cohort != cohorts_by_id_.end() && !
cohort->second.opened.empty() ? 1 : 0;
29672namespace pineforge::source {
29678struct PlacementTable::SequenceIndex {
29681 using Buffer = ::pineforge::detail::ScratchBuffer;
29682 using Bytes = ::pineforge::detail::ScratchBytes;
29684 static constexpr auto absent = std::numeric_limits<std::uint64_t>::max();
29690 class Key final :
public ::pineforge::detail::ScratchByteSource {
29692 Key(
unsigned domain, std::string_view
id,
29693 std::optional<std::string_view> from = std::nullopt,
29694 std::uint64_t sequence = 0, std::uint64_t incarnation = 0)
29695 : domain_(domain), id_(id), from_(from),
29696 sequence_(
sequence), incarnation_(incarnation) {
29697 if (
id.size() > absent - 33
29698 || (from && from->size() > absent - 33 -
id.size()))
29699 throw std::length_error(
"retained identity key is too long");
29701 std::uint64_t prefix_size() const noexcept {
29702 return 9 + id_.size() + (from_ ? 8 + from_->size() : 0);
29704 std::uint64_t id_prefix_size() const noexcept {
return 9 + id_.size(); }
29705 std::uint64_t size() const noexcept
override {
return prefix_size() + 16; }
29706 Status read(std::uint64_t offset, Buffer destination)
const noexcept override {
29707 if (offset > size() || destination.size > size() - offset
29708 || (destination.size && !destination.data))
return Status::programming_error;
29709 for (std::size_t index = 0; index < destination.size; ++index)
29710 destination.data[index] =
static_cast<std::byte
>(
byte(offset + index));
29714 static unsigned char word(std::uint64_t value, std::uint64_t at)
noexcept {
29715 return static_cast<unsigned char>(
value >> (8 * (7 - at)));
29717 unsigned char byte(std::uint64_t at)
const noexcept {
29718 if (at == 0)
return static_cast<unsigned char>(domain_);
29720 if (at < 8)
return word(id_.size(), at);
29722 if (at < id_.size())
return static_cast<unsigned char>(id_[at]);
29724 if (domain_ == 2) {
29725 if (at < 8)
return word(sequence_, at);
29729 if (at < 8)
return word(from_->size(), at);
29731 if (at < from_->size())
return static_cast<unsigned char>((*from_)[at]);
29732 at -= from_->size();
29734 if (domain_ == 2)
return word(incarnation_, at);
29735 return at < 8 ? word(sequence_, at) : word(incarnation_, at - 8);
29738 std::string_view id_;
29739 std::optional<std::string_view> from_;
29740 std::uint64_t sequence_, incarnation_;
29743 static Status compare(
void* context, const ::pineforge::detail::ScratchByteSource& a,
29744 const ::pineforge::detail::ScratchByteSource& b,
int& order)
noexcept {
29745 ++
static_cast<SequenceIndex*
>(context)->operations;
29746 std::array<std::byte, 128> left{}, right{};
29747 const auto common = std::min(a.size(),
b.size());
29748 for (std::uint64_t offset = 0; offset < common;) {
29749 const auto count =
static_cast<std::size_t
>(
29750 std::min<std::uint64_t>(left.size(), common - offset));
29751 auto status = a.read(offset, {left.data(), count});
29752 if (status != Status::ok)
return status;
29753 status =
b.read(offset, {right.data(), count});
29754 if (status != Status::ok)
return status;
29755 const int compared = std::memcmp(left.data(), right.data(), count);
29756 if (compared != 0) { order = compared;
return Status::ok; }
29759 order = a.size() <
b.size() ? -1 :
b.size() < a.size() ? 1 : 0;
29763 static std::unique_ptr<::pineforge::detail::ScratchBacking> make_backing(
29764 CacheCapacity capacity) {
29768 struct RestoreObserver {
29769 ::pineforge::detail::ScratchIoObserver previous =
29770 detail::set_command_scratch_io_observer({});
29771 ~RestoreObserver() { detail::set_command_scratch_io_observer(previous); }
29774 auto backing = ::pineforge::detail::make_command_scratch_backing(512, capacity);
29775 const auto observer = detail::sequence_cache_observer;
29776 if (observer.created) observer.created(observer.context, *backing);
29780 mutable std::uint64_t operations = 0;
29781 std::unique_ptr<::pineforge::detail::ScratchBacking> backing;
29782 ::pineforge::detail::CommandScratchStore store{
29783 {2, 512, 1, 1, {0x6578697473657131ULL,
this, compare},
29785 bool certified =
false;
29787 explicit SequenceIndex(CacheCapacity capacity)
29788 : backing(make_backing(capacity)) {
require(store.status()); }
29789 void require(Status status)
const {
29790 if (status == Status::ok)
return;
29791 if (status == Status::io_error)
29793 throw coded<std::logic_error>(
29794 status == Status::io_error ? RunFailureCode::scratch_io_failed
29795 : RunFailureCode::engine_invariant,
29796 {},
"retained sequence index operation failed");
29798 struct Transaction {
29799 SequenceIndex& index;
29800 explicit Transaction(SequenceIndex& value) : index(
value) {
29801 ++index.operations;
29802 index.require(index.store.begin());
29805 if (index.store.transaction_active()) (void)index.store.rollback();
29808 ++index.operations;
29812 Handle seek(
const Key& key)
const {
29815 const auto status = store.lower_bound(key, handle);
29816 if (status != Status::end)
require(status);
29819 bool matches(Handle handle,
const Key& key, std::uint64_t bytes)
const {
29821 ::pineforge::detail::ScratchRecordInfo info{};
29823 require(store.point(handle, info));
29824 if (info.key_bytes < bytes || (bytes ==
key.size() && info.key_bytes != bytes))
29826 std::array<std::byte, 128> left{}, right{};
29827 for (std::uint64_t offset = 0; offset < bytes;) {
29828 const auto count =
static_cast<std::size_t
>(
29829 std::min<std::uint64_t>(left.size(), bytes - offset));
29831 require(store.read(handle, Field::key, offset, {left.data(), count}));
29832 require(
key.read(offset, {right.data(), count}));
29833 if (std::memcmp(left.data(), right.data(), count) != 0)
return false;
29838 Handle
find(
const Key& key)
const {
29839 const auto handle = seek(key);
29842 std::uint64_t minimum(
unsigned domain, std::string_view
id,
29843 std::optional<std::string_view> from = std::nullopt)
const {
29844 const Key probe(domain,
id, from);
29845 const auto handle = seek(probe);
29846 if (!matches(handle, probe, probe.prefix_size()))
return absent;
29847 std::array<std::byte, 8> bytes{};
29849 require(store.read(handle, Field::key, probe.prefix_size(), {bytes.data(), bytes.size()}));
29850 std::uint64_t result = 0;
29851 for (
const auto byte : bytes)
29852 result = (result << 8) | std::to_integer<unsigned char>(
byte);
29855 void put(
const Key& key, Bytes bytes = {}) {
29856 ::pineforge::detail::ScratchSpanSource
value(bytes);
29859 require(store.put(key, value, handle));
29861 void erase(
const Key& key) {
29862 const auto handle =
find(key);
29864 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
29865 "retained sequence identity is absent");
29867 require(store.erase(handle));
29869 void insert(std::uint64_t incarnation,
const PlacementSnapshot& row) {
29870 put(Key(0, row.source_id, std::nullopt, row.command_sequence, incarnation));
29871 put(Key(1, row.source_id, row.from_entry, row.command_sequence, incarnation));
29873 void remove(std::uint64_t incarnation,
const PlacementSnapshot& row) {
29874 erase(Key(0, row.source_id, std::nullopt, row.command_sequence, incarnation));
29875 erase(Key(1, row.source_id, row.from_entry, row.command_sequence, incarnation));
29877 void clear_dependencies() {
29879 Transaction transaction(*
this);
29880 const Key first(2, {});
29882 handle = seek(first)) {
29884 require(store.erase(handle));
29886 transaction.commit();
29888 void watch(
unsigned domain, std::string_view
id,
29889 std::optional<std::string_view> from, std::uint64_t minimum) {
29890 const Key
key(domain,
id, from, domain == 2 ? minimum : 0);
29892 Transaction transaction(*
this);
29893 put(key, {
reinterpret_cast<const std::byte*
>(&minimum),
sizeof minimum});
29894 transaction.commit();
29896 template<
class Erased>
29897 std::uint64_t combined_minimum(
unsigned domain, std::string_view
id,
29898 std::optional<std::string_view> from,
29899 const Erased& erased)
const {
29900 auto current = minimum(domain,
id, from);
29901 const auto historical = from ? erased.lookup(
id, *from) : erased.lookup(
id);
29902 return historical ? std::min(current, *historical) : current;
29904 template<
class Erased>
29905 void check_pair(std::string_view
id, std::string_view from,
const Erased& erased) {
29906 if (!certified)
return;
29907 const auto handle =
find(Key(3,
id, from));
29909 std::uint64_t previous =
absent;
29911 require(store.read(handle, Field::value, 0,
29912 {reinterpret_cast<std::byte*>(&previous), sizeof previous}));
29913 const auto paired = combined_minimum(1,
id, from, erased);
29914 const auto current = paired !=
absent
29915 ? paired : combined_minimum(0,
id, std::nullopt, erased);
29916 if (previous != current) { certified =
false;
return; }
29919 const Key fallback(2,
id, from, previous);
29920 const auto dependency =
find(fallback);
29925 require(store.erase(dependency));
29928 template<
class Erased>
29929 void check_fallbacks(std::string_view
id,
const Erased& erased) {
29930 if (!certified)
return;
29931 const Key first(2,
id);
29932 const auto handle = seek(first);
29933 if (!matches(handle, first, first.id_prefix_size()))
return;
29934 const auto current = combined_minimum(0,
id, std::nullopt, erased);
29935 std::array<std::byte, 8> bytes{};
29937 require(store.read(handle, Field::key, first.id_prefix_size(), {bytes.data(), bytes.size()}));
29938 std::uint64_t minimum = 0;
29939 for (
const auto byte : bytes)
29940 minimum = (minimum << 8) | std::to_integer<unsigned char>(
byte);
29941 if (minimum != current) { certified =
false;
return; }
29942 if (current == absent)
return;
29945 const Key greater(2,
id, std::nullopt, current + 1);
29946 if (matches(seek(greater), greater, greater.id_prefix_size())) certified =
false;
29950PlacementTable::SequenceIndex& PlacementTable::sequence_index()
const {
29951 if (!sequence_index_) {
29952 auto next = std::make_shared<SequenceIndex>(sequence_cache_capacity_);
29953 SequenceIndex::Transaction transaction(*next);
29956 visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& row) {
29958 next->insert(incarnation, row);
29961 transaction.commit();
29962 sequence_index_ = std::move(next);
29964 return *sequence_index_;
29967void PlacementTable::prepare_place(std::uint64_t incarnation) {
29970 const auto reserve = [](
auto& rows, std::size_t required) {
29971 if (required <= rows.capacity())
return;
29972 const auto grown = rows.capacity()
29973 + std::min(rows.capacity(), rows.max_size() - rows.capacity());
29974 rows.reserve(std::max(required, grown));
29977 reserve(old_, old_.size() + 1U);
29980 if (window_rows_ == 0 && (old_.empty() || incarnation > old_.back().first)) {
29982 }
else if (incarnation < base_) {
29983 reserve(old_, old_.size() + 1);
29985 const auto index = head_ +
static_cast<std::size_t
>(incarnation - base_);
29986 constexpr std::size_t kDenseFloor = 64;
29987 const auto span = std::max(window_.size() - head_,
29988 static_cast<std::size_t
>(incarnation - base_) + 1U);
29989 const bool dense = span <= kDenseFloor || span <= 4U * window_rows_;
29990 if (window_rows_ != 0 && !dense) {
29991 reserve(old_, old_.size() + window_rows_ + 1U);
29992 for (std::size_t slot = head_; slot < window_.size(); ++slot) {
29993 if (!window_[slot])
continue;
29995 base_ +
static_cast<std::uint64_t
>(slot - head_),
29996 std::move(window_[slot]));
30002 base_ = std::numeric_limits<std::uint64_t>::max();
30006 if (index >= window_.size())
reserve(window_, index + 1);
30010void PlacementTable::change_sequences(std::uint64_t incarnation,
30011 const PlacementSnapshot* before,
30012 const PlacementSnapshot* after) {
30013 static_assert(std::is_nothrow_move_assignable_v<PlacementSnapshot>,
30014 "placement publication must not fail after its identity update");
30018 if (!before && size_ == 0 && !sequence_index_)
return;
30019 if (before && after && before->source_id == after->source_id
30020 && before->from_entry == after->from_entry
30021 && before->command_sequence == after->command_sequence)
return;
30024 SequenceIndex::Transaction transaction(index);
30025 if (before) index.remove(incarnation, *before);
30026 if (after) index.insert(incarnation, *after);
30030 if (before) index.check_pair(before->source_id, before->from_entry, erased_minima_);
30031 index.check_pair(after->source_id, after->from_entry, erased_minima_);
30032 if (before) index.check_fallbacks(before->source_id, erased_minima_);
30033 index.check_fallbacks(after->source_id, erased_minima_);
30035 transaction.commit();
30039 sequence_index_.reset();
30044std::array<std::uint64_t, 2> PlacementTable::retained_sequences(
30045 const SourceId&
id,
const SourceId& from)
const {
30046 constexpr auto absent = SequenceIndex::absent;
30047 if (size_ == 0 && !sequence_index_)
return {
absent,
absent};
30049 return {index.minimum(0,
id), index.minimum(1,
id, from)};
30053 if (!sequence_index_)
return {sequence_queries_, sequence_rows_, 0, 0, 0};
30054 const auto stats = sequence_index_->store.stats();
30055 return {sequence_queries_, sequence_rows_, sequence_index_->operations,
30056 stats.page_reads + stats.page_writes + stats.syncs, stats.resident_bytes};
30059bool PlacementTable::exit_sequences_certified() const noexcept {
30060 return sequence_index_ && sequence_index_->certified;
30063void PlacementTable::begin_exit_sequence_certificate() {
30064 sequence_index().clear_dependencies();
30067void PlacementTable::watch_exit_sequence(
const SourceId&
id,
const SourceId& from) {
30068 auto& index = sequence_index();
30069 const auto paired = index.combined_minimum(1,
id, from, erased_minima_);
30070 const auto answer = paired != SequenceIndex::absent
30071 ? paired : index.combined_minimum(0,
id, std::nullopt, erased_minima_);
30072 index.watch(3,
id, from, answer);
30074 if (paired != SequenceIndex::absent)
return;
30075 index.watch(2,
id, from, answer);
30078void PlacementTable::certify_exit_sequences() noexcept {
30079 if (sequence_index_) sequence_index_->certified =
true;
30083 auto result = exit_group_work_;
30084 const auto sequence = placement_.sequence_work();
30085 result.sequence_queries = sequence[0];
30086 result.sequence_rows = sequence[1];
30087 result.sequence_index_operations = sequence[2];
30088 result.sequence_index_pages = sequence[3];
30089 result.sequence_index_resident_bytes = sequence[4];
30090 result.retained_placements = placement_.size();
30094bool PineExecutionAdapter::exit_group_certified(
const TradeLedger& trades)
const noexcept {
30095 const auto& certificate = exit_group_certificate_;
30096 return certificate.rows_valid && certificate.ledger == &trades
30097 && certificate.base == trades.base() && certificate.end == trades.size()
30098 && placement_.exit_sequences_certified();
30101void PineExecutionAdapter::invalidate_exit_group(std::size_t first, std::size_t count)
noexcept {
30102 auto& certificate = exit_group_certificate_;
30105 const auto boundary = certificate.start == 0 ? 0 : certificate.start - 1;
30106 if (count && first < certificate.end
30107 && (first >= boundary || count > boundary - first))
30108 certificate.rows_valid =
false;
30111void PineExecutionAdapter::certify_exit_group(
const TradeLedger& trades, std::size_t start,
bool ordered) {
30112 exit_group_certificate_.rows_valid =
false;
30113 placement_.begin_exit_sequence_certificate();
30114 for (std::size_t index = start; index < trades.size(); ++index) {
30115 ++exit_group_work_.tail_rows;
30116 const Trade& row = trades[index];
30117 placement_.watch_exit_sequence(row.exit_id, row.entry_id);
30119 placement_.certify_exit_sequences();
30120 exit_group_certificate_ = {&trades, trades.base(), start, trades.size(), ordered};
30123void PineExecutionAdapter::finish_exit_group_sort(
30124 ::pineforge::detail::CommandScratchStore& scratch,
30125 const ::pineforge::detail::ScratchStoreStats& before)
noexcept {
30126 const auto after = scratch.stats();
30127 exit_group_work_.scratch_operations += (after.page_reads - before.page_reads)
30128 + (after.page_writes - before.page_writes) + (after.syncs - before.syncs);
30131 exit_group_certificate_.rows_valid =
true;
30136namespace pineforge::source {
30139bool retirement_capacity_excessive(
const std::vector<T>& values) {
30141 return values.capacity() != 0 && values.size() <= (values.capacity() - 1) / 4;
30145void compact_retirement_set(std::vector<T>& values) {
30146 if (retirement_capacity_excessive(values)) std::vector<T>(values).swap(values);
30150void canonical_retirement_set(std::vector<T>& values,
bool compact) {
30153 if (compact) compact_retirement_set(values);
30154 std::sort(values.begin(), values.end());
30155 values.erase(std::unique(values.begin(), values.end()), values.end());
30159bool PineExecutionAdapter::capture_retirement_inputs(RetirementInputs& inputs)
const {
30160 inputs.roots.clear();
30161 inputs.askable.clear();
30162 inputs.groups.clear();
30163 inputs.bar_numbers.clear();
30164 inputs.marks.clear();
30165 const auto root = [&](std::uint64_t
value) {
30166 if (value != 0) inputs.roots.push_back(value);
30168 const auto snapshot = [&](
const PlacementSnapshot&
value) {
30169 root(
value.bracket_origin.incarnation);
30170 root(
value.paired_reversal_parent.incarnation);
30171 root(
value.preserved_by_close_all.incarnation);
30172 root(
value.projection_predecessor);
30173 root(
value.legs.target().incarnation);
30174 root(
value.reservation_growth_owner_incarnation);
30175 if (
value.sequential_group != 0) inputs.groups.push_back(
value.sequential_group);
30176 if (
value.projection_created_bar_pinned)
30177 inputs.bar_numbers.push_back(
value.projection_created_bar);
30179 const auto request = [&](
const native_order::Request&
value) {
30180 if (
const auto* wait = std::get_if<native_order::WaitForApplied>(&
value.owner)) {
30181 root(wait->parent.incarnation);
30182 }
else if (
const auto* opening = std::get_if<native_order::BindOpening>(&
value.owner)) {
30183 root(opening->opening.incarnation);
30184 }
else if (
const auto* openings = std::get_if<native_order::BindOpenings>(&
value.owner)) {
30185 for (
const auto& opening : openings->openings) root(opening.incarnation);
30188 for (
const auto& handle : live_handles_) {
30189 root(handle.incarnation);
30190 inputs.askable.push_back(handle.incarnation);
30191 const auto row = placement_.find(handle.incarnation);
30192 if (row == placement_.end())
continue;
30193 if (row->second.sequential_group != 0)
30194 inputs.groups.push_back(row->second.sequential_group);
30195 if (row->second.family == PineOrderFamily::Entry)
30196 inputs.bar_numbers.push_back(row->second.projection_created_bar);
30198 for (
const auto& cohort : cohorts_by_id_.held_rows()) {
30199 for (
const auto& opening :
cohort.second.opened) {
30200 root(opening.incarnation);
30201 inputs.askable.push_back(opening.incarnation);
30204 if (
auto* pine = pine_view_of(host_))
30205 for (
const auto& lot : pine->pyramid_entries_) root(lot.entry_incarnation);
30206 for (
const auto& handle : first_open_newborns_) root(handle.incarnation);
30207 for (
const auto& entry : live_by_source_key_) root(entry.second.incarnation);
30208 for (
const auto& leg : pending_bracket_legs_) {
snapshot(leg.snapshot);
request(leg.request); }
30209 for (
const auto& entry : pending_entries_) {
snapshot(entry.snapshot);
request(entry.request); }
30210 for (
const auto& order : delayed_market_orders_) {
snapshot(order.snapshot);
request(order.request); }
30211 for (
const auto& sell : deferred_open_marketable_sells_)
snapshot(sell.snapshot);
30212 for (
const auto& value : throttled_reopen_rearm_)
snapshot(value);
30214 for (
const auto& shadow : source_shadow_pending_)
snapshot(shadow.snapshot);
30216 for (
const auto& revival : pending_margin_revivals_)
snapshot(revival.snapshot);
30217 for (
const auto& leg : anchored_relative_legs_) {
30218 root(leg.handle.incarnation); root(leg.parent.incarnation);
request(leg.request);
30220 root(materializing_parent_.incarnation);
30221 for (
const auto* plan : {&short_seed_, &pending_short_seed_.plan}) {
30222 root(
plan->long_entry.incarnation);
30223 root(
plan->materialize_long.incarnation);
30224 root(
plan->final_short.incarnation);
30226 root(short_seed_long_candidate_.incarnation);
30227 for (
const auto mark : market_pyramid_adds_) inputs.marks.push_back(mark);
30228 if (
auto* consumer = bound_consumer()) {
30229 const auto& native = as_native_consumer(*consumer);
30230 for (
const auto& working : native.request_core().live())
30231 root(working.definition->handle.incarnation);
30232 assert(native.event_window_start() <= receipt_cursor_ + 1);
30233 native.visit_commands_after(receipt_cursor_, [&](
const native_order::CommandEvent& command) {
30234 std::visit([&](
const auto& event) {
30235 using Event = std::decay_t<
decltype(event)>;
30236 if constexpr (std::is_same_v<Event, native_order::RejectedEvent>
30237 || std::is_same_v<Event, native_order::NativeRiskEvent>) {
30239 }
else if constexpr (std::is_same_v<Event, native_order::ReplacedEvent>) {
30240 root(event.predecessor().incarnation); root(event.successor().incarnation);
30241 }
else if constexpr (std::is_same_v<Event, native_order::ReplaceRejectedEvent>) {
30242 root(event.target().incarnation);
30243 }
else if constexpr (std::is_same_v<Event, native_order::NotWorkingEvent>
30244 || std::is_same_v<Event, native_order::InvalidHandleEvent>) {
30245 root(event.target.incarnation);
30247 if (event.definition) root(event.definition->handle.incarnation);
30248 if constexpr (std::is_same_v<Event, native_order::ReservationReducedEvent>
30249 || std::is_same_v<Event, native_order::DeferredGroupAdjustmentEvent>)
30250 root(event.recipient.incarnation);
30258 const bool excess_capacity = retirement_capacity_excessive(inputs.roots)
30259 || retirement_capacity_excessive(inputs.askable)
30260 || retirement_capacity_excessive(inputs.groups)
30261 || retirement_capacity_excessive(inputs.bar_numbers)
30262 || retirement_capacity_excessive(inputs.marks);
30263 const bool compact = !retirement_gate_.certified;
30264 canonical_retirement_set(inputs.roots, compact);
30265 canonical_retirement_set(inputs.askable, compact);
30266 canonical_retirement_set(inputs.groups, compact);
30267 canonical_retirement_set(inputs.bar_numbers, compact);
30268 canonical_retirement_set(inputs.marks, compact);
30269 return excess_capacity;
30272void PineExecutionAdapter::observe_retirement_expiry(
30273 const PlacementSnapshot& value, std::int64_t interval)
noexcept {
30274 auto& gate = retirement_gate_;
30275 const bool close_age =
value.family == PineOrderFamily::Close &&
value.immediately;
30276 const bool stop_age =
value.opening &&
value.family == PineOrderFamily::Entry
30277 &&
value.projection_position_side ==
static_cast<std::int32_t
>(PositionSide::FLAT)
30278 && finite_positive(
value.exit_levels.stop) && !finite_positive(
value.exit_levels.limit);
30279 if ((close_age || stop_age) &&
value.projection_created_bar >= interval)
30280 gate.interval_expiry = std::min(gate.interval_expiry,
30281 static_cast<std::int64_t
>(
value.projection_created_bar) + 1);
30282 if ((
value.family == PineOrderFamily::Entry ||
value.family == PineOrderFamily::Order)
30283 &&
value.placement_script_open_ms >= last_broker_open_ms_)
30284 gate.broker_expiry = std::min(gate.broker_expiry,
value.placement_script_open_ms);
30287void PineExecutionAdapter::finish_retirement_phases() {
30288 const std::size_t base = detail::run_consumer(require_host()).closed_trade_prefix().base;
30289 if (exit_phase_final_ < base)
30290 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
30291 "exit-phase fold precedes the sealed prefix");
30292 for (; exit_phase_final_ < base + trade_exit_phase_.size(); ++exit_phase_final_)
30293 exit_phase_digest_ = fold_exit_phase(exit_phase_digest_,
30294 trade_exit_phase_.at(exit_phase_final_ - base));
30295 detail::run_consumer(require_host()).finish_exit_phase_restart();
30298void PineExecutionAdapter::erase_retired_rows(
const NativeDecisionContext& context) {
30299 auto& gate = retirement_gate_;
30300 if (detail::retain_retired_rows() || !bound_consumer()
30301 || placement_.size() <= live_handles_.size()) {
30303 sweep_retired_rows(context, retirement_work_);
30306 const bool excess_capacity = capture_retirement_inputs(gate.current);
30307 const auto interval =
static_cast<std::int64_t
>(context.coordinate.interval_index);
30308 const bool unchanged = gate.certified && gate.cycle == current_position_cycle_
30309 && interval >= gate.interval && interval < gate.interval_expiry
30310 && last_broker_open_ms_ >= gate.broker_open && last_broker_open_ms_ <= gate.broker_expiry
30311 && gate.current == gate.saved;
30312 if (unchanged && !excess_capacity) {
30316 const auto count = placement_.size();
30317 const auto erased = placement_.erased_rows();
30318 const auto digest = placement_.erased_digest();
30319 const auto before = retirement_validation_state();
30320 RetirementWork validation;
30321 sweep_retired_rows(context, validation);
30322 assert(placement_.size() == count && placement_.erased_rows() == erased
30323 && placement_.erased_digest() == digest
30324 &&
"unchanged retirement sweep erased a placement");
30325 assert(retired_row_scratch_.doomed.empty()
30326 &&
"unchanged retirement sweep found an erasable placement");
30327 assert(retirement_validation_state() == before
30328 &&
"unchanged retirement sweep changed placement, pin or cohort state");
30329 retirement_work_.validation_sweeps += validation.placement_sweeps;
30330 retirement_work_.validation_row_visits += validation.row_visits;
30332 finish_retirement_phases();
30336 gate.certified =
false;
30337 std::swap(gate.saved, gate.current);
30340 compact_retirement_set(gate.saved.roots);
30341 compact_retirement_set(gate.saved.askable);
30342 compact_retirement_set(gate.saved.groups);
30343 compact_retirement_set(gate.saved.bar_numbers);
30344 compact_retirement_set(gate.saved.marks);
30345 gate.interval_expiry = gate.broker_expiry = std::numeric_limits<std::int64_t>::max();
30346 const auto erased = placement_.erased_rows();
30347 sweep_retired_rows(context, retirement_work_);
30350 capture_retirement_inputs(gate.current);
30353 if (placement_.erased_rows() != erased || !retired_row_scratch_.doomed.empty())
return;
30354 if (!(gate.current == gate.saved))
return;
30355 gate.cycle = current_position_cycle_;
30356 gate.interval = interval;
30357 gate.broker_open = last_broker_open_ms_;
30358 gate.certified =
true;
30362std::array<std::uint64_t, 3> PineExecutionAdapter::retirement_validation_state()
const {
30365 BrokerStateHashSink placements;
30366 placements.u(placement_.size());
30367 placements.u(placement_.high_water());
30368 placements.u(placement_.erased_rows());
30369 placements.u(placement_.erased_digest());
30370 placement_.visit_rows([&](std::uint64_t incarnation,
const PlacementSnapshot& row) {
30371 placements.u(incarnation);
30372 placements.bytes(&row,
sizeof(row));
30373 placements.s(row.source_id); placements.s(row.from_entry);
30374 placements.s(row.comment); placements.s(row.oca_name); placements.s(row.close_first_id);
30375 row.legs.visit(placements);
30378 std::uint64_t pins = 0, cohorts = 0;
30379 for (
const auto& entry : bracket_families_) {
30380 BrokerStateHashSink row;
30381 row.u(entry.first); row.u(entry.second.settled_count()); row.u(entry.second.settled_digest());
30382 for (
const auto& handle : entry.second.working_tail()) row.u(handle.incarnation);
30383 for (
const auto& fact : entry.second.consumed_facts()) { row.u(fact.first); row.u(fact.second); }
30386 for (
const auto mark : market_pyramid_adds_) {
30387 BrokerStateHashSink row;
30391 BrokerStateHashSink witnesses;
30392 const auto& sweep = retired_row_scratch_;
30393 for (
const auto* names : {&sweep.roots, &sweep.askable_origins,
30394 &sweep.candidate_names, &sweep.first_reissues, &sweep.released}) {
30395 witnesses.u(names->size());
30396 for (
const auto value : *names) witnesses.u(value);
30398 witnesses.u(sweep.origin_pins.size());
30399 for (
const auto* pin : sweep.origin_pins)
30400 witnesses.u(
reinterpret_cast<std::uintptr_t
>(pin));
30401 for (
const auto* memo : {&sweep.unsuperseded, &sweep.reissues}) {
30402 witnesses.u(memo->size());
30403 for (
const auto& value : *memo) { witnesses.u(
value.first); witnesses.u(
value.second); }
30405 witnesses.u(sweep.reissue_high_water); witnesses.i(sweep.reissue_cycle);
30406 pins ^= witnesses.h;
30407 for (
const auto& entry : cohorts_by_id_.resident_rows()) {
30408 const auto&
facts = entry.second;
30409 BrokerStateHashSink row;
30410 row.s(entry.first); row.u(
facts.handle.value); row.i(
facts.cycle);
30411 row.u(
facts.origins.base()); row.u(
facts.origins.size()); row.u(
facts.origins.digest());
30412 row.u(
facts.origins.members().size());
30413 if (!
facts.origins.members().empty()) row.u(
facts.origins.members().front().incarnation);
30414 row.u(
facts.erased_opening_sides);
30415 for (
const auto& opening :
facts.opened) row.u(opening.incarnation);
30416 std::uint64_t units = 0;
30417 for (
const auto& value :
facts.live_units_by_origin) {
30418 BrokerStateHashSink unit;
30425 BrokerStateHashSink summary;
30426 summary.u(cohorts_by_id_.size()); summary.u(cohorts);
30427 summary.u(settled_bracket_digest_); summary.u(settled_bracket_families_.size());
30428 summary.b(retired_row_scratch_.origin_fronts_pinned);
30429 return {placements.h, pins, summary.h};
30436namespace pineforge::source::detail {
30440 const std::unordered_map<std::uint64_t, double>& live_units_by_origin)
noexcept {
30441 if (live_units_by_origin.empty() || origins.members().empty())
return {0.0, 0, 0};
30442 std::uint64_t steps = 0;
30443 std::uint8_t path = 1;
30444 if (origins.members().size() > 4 * live_units_by_origin.size() + 16) {
30447 struct Term { std::size_t position;
double units; };
30450 std::size_t mapped = 0;
30451 bool abandoned =
false;
30452 for (
const auto& unit : live_units_by_origin) {
30454 const auto* positions = origins.positions_of(unit.first);
30455 if (!positions)
continue;
30456 for (
const auto position : *positions) {
30463 buf[n++] = {position, unit.second};
30467 std::sort(buf, buf + n, [](
const Term& a,
const Term& b)
noexcept {
30468 return a.position < b.position;
30471 std::uint64_t levels = 0;
30472 for (
auto remaining = n > 0 ? n - 1 : 0; remaining; remaining >>= 1)
30474 steps +=
static_cast<std::uint64_t
>(n) * levels;
30475 double total = 0.0;
30476 for (std::size_t i = 0; i < n; ++i) {
30478 const double units = buf[i].units;
30479 if (std::isfinite(units) && units > 0.0) total += units;
30481 return {std::isfinite(total) && total > 0.0 ? total : 0.0, steps, 2};
30485 const auto mapped_count =
static_cast<std::uint64_t
>(mapped);
30486 const auto row_count =
30487 static_cast<std::uint64_t
>(live_units_by_origin.size());
30490 const auto charge_max = std::numeric_limits<std::uint64_t>::max();
30491 const auto charge_add = [charge_max](std::uint64_t left,
30492 std::uint64_t right)
noexcept {
30493 return right > charge_max - left ? charge_max : left + right;
30495 const auto charge_mul = [charge_max](std::uint64_t left,
30496 std::uint64_t right)
noexcept {
30497 return left != 0 && right > charge_max / left
30498 ? charge_max : left * right;
30500 const auto pass_count = mapped_count == charge_max
30501 ? charge_max : mapped_count + 1;
30502 const auto selector_charge =
30503 charge_mul(pass_count, charge_add(row_count, mapped_count));
30505 if (
static_cast<std::uint64_t
>(origins.members().size()) >= selector_charge) {
30508 double total = 0.0;
30509 bool have_previous =
false;
30510 std::size_t previous = 0;
30512 bool have_next =
false;
30513 std::size_t next = 0;
30514 double units = 0.0;
30515 for (
const auto& unit : live_units_by_origin) {
30517 const auto* positions = origins.positions_of(unit.first);
30518 if (!positions)
continue;
30519 for (
const auto position : *positions) {
30521 if (have_previous && position <= previous)
continue;
30522 if (!have_next || position < next) {
30525 units = unit.second;
30530 if (!have_next)
break;
30531 if (std::isfinite(units) && units > 0.0) total += units;
30532 have_previous =
true;
30535 return {std::isfinite(total) && total > 0.0 ? total : 0.0, steps, path};
30538 double total = 0.0;
30539 for (
const auto& origin : origins) {
30541 const auto unit = live_units_by_origin.find(origin.incarnation);
30542 if (unit == live_units_by_origin.end())
continue;
30543 const double units = unit->second;
30544 if (std::isfinite(units) && units > 0.0) total += units;
30546 return {std::isfinite(total) && total > 0.0 ? total : 0.0, steps, path};
30549#ifdef PINEFORGE_COHORT_EXPOSURE_FIXTURES
30550void verify_cohort_exposure(
30552 const std::unordered_map<std::uint64_t, double>& live_units_by_origin,
30553 double actual)
noexcept {
30555 double total = 0.0;
30561 bool have_previous =
false;
30562 std::size_t previous = 0;
30564 bool have_next =
false;
30565 std::size_t next = 0;
30566 double units = 0.0;
30567 for (
const auto& unit : live_units_by_origin) {
30568 const auto* positions = origins.positions_of(unit.first);
30569 if (!positions)
continue;
30570 for (
const auto position : *positions) {
30571 if (have_previous && position <= previous)
continue;
30572 if (!have_next || position < next) {
30575 units = unit.second;
30580 if (!have_next)
break;
30581 if (std::isfinite(units) && units > 0.0) total += units;
30582 have_previous =
true;
30585 const double expected = std::isfinite(total) && total > 0.0 ? total : 0.0;
30586 if (std::memcmp(&actual, &expected,
sizeof expected) != 0) std::abort();
30591 if (cache ==
nullptr ||
typeid(*cache) !=
typeid(AdapterLookupIndex))
return {};
30592 const auto& index =
static_cast<const AdapterLookupIndex&
>(*cache);
30593 LookupIndexCounts counts{
true, index.cohort_sides.size(), index.unbound.size()};
30594 std::size_t kind = 0;
30595 for (
const auto& by_origin : index.legs_by_origin) {
30596 counts.legs_by_origin_keys[kind] = by_origin.size();
30597 for (
const auto& row : by_origin)
30598 counts.legs_by_origin_ids[kind] += row.second.size();
30602 for (
const auto& row : index.immediate_closes_by_bar)
30603 counts.immediate_closes_ids += row.second.size();
30613void PlacementTable::reset_storage_work() noexcept {
30615 placement_work_ = 0;
30620void PlacementTable::grow_window(std::size_t index) {
30621 placement_work_ +=
static_cast<std::uint64_t
>(index + 1U - window_.size());
30622 window_.resize(index + 1U);
30625void PlacementTable::place_old(std::uint64_t incarnation,
30626 std::unique_ptr<PlacementSnapshot> node) {
30629 const auto at = old_.empty() || incarnation > old_.back().first
30631 : std::lower_bound(old_.begin(), old_.end(), incarnation,
30632 [
this](
const auto& entry, std::uint64_t key) {
30634 return entry.first < key;
30636 placement_work_ +=
static_cast<std::uint64_t
>(old_.end() - at) + 1U;
30637 old_.emplace(at, incarnation, std::move(node));
30640bool PlacementTable::compact_old() noexcept {
30641 if (!old_only_)
return false;
30642 if (old_.empty()) {
30644 base_ = high_water_ + 1U;
static BirthCursor point(BirthCursorDomain domain, int index, int count)
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)
const std::optional< SizingRevision > & sizing_revision() const
void retire_prefix(std::size_t count, uint64_t digest, uint64_t last_sequence)
Base with a code: throw coded<std::invalid_argument>(code, {args}, text).
coded(RunFailureCode code, const RunFailureArgs &args, const std::string &what)
bool retained_parent_first() const
ScratchStatus status() const noexcept
bool transaction_active() const noexcept
std::pair< Offset, bool > insert(const std::string &key, const Value &value)
void require_insert(const std::string &key) const
void bind_scratch(const std::shared_ptr< ScratchSlab > &slab, ScratchResource resource)
std::optional< Offset > find(const std::string &key) const
std::uint64_t size() const noexcept
std::uint64_t size() const noexcept
Iterator< false > iterator
void require_insert(const std::string &key) const
iterator find(const std::string &key)
void spill_implicitly() const
void bind_scratch(const std::shared_ptr< ScratchSlab > &slab, ScratchResource resource)
void admit(const std::string &key, bool spillable)
static constexpr std::size_t resident_row_bytes() noexcept
iterator begin() noexcept
void bind_scratch_tables(const std::shared_ptr< ScratchSlab > &slab, ScratchResource data, ScratchResource index)
void set_directory(const std::string &directory)
void resize(std::size_t count, T value=T{})
bool empty() const noexcept
std::size_t size() const noexcept
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
native_order::CohortHandle cohort_open()
Open a roster a later close can bind to.
void retire_before(std::size_t global)
void append_positions_of(const native_order::RequestHandle &handle, std::vector< std::size_t > &out) const
std::size_t size() const noexcept
const native_order::RequestHandle & operator[](std::size_t global) const
static OriginRoster from_retired(std::size_t count, std::uint64_t digest)
std::uint64_t digest() 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()
void restore_replay_risk_config(const PineReplayRiskConfig &) noexcept
std::optional< double > resolve_anchored_level(const NativeAnchoredLevelView &) const
void set_risk_max_drawdown(double value, bool percent) noexcept
friend detail::ExitScratchContext & detail::exit_scratch(const PineExecutionAdapter &)
std::optional< std::vector< std::uint32_t > > global_exit_child_ranks(const std::vector< SourceId > &ids) const
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 bind_exit_scratch(detail::ExitScratchContext &context)
void end_coof_recalc() noexcept
std::optional< NativeMarginDecision > resolve_margin_requirement(const NativeMarginRequirementView &) const
double lagged_margin_follow_up_units(const std::vector< NativeOpenLot > &lots, const SourceMarginMoney &money, double called, double fill, std::int64_t sub_bar_open_ms) const
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
CohortHeapEstimate cohort_heap_estimate() const noexcept
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
ExitGroupWork exit_group_work() const noexcept
std::uint64_t command_sequence_for_exit(const SourceId &exit_id, const SourceId &from_entry={}) const
static std::uint64_t fold_exit_phase(std::uint64_t digest, std::uint8_t phase) noexcept
void on_bar_close_before_script(const Bar &, const NativeDecisionContext &)
PineReplayRiskConfig capture_replay_risk_config() 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)
void set_auxiliary_tables_directory(const std::string &directory)
NativeRunSpec project(const PineStrategyConfig &, const StagedConfiguration &, const NativeBeginArgs &, NativePathOrder path_order=NativePathOrder::Auto, std::vector< NativeInstrumentFeed > *instrument_feeds=nullptr) const
double source_margin_fill_price(double fire, bool close_is_buy) const
std::uint8_t exit_phase_value(std::size_t trade_index) const
PositionTailWork position_tail_work() const noexcept
void set_id_tables_directory(const std::string &directory)
void preflight_id_pair_precommit(const NativePrecommitView &) const
std::int64_t chart_day_key(std::int64_t timestamp_ms) const noexcept
void set_risk_max_intraday_loss(double value, bool percent) noexcept
double source_margin_units(const SourceMarginMoney &, bool opening_checkpoint, bool gated=true, double print=std::numeric_limits< double >::quiet_NaN()) const
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)
RetirementPopulation retirement_population() const
void enable_intraday_cap() noexcept
std::vector< FixturePendingSnapshot > fixture_pending_snapshots() const
void permute_exit_phase_rows(std::size_t start, std::size_t count, const std::function< std::size_t(std::size_t)> &source_at)
compat::pine::OrderPriority priority
bool take_intraday_loss_relabel(std::uint64_t ordinal) noexcept
bool core_sizes_default_opening(bool is_long) const
void on_applied(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &)
bool is_open_phase_exit(std::size_t trade_index) const
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)
friend class PineStrategyHost
std::optional< double > resolve_margin_call_units(const NativeMarginCallView &) const
bool margin_check_allowed(const NativeMarginCheckPoint &) const
void order(const SourceId &id, bool is_long, double qty, double limit_price=std::numeric_limits< double >::quiet_NaN(), double stop_price=std::numeric_limits< double >::quiet_NaN(), const std::string &oca_name={}, int oca_type=0)
void anchor_relative_exits()
bool intrabar_sample_checked(const NativeMarginCheckPoint &) const
bool calc_on_order_fills() const noexcept
PineCancellationReceipt * fixture_mutable_cancellation(int index) noexcept
std::uint64_t fixture_auxiliary_disk_bytes() const noexcept
int projection_bar_index(const NativeDecisionContext &) const noexcept
int short_seed_collision_role_v1(native_order::RequestHandle) const noexcept
void on_bar_close(const Bar &, const NativeDecisionContext &)
bool other_exit_reaches(double price, bool closing_long, const SourceId &exit_id, const native_order::RequestHandle &filled) const
void fixture_use_memory_auxiliary_tables()
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)
static std::uint64_t fold_dropped_close(std::uint64_t digest, const DroppedCloseReceipt &receipt) noexcept
MarketAdmissionJournal admission_journal
bool immediate_calculation_close(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &) const noexcept
void set_cache_policy_for_run(const CacheDefaults &policy)
bool input_is_observed_ticks() const noexcept
PineExecutionAdapter adapter_
double default_entry_qty(double fill_price) const
void set_auxiliary_tables_directory(const std::string &directory)
PlacementSnapshot & at(std::uint64_t incarnation)
std::uint64_t command_sequence_for(const std::string &source_id, const std::string &from_entry) const
std::array< std::uint64_t, 5 > sequence_work() const noexcept
void bind_auxiliary_scratch_tables(const std::shared_ptr<::pineforge::detail::ScratchSlab > &)
std::uint64_t auxiliary_disk_bytes() const noexcept
void fixture_use_memory_auxiliary_tables()
ScratchStatus status() const noexcept
ExitScratchError(ScratchStatus status)
void put(std::uint64_t major, std::uint64_t value, std::uint64_t ordinal=0)
std::uint64_t find(std::uint64_t major) const
static constexpr std::uint64_t absent
void each(Visit visit) const
void ordered(Key key, Visit visit) const
std::uint64_t scope() const noexcept
void push_back(const T &value)
std::size_t size() const noexcept
ExitScratchContext & context() const noexcept
void set(std::size_t index, const T &value)
virtual native_order::Owner freeze_openings(const native_order::RunIdentity &, std::int64_t cycle, std::uint64_t count, void *, OpeningReader)=0
std::vector< Field > fields(const Draft &draft)
uint64_t sequence(const Event &event)
std::variant< CommandEvent, ReviewEvent, SizingEvent > Event
bool operator==(const OpeningOwner &owner, const OpeningOwner &other)
void require(bool condition, const char *message, int status=PF_SETTINGS_INVALID_ARGUMENT)
std::string quote(const std::string &value)
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 kernel_trail_stop_level(double best, double distance, bool is_buy, double tick) 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 &)
constexpr double kTrailPointsCeilMintickScale
bool historical_cascade_reach(HistoricalBirthReach reach) noexcept
ExitActivationPolicy select_exit_activation(const ExitActivationRequest &request, double stop, double limit, const ExitActivationContext &context)
ScratchStatus find(CommandScratchStore &store, std::uint64_t key, ScratchRecordInfo &out) noexcept
ScratchStatus put(CommandScratchStore &store, std::uint64_t key, const ScratchByteSource &value) noexcept
std::uint64_t add(std::uint64_t left, std::uint64_t right)
void bind_id_pair_scratch_tables(source::PineExecutionAdapter &, const std::shared_ptr< ScratchSlab > &)
std::uint64_t ScratchHandle
constexpr ScratchHandle scratch_no_handle
void bind_tree_scratch_tables(source::PineExecutionAdapter &, const std::shared_ptr< ScratchSlab > &)
void rethrow_carried_scratch_failure(const ReportScratchIoError *failure)
void raise_scratch_io_error(int error, const char *operation)
void clear_exposure_storage(Container &container)
void release_empty_exposure_capacity(Container &container)
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
bool entry_stop_first_touch(const Bar &bar, bool high_first, double stop_level, bool is_long, double *out_pos)
NativeRunSpecValidation normalize_native_run_spec(NativeRunSpec &spec, const NativeBarViews &views) noexcept
Existing full validation and zero-fee normalization over effective data.
constexpr NativeCivilDate native_civil_date(int64_t days) noexcept
NativePathPhase
Which leg of a modeled OHLC walk a point sits on.
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< Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort > Owner
std::variant< AcceptedEvent, RejectedEvent, ReplacedEvent, ReplaceRejectedEvent, CancelledEvent, NotWorkingEvent, InvalidHandleEvent, NoEffectEvent, MatchRejectedEvent, ExecutionAppliedEvent, CloseBoundEvent, ActivatedEvent, ReservationReducedEvent, DeferredGroupAdjustmentEvent, QuantityBoundEvent, ArmedEvent, TermsResolvedEvent, MarginCallEvent, NativeRiskEvent > CommandEvent
Every alternative of one request's history, as one value.
std::variant< Market, Limit, Stop, StopLimit, Trail > Trigger
std::variant< Flatten, Reduce, Transact, ReverseTo, HostSized, Sized > OrderIntent
What a request does.
std::variant< NoGroup, Member > Group
execution::Flatten Flatten
order_action::Transact Transact
A signed book transaction: finite nonzero units on the side their sign names.
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.
NativeRunSpecField
Which field a validation refused, in deterministic first-error order.
std::optional< Plan > plan(double signed_position, Reduce request) noexcept
bool carry_reissue_bindings() noexcept
constexpr std::size_t kCohortFoldStack
PineCallbackLifecycleRule
@ DeclinedReversalReissueRevives
@ TrailPointsMintickTolerance
static constexpr std::uint64_t source_key_test_mask(std::uint64_t value) noexcept
double replay_position_tail(std::size_t end, std::size_t base, Read &&read, const PositionTailSpill *spill, std::int64_t barrier, Include &&include, const std::string &from_entry, PineExecutionAdapter::PositionTailWork &work, Diagnostics={})
MarginRuleSwitches & margin_rule_switches() noexcept
void placement_audit_erase(const PlacementTable *table, std::uint64_t incarnation, PlacementSnapshot &&row)
RunState run_state(const NativeStrategyHost &host)
NativePhysicalPosition run_position(const NativeStrategyHost &host)
LookupIndexCounts probe_lookup_index(const NativeHostCache *cache) noexcept
void take_run_facts(NativeExecutionConsumer &consumer, const PineExecutionAdapter &adapter, std::uint64_t run)
void exit_scratch_lots(ExitScratchContext &scratch, const NativeStrategyHost &host, double mark, Visit visit)
MarginOpeningSwitches & margin_opening_switches() noexcept
bool retain_retired_rows() noexcept
const NativeCurrentPointView * callback_point(const NativeStrategyHost &host)
ScriptRuleSwitches & script_rule_switches() noexcept
ExitScratchVersions & exit_scratch_versions(ExitScratchContext &context)
MarginScheduleSwitches & margin_schedule_switches() noexcept
ExitScratchContext & exit_scratch(const PineExecutionAdapter &)
void exit_scratch_check(ScratchStatus status)
std::uint64_t retired_rows_erased() noexcept
bool exit_scratch_equal(const ScratchByteSource &left, const ScratchByteSource &right)
ScratchKeyOrder exit_scratch_key_order(ExitScratchOrderStats *stats=nullptr) noexcept
ExitScratchRows< T, ExitScratchRowMode::bounded_batch > ExitScratchBatchRows
void exit_scratch_working(ExitScratchContext &scratch, const NativeStrategyHost &host, Visit visit)
ExitBindingRuleSwitches & exit_binding_rule_switches() noexcept
void set_carry_reissue_bindings(bool carry) noexcept
void placement_audit_forget(const PlacementTable *table) noexcept
void set_retain_retired_rows(bool retain) noexcept
PoocCloseFillViewSwitches & pooc_close_fill_view_switches() noexcept
ScratchSpanSource exit_scratch_text(const std::string &text) noexcept
bool pine_callback_lifecycle_rule(PineCallbackLifecycleRule rule) noexcept
const std::vector< std::pair< std::uint64_t, const PlacementSnapshot * > > * placement_audit_tombstones(const PlacementTable *table)
const NativeCurrentPointView * live_callback_point(const NativeStrategyHost &host)
const NativeRunSpec * run_spec(const NativeStrategyHost &host)
NativeExecutionConsumer & run_consumer(const NativeStrategyHost &host)
const char kPineRunCacheKind
void placement_audit_scan_hit(const PlacementTable *table, std::uint64_t incarnation, unsigned line) noexcept
constexpr int kPineCallbackLifecycleRuleCount
CohortExposureFold fold_cohort_exposure(const OriginRoster &origins, const std::unordered_map< std::uint64_t, double > &live_units_by_origin) noexcept
bool skip_quiet(QuietHook hook, bool quiet) noexcept
void set_pine_callback_lifecycle_rule(PineCallbackLifecycleRule rule, bool on) noexcept
void exit_scratch_failure(ScratchStatus)
void placement_audit_miss(const PlacementTable *table, std::uint64_t incarnation, unsigned line) noexcept
constexpr const char * order_limit_price
constexpr const char * exit_trail_points
constexpr const char * entry_qty
constexpr const char * order_stop_price
constexpr const char * entry_stop_price
constexpr const char * entry_limit_price
constexpr const char * exit_id
constexpr const char * entry_id
constexpr std::uint32_t kCoofLoopGuard
constexpr char kMarginCallLabel[]
void throw_strategy_value_invalid(const char *field, StrategyValueReason reason)
The coded failure of a source-owned invalid actionable value.
constexpr char kIntradayLossComment[]
std::unique_ptr< NativeLowerPathSource > make_magnifier_path_source(const Bar *bars, int n, const std::string &input_tf, const std::string &script_tf, const std::string &session, const std::string &timezone)
The same logical path without retaining the input or a whole-run path.
bool modeled_pine_input(NativePriceProvenance provenance) noexcept
std::optional< double > checked_mul(double left, double right) noexcept
const char * instrument_feed_field_name(NativeRunSpecField field) noexcept
std::optional< double > checked_add(double left, double right) noexcept
RunFailureValue run_spec_field_failure(NativeRunSpecField field) noexcept
std::vector< Bar > tradingview_magnifier_bars(const Bar *bars, int n, const std::string &input_tf, const std::string &script_tf, const std::string &session, const std::string &timezone)
The lower-timeframe path TradingView's bar magnifier walks for every chart bar of a run whose chart b...
std::optional< double > checked_div(double left, double right) noexcept
int tf_ratio(const std::string &input_tf, const std::string &target_tf)
Compute how many input bars fit into one target bar.
RunFailureValue make_run_failure(RunFailureCode code, const RunFailureArgs &args) noexcept
Validate args against code's registry entry and canonicalize them.
@ strategy_settings_rejected
@ symbol_metadata_rejected
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...
static void submit(struct host_state *state, uint32_t intent, double value, uint32_t trigger, double price, const char *label)
#define PINEFORGE_AUDIT_PLACEMENT_SCAN(table,...)
#define PINEFORGE_MARGIN_REVIVAL_OBSERVE(before, rows, event, context, units, cycle, selected)
#define PINEFORGE_FULL_CLOSE_EXIT_CALL()
#define PINEFORGE_LOOKUP_INDEX_WORK(site, owner, run, count)
#define PINEFORGE_RETIREMENT_OBSERVE(rows, cycle, askable, roots, candidates, first)
#define PINEFORGE_POSITION_TAIL_DIAGNOSTICS
#define PF_RARE(condition)
#define PINEFORGE_FULL_CLOSE_EXIT_ORACLE()
#define PINEFORGE_TAIL_WORK(site, count)
#define PF_ROSTER_PASS_WORK(field)
CacheCapacity consumed_cycle_pages
CacheCapacity cohort_id_pages
CacheCapacity retained_sequence_pages
CacheCapacity erased_minima_pages
CacheCapacity exit_rank_pages
A validated code and its canonical arguments: the JSON object with its keys sorted and no whitespace,...
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.
bool magnifier_volume_weighted
MagnifierDistribution magnifier_distribution
int magnifier_volume_weighted_min_samples
Read-only owning-value facts for one candidate.
Ephemeral factual view of one kernel-issued liquidation before its units are fixed.
NativePhysicalPosition position
native_order::MatchCursor cursor
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
NativePhysicalPosition position
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.
execution::Status settlement_readiness
native_order::RequestHandle target
native_order::MatchCursor cursor
native_order::DefinitionRef definition
double inspected_closed_units
double inspected_opened_units
execution::AccountEffectProjection account
One accepted realtime print before native matching at its current decision point.
NativeDecisionContext decision
double resulting_abs_notional
double signed_units_after
Everything the kernel knows about WHERE a point is, as one owning value: its event ordinal,...
NativePathPhase path_phase
int64_t effective_time_ms
NativePriceProvenance provenance
NativeDriverStatistics driver_statistics
int64_t script_bar_open_ms
NativeCoordinate coordinate
bool intrabar_path_enabled
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,...
std::size_t closed_trade_count
const Request & request() const noexcept
const RequestHandle & handle() const noexcept
std::optional< double > units
ExecutionGridPolicy grid_policy
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.
Replacement behaviour that is not expressible in the successor request.
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...
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::vector< NativeInstrumentFeed > instrument_feeds
Opt-in feeds of other instruments.
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
NativeEventRetention event_retention
What native_events() can still return; see NativeEventRetention.
NativeReportPolicy report_policy
std::size_t resident_rows
std::size_t bytes_per_row
std::size_t live_unit_bytes
std::size_t directory_bytes
std::uint64_t retired_exit_rows
std::uint64_t prefix_sweeps
bool process_orders_on_close
bool close_entries_rule_any
bool retained_parent_topology
double signal_close_mc_remaining_qty
MarketAdmissionDraft market_admission
bool terms_priced_reverse
std::uint64_t recreated_after_named_cancelled_entry_incarnation
std::int32_t signal_close_mc_bar
std::uint64_t named_cancel_surviving_exit_incarnation
bool replacement_predecessor_market
std::uint64_t per_entry_origin
bool projection_opposite_market_predecessor
bool reservation_deferred_to_pending_entry
bool projection_over_pyramiding
std::uint64_t projection_predecessor
std::uint64_t command_sequence
bool affordability_policy_active
PineSizingSnapshot sizing
std::uint8_t sequential_rank
std::int64_t placement_cycle
bool signal_price_refused
bool stop_limit_activated
PineExitLevels exit_levels
std::int32_t projection_created_bar
native_order::RequestHandle paired_reversal_parent
native_order::RequestHandle bracket_origin
std::uint64_t sequential_group
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::uint64_t signal_close_mc_entry_incarnation
bool projection_created_bar_pinned
std::uint64_t source_sequence
bool fixed_exit_reservation
std::uint32_t close_batch_calls
double projection_tv_carry_qty
bool frozen_market_instruction
std::int64_t placement_script_open_ms
bool projection_after_close
double retained_trail_best
std::uint64_t command_ordinal
bool affordability_close_only
std::uint64_t signal_close_mc_fill_seq
double projection_remaining_qty
double trail_activation_level
std::uint64_t reservation_growth_owner_incarnation
native_order::RequestHandle preserved_by_close_all
bool affordability_keep_mc_close_surplus
bool has_full_entry_bracket
double frozen_reversal_transaction
native_order::RequestHandle final_short
std::string chart_timezone
std::optional< double > quantity_grid
bool resting_stop_entry_survives_close
bool pending_bound_exit_survives_flat
CommandScratchStore & store
std::size_t immediate_closes_keys
bool open_marketable_exit_first
bool void_flat_global_exit
bool close_call_gate_at_print
bool point_order_trailing_exits
bool close_point_reversal
bool close_first_admission
bool slipped_signal_admission
bool point_fills_before_margin
bool long_open_close_checks
bool whole_share_lagged_follow_up
bool lagged_short_follow_up
static void preserve(std::shared_ptr< PositionTailSpill > &spill, const TradeLedger &trades, std::size_t end, std::int64_t position_open, const std::string &directory, std::size_t cache_bytes=PositionTailSpill::kWindowBytes)
bool coof_forced_fill_at_waypoint