6#include "../engine_internal.hpp"
18#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
21 const std::string& input_tf) {
22 guard_native_mutation(
"set_aux_security_feed");
26 clear_aux_security_chart_ranges();
29 if (n < 0 || bars ==
nullptr || input_tf.empty()) {
31 "auxiliary request.security feed requires bars, a positive count, and input_tf";
42 "auxiliary request.security feed requires a fixed positive input_tf";
45 for (
int i = 1; i < n; ++i) {
46 if (bars[i].timestamp <= bars[i - 1].timestamp) {
48 "auxiliary request.security feed timestamps must be strictly increasing";
54 clear_aux_security_chart_ranges();
64 const Bar*& bars,
int& n)
const {
69void source::PineStrategyHost::clear_aux_security_chart_ranges() {
70 aux_security_chart_begin_.clear();
71 aux_security_chart_end_.clear();
74void source::PineStrategyHost::prepare_aux_security_chart_ranges(
75 const Bar* chart_bars,
int n_chart,
const std::string& chart_tf) {
76 clear_aux_security_chart_ranges();
77 if (!aux_security_feed_enabled())
return;
78 if (chart_bars ==
nullptr || n_chart <= 0) {
79 throw std::runtime_error(
80 "auxiliary request.security feed requires at least one native chart bar");
83 int chart_seconds = 0;
92 if (chart_seconds <= 0 || aux_seconds <= 0
93 || aux_seconds >= chart_seconds) {
94 throw std::runtime_error(
95 "auxiliary request.security feed input_tf must be fixed and strictly finer than the native chart timeframe");
97 if (chart_seconds % aux_seconds != 0) {
98 throw std::runtime_error(
99 "auxiliary request.security feed input_tf must evenly divide the native chart timeframe");
101 for (
int i = 1; i < n_chart; ++i) {
102 if (chart_bars[i].timestamp <= chart_bars[i - 1].timestamp) {
103 throw std::runtime_error(
104 "native chart feed timestamps must be strictly increasing with an auxiliary security feed");
108 const std::size_t missing = std::numeric_limits<std::size_t>::max();
109 aux_security_chart_begin_.assign(
static_cast<std::size_t
>(n_chart), missing);
110 aux_security_chart_end_.assign(
static_cast<std::size_t
>(n_chart), missing);
111 TimeframeAggregator chart_router(chart_tf, aux_security_input_tf_,
112 syminfo_.timezone, syminfo_.session);
122 std::vector<int64_t> chart_route_keys;
123 chart_route_keys.reserve(
static_cast<std::size_t
>(n_chart));
124 for (
int i = 0; i < n_chart; ++i) {
130 const int64_t key = calendar_chart
135 syminfo_.timezone, syminfo_.session, chart_period)
136 : chart_bars[i].timestamp;
137 if (!chart_route_keys.empty() && key <= chart_route_keys.back()) {
138 throw std::runtime_error(
139 "native chart feed trading-period identities must be unique and strictly increasing with an auxiliary security feed");
141 chart_route_keys.push_back(key);
143 const int64_t first_chart_key = chart_route_keys.front();
144 const int64_t last_chart_key = chart_route_keys.back();
145 std::size_t chart_index = 0;
146 auto record_aux = [&](std::size_t aux_index) {
147 if (aux_security_chart_begin_[chart_index] == missing) {
148 aux_security_chart_begin_[chart_index] = aux_index;
150 aux_security_chart_end_[chart_index] = aux_index + 1;
152 if (calendar_chart) {
153 const int64_t first_chart_timestamp = chart_bars[0].
timestamp;
154 for (std::size_t aux_index = 0;
155 aux_index < aux_security_bars_.size(); ++aux_index) {
156 const int64_t aux_timestamp =
157 aux_security_bars_[aux_index].timestamp;
159 aux_timestamp, syminfo_.timezone, syminfo_.session,
161 if (period_key < first_chart_key || period_key > last_chart_key
162 || aux_timestamp < first_chart_timestamp) {
165 while (chart_index + 1 <
static_cast<std::size_t
>(n_chart)
166 && chart_bars[chart_index + 1].timestamp <= aux_timestamp) {
169 record_aux(aux_index);
172 for (std::size_t aux_index = 0;
173 aux_index < aux_security_bars_.size(); ++aux_index) {
174 const int64_t label = chart_router.bar_label_ms(
175 aux_security_bars_[aux_index].timestamp);
179 if (label < first_chart_key || label > last_chart_key) {
182 while (chart_index + 1 <
static_cast<std::size_t
>(n_chart)
183 && chart_route_keys[chart_index] < label) {
186 if (chart_route_keys[chart_index] != label) {
187 throw std::runtime_error(
188 "auxiliary request.security bar does not map to a native chart bar");
190 record_aux(aux_index);
199int64_t source::PineStrategyHost::aux_security_calling_close_ms()
const {
204 syminfo_.timezone, syminfo_.session),
205 syminfo_.timezone, syminfo_.session, chart_period);
207 int chart_seconds = 0;
213 return chart_seconds > 0
214 ? current_bar_.timestamp +
static_cast<int64_t
>(chart_seconds) * 1000
218void source::PineStrategyHost::feed_aux_security_for_chart_bar(
int chart_index) {
219 const std::size_t idx =
static_cast<std::size_t
>(chart_index);
220 if (idx >= aux_security_chart_begin_.size()
221 || idx >= aux_security_chart_end_.size()) {
222 throw std::runtime_error(
223 "auxiliary request.security chart routing is not initialized");
225 const std::size_t begin = aux_security_chart_begin_[idx];
226 const std::size_t end = aux_security_chart_end_[idx];
227 const std::size_t missing = std::numeric_limits<std::size_t>::max();
228 if (begin == missing || end == missing)
return;
230 security_calling_close_ms_ = aux_security_calling_close_ms();
235 for (
auto& state : security_eval_states_) {
237 pine.first_bucket_published =
false;
238 pine.deferred_aux.clear();
239 pine.slice_open_label = begin < end
240 ? state.aggregator.bucket_open_ms(aux_security_bars_[begin].timestamp)
248 for (std::size_t i = begin; i < end; ++i) {
249 const Bar& aux_bar = aux_security_bars_[i];
250 const bool calling_bar_complete = (i + 1 == end);
254 security_next_input_ms_ = (i + 1 < aux_security_bars_.size())
255 ? aux_security_bars_[i + 1].timestamp : 0;
256 for (
auto& state : security_eval_states_) {
258 if (!pine.lower_tf_array_requested) {
259 if (pine.calling_open_latches_first
260 && pine.first_bucket_published) {
265 pine.deferred_aux.push_back(
266 {aux_bar, security_next_input_ms_,
267 calling_bar_complete});
270 const int64_t published_before = state.eval_complete_count;
271 pine_feed_security_eval_state(
272 state, aux_bar, calling_bar_complete);
279 if (pine.calling_open_latches_first
280 && state.eval_complete_count > published_before
281 && pine.last_published_label >= pine.slice_open_label) {
282 pine.first_bucket_published =
true;
286 if (security_input_precedes_range_start(state, aux_bar.timestamp)) {
289 if (pine.lower_tf_use_input) {
290 pine.lower_tf_input_buffer.push_back(aux_bar);
291 }
else if (pine.lower_tf_emulation) {
292 std::vector<Bar> synthetic = internal::synthesize_lower_tf_bars(
293 aux_bar, pine.lower_tf_ratio, pine.lower_tf_seconds);
294 if (synthetic.empty()) {
295 throw std::runtime_error(
296 "request.security_lower_tf could not synthesize auxiliary sub-bars");
298 pine.lower_tf_input_buffer.insert(
299 pine.lower_tf_input_buffer.end(),
300 synthetic.begin(), synthetic.end());
302 throw std::runtime_error(
303 "request.security_lower_tf auxiliary routing is not initialized");
308 security_calling_close_ms_ = 0;
310 struct SecurityNaWarmupScope {
312 explicit SecurityNaWarmupScope(
bool enabled)
314 ta::ema_na_warmup_flag() = enabled;
316 ~SecurityNaWarmupScope() { ta::ema_na_warmup_flag() = previous; }
317 } warmup_scope(security_range_start_na_warmup_);
319 for (
auto& state : security_eval_states_) {
321 if (!pine.lower_tf_array_requested)
continue;
322 int aggregate_ratio = pine.lower_tf_emulation
323 ? 1 : pine.lower_tf_input_aggregation_ratio;
324 if (aggregate_ratio < 1) aggregate_ratio = 1;
325 const int count =
static_cast<int>(pine.lower_tf_input_buffer.size());
326 std::vector<Bar> requested_bars;
327 requested_bars.reserve(
328 static_cast<std::size_t
>(count / aggregate_ratio + 1));
329 if (aggregate_ratio == 1) {
330 requested_bars.assign(pine.lower_tf_input_buffer.begin(),
331 pine.lower_tf_input_buffer.end());
333 for (
int i = 0; i + aggregate_ratio <= count;
334 i += aggregate_ratio) {
335 Bar aggregate = pine.lower_tf_input_buffer[
336 static_cast<std::size_t
>(i)];
337 double volume = aggregate.volume;
338 for (
int j = 1; j < aggregate_ratio; ++j) {
339 const Bar& next = pine.lower_tf_input_buffer[
340 static_cast<std::size_t
>(i + j)];
341 aggregate.high = std::max(aggregate.high, next.high);
342 aggregate.low = std::min(aggregate.low, next.low);
343 aggregate.close = next.close;
344 volume += next.volume;
346 aggregate.volume = volume;
347 requested_bars.push_back(aggregate);
351 pine.lower_tf_sub_bar_index = 0;
352 for (
const Bar& bar : requested_bars) {
354 state.current_bar = bar;
355 state.current_sub_bar_count = 1;
356 state.eval_complete_count++;
357 dispatch_security_eval(state, bar,
true,
358 state.eval_complete_count - 1);
359 pine.lower_tf_sub_bar_index++;
361 pine.lower_tf_input_buffer.clear();
365void source::PineStrategyHost::feed_deferred_aux_security_for_chart_bar(
int chart_index) {
368 for (
const auto& entry : pine_security_states_) {
369 if (entry.second.calling_open_latches_first && !entry.second.deferred_aux.empty()) {
375 security_calling_close_ms_ = aux_security_calling_close_ms();
376 for (
auto& state : security_eval_states_) {
378 if (!pine.calling_open_latches_first || pine.deferred_aux.empty()) {
385 std::vector<DeferredAuxBar> held;
386 held.swap(pine.deferred_aux);
387 for (
const auto& d : held) {
388 security_next_input_ms_ = d.next_input_ms;
389 pine_feed_security_eval_state(state, d.bar,
390 d.calling_bar_complete);
393 security_calling_close_ms_ = 0;
std::vector< Bar > aux_security_bars_
bool set_aux_security_feed(const Bar *bars, int n, const std::string &input_tf) override
bool source_aux_security_feed_enabled() const override
void source_aux_security_input_view(const Bar *&, int &) const override
std::string aux_security_input_tf_
std::optional< int64_t > period_key(const SessionCalendar &calendar, const Timeframe &tf, int64_t ms)
bool & ema_na_warmup_flag()
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.
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...