5#include "../engine_internal.hpp"
7#include "../timezone.hpp"
8#include "../native_execution_consumer.hpp"
25 return std::holds_alternative<native_order::Stop>(trigger)
26 || std::holds_alternative<native_order::Limit>(trigger)
27 || std::holds_alternative<native_order::StopLimit>(trigger);
32void set_entry_fill_excursion_masks(
PyramidEntry& pe,
const Bar& bar,
double fill_price) {
33 double fill_pos = 0.0;
34 if (!internal::first_touch_position(bar, fill_price, &fill_pos))
return;
35 const bool high_first = internal::bar_path_uses_high_first(bar);
36 const double high_pos = high_first ? 1.0 : 2.0;
37 const double low_pos = high_first ? 2.0 : 1.0;
38 pe.skip_entry_bar_high = (high_pos < fill_pos);
39 pe.skip_entry_bar_low = (low_pos < fill_pos);
48void replace_masked_entry_bar_extremes(std::vector<PyramidEntry>& lots,
PositionSide side,
51 if (!std::isfinite(bar.high) || !std::isfinite(bar.low) || !std::isfinite(bar.close))
54 for (
auto& pe : lots) {
55 if (pe.entry_bar_index !=
bar_index)
continue;
56 if (!pe.skip_entry_bar_high && !pe.skip_entry_bar_low)
continue;
57 const double pe_hi = pe.skip_entry_bar_high ? pe.price : bar.high;
58 const double pe_lo = pe.skip_entry_bar_low ? pe.price : bar.low;
59 const double fav_px = is_long ? pe_hi : pe_lo;
60 const double adv_px = is_long ? pe_lo : pe_hi;
61 const double favorable = is_long ? (fav_px - pe.price) * pe.qty
62 : (pe.
price - fav_px) * pe.qty;
63 const double adverse = is_long ? (pe.price - adv_px) * pe.qty
64 : (adv_px - pe.price) * pe.qty;
65 const double closing = is_long ? (bar.close - pe.price) * pe.qty
66 : (pe.price - bar.close) * pe.qty;
67 pe.max_runup = std::max(0.0, std::max(favorable, closing));
68 pe.max_drawdown = std::max(0.0, std::max(adverse, -closing));
72[[noreturn]]
void reject_begin_bar(
int index,
const char* field,
const char*
detail) {
73 throw std::invalid_argument(
74 "bar[" + std::to_string(index) +
"]." + field + (
detail ?
detail :
""));
82 if (args.n < 0)
throw std::invalid_argument(
"bar count must be non-negative");
83 if (args.n > 0 && args.bars ==
nullptr)
84 throw std::invalid_argument(
"bars must be non-null for a nonempty array");
85 for (
int i = 0; i < args.n; ++i) {
86 const Bar& bar = args.bars[i];
87 if (!std::isfinite(bar.open)) reject_begin_bar(i,
"open",
" must be finite");
88 if (!std::isfinite(bar.high)) reject_begin_bar(i,
"high",
" must be finite");
89 if (!std::isfinite(bar.low)) reject_begin_bar(i,
"low",
" must be finite");
90 if (!std::isfinite(bar.close)) reject_begin_bar(i,
"close",
" must be finite");
92 if (bar.timestamp < 0)
93 reject_begin_bar(i,
"timestamp",
" must be non-negative");
94 if (bar.open < 0.0) reject_begin_bar(i,
"open",
" must be non-negative");
95 if (bar.high < 0.0) reject_begin_bar(i,
"high",
" must be non-negative");
96 if (bar.low < 0.0) reject_begin_bar(i,
"low",
" must be non-negative");
97 if (bar.close < 0.0) reject_begin_bar(i,
"close",
" must be non-negative");
98 if (!std::isfinite(bar.volume) || bar.volume < 0.0)
99 reject_begin_bar(i,
"volume",
" must be non-negative finite");
100 }
else if (!std::isnan(bar.volume)
101 && (!std::isfinite(bar.volume) || bar.volume < 0.0)) {
102 reject_begin_bar(i,
"volume",
" must be non-negative finite or NaN (unavailable)");
104 if (bar.low > std::min(bar.open, bar.close))
105 reject_begin_bar(i,
"low",
" must not exceed open or close");
106 if (bar.high < std::max(bar.open, bar.close))
107 reject_begin_bar(i,
"high",
" must not be below open or close");
109 const std::int64_t previous = args.bars[i - 1].timestamp;
110 if (bar.timestamp <= previous)
111 reject_begin_bar(i,
"timestamp",
" must be strictly increasing");
113 && bar.timestamp > std::numeric_limits<std::int64_t>::max() + previous) {
114 reject_begin_bar(i,
"timestamp",
" delta exceeds int64 range");
118 if (args.is_stream && args.n > 0
119 && (!std::isfinite(args.bars[args.n - 1].close)
120 || args.bars[args.n - 1].close <= 0.0)) {
121 throw std::invalid_argument(
"stream warmup final close must be finite and positive");
142 host_mutation_guard_inert_ =
true;
145std::uint64_t source::PineStrategyHost::adapter_event_high_water(
152std::uint64_t source::PineStrategyHost::adapter_terminal_receipt_high_water(
156 .terminal_receipt_high_water();
164 const std::uint64_t
execution = last_script_continuation_valid_
165 ? last_script_continuation_hash_
166 : execution_consumer().continuation_hash();
167 return broker_state_hash_from_execution_hash(
execution);
171 return compute_liquidation_price();
174double source::PineStrategyHost::compute_liquidation_price()
const {
176 const double point_value = syminfo_.pointvalue;
177 const double quantity = position_qty_;
178 if (!(quantity > 0.0) || !(point_value > 0.0))
return na<double>();
181 ? config_.margin_long : config_.margin_short;
182 const double denominator = (margin_pct / 100.0) - direction;
183 if (std::abs(denominator) < 1e-12)
return na<double>();
184 const double equity_basis =
185 (initial_capital_ + net_profit_sum_) / active_account_currency_fx();
187 (equity_basis / (quantity * point_value) - direction * position_entry_price_)
189 if (syminfo_mintick_ > 0.0) {
191 ? std::ceil(liquidation / syminfo_mintick_) * syminfo_mintick_
192 : std::floor(liquidation / syminfo_mintick_) * syminfo_mintick_;
197PineStrategyConfig source::PineStrategyHost::apply_overrides(
198 PineStrategyConfig config,
const StrategyOverrides& overrides) {
199 if (!std::isnan(overrides.initial_capital)) config.initial_capital = overrides.initial_capital;
200 if (!std::isnan(overrides.commission_value)) config.commission_value = overrides.commission_value;
201 if (!std::isnan(overrides.default_qty_value)) config.default_qty_value = overrides.default_qty_value;
202 if (overrides.pyramiding >= 0) config.pyramiding = overrides.pyramiding;
203 if (overrides.slippage >= 0) config.slippage = overrides.slippage;
204 if (overrides.commission_type >= 0) config.commission_type = overrides.commission_type;
205 if (overrides.default_qty_type >= 0) config.default_qty_type = overrides.default_qty_type;
206 if (overrides.process_orders_on_close >= 0)
207 config.process_orders_on_close = overrides.process_orders_on_close != 0;
208 if (overrides.calc_on_order_fills >= 0)
209 config.calc_on_order_fills = overrides.calc_on_order_fills != 0;
210 if (overrides.close_entries_rule >= 0)
211 config.close_entries_rule_any = overrides.close_entries_rule != 0;
218 staged.syminfo.
mintick = syminfo_mintick_;
219 staged.inputs = inputs_;
220 staged.chart_timezone = chart_timezone_;
221 staged.account_fx = account_currency_fx_;
222 staged.account_fx_effective_from_ms = account_currency_fx_timestamps_;
223 staged.account_fx_per_quote = account_currency_fx_rates_;
224 if (std::isfinite(qty_step_) && qty_step_ > 0.0) staged.quantity_grid = qty_step_;
232 abort_requested_.store(
false, std::memory_order_relaxed);
233 validate_source_begin_bars(args);
236 syminfo_mintick_ = syminfo_.
mintick;
237 if (std::isfinite(syminfo_.qty_step) && syminfo_.qty_step > 0.0)
238 qty_step_ = syminfo_.qty_step;
242 if (args.
is_stream && native_security_feed_enabled()) {
243 throw std::runtime_error(
244 "native request.security feed supports historical runs only");
248 std::string effective_input = args.
input_tf;
249 if (effective_input.empty() && args.
n >= 2 && args.
bars !=
nullptr)
251 const std::string effective_script = args.
script_tf.empty()
254 if (!effective_input.empty() && !effective_script.empty()
255 &&
tf_ratio(effective_input, effective_script) == -2) {
256 throw std::runtime_error(
257 "script timeframe must be coarser than or equal to input timeframe: requested script_tf "
258 + effective_script +
" from input timeframe " + effective_input);
260 }
catch (
const std::runtime_error&) {
268 throw std::runtime_error(
269 "native stream requires close-only calculation; calc_on_order_fills is unsupported");
272 throw std::runtime_error(
"native stream cannot use historical probe/tail overrides");
277 effective = apply_overrides(effective, *overrides);
281 throw std::logic_error(
282 "timestamped account-currency FX does not support calc_on_order_fills");
284 throw std::logic_error(
285 "timestamped account-currency FX is not supported with bar magnifier");
288 adapter_.set_receipt_high_water_readers(&PineStrategyHost::adapter_event_high_water,
289 &PineStrategyHost::adapter_terminal_receipt_high_water);
290 if (args.
n > 0 &&
static_cast<std::size_t
>(args.
n)
291 <= std::numeric_limits<std::size_t>::max() / 4U) {
292 as_native_consumer(execution_consumer()).reserve_driver_log(
293 static_cast<std::size_t
>(args.
n) * 4U);
300 if (args.
n > 0 &&
static_cast<std::size_t
>(args.
n)
301 <=
adapter_.placement_.max_size() / 2U) {
302 adapter_.placement_.reserve(
static_cast<std::size_t
>(args.
n) * 2U);
304 adapter_.set_configuration(effective);
305 adapter_.set_staged_configuration(staged);
311 ? NativePathOrder::HighFirst
312 : (path_order_mode_ == 2 ? NativePathOrder::LowFirst
313 : NativePathOrder::Auto);
314 adapter_.set_path_order(path_order);
316 const auto setup = configure_native(spec);
317 if (setup.status != NativeSetupStatus::Applied)
318 throw std::logic_error(
"Pine native adapter failed to configure projected run spec");
330 }
catch (
const std::exception& error) {
332 last_error_ = error.what();
335 last_error_ =
"unknown error during Pine script preparation";
339void source::PineStrategyHost::capture_script_continuation_hash() {
340 last_script_continuation_hash_ = execution_consumer().continuation_hash();
341 last_script_continuation_valid_ =
true;
342 if (broker_state_hash_recording_ && !broker_state_hashes_.empty()) {
343 broker_state_hashes_.back() = broker_state_hash();
350 if (native_state().phase == NativeRunPhase::Realtime)
351 stream_warmup_mode_ =
false;
356 if (
scheduler_.terminal_source_bar()) capture_script_continuation_hash();
362 if (native_state().phase == NativeRunPhase::Realtime)
363 stream_warmup_mode_ =
false;
367 const int sample_index =
scheduler_.bar_magnifier_enabled()
371 pyramid_entries_, position_side_, sample_index, tick);
380 bar_magnifier_enabled_ =
scheduler_.bar_magnifier_enabled();
381 diag_magnifier_sub_bars_processed_ = bar_magnifier_enabled_
383 diag_magnifier_sample_ticks_processed_ = bar_magnifier_enabled_
392 bar_magnifier_enabled_ =
scheduler_.bar_magnifier_enabled();
393 diag_magnifier_sub_bars_processed_ = bar_magnifier_enabled_
395 diag_magnifier_sample_ticks_processed_ = bar_magnifier_enabled_
397 adapter_.observe_terminal_receipts();
399 const int sample_index =
scheduler_.bar_magnifier_enabled()
403 pyramid_entries_, position_side_, sample_index, bar);
404 replace_masked_entry_bar_extremes(
405 pyramid_entries_, position_side_, sample_index, bar);
408 adapter_.on_bar_close(bar, context);
409 if (
adapter_.config_.slippage > 0) {
410 for (
auto& lot : pyramid_entries_) {
412 const auto found =
adapter_.placement_.find(lot.entry_incarnation);
413 if (found !=
adapter_.placement_.end()) {
414 const auto& snap = found->second;
416 && std::isfinite(snap.exit_levels.stop) && snap.exit_levels.stop > 0.0
417 && !std::isfinite(snap.exit_levels.limit);
418 if (pure_stop_entry) {
420 if (lot.price > bar.
high && std::isfinite(bar.
low) && bar.
low > 0.0) {
421 lot.max_drawdown = std::max(lot.max_drawdown, (lot.price - bar.
low) * lot.qty);
422 }
else if (lot.price < bar.
low && std::isfinite(bar.
high) && bar.
high > 0.0) {
423 lot.max_drawdown = std::max(lot.max_drawdown, (bar.
high - lot.price) * lot.qty);
432 scheduler_record_range_end(bar);
434 const bool recording = broker_state_hash_recording_ && !broker_state_hashes_.empty();
438 && stream_phase_ == StreamPhase::REALTIME;
439 if (recording || last_batch || stream_script) {
445 capture_script_continuation_hash();
454 const int source_index =
scheduler_.source_bar_index_for(context);
455 for (
auto& lot : pyramid_entries_) {
458 lot.entry_bar_index = source_index;
461 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
462 const std::size_t index =
event.first_trade_index + i;
463 if (index >= trades_.size())
continue;
464 trades_[index].exit_bar_index = source_index;
470 const bool pine_priced = p !=
adapter_.placement_.end()
471 && ((std::isfinite(p->second.exit_levels.stop) && p->second.exit_levels.stop > 0.0)
472 || (std::isfinite(p->second.exit_levels.limit) && p->second.exit_levels.limit > 0.0));
475 const Bar& mask_bar = current_bar_;
476 for (
auto& lot : pyramid_entries_) {
478 set_entry_fill_excursion_masks(lot, mask_bar, lot.price);
481 for (
auto& lot : pyramid_entries_) {
483 lot.skip_entry_bar_high =
true;
484 lot.skip_entry_bar_low =
true;
490 const bool market_add = !pine_priced &&
event.closed_units == 0.0
494 && std::any_of(pyramid_entries_.begin(), pyramid_entries_.end(),
496 return lot.entry_incarnation != event.handle().incarnation;
508 if (broker_fill_event_seq_ == std::numeric_limits<std::uint64_t>::max())
509 throw std::overflow_error(
"source broker fill sequence exhausted");
510 ++broker_fill_event_seq_;
520 bool changed =
false;
521 for (
auto it = pyramid_entries_.begin(); it != pyramid_entries_.end(); ) {
522 if (it->qty <= internal::kQtyEpsilon) {
523 it = pyramid_entries_.erase(it);
529 if (pyramid_entries_.empty() || position_qty_ <= internal::kQtyEpsilon) {
530 reset_position_state_to_flat();
531 }
else if (changed) {
532 double total_qty = 0.0;
533 double weighted_price = 0.0;
534 for (
const auto& pe : pyramid_entries_) {
536 weighted_price += pe.price * pe.qty;
538 position_qty_ = total_qty;
539 position_entry_price_ = weighted_price / total_qty;
540 position_entry_count_ =
static_cast<int>(pyramid_entries_.size());
543 adapter_.on_applied(event, context);
546 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
547 const std::size_t index =
event.first_trade_index + i;
548 if (index >= trades_.size())
continue;
549 trades_[index].exit_id.clear();
559 for (std::size_t i = 0; i <
event.closed_trade_count; ++i) {
560 const std::size_t index =
event.first_trade_index + i;
561 if (index >= trades_.size())
continue;
562 Trade& row = trades_[index];
565 }
else if (row.
exit_id.empty()) {
572 project_short_seed_report_rows(event);
578 if (!
scheduler_.coof_recalculation_due(event, context, *
this))
579 record_applied_range_end();
582void source::PineStrategyHost::record_applied_range_end() {
583 if (scheduler_.terminal_source_bar() || barstate_islast_) {
584 const Bar terminal = scheduler_.current_script_bar()
585 ? *scheduler_.current_script_bar() : current_bar_;
586 scheduler_record_range_end(terminal);
594 if (reason != NativeCalculationReason::OrderFill) {
604 if (!
scheduler_.coof_recalculation_due(*cause, context, *
this))
return;
605 scheduler_.recalculate(*cause, context, *
this);
606 record_applied_range_end();
611 return adapter_.resolve_terms(facts);
616 return adapter_.margin_check_allowed(point);
621 return adapter_.resolve_margin_requirement(view);
626 return adapter_.resolve_margin_call_units(view);
631 return adapter_.resolve_anchored_level(view);
641 const auto& trigger = view.
definition->request.trigger;
642 priced = priced_opening_trigger(trigger)
643 || std::holds_alternative<native_order::Trail>(trigger);
656 double trail_ticks = std::numeric_limits<double>::quiet_NaN();
671 (!std::isnan(trail_ticks) && std::isfinite(view.
raw_price)
674 : std::numeric_limits<double>::quiet_NaN();
683 }
else if (trail_ticks == 0.0 && std::isfinite(view.
resolved_price)
685 const double slip =
config_.slippage * syminfo_.mintick;
687 physical_position().signed_units > 0.0 ? view.
resolved_price + slip
699 const auto* touch = std::get_if<native_order::Limit>(&view.
definition->request.trigger);
700 if (touch && touch->fill_through) {
705 const double slip =
config_.slippage * syminfo_.mintick;
716 return adapter_.validate_precommit(view);
749 const double carried_favorable = unsampled_entry_bar ? 0.0 : facts.
carried_favorable;
750 const double carried_adverse = unsampled_entry_bar ? 0.0 : facts.
carried_adverse;
751 owned.
favorable = std::max(carried_favorable * slice, fill_fav);
752 owned.
adverse = std::max(carried_adverse * slice, -fill_fav);
761 const double peak = facts.
is_long ? (basis + off) : (basis - off);
781 internal::bar_path_uses_high_first(current_bar_),
782 syminfo_.mintick,
config_.slippage);
788 const double fav_px = facts.
is_long ? margin_high : margin_low;
789 const double adv_px = facts.
is_long ? margin_low : margin_high;
810 && std::abs(facts.
fill_price - current_bar_.open) < 1e-7) {
813 const double touch_price = bar_fill_price(facts.
fill_price);
814 double fill_pos = 0.0;
815 if (!internal::first_touch_position(current_bar_, touch_price, &fill_pos))
817 const bool high_first = internal::bar_path_uses_high_first(current_bar_);
818 const double high_pos = high_first ? 1.0 : 2.0;
819 const double low_pos = high_first ? 2.0 : 1.0;
823 if (high_pos < fill_pos && !mask_high) {
824 const double hi_fav = (facts.
is_long
831 if (low_pos < fill_pos && !mask_low) {
832 const double lo_fav = (facts.
is_long
843 guard_native_mutation(
"configure_pine_strategy");
851 guard_native_mutation(
"set_strategy_override");
860 if (stream_warmup_mode_) {
864 BacktestEngine::set_syminfo_session(session);
868 adapter_.set_risk_direction(direction);
872 adapter_.set_risk_max_cons_loss_days(value);
876 adapter_.set_risk_max_drawdown(value, percent);
880 adapter_.set_risk_max_intraday_loss(value, percent);
888 adapter_.set_risk_max_position_size(value);
909 if (
const auto point = current_execution_point()) {
910 context = point->decision;
918 syminfo_.session.empty() ?
"24x7" : syminfo_.session,
919 syminfo_.timezone.empty() ?
"UTC" : syminfo_.timezone,
920 time.dayofmonth, time.month};
925 if (
const auto point = current_execution_point()) {
926 context = point->decision;
932 return adapter_.cap_calculation(context);
941 std::int64_t timestamp_ms)
const {
942 const std::time_t seconds =
static_cast<std::time_t
>(timestamp_ms / 1000);
944 const auto utc = [&]() {
945 return ::gmtime_r(&seconds, &tm) !=
nullptr;
947 if (chart_timezone_.empty() || chart_timezone_ ==
"UTC"
948 || chart_timezone_ ==
"Etc/UTC") {
952 tz_util::ScopedTimezone guard(chart_timezone_);
953 if (::localtime_r(&seconds, &tm) ==
nullptr) (void)utc();
959 result.year = tm.tm_year + 1900;
960 result.month = tm.tm_mon + 1;
961 result.dayofmonth = tm.tm_mday;
962 result.hour = tm.tm_hour;
963 result.minute = tm.tm_min;
964 result.second = tm.tm_sec;
965 result.dayofweek = tm.tm_wday + 1;
966 result.weekofyear = (tm.tm_yday + 7 - ((tm.tm_wday + 6) % 7)) / 7;
971 std::uint64_t count = 0;
972 for (
const auto& event : native_events(0)) {
974 || !std::holds_alternative<native_order::ExecutionAppliedEvent>(*event.command)) {
992 return adapter_.pending_intent_view().last_bar_dual_entry_path();
996 return scheduler_.script_position_view(bar_index_, position_side_, position_qty_);
1001 bar_index_, position_side_, position_qty_, pyramid_entries_);
1014 const auto found = inputs_.find(key);
1015 if (found == inputs_.end() || found->second.empty())
return fallback;
1017 return scheduler_.source_series(found->second);
1018 }
catch (
const std::invalid_argument&) {
1024 return physical_position().signed_units;
1040 std::vector<admission::Field> fields;
1042 fields.push_back(field);
1048 int index,
double fill_price,
double* qty,
int* close_only,
int* partition)
const {
1049 return pending_intent_view().probe_fill_qty(index, fill_price, qty, close_only, partition);
1057 int index,
double* stop,
double* limit,
double* trail_activation)
const {
1062 return adapter_.pending_intent_view();
1067 return adapter_.short_seed_collision_role_v1(std::move(handle));
1075 adapter_.attach_execution_adapter();
1084 const std::string& key,
double value) {
1085 BacktestEngine::set_syminfo_metadata(key, value);
1086 if (key ==
"bar_index_offset") {
1087 scheduler_.set_bar_index_offset(std::isfinite(value)
1088 ?
static_cast<int>(std::llround(value)) : 0);
1090 if (key ==
"security_range_start_na_warmup") {
1091 if (std::isfinite(value) && value > 0.0) {
1099 if (key ==
"chart_ema_na_warmup")
1101 if (key ==
"historical_security_lookahead_projection")
1103 if (key ==
"margin_long" &&
config_.margin_long == 100.0)
1104 config_.margin_long = (std::isfinite(value) && value > 0.0) ? value : 100.0;
1105 if (key ==
"margin_short" &&
config_.margin_short == 100.0)
1106 config_.margin_short = (std::isfinite(value) && value > 0.0) ? value : 100.0;
1108 adapter_.priority.metadata(key, value);
1126 int index,
double fill_price,
double* qty,
int* close_only,
int* partition)
const {
1127 return pending_intent_view().probe_fill_qty(index, fill_price, qty, close_only, partition);
1135 int index,
double* stop,
double* limit,
double* trail_activation)
const {
1140 return adapter_.pending_intent_view().trail_best_price();
1143void source::PineStrategyHost::adapter_label_bracket_trades(
1148 for (std::size_t offset = 0; offset <
event.closed_trade_count; ++offset) {
1149 const std::size_t index =
event.first_trade_index + offset;
1150 if (index >= trades_.size())
continue;
1151 auto& trade = trades_[index];
1152 trade.exit_from_bracket = from_bracket;
1156bool source::PineStrategyHost::adapter_has_open_entry_id(
1157 const std::string&
id)
const {
1158 return std::any_of(pyramid_entries_.begin(), pyramid_entries_.end(),
1159 [&](
const PyramidEntry& row) { return row.entry_id == id && row.qty > 0.0; });
1162const std::vector<source::PineStrategyHost::FixtureIntentRow>&
1167 +
adapter_.pending_coof_requests_.size()
1168 +
adapter_.delayed_market_orders_.size()
1170 const auto append = [&](
const PlacementSnapshot& snapshot,
const std::string& label) {
1172 switch (snapshot.
family) {
1180 type = FixtureIntentKind::EXIT;
1183 type = FixtureIntentKind::RAW_ORDER;
1198 &&
config_.default_qty_value <= 100.0;
1199 const double absent = std::numeric_limits<double>::quiet_NaN();
1228 auto observation = std::make_shared<admission::CommandObservation>();
1230 observation->kind = admission::CommandKind::Entry;
1236 observation->quantity_type = snapshot.
qty_type;
1237 observation->buy = snapshot.
is_long;
1239 observation->oca_name = snapshot.
oca_name;
1240 observation->oca_type = snapshot.
oca_type;
1241 auto& configuration = observation->configuration;
1242 configuration.process_on_close =
config_.process_orders_on_close;
1243 configuration.calc_on_fills =
config_.calc_on_order_fills;
1244 configuration.slippage =
config_.slippage;
1245 configuration.pyramiding =
config_.pyramiding;
1246 configuration.default_quantity_type =
config_.default_qty_type;
1247 configuration.default_quantity_value =
config_.default_qty_value;
1248 configuration.long_margin =
config_.margin_long;
1249 configuration.short_margin =
config_.margin_short;
1250 configuration.commission_value =
config_.commission_value;
1251 configuration.commission_type =
config_.commission_type;
1252 configuration.pointvalue = staged_configuration().syminfo.pointvalue;
1253 configuration.fx = snapshot.
sizing.
fx;
1254 configuration.quantity_step = staged_configuration().quantity_grid
1255 ? *staged_configuration().quantity_grid : 0.0;
1256 configuration.mintick = staged_configuration().syminfo.mintick;
1260 observation->held_quantity = 0.0;
1261 observation->held_entries = 0;
1263 observation->placement_equity = snapshot.
sizing.
equity;
1264 observation->signal_close = snapshot.
sizing.
price;
1265 observation->quantized_fixed_quantity =
1274 for (
const auto& command :
adapter_.pending_same_bar_commands_) {
1279 if (
config_.process_orders_on_close
1281 && !command.snapshot.birth.at_terminal_fill()) {
1284 append(command.snapshot, command.request.label);
1286 for (
const auto& pending :
adapter_.pending_entries_)
1287 append(pending.snapshot, pending.request.label);
1288 for (
const auto& delayed :
adapter_.delayed_market_orders_)
1289 append(delayed.snapshot, delayed.request.label);
1290 for (
const auto& leg :
adapter_.pending_bracket_legs_)
1291 append(leg.snapshot, leg.request.label);
1292 for (
const auto& pending :
adapter_.pending_coof_requests_)
1293 append(pending.snapshot, pending.request.label);
1294 for (
const auto& shadow :
adapter_.source_shadow_pending_)
1295 append(shadow.snapshot, shadow.label);
1296 for (
const auto& handle :
adapter_.live_handles_) {
1297 const auto found =
adapter_.placement_.find(handle.incarnation);
1298 if (found ==
adapter_.placement_.end())
continue;
1299 if (
config_.process_orders_on_close
1302 && !found->second.birth.at_terminal_fill()) {
1305 append(found->second, found->second.source_id);
1313void source::PineStrategyHost::project_short_seed_report_rows(
1317 || event.
handle() == plan.final_short) {
1320 std::optional<PlacementSnapshot> placement_snapshot;
1321 if (
const auto placement = adapter_.placement_.find(event.
handle().
incarnation);
1322 placement != adapter_.placement_.end()) {
1323 placement_snapshot = placement->second;
1326 || (placement_snapshot->from_entry != plan.seed_id
1327 && placement_snapshot->source_id != plan.seed_id)) {
1330 for (
auto& trade : trades_) {
1331 if (trade.entry_incarnation ==
plan.materialize_long.incarnation
1332 && trade.entry_id ==
plan.materialize_label) {
1333 trade.entry_incarnation =
plan.final_short.incarnation;
1336 const std::size_t begin =
event.first_trade_index;
1337 const std::size_t end = begin +
event.closed_trade_count;
1338 for (std::size_t index = begin; index < end && index < trades_.size(); ++index) {
1339 if (trades_[index].entry_incarnation ==
plan.final_short.incarnation
1340 && trades_[index].entry_id ==
plan.final_short_id) {
1341 trades_[index].entry_incarnation =
plan.materialize_long.incarnation;
1344 adapter_.short_seed_.report_swap_pending =
false;
1347void source::PineStrategyHost::scheduler_prepare_script_run(
1348 const std::vector<Bar>& bars,
bool static_eligible,
1349 int expected_script_bars,
bool script_bar_geometry) {
1350 if (
const auto state = native_state(); state.spec) {
1351 input_tf_ = state.spec->timeframe_undetected ?
"" : state.spec->input_tf;
1352 script_tf_ = state.spec->timeframe_undetected ?
"" : state.spec->script_tf;
1355 prepare_script_run(bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()),
1357 last_bar_index_ = expected_script_bars - 1;
1358 last_bar_time_ = bars.empty() ? 0 : bars.back().timestamp;
1359 apply_realtime_tail_horizon(
1360 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()),
1361 script_bar_geometry);
1362 source_last_bar_index_ = last_bar_index_;
1369 bool script_bar_geometry) {
1372 last_bar_index_ = horizon - 1;
1373 const int64_t script_tf_ms =
1374 static_cast<int64_t
>(script_tf_seconds_ > 0 ? script_tf_seconds_ : 0) * 1000;
1375 if (script_bar_geometry) {
1377 last_bar_time_ = bars[horizon - 1].
timestamp;
1380 +
static_cast<int64_t
>(horizon - n) * script_tf_ms;
1384 +
static_cast<int64_t
>(horizon - 1) * script_tf_ms;
1388void source::PineStrategyHost::scheduler_configure_security_evaluators() {
1389 configure_security_evaluators();
1390 prune_pine_security_states();
1393bool source::PineStrategyHost::scheduler_uses_aux_security_feed() const noexcept {
1394#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1395 return aux_security_feed_enabled();
1401void source::PineStrategyHost::scheduler_prepare_security_sequence(
1402 const std::vector<Bar>& bars) {
1403 security_input_tf_ = input_tf_;
1404#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1405 if (aux_security_feed_enabled()) security_input_tf_ = aux_security_input_tf_;
1407 validate_security_timeframes(security_input_tf_);
1408 security_first_chart_bar_ms_ = bars.empty() ? 0 : bars.front().timestamp;
1409 if (declare_security_sites_to_kernel()) {
1413 security_next_input_ms_ = 0;
1414 security_calling_close_ms_ = 0;
1415 clear_historical_security_lookahead_projections();
1416 prepare_chart_day_partition(
1417 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()));
1420 init_security_eval_states_for_run(security_input_tf_);
1421 prepare_native_security_feeds(
1422 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()));
1423#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1424 if (aux_security_feed_enabled()) {
1425 prepare_aux_security_chart_ranges(
1426 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()), script_tf_);
1429 prepare_historical_security_lookahead_projections(
1430 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()), input_tf_);
1431 prepare_chart_day_partition(
1432 bars.empty() ?
nullptr : bars.data(),
static_cast<int>(bars.size()));
1435bool source::PineStrategyHost::security_sites_kernel_routed() const noexcept {
1436 const auto view = native_state();
1437 return view.spec !=
nullptr && !view.spec->subscriptions.empty();
1440bool source::PineStrategyHost::declare_security_sites_to_kernel() {
1441 if (security_eval_states_.empty())
return false;
1449 if (stream_warmup_mode_ || scheduler_.bar_magnifier_enabled())
return false;
1450 if (security_range_start_na_warmup_ || historical_security_lookahead_projection_)
1452#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1453 if (aux_security_feed_enabled())
return false;
1455 if (input_tf_.empty() || input_tf_ != script_tf_ || security_input_tf_ != input_tf_)
1457 const auto view = native_state();
1458 if (view.spec ==
nullptr || view.spec->timeframe_undetected
1459 || view.spec->input_tf != input_tf_) {
1462 const bool otc_daily_pins = (syminfo_.type ==
"forex" || syminfo_.type ==
"cfd")
1463 && native_security_feeds_.empty() && script_tf_seconds_ > 0
1464 && script_tf_seconds_ < 86400;
1465 std::vector<NativeTimeframeSubscription> declared;
1466 declared.reserve(security_eval_states_.size());
1467 for (std::size_t i = 0; i < security_eval_states_.size(); ++i) {
1468 const SecurityEvalState& state = security_eval_states_[i];
1471 if (state.sec_id !=
static_cast<int>(i) || state.tf.empty())
return false;
1476 if (pine.lookahead_on || pine.heikinashi || pine.lower_tf_requested
1477 || pine.lower_tf_emulation || pine.lower_tf_use_input
1478 || pine.lower_tf_array_requested || pine.publish_gate_tf_seconds > 0
1479 || pine.calling_close_completes_partial || pine.calling_open_latches_first) {
1482 if (otc_daily_pins && (state.tf ==
"D" || state.tf ==
"1D"))
return false;
1483 NativeTimeframeSubscription subscription;
1484 subscription.tf = state.tf;
1485 subscription.gaps = pine.gaps_on;
1486 declared.push_back(std::move(subscription));
1488 if (!declare_timeframe_subscriptions(std::move(declared)))
return false;
1491 security_eval_states_.clear();
1495bool source::PineStrategyHost::scheduler_feed_security_input(
1496 const Bar& bar, std::int64_t next_input_ms,
bool calling_bar_complete,
1497 bool defer_boundary_gate) {
1498 security_next_input_ms_ = next_input_ms;
1499 security_calling_close_ms_ = 0;
1500 bool deferred =
false;
1501 for (
auto& state : security_eval_states_) {
1502 if (defer_boundary_gate
1503 && pine_security_state(state.sec_id).publish_gate_tf_seconds > 0) {
1507 pine_feed_security_eval_state(state, bar, calling_bar_complete);
1512void source::PineStrategyHost::scheduler_publish_security_boundary() {
1513 for (
auto& state : security_eval_states_) {
1514 if (pine_security_state(state.sec_id).publish_gate_tf_seconds > 0)
1515 publish_security_eval_state_at_calling_boundary(state);
1519void source::PineStrategyHost::scheduler_feed_deferred_security_input(
1520 const Bar& bar, std::int64_t next_input_ms) {
1521 security_next_input_ms_ = next_input_ms;
1522 security_calling_close_ms_ = 0;
1523 for (
auto& state : security_eval_states_) {
1524 if (pine_security_state(state.sec_id).publish_gate_tf_seconds > 0)
1525 pine_feed_security_eval_state(state, bar,
false);
1529void source::PineStrategyHost::scheduler_feed_aux_security(
int chart_index) {
1530#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1531 if (aux_security_feed_enabled() && scheduler_coof_enabled()) {
1539 restore_script_state();
1540 feed_aux_security_for_chart_bar(chart_index);
1541 snapshot_script_state();
1544 if (aux_security_feed_enabled()) feed_aux_security_for_chart_bar(chart_index);
1550void source::PineStrategyHost::scheduler_feed_deferred_aux_security(
int chart_index) {
1551#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1552 if (aux_security_feed_enabled()) feed_deferred_aux_security_for_chart_bar(chart_index);
1558void source::PineStrategyHost::scheduler_finish_security_sequence() {
1559 clear_historical_security_lookahead_projections();
1560#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1561 clear_aux_security_chart_ranges();
1567void source::PineStrategyHost::scheduler_record_range_end(
const Bar& terminal_bar) {
1568 range_end_trades_.clear();
1569 if (stream_warmup_mode_ || realtime_tail_
1571 || !std::isfinite(terminal_bar.close))
return;
1572 const Bar saved = current_bar_;
1573 current_bar_ = terminal_bar;
1574 const double fill_price = bar_fill_price(current_bar_.close);
1575 const auto saved_timestamp = current_bar_.timestamp;
1576 current_bar_.timestamp = equity_curve_.back().time_ms;
1582 excursion_range_end_projection_ =
true;
1583 const double range_end_pnl = as_native_consumer(execution_consumer())
1584 .append_open_position_report_rows(*
this, fill_price, current_bar_.timestamp, bar_index_);
1585 excursion_range_end_projection_ =
false;
1586 current_bar_.timestamp = saved_timestamp;
1587 auto& last = equity_curve_.back();
1588 last.open_profit = 0.0;
1589 last.equity = initial_capital_ + net_profit_sum_ + range_end_pnl;
1590 max_equity_ = initial_capital_;
1591 min_equity_ = initial_capital_;
1592 max_drawdown_ = 0.0;
1594 for (
const auto& point : equity_curve_) fold_equity_extreme(point.equity);
1596 current_bar_ = saved;
1602 if (trades.size() < 2)
return;
1603 const std::size_t end = trades.size();
1604 std::size_t start = end - 1;
1606 && trades[start - 1].exit_time == trades[end - 1].exit_time
1607 && trades[start - 1].entry_time == trades[end - 1].entry_time
1608 && trades[start - 1].entry_id == trades[end - 1].entry_id
1609 && trades[start - 1].exit_from_bracket
1610 && trades[end - 1].exit_from_bracket) {
1613 if (end - start > 1) {
1618 std::vector<std::size_t> indices(end - start);
1619 std::iota(indices.begin(), indices.end(), start);
1620 std::stable_sort(indices.begin(), indices.end(),
1621 [&](std::size_t ia, std::size_t ib) {
1622 const bool open_a = adapter.is_open_phase_exit(ia);
1623 const bool open_b = adapter.is_open_phase_exit(ib);
1624 if (open_a != open_b) return open_a;
1625 const auto sa = adapter.command_sequence_for_exit(trades[ia].exit_id, trades[ia].entry_id);
1626 const auto sb = adapter.command_sequence_for_exit(trades[ib].exit_id, trades[ib].entry_id);
1629 std::vector<Trade> sorted;
1630 sorted.reserve(end - start);
1631 for (std::size_t idx : indices) sorted.push_back(std::move(trades[idx]));
1632 for (std::size_t i = 0; i < sorted.size(); ++i) trades[start + i] = std::move(sorted[i]);
1639void source::PineStrategyHost::scheduler_update_session_state(
1640 const Bar& bar, std::optional<std::int64_t> next_script_open_ms) {
1641 const bool in_session = chart_bar_ismarket(bar.timestamp);
1642 bool next_in_session =
false;
1643 if (in_session && next_script_open_ms) {
1644 next_in_session = chart_bar_ismarket(*next_script_open_ms);
1645 }
else if (in_session && realtime_tail_ && script_tf_seconds_ > 0
1646 && bar.timestamp <= std::numeric_limits<std::int64_t>::max()
1647 -
static_cast<std::int64_t
>(script_tf_seconds_) * 1000) {
1648 next_in_session = chart_bar_ismarket(
1649 bar.timestamp +
static_cast<std::int64_t
>(script_tf_seconds_) * 1000);
1650 }
else if (in_session && realtime_tail_) {
1651 next_in_session =
true;
1658 session_isfirstbar_ =
in_session && !prev_in_session_;
1659 session_islastbar_ =
in_session && !next_in_session;
1664void source::PineStrategyHost::scheduler_publish_source_bar(
1665 const Bar& bar,
bool,
bool advance_source_index) {
1667 const bool temporary_index = !advance_source_index
1668 && (!scheduler_.current_script_bar()
1669 || scheduler_.current_script_bar()->timestamp != bar.timestamp);
1670 const int previous_bar_index = bar_index_;
1671 const bool previous_barstate_islast = barstate_islast_;
1672 if (advance_source_index || temporary_index) ++source_bar_index_;
1673 ++source_callback_count_;
1674 bar_index_ = source_bar_index_;
1675 const auto lifecycle = native_state();
1676 if (lifecycle.kind == NativeLifecycleKind::Running
1677 && lifecycle.phase == NativeRunPhase::Warmup) {
1678 barstate_islast_ =
false;
1679 }
else if (lifecycle.kind == NativeLifecycleKind::Running
1680 && lifecycle.phase == NativeRunPhase::Realtime) {
1681 barstate_islast_ =
true;
1683 barstate_islast_ = source_bar_index_ == source_last_bar_index_;
1685 NativeDayPartitionScope chart_day_partition(
1686 chart_day_partition_.empty() ?
nullptr : &chart_day_partition_);
1689 adapter_.begin_source_evaluation();
1693 adapter_.observe_terminal_receipts();
1702 adapter_.flush_pending_bracket_legs({},
false,
1704 struct ChartEmaNaWarmupScope {
1706 explicit ChartEmaNaWarmupScope(
bool enabled)
1708 ta::ema_na_warmup_flag() = enabled;
1710 ~ChartEmaNaWarmupScope() { ta::ema_na_warmup_flag() = previous; }
1711 } ema_scope(chart_ema_na_warmup_);
1712 ta::BarContextScope bar_scope(pine_bar_index(), scheduler_.bar_index_offset());
1713 position_entry_count_ = physical_position().signed_units == 0.0
1714 ? 0 : adapter_.source_entry_slot_count();
1720 adapter_.set_configuration(config_);
1721 if (temporary_index) {
1722 --source_bar_index_;
1723 bar_index_ = previous_bar_index;
1724 barstate_islast_ = previous_barstate_islast;
1726 adapter_.flush_pending_closes();
1727 adapter_.flush_pending_entries();
1728 adapter_.flush_pending_bracket_legs();
1732 adapter_.anchor_relative_exits();
1733 if (advance_source_index) {
1734 scheduler_mark_report_point(bar.timestamp);
1735 prev_bar_timestamp_ = bar.timestamp;
1739void source::PineStrategyHost::scheduler_publish_suppressed_tail(
const Bar& bar) {
1744 ++source_bar_index_;
1745 bar_index_ = source_bar_index_;
1746 barstate_islast_ =
false;
1747 NativeDayPartitionScope chart_day_partition(
1748 chart_day_partition_.empty() ?
nullptr : &chart_day_partition_);
1749 adapter_.begin_source_evaluation();
1750 adapter_.observe_terminal_receipts();
1751 scheduler_mark_report_point(bar.timestamp);
1752 prev_bar_timestamp_ = bar.timestamp;
1765void source::PineStrategyHost::scheduler_mark_report_point(std::int64_t script_bar_ts) {
1766 as_native_consumer(execution_consumer())
1767 .mark_script_report_point(*
this, script_bar_ts);
1770void source::PineStrategyHost::scheduler_record_broker_hash() {
1771 if (!broker_state_hash_recording_)
return;
1772 last_script_continuation_hash_ = execution_consumer().continuation_hash();
1773 last_script_continuation_valid_ =
true;
1774 broker_state_hashes_.push_back(broker_state_hash());
1777void source::PineStrategyHost::scheduler_set_session_bar_state(
1778 bool in_session,
bool intraday_is_last_bar) {
1788 session_isfirstbar_ =
in_session && !prev_in_session_;
1789 session_islastbar_ = intraday_is_last_bar;
1792execution::AccountEffectProjection source::PineStrategyHost::adapter_project_flatten(
1793 double price,
const std::string&
id,
const std::string& comment,
1794 std::uint64_t incarnation)
const {
1795 return project_native_settlement_v1(
1796 execution::Flatten{}, execution::Fill{
price, id, comment, incarnation});
1800 return adapter_.core_sizes_default_opening(is_long);
static OrderBirth chart_evaluation(int bar, int64_t timestamp)
OrderBirthCause cause() const
const std::shared_ptr< const CommandObservation > & observation() const
void bind(std::shared_ptr< const CommandObservation > observation)
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
void permute_exit_phases(std::size_t start, const std::vector< std::size_t > &indices)
static bool source_kernel_liquidation(const native_order::DefinitionRef &) noexcept
int observe_pending_count_v1() const override
compat::pine::Calculation fixture_cap_calculation() const
Series< double > & _src_volume_
void attach_pine_execution_adapter()
void set_syminfo_session(const std::string &)
PineExecutionAdapter adapter_
std::optional< NativeMarginDecision > resolve_margin_requirement(const NativeMarginRequirementView &) const final
std::optional< double > resolve_margin_call_units(const NativeMarginCallView &) const final
void set_pine_risk_direction(int)
void prepare_native_begin(const NativeBeginArgs &) final
BarTime fixture_chart_time(std::int64_t timestamp_ms) const
bool & _src_series_active_
compat::pine::CapClock fixture_cap_clock() const
std::uint64_t source_callback_count_
void enable_pine_intraday_cap()
Series< double > & _src_hlcc4_
const PendingIntentView & pending_intent_view() const noexcept
double excursion_trail_offset_ticks_
const std::vector< FixtureIntentRow > & source_pending_view() const
void set_pine_risk_max_intraday_filled_orders(int)
int pending_order_count() const
void on_native_tick(const Bar &, const NativeTickContext &) final
bool excursion_margin_fill_only_
ClosedLotExcursion closed_lot_excursion(const ClosedLotExcursionFacts &) const final
Series< double > & _src_open_
const Series< double > & source_input_series(const std::string &key, const Series< double > &fallback) const
Series< double > & _src_hl2_
int pending_order_effective_levels(int index, double *stop, double *limit, double *trail_activation) const
int observe_pending_effective_levels(int, double *, double *, double *) const override
double observe_trail_best_price_v1() const override
bool excursion_level_fill_
bool margin_call_enabled_
Series< double > & _src_low_
bool fixture_intraday_cap_latched()
void configure_pine_strategy(const PineStrategyConfig &)
void set_pine_risk_max_position_size(double)
bool is_last_tick() const noexcept
bool excursion_priced_fill_
void set_pine_risk_max_cons_loss_days(int)
int observe_pending_level_resolved(int) const override
int64_t security_range_start_ms_
const Series< double > & source_series(const std::string &) const
void apply_realtime_tail_horizon(const Bar *bars, int n, bool script_bar_geometry)
void on_native_applied(const native_order::ExecutionAppliedEvent &, const NativeDecisionContext &) final
double excursion_trail_raw_price_
std::vector< admission::Field > market_admission_fields() const
PineStrategyHost(compat::pine::CapAttachment cap=compat::pine::CapAttachment::None)
bool excursion_margin_prefix_
int pending_order_level_resolved(int index) const
int observe_last_bar_dual_entry_path_v1() const override
int pine_last_bar_index() const
void on_native_run_begin() final
std::uint64_t broker_state_hash_projection() const override
StrategyOverrides override_
Series< double > & _src_close_
bool security_range_start_na_warmup_
PineStrategyConfig config_
std::vector< FixtureIntentRow > source_pending_view_cache_
bool margin_check_allowed(const NativeMarginCheckPoint &) const final
std::optional< double > resolve_anchored_level(const NativeAnchoredLevelView &) const final
void freeze_script_position_view()
std::uint64_t fixture_applied_receipt_count() const
Series< double > & _src_hlc3_
bool chart_ema_na_warmup_
bool excursion_range_end_projection_
int last_bar_dual_entry_path() const
double prev_chart_close() const
int source_last_bar_index_
void on_native_input(const Bar &, const NativeInputContext &) final
double live_position_size() const override
bool excursion_margin_call_
void set_pine_risk_max_intraday_loss(double, bool)
int short_seed_collision_role_v1(native_order::RequestHandle) const noexcept
bool history_advances_new_bar() const noexcept
int pine_bar_index() const
void on_native_bar_open(const Bar &, const NativeDecisionContext &) final
bool source_prepare_failed_
bool historical_security_lookahead_projection_
bool is_first_tick() const noexcept
void set_pine_risk_max_drawdown(double, bool)
double margin_liquidation_price() const
int realtime_tail_horizon_bars_
bool probe_suppress_tail_logic_
MarketAdmissionJournal & market_admission_journal()
double precommit_held_units_
Series< double > & _src_ohlc4_
NativePrecommitVerdict validate_execution_precommit(const NativePrecommitView &) const final
void clear_script_position_view()
const bool & is_last_tick_
void on_native_bar(const Bar &, const NativeDecisionContext &) final
int observe_probe_fill_qty(int, double, double *, int *, int *) const override
bool source_configuration_captured_
void set_syminfo_metadata(const std::string &, double) override
void set_strategy_override(const StrategyOverrides &)
Series< double > & _src_high_
int observe_pending_copy_v1(int, pf_pending_order_v1_t *) const override
bool adapter_core_sizes_default_opening(bool is_long) const
native_order::ExecutionTerms resolve_execution_terms(const NativeExecutionTermsFacts &) const final
int probe_fill_qty(int index, double fill_price, double *qty, int *close_only, int *partition) const
double signed_position_size() const
void on_native_recalculate(const Bar &, const NativeDecisionContext &, NativeCalculationReason, const native_order::ExecutionAppliedEvent *) final
void source_stream_entry_comment(const PyramidEntry &, std::string &) const override
double snap_trail_level_to_tick_grid(double price, double mintick)
NativeCalculationReason
Why the kernel is asking the host to calculate.
NativePrecommitVerdict
The host is consulted before generic opening-margin admission.
bool in_session(const SessionCalendar &calendar, int64_t ms)
std::variant< Market, Limit, Stop, StopLimit, Trail > Trigger
NativePathOrder
Generic ordering for a modeled OHLC path.
std::optional< Plan > plan(double signed_position, Reduce request) noexcept
constexpr char kMarginCallLabel[]
constexpr char kIntradayLossComment[]
Bar margin_call_sample_bar(const Bar &bar, double fire_price, bool prefix_sample, bool high_first, double mintick=0.0, int slippage=0)
constexpr char kFillCapCommentPrefix[]
void sample_open_trade_extremes(std::vector< PyramidEntry > &lots, PositionSide side, int bar_index, const Bar &bar)
bool & ema_na_warmup_flag()
int tf_to_seconds(const std::string &tf)
Convert a TradingView timeframe string to seconds.
int tf_ratio(const std::string &input_tf, const std::string &target_tf)
Compute how many input bars fit into one target bar.
static void sort_same_bar_exit_trades(std::vector< Trade > &, source::PineExecutionAdapter &)
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...
bool tf_is_daily_or_higher(const std::string &tf)
True for a daily-or-higher chart timeframe ("D", "1D", "2D", "W", "M"): a bar that covers whole sessi...
admission::Journal MarketAdmissionJournal
bool entry_bar_high_masked
bool entry_bar_low_masked
execution::CloseCause close_cause
Ephemeral read-only facts of one anchored-leg materialization (L7b), offered to the host exactly once...
Borrowed begin-call facts.
const SymInfo * syminfo
The rich run overload's symbol metadata is borrowed only for this callback.
const void * overrides_opaque
Read-only owning-value facts for one candidate.
Accepted input facts presented before the generic consumer aggregates the bar into its script interva...
Ephemeral factual view of one kernel-issued liquidation before its units are fixed.
Ephemeral factual view of one kernel check point, offered to the host before the check runs.
Ephemeral factual view of the numbers the kernel is about to compare, at one check point,...
Ephemeral factual view of one prepared execution before any physical effect.
native_order::MatchCursor cursor
native_order::DefinitionRef definition
native_order::RequestHandle target
One accepted realtime print before native matching at its current decision point.
NativeDecisionContext decision
NativePathPhase path_phase
NativeDriverStatistics driver_statistics
int64_t script_bar_open_ms
NativeCoordinate coordinate
uint64_t sub_bars_processed
uint64_t sample_ticks_processed
One committed execution: the definition, the cursor, the resolved price and units,...
const RequestHandle & handle() const noexcept
std::size_t closed_trade_count
One complete setup value, staged/copied by NativeStrategyHost before it is applied at begin.
double frozen_market_transaction_units
double paired_flat_market_transaction_qty
double affordability_placement_equity
std::uint64_t incarnation
double default_stop_placement_equity
double default_stop_sizing_price
double frozen_market_own_units
MarketAdmissionDraft market_admission
std::int64_t paired_flat_market_peer_seq
double frozen_default_qty
double default_stop_placement_qty
bool over_pyramiding_cap_at_placement
double default_stop_placement_signal_close
MarketAdmissionDraft market_admission
double projection_default_stop_signal_close
bool projection_over_pyramiding
PineSizingSnapshot sizing
std::int64_t placement_cycle
PineExitLevels exit_levels
std::int32_t projection_created_bar
double projection_default_stop_equity
std::uint64_t source_sequence
bool frozen_market_targeted_close
bool frozen_market_instruction
double projection_affordability_equity
std::uint64_t command_ordinal
double frozen_market_transaction_units
double projection_remaining_qty
double frozen_market_own_units
std::vector< std::int64_t > account_fx_effective_from_ms