6#include "../engine_internal.hpp"
7#include "../native_execution_consumer.hpp"
21#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
24 const std::string& input_tf) {
25 guard_native_mutation(
"set_aux_security_feed");
32 if (n < 0 || bars ==
nullptr || input_tf.empty()) {
34 *
this,
"auxiliary request.security feed requires bars, a positive count, and input_tf",
45 note_run_failure(*
this,
"auxiliary request.security feed requires a fixed positive input_tf",
47 {{
"reason",
"aux_feed_timeframe"}});
50 for (
int i = 1; i < n; ++i) {
51 if (bars[i].timestamp <= bars[i - 1].timestamp) {
53 *
this,
"auxiliary request.security feed timestamps must be strictly increasing",
60 clear_aux_security_chart_ranges();
69 f.
d(bar.open); f.
d(bar.high); f.
d(bar.low); f.
d(bar.close);
70 f.
d(bar.volume); f.
i(bar.timestamp);
85 const Bar*& bars,
int& n)
const {
90void source::PineStrategyHost::clear_aux_security_chart_ranges() {
91 aux_security_chart_begin_.clear();
92 aux_security_chart_end_.clear();
93 refresh_aux_security_digest();
96void source::PineStrategyHost::prepare_aux_security_chart_ranges(
97 const Bar* chart_bars,
int n_chart,
const std::string& chart_tf) {
102 ~Refresh() { host.refresh_aux_security_digest(); }
104 clear_aux_security_chart_ranges();
105 if (!aux_security_feed_enabled())
return;
106 if (chart_bars ==
nullptr || n_chart <= 0) {
107 throw coded<std::runtime_error>(
109 "auxiliary request.security feed requires at least one native chart bar");
112 int chart_seconds = 0;
121 if (chart_seconds <= 0 || aux_seconds <= 0
122 || aux_seconds >= chart_seconds) {
123 throw coded<std::runtime_error>(
125 "auxiliary request.security feed input_tf must be fixed and strictly finer than the native chart timeframe");
127 if (chart_seconds % aux_seconds != 0) {
128 throw coded<std::runtime_error>(
130 "auxiliary request.security feed input_tf must evenly divide the native chart timeframe");
132 for (
int i = 1; i < n_chart; ++i) {
133 if (chart_bars[i].timestamp <= chart_bars[i - 1].timestamp) {
134 throw coded<std::runtime_error>(
136 "native chart feed timestamps must be strictly increasing with an auxiliary security feed");
140 const std::size_t missing = std::numeric_limits<std::size_t>::max();
141 aux_security_chart_begin_.assign(
static_cast<std::size_t
>(n_chart), missing);
142 aux_security_chart_end_.assign(
static_cast<std::size_t
>(n_chart), missing);
143 TimeframeAggregator chart_router(chart_tf, aux_security_input_tf_,
144 syminfo_.timezone, syminfo_.session);
154 std::vector<int64_t> chart_route_keys;
155 chart_route_keys.reserve(
static_cast<std::size_t
>(n_chart));
156 for (
int i = 0; i < n_chart; ++i) {
162 const int64_t
key = calendar_chart
167 syminfo_.timezone, syminfo_.session, chart_period)
168 : chart_bars[i].timestamp;
177 if (!chart_route_keys.empty()
178 && (calendar_chart ? key < chart_route_keys.back()
179 : key <= chart_route_keys.back())) {
180 throw coded<std::runtime_error>(
182 "native chart feed trading-period identities must be unique and strictly increasing with an auxiliary security feed");
184 chart_route_keys.push_back(key);
186 const int64_t first_chart_key = chart_route_keys.front();
187 const int64_t last_chart_key = chart_route_keys.back();
188 std::size_t chart_index = 0;
189 auto record_aux = [&](std::size_t aux_index) {
190 if (aux_security_chart_begin_[chart_index] == missing) {
191 aux_security_chart_begin_[chart_index] = aux_index;
193 aux_security_chart_end_[chart_index] = aux_index + 1;
195 if (calendar_chart) {
196 const int64_t first_chart_timestamp = chart_bars[0].
timestamp;
197 for (std::size_t aux_index = 0;
198 aux_index < aux_security_bars_.size(); ++aux_index) {
199 const int64_t aux_timestamp =
200 aux_security_bars_[aux_index].timestamp;
202 aux_timestamp, syminfo_.timezone, syminfo_.session,
204 if (period_key < first_chart_key || period_key > last_chart_key
205 || aux_timestamp < first_chart_timestamp) {
208 while (chart_index + 1 <
static_cast<std::size_t
>(n_chart)
209 && chart_bars[chart_index + 1].timestamp <= aux_timestamp) {
212 record_aux(aux_index);
215 for (std::size_t aux_index = 0;
216 aux_index < aux_security_bars_.size(); ++aux_index) {
217 const int64_t label = chart_router.bar_label_ms(
218 aux_security_bars_[aux_index].timestamp);
222 if (label < first_chart_key || label > last_chart_key) {
225 while (chart_index + 1 <
static_cast<std::size_t
>(n_chart)
226 && chart_route_keys[chart_index] < label) {
229 if (chart_route_keys[chart_index] != label) {
230 throw coded<std::runtime_error>(
232 "auxiliary request.security bar does not map to a native chart bar");
234 record_aux(aux_index);
243int64_t source::PineStrategyHost::aux_security_calling_close_ms()
const {
248 syminfo_.timezone, syminfo_.session),
249 syminfo_.timezone, syminfo_.session, chart_period);
251 int chart_seconds = 0;
257 return chart_seconds > 0
258 ? current_bar_.timestamp +
static_cast<int64_t
>(chart_seconds) * 1000
262void source::PineStrategyHost::feed_aux_security_for_chart_bar(
int chart_index) {
263 const std::size_t idx =
static_cast<std::size_t
>(chart_index);
264 if (idx >= aux_security_chart_begin_.size()
265 || idx >= aux_security_chart_end_.size()) {
266 throw coded<std::runtime_error>(
268 "auxiliary request.security chart routing is not initialized");
270 const std::size_t begin = aux_security_chart_begin_[idx];
271 const std::size_t
end = aux_security_chart_end_[idx];
272 const Bar* calling_bar = scheduler_.current_script_bar();
273 if (calling_bar ==
nullptr) {
274 throw coded<std::runtime_error>(
276 "auxiliary request.security routing requires a calling chart bar");
280 const bool native_calendar_input = chart_timeframe && input_timeframe
282 std::vector<bool> uses_native_chart;
283 uses_native_chart.reserve(security_eval_states_.size());
284 for (
const auto& state : security_eval_states_) {
285 const auto& pine = pine_security_state(state.sec_id);
287 uses_native_chart.push_back(native_calendar_input && requested_timeframe
289 && !pine.heikinashi && !pine.lower_tf_array_requested);
291 for (std::size_t state_index = 0; state_index < security_eval_states_.size();
293 auto& state = security_eval_states_[state_index];
294 if (!uses_native_chart[state_index]
295 || security_input_precedes_range_start(state, calling_bar->timestamp)) {
298 auto& pine = pine_security_state(state.sec_id);
299 if (pine.no_loaded_bars
300 || (state.eval_complete_count > 0
301 && state.current_bar.timestamp == calling_bar->timestamp)) {
304 state.current_bar = *calling_bar;
305 state.current_sub_bar_count = 1;
307 ++state.eval_complete_count;
308 pine.last_published_label = calling_bar->timestamp;
309 internal::AmbientEmaSeedingScope warmup_scope(
310 NativeExecutionConsumer::bound(*this).pump_ambient(),
311 security_range_start_na_warmup_);
312 dispatch_security_eval(state, *calling_bar,
true,
313 state.eval_complete_count - 1);
315 const std::size_t missing = std::numeric_limits<std::size_t>::max();
316 if (begin == missing || end == missing)
return;
318 security_calling_close_ms_ = aux_security_calling_close_ms();
321 const int64_t calling_open_ms = calling_bar->timestamp;
326 for (
auto& state : security_eval_states_) {
328 pine.first_bucket_published =
false;
329 pine.deferred_aux.clear();
330 pine.slice_open_label = begin <
end
331 ? state.aggregator.bucket_open_ms(aux_security_bars_[begin].timestamp)
339 for (std::size_t i = begin; i <
end; ++i) {
340 const Bar& aux_bar = aux_security_bars_[i];
341 const bool calling_bar_complete = (i + 1 ==
end);
345 security_next_input_ms_ = (i + 1 < aux_security_bars_.size())
346 ? aux_security_bars_[i + 1].timestamp : 0;
347 for (std::size_t state_index = 0; state_index < security_eval_states_.size();
349 auto& state = security_eval_states_[state_index];
350 if (uses_native_chart[state_index])
continue;
352 if (!pine.lower_tf_array_requested) {
365 if (pine.calling_open_latches_first && !pine.heikinashi
366 && !pine.first_bucket_published
367 && state.aggregator.bucket_open_ms(aux_bar.timestamp)
369 if (state.aggregator.has_pending_partial()) {
370 AggregatedBar tail = state.aggregator.complete_pending_partial();
371 if (tail.is_complete) {
372 state.current_sub_bar_count =
373 std::max(tail.sub_bar_count, 2);
374 substitute_native_security_bar(state, tail.bar);
375 state.current_bar = tail.bar;
376 state.eval_complete_count++;
377 pine.last_published_label = tail.bar.timestamp;
378 internal::AmbientEmaSeedingScope tail_warmup_scope(
379 NativeExecutionConsumer::bound(*this).pump_ambient(),
380 security_range_start_na_warmup_);
381 dispatch_security_eval(state, tail.bar,
true,
382 state.eval_complete_count - 1);
385 pine.first_bucket_published =
true;
387 if (pine.calling_open_latches_first
388 && pine.first_bucket_published) {
393 pine.deferred_aux.push_back(
394 {aux_bar, security_next_input_ms_,
395 calling_bar_complete});
398 const int64_t published_before = state.eval_complete_count;
399 pine_feed_security_eval_state(
400 state, aux_bar, calling_bar_complete);
407 if (pine.calling_open_latches_first
408 && state.eval_complete_count > published_before
409 && pine.last_published_label >= pine.slice_open_label) {
410 pine.first_bucket_published =
true;
414 if (security_input_precedes_range_start(state, aux_bar.timestamp)) {
417 if (pine.lower_tf_use_input) {
418 pine.lower_tf_input_buffer.push_back(aux_bar);
419 }
else if (pine.lower_tf_emulation) {
420 std::vector<Bar> synthetic = internal::synthesize_lower_tf_bars(
421 aux_bar, pine.lower_tf_ratio, pine.lower_tf_seconds);
422 if (synthetic.empty()) {
423 throw coded<std::runtime_error>(
425 "request.security_lower_tf could not synthesize auxiliary sub-bars");
427 pine.lower_tf_input_buffer.insert(
428 pine.lower_tf_input_buffer.end(),
429 synthetic.begin(), synthetic.end());
431 throw coded<std::runtime_error>(
433 "request.security_lower_tf auxiliary routing is not initialized");
438 security_calling_close_ms_ = 0;
441 internal::AmbientEmaSeedingScope warmup_scope(
442 NativeExecutionConsumer::bound(*this).pump_ambient(),
443 security_range_start_na_warmup_);
445 for (
auto& state : security_eval_states_) {
447 if (!pine.lower_tf_array_requested)
continue;
448 int aggregate_ratio = pine.lower_tf_emulation
449 ? 1 : pine.lower_tf_input_aggregation_ratio;
450 if (aggregate_ratio < 1) aggregate_ratio = 1;
451 const int count =
static_cast<int>(pine.lower_tf_input_buffer.size());
452 std::vector<Bar> requested_bars;
453 requested_bars.reserve(
454 static_cast<std::size_t
>(count / aggregate_ratio + 1));
455 if (aggregate_ratio == 1) {
456 requested_bars.assign(pine.lower_tf_input_buffer.begin(),
457 pine.lower_tf_input_buffer.end());
459 for (
int i = 0; i + aggregate_ratio <= count;
460 i += aggregate_ratio) {
461 Bar aggregate = pine.lower_tf_input_buffer[
462 static_cast<std::size_t
>(i)];
463 double volume = aggregate.volume;
464 for (
int j = 1; j < aggregate_ratio; ++j) {
465 const Bar& next = pine.lower_tf_input_buffer[
466 static_cast<std::size_t
>(i + j)];
467 aggregate.high = std::max(aggregate.high, next.high);
468 aggregate.low = std::min(aggregate.low, next.low);
469 aggregate.close = next.close;
470 volume += next.volume;
472 aggregate.volume = volume;
473 requested_bars.push_back(aggregate);
477 pine.lower_tf_sub_bar_index = 0;
478 for (
const Bar& bar : requested_bars) {
480 state.current_bar = bar;
481 state.current_sub_bar_count = 1;
482 state.eval_complete_count++;
483 dispatch_security_eval(state, bar,
true,
484 state.eval_complete_count - 1);
485 pine.lower_tf_sub_bar_index++;
487 pine.lower_tf_input_buffer.clear();
491void source::PineStrategyHost::feed_deferred_aux_security_for_chart_bar(
int chart_index) {
494 for (
const auto& entry : pine_security_states_) {
495 if (entry.second.calling_open_latches_first && !entry.second.deferred_aux.empty()) {
501 security_calling_close_ms_ = aux_security_calling_close_ms();
502 for (
auto& state : security_eval_states_) {
504 if (!pine.calling_open_latches_first || pine.deferred_aux.empty()) {
511 std::vector<DeferredAuxBar> held;
512 held.swap(pine.deferred_aux);
513 for (
const auto& d : held) {
514 security_next_input_ms_ = d.next_input_ms;
515 pine_feed_security_eval_state(state, d.bar,
516 d.calling_bar_complete);
519 security_calling_close_ms_ = 0;
523 clear_aux_security_chart_ranges();
void s(const std::string &v)
std::vector< Bar > aux_security_bars_
bool set_aux_security_feed(const Bar *bars, int n, const std::string &input_tf) override
std::vector< std::size_t > aux_security_chart_begin_
void refresh_aux_security_digest() noexcept
bool source_aux_security_feed_enabled() const override
void source_aux_security_input_view(const Bar *&, int &) const override
void release_aux_security_chart_ranges()
std::string aux_security_input_tf_
std::vector< std::size_t > aux_security_chart_end_
std::uint64_t aux_security_digest_
std::optional< Timeframe > parse_timeframe(std::string_view text)
std::optional< int64_t > period_key(const SessionCalendar &calendar, const Timeframe &tf, int64_t ms)
bool same_calendar_timeframe(const native_calendar::Timeframe &requested, const native_calendar::Timeframe &chart) noexcept
int64_t session_covered_instant_ms(int64_t ms, const std::string &tz, const std::string &session)
The session instant a native CALENDAR chart stamp covers.
int64_t session_period_last_traded_close_ms(int64_t ms, const std::string &tz, const std::string &session, CalendarPeriod period)
Exclusive close (Unix ms) of the LAST TRADED session-day of the D/W/M bar that contains ms: DAY is se...
int tf_to_seconds(const std::string &tf)
Convert a TradingView timeframe string to seconds.
CalendarPeriod calendar_period_for(const std::string &tf)
Determine the calendar period for a target TF string.
void note_run_failure(BacktestEngine &engine, std::string text, RunFailureCode code, const RunFailureArgs &args={})
void clear_run_failure(BacktestEngine &engine) noexcept
int64_t session_period_open_ms(int64_t ms, const std::string &tz, const std::string &session, CalendarPeriod period)
Open (Unix ms) of the symbol's D/W/M bar that contains ms: the day stamp of the period's first sessio...