PineForge v1.4.0-4-g873b25da
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
magnifier_intrabars.cpp
Go to the documentation of this file.
1// TradingView's bar-magnifier intrabars (R5 lane MAG-INTRABAR): the table
2// that picks the intrabar timeframe for a chart, and the lower-timeframe path
3// the Pine adapter hands the kernel's intrabar path for a magnified run.
5
7
8#include "../native_calendar_memo.hpp"
9
10#include <algorithm>
11#include <cstdint>
12#include <optional>
13
14namespace pineforge::source {
15
16namespace {
17
18namespace cal = native_calendar;
19
20constexpr std::int64_t kSecondMs = 1000;
21constexpr std::int64_t kMinuteMs = 60 * kSecondMs;
22
23// A fixed timeframe's duration, or 0 for a calendar one.
24std::int64_t fixed_ms(const cal::Timeframe& tf) noexcept {
25 switch (tf.unit()) {
26 case cal::TimeframeUnit::Second: return tf.count() * kSecondMs;
27 case cal::TimeframeUnit::Minute: return tf.count() * kMinuteMs;
28 default: return 0;
29 }
30}
31
32Bar one_price(double price, std::int64_t timestamp) noexcept {
33 Bar bar{};
34 bar.open = bar.high = bar.low = bar.close = price;
35 bar.volume = 0.0;
36 bar.timestamp = timestamp;
37 return bar;
38}
39
40} // namespace
41
42std::string tradingview_intrabar_timeframe(std::string_view chart_tf) {
43 const auto chart = cal::parse_timeframe(chart_tf);
44 if (!chart || !chart->valid()) return {};
45 std::int64_t seconds = 0;
46 switch (chart->unit()) {
47 case cal::TimeframeUnit::Second: seconds = chart->count(); break;
48 case cal::TimeframeUnit::Minute: seconds = std::int64_t{chart->count()} * 60; break;
49 case cal::TimeframeUnit::Day: seconds = std::int64_t{chart->count()} * 86400; break;
50 case cal::TimeframeUnit::Week:
51 case cal::TimeframeUnit::Month:
52 return "1D";
53 }
54 constexpr std::int64_t kMinute = 60;
55 constexpr std::int64_t kDay = 1440 * kMinute;
56 if (seconds < 30) return "1S";
57 if (seconds < kMinute) return "5S";
58 if (seconds < 5 * kMinute) return "10S";
59 if (seconds < 10 * kMinute) return "30S";
60 if (seconds < 15 * kMinute) return "1";
61 if (seconds < 30 * kMinute) return "2";
62 if (seconds < 60 * kMinute) return "5";
63 if (seconds < 240 * kMinute) return "10";
64 if (seconds < kDay) return "30";
65 if (seconds < 3 * kDay) return "60";
66 if (seconds < 7 * kDay) return "240";
67 return "1D";
68}
69
70std::vector<Bar> tradingview_magnifier_bars(const Bar* bars, int n,
71 const std::string& input_tf,
72 const std::string& script_tf,
73 const std::string& session,
74 const std::string& timezone) {
75 std::vector<Bar> unchanged;
76 if (!bars || n <= 0) return unchanged;
77 unchanged.assign(bars, bars + n);
78
79 const auto input = cal::parse_timeframe(input_tf);
80 const auto chart = cal::parse_timeframe(script_tf);
81 const auto intrabar = cal::parse_timeframe(tradingview_intrabar_timeframe(script_tf));
82 if (!input || !chart || !intrabar) return unchanged;
83 // The intrabars are aggregated from the input, so the input has to be a
84 // strictly finer fixed grid that tiles them: an intrabar of the input's
85 // own timeframe is the input path already, and a finer one (a 1- or
86 // 5-minute chart walks 10- or 30-second bars) cannot be built from it.
87 const std::int64_t input_ms = fixed_ms(*input);
88 const std::int64_t intrabar_ms = fixed_ms(*intrabar);
89 if (input_ms <= 0) return unchanged;
90 if (intrabar_ms != 0 && (intrabar_ms <= input_ms || intrabar_ms % input_ms != 0))
91 return unchanged;
92 for (int i = 1; i < n; ++i) {
93 if (bars[i].timestamp <= bars[i - 1].timestamp) return unchanged;
94 }
95 const auto calendar = cal::parse_session(session, timezone);
96 if (!calendar) return unchanged;
97 cal::SessionDayMemo memo;
98
99 // Every input bar's chart bar, and its intrabar with the chart bar that
100 // owns it -- the one holding the intrabar's last traded instant. Both are
101 // the calendar's own intervals, the ones the kernel aggregates with.
102 struct Row { std::int64_t chart_open; std::int64_t slot_open; std::int64_t owner_open; };
103 std::vector<Row> rows(static_cast<std::size_t>(n));
104 std::optional<cal::NativeInterval> chart_at, slot_at, owner_at;
105 for (int i = 0; i < n; ++i) {
106 const std::int64_t t = bars[i].timestamp;
107 if (!chart_at || t < chart_at->open_ms || t >= chart_at->next_period_open_ms) {
108 chart_at = cal::interval_containing(*calendar, *chart, t, memo);
109 if (!chart_at) return unchanged;
110 }
111 if (!slot_at || t < slot_at->open_ms || t >= slot_at->next_period_open_ms) {
112 slot_at = cal::interval_containing(*calendar, *intrabar, t, memo);
113 if (!slot_at || slot_at->last_traded_close_ms <= slot_at->open_ms) return unchanged;
114 owner_at = cal::interval_containing(*calendar, *chart,
115 slot_at->last_traded_close_ms - 1, memo);
116 if (!owner_at) return unchanged;
117 }
118 rows[static_cast<std::size_t>(i)] = {chart_at->open_ms, slot_at->open_ms, owner_at->open_ms};
119 }
120
121 // Consecutive runs of the input: chart bars, and intrabars with their
122 // owner. Both partitions are time-ordered, and an owner never precedes
123 // the chart bar of its intrabar's first input.
124 struct Span { std::int64_t key; std::int64_t owner; int first; int last; };
125 std::vector<Span> charts, slots;
126 for (int i = 0; i < n; ++i) {
127 const auto& row = rows[static_cast<std::size_t>(i)];
128 if (charts.empty() || charts.back().key != row.chart_open)
129 charts.push_back({row.chart_open, row.chart_open, i, i});
130 else
131 charts.back().last = i;
132 if (slots.empty() || slots.back().key != row.slot_open)
133 slots.push_back({row.slot_open, row.owner_open, i, i});
134 else
135 slots.back().last = i;
136 }
137
138 std::vector<Bar> path;
139 path.reserve(slots.size() + 2 * charts.size());
140 std::size_t slot = 0;
141 for (const auto& bar : charts) {
142 // An intrabar owned by a chart bar the input never reaches has no
143 // chart bar to walk in; TradingView has none either.
144 while (slot < slots.size() && slots[slot].owner < bar.key) ++slot;
145 const std::size_t owned_begin = slot;
146 while (slot < slots.size() && slots[slot].owner == bar.key) ++slot;
147 if (owned_begin == slot) continue; // the kernel walks the chart bar's own OHLC
148 const Bar& chart_first = bars[bar.first];
149 const Bar& chart_last = bars[bar.last];
150 std::int64_t floor = chart_first.timestamp;
151 if (slots[owned_begin].first < bar.first) {
152 // The straddler reaches back into the previous chart bar, so its
153 // open is not this bar's: the path opens at the chart bar's own
154 // open first (where an order placed at the last close fills).
155 path.push_back(one_price(chart_first.open, floor));
156 ++floor;
157 }
158 for (std::size_t s = owned_begin; s < slot; ++s) {
159 const Span& span = slots[s];
160 Bar intrabar = bars[span.first];
161 for (int i = span.first + 1; i <= span.last; ++i) {
162 intrabar.high = std::max(intrabar.high, bars[i].high);
163 intrabar.low = std::min(intrabar.low, bars[i].low);
164 intrabar.close = bars[i].close;
165 intrabar.volume += bars[i].volume;
166 }
167 intrabar.timestamp = std::max(bars[span.first].timestamp, floor);
168 floor = intrabar.timestamp + 1;
169 path.push_back(intrabar);
170 }
171 if (slots[slot - 1].last < bar.last) {
172 // The chart bar's last inputs open a later bar's straddler: the
173 // path still closes at this chart bar's own close.
174 path.push_back(one_price(chart_last.close, std::max(chart_last.timestamp, floor)));
175 }
176 }
177 return path;
178}
179
180} // namespace pineforge::source
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::string tradingview_intrabar_timeframe(std::string_view chart_tf)
The intrabar timeframe TradingView's bar magnifier walks on a chart of chart_tf, from the table in Tr...
double open
Definition bar.hpp:7
double close
Definition bar.hpp:7
double low
Definition bar.hpp:7
double volume
Definition bar.hpp:7
int64_t timestamp
Definition bar.hpp:8
double high
Definition bar.hpp:7