9#include "../native_calendar_memo.hpp"
21namespace cal = native_calendar;
23constexpr std::int64_t kSecondMs = 1000;
24constexpr std::int64_t kMinuteMs = 60 * kSecondMs;
27std::int64_t fixed_ms(
const cal::Timeframe& tf)
noexcept {
29 case cal::TimeframeUnit::Second:
return tf.count() * kSecondMs;
30 case cal::TimeframeUnit::Minute:
return tf.count() * kMinuteMs;
35Bar one_price(
double price, std::int64_t timestamp)
noexcept {
37 bar.open = bar.high = bar.low = bar.close =
price;
39 bar.timestamp = timestamp;
46 const auto chart = cal::parse_timeframe(chart_tf);
47 if (!chart || !chart->valid())
return {};
48 std::int64_t seconds = 0;
49 switch (chart->unit()) {
50 case cal::TimeframeUnit::Second: seconds = chart->count();
break;
51 case cal::TimeframeUnit::Minute: seconds = std::int64_t{chart->count()} * 60;
break;
52 case cal::TimeframeUnit::Day: seconds = std::int64_t{chart->count()} * 86400;
break;
53 case cal::TimeframeUnit::Week:
54 case cal::TimeframeUnit::Month:
57 constexpr std::int64_t kMinute = 60;
58 constexpr std::int64_t kDay = 1440 * kMinute;
59 if (seconds < 30)
return "1S";
60 if (seconds < kMinute)
return "5S";
61 if (seconds < 5 * kMinute)
return "10S";
62 if (seconds < 10 * kMinute)
return "30S";
63 if (seconds < 15 * kMinute)
return "1";
64 if (seconds < 30 * kMinute)
return "2";
65 if (seconds < 60 * kMinute)
return "5";
66 if (seconds < 240 * kMinute)
return "10";
67 if (seconds < kDay)
return "30";
68 if (seconds < 3 * kDay)
return "60";
69 if (seconds < 7 * kDay)
return "240";
74 const std::string& input_tf,
75 const std::string& script_tf,
76 const std::string& session,
77 const std::string& timezone) {
78 std::vector<Bar> unchanged;
79 if (!bars || n <= 0)
return unchanged;
80 unchanged.assign(bars, bars + n);
82 const auto input = cal::parse_timeframe(input_tf);
83 const auto chart = cal::parse_timeframe(script_tf);
85 if (!
input || !chart || !intrabar)
return unchanged;
90 const std::int64_t input_ms = fixed_ms(*
input);
91 const std::int64_t intrabar_ms = fixed_ms(*intrabar);
92 if (input_ms <= 0)
return unchanged;
93 if (intrabar_ms != 0 && (intrabar_ms <= input_ms || intrabar_ms % input_ms != 0))
95 for (
int i = 1; i < n; ++i) {
96 if (bars[i].timestamp <= bars[i - 1].timestamp)
return unchanged;
98 const auto calendar = cal::parse_session(session, timezone);
99 if (!calendar)
return unchanged;
100 cal::SessionDayMemo memo;
105 struct Row { std::int64_t chart_open; std::int64_t slot_open; std::int64_t owner_open; };
106 std::vector<Row> rows(
static_cast<std::size_t
>(n));
107 std::optional<cal::NativeInterval> chart_at, slot_at, owner_at;
108 for (
int i = 0; i < n; ++i) {
109 const std::int64_t t = bars[i].
timestamp;
110 if (!chart_at || t < chart_at->open_ms || t >= chart_at->next_period_open_ms) {
111 chart_at = cal::interval_containing(*calendar, *chart, t, memo);
112 if (!chart_at)
return unchanged;
114 if (!slot_at || t < slot_at->open_ms || t >= slot_at->next_period_open_ms) {
115 slot_at = cal::interval_containing(*calendar, *intrabar, t, memo);
116 if (!slot_at || slot_at->last_traded_close_ms <= slot_at->open_ms)
return unchanged;
117 owner_at = cal::interval_containing(*calendar, *chart,
118 slot_at->last_traded_close_ms - 1, memo);
119 if (!owner_at)
return unchanged;
121 rows[
static_cast<std::size_t
>(i)] = {chart_at->open_ms, slot_at->open_ms, owner_at->open_ms};
127 struct Span { std::int64_t key; std::int64_t owner;
int first;
int last; };
128 std::vector<Span> charts, slots;
129 for (
int i = 0; i < n; ++i) {
130 const auto& row = rows[
static_cast<std::size_t
>(i)];
131 if (charts.empty() || charts.back().key != row.chart_open)
132 charts.push_back({row.chart_open, row.chart_open, i, i});
134 charts.back().last = i;
135 if (slots.empty() || slots.back().key != row.slot_open)
136 slots.push_back({row.slot_open, row.owner_open, i, i});
138 slots.back().last = i;
141 std::vector<Bar> path;
142 path.reserve(slots.size() + 2 * charts.size());
143 std::size_t slot = 0;
144 for (
const auto& bar : charts) {
147 while (slot < slots.size() && slots[slot].owner < bar.key) ++slot;
148 const std::size_t owned_begin = slot;
149 while (slot < slots.size() && slots[slot].owner == bar.key) ++slot;
150 if (owned_begin == slot)
continue;
151 const Bar& chart_first = bars[bar.first];
152 const Bar& chart_last = bars[bar.last];
153 std::int64_t floor = chart_first.
timestamp;
154 if (slots[owned_begin].first < bar.first) {
158 path.push_back(one_price(chart_first.
open, floor));
161 for (std::size_t s = owned_begin; s < slot; ++s) {
162 const Span& span = slots[s];
163 Bar intrabar = bars[span.first];
164 for (
int i = span.first + 1; i <= span.last; ++i) {
166 intrabar.
low = std::min(intrabar.
low, bars[i].
low);
172 path.push_back(intrabar);
174 if (slots[slot - 1].last < bar.last) {
177 path.push_back(one_price(chart_last.
close, std::max(chart_last.
timestamp, floor)));
185[[noreturn]]
void path_failure(
const char* message) {
187 {{
"option",
"intrabar_path"}}, message);
190[[noreturn]]
void path_invariant(
const char* message) {
194std::int64_t after_stamp(std::int64_t stamp) {
195 if (stamp == std::numeric_limits<std::int64_t>::max())
197 {{
"option",
"intrabar_path"}},
198 "magnifier timestamp exhausted");
205class MagnifierPathSource final :
public NativeLowerPathSource {
208 std::optional<cal::Timeframe> chart_tf_, slot_tf_;
209 std::optional<cal::SessionCalendar> calendar_;
210 bool fallback_ =
true;
211 std::size_t count_ = 0;
214 std::int64_t chart, slot, owner;
218 const MagnifierPathSource* source;
221 cal::SessionDayMemo memo;
222 std::optional<cal::NativeInterval> chart, slot, owner;
224 explicit Rows(
const MagnifierPathSource* value) : source(
value) {}
226 Rows(
const Rows& other)
227 : source(other.source), at(other.at), failed(other.failed),
228 chart(other.chart), slot(other.slot), owner(other.owner) {}
229 bool next(Row& row) {
230 if (at == source->n_)
return false;
231 const auto t = source->input_[at].timestamp;
232 if (!chart || t < chart->open_ms || t >= chart->next_period_open_ms) {
233 chart = cal::interval_containing(*source->calendar_, *source->chart_tf_, t, memo);
234 if (!chart) { failed =
true;
return false; }
236 if (!slot || t < slot->open_ms || t >= slot->next_period_open_ms) {
237 slot = cal::interval_containing(*source->calendar_, *source->slot_tf_, t, memo);
238 if (!slot || slot->last_traded_close_ms <= slot->open_ms) {
239 failed =
true;
return false;
241 owner = cal::interval_containing(*source->calendar_, *source->chart_tf_,
242 slot->last_traded_close_ms - 1, memo);
243 if (!owner) { failed =
true;
return false; }
245 row = {chart->open_ms, slot->open_ms, owner->open_ms, at++};
250 std::int64_t key, owner;
257 std::optional<Row> pending;
258 Spans(
const MagnifierPathSource* source,
bool is_slot) : rows(source), slots(is_slot) {}
259 std::optional<Span> next() {
261 if (pending) { row = *pending; pending.reset(); }
262 else if (!rows.next(row)) {
263 if (rows.failed) path_failure(
"magnifier calendar lookup changed after pre-scan");
266 Span out{slots ? row.slot : row.chart, slots ? row.owner : row.chart,
267 row.index, row.index, rows.source->input_[row.index]};
268 while (rows.next(row)) {
269 if ((slots ? row.slot : row.chart) != out.key) {
273 out.last = row.index;
275 const Bar& bar = rows.source->input_[row.index];
276 out.value.high = std::max(out.value.high, bar.high);
277 out.value.low = std::min(out.value.low, bar.low);
278 out.value.close = bar.close;
279 out.value.volume += bar.volume;
282 if (rows.failed) path_failure(
"magnifier calendar lookup changed after pre-scan");
287 const MagnifierPathSource* source;
289 std::optional<Span> chart, slot;
290 std::int64_t floor = 0;
291 bool active =
false, opening =
false, closing =
false;
292 explicit Cursor(
const MagnifierPathSource* value)
293 : source(
value), charts(
value, false), slots(
value, true), slot(slots.next()) {}
295 bool next(Bar& out) {
297 chart = charts.next();
298 if (!chart)
return false;
299 while (slot && slot->owner < chart->key) slot = slots.next();
301 if (!slot || slot->owner != chart->key)
continue;
302 floor = source->input_[chart->first].timestamp;
303 opening = slot->first < chart->first;
308 out = one_price(source->input_[chart->first].open, floor);
309 floor = after_stamp(floor);
311 }
else if (closing) {
312 const Bar& last = source->input_[chart->last];
313 out = one_price(last.close, std::max(last.timestamp, floor));
317 out.timestamp = std::max(out.timestamp, floor);
318 floor = after_stamp(out.timestamp);
319 const int last = slot->last;
321 if (!slot || slot->owner != chart->key) {
322 closing = last < chart->last;
323 if (!closing) active =
false;
330 bool selected_ =
false;
331 std::int64_t begin_ = 0, end_ = 0;
332 NativeBarView view_{
nullptr, 0};
333 std::vector<Bar> segment_, scratch_;
334 std::unique_ptr<Cursor> cursor_;
335 std::optional<Bar> next_;
338 MagnifierPathSource(
const Bar*
input,
int n,
const std::string& input_tf,
339 const std::string& script_tf,
const std::string& session,
340 const std::string& timezone)
342 count_ =
static_cast<std::size_t
>(n_);
344 const auto input_grid = cal::parse_timeframe(input_tf);
345 chart_tf_ = cal::parse_timeframe(script_tf);
347 if (!input_grid || !chart_tf_ || !slot_tf_)
return;
348 const auto input_ms = fixed_ms(*input_grid);
349 const auto slot_ms = fixed_ms(*slot_tf_);
350 if (input_ms <= 0 || (slot_ms != 0 && (slot_ms <= input_ms || slot_ms % input_ms != 0)))
352 for (
int i = 1; i < n_; ++i)
353 if (input_[i].timestamp <= input_[i - 1].timestamp)
return;
354 calendar_ = cal::parse_session(session, timezone);
355 if (!calendar_)
return;
360 while (rows.next(row)) {}
361 if (rows.failed)
return;
366 while (cursor.next(bar)) {
367 if (count_ == std::numeric_limits<std::size_t>::max())
369 {{
"option",
"intrabar_path"}},
370 "magnifier path count exhausted");
375 std::size_t count()
const override {
return count_; }
376 bool for_each(Visitor visitor,
void* context)
const override {
377 if (visitor ==
nullptr) path_invariant(
"null magnifier visitor");
379 for (
int i = 0; i < n_; ++i)
if (!visitor(context, input_[i]))
return false;
383 while (cursor.next(bar))
if (!visitor(context, bar))
return false;
388 NativeBarView segment(std::int64_t begin, std::int64_t end)
override {
389 if (end < begin || (selected_ && (begin < begin_ || end < end_)))
390 path_invariant(
"magnifier window moved backwards");
391 if (selected_ && begin == begin_ && end == end_)
return view_;
394 const Bar* first = input_;
395 const Bar* last = input_;
397 const auto less = [](
const Bar& bar, std::int64_t
t) {
return bar.timestamp <
t; };
398 first = std::lower_bound(input_, input_ + n_, begin, less);
399 last = std::lower_bound(first, input_ + n_, end, less);
401 view_ = {first, n_ == 0 ? 0 :
static_cast<std::size_t
>(last - first)};
402 next_ = n_ == 0 || last == input_ + n_ ? std::nullopt : std::optional<Bar>(*last);
407 auto candidate = cursor_ ? std::make_unique<Cursor>(*cursor_)
408 : std::make_unique<Cursor>(this);
411 for (
const auto& bar : segment_)
412 if (bar.timestamp >= begin) scratch_.push_back(bar);
415 if (next) { bar = *next; next.reset(); }
416 else if (!candidate->next(bar))
break;
417 if (bar.timestamp >= end) { next = bar;
break; }
418 if (bar.timestamp >= begin) scratch_.push_back(bar);
420 segment_.swap(scratch_);
421 cursor_.swap(candidate);
423 view_ = {segment_.data(), segment_.size()};
430 const Bar* next_after_segment() const noexcept
override {
431 return next_ ? &*next_ :
nullptr;
438 const Bar* bars,
int n,
const std::string& input_tf,
439 const std::string& script_tf,
const std::string& session,
440 const std::string& timezone) {
441 return std::make_unique<MagnifierPathSource>(bars, n, input_tf, script_tf, session, timezone);
Base with a code: throw coded<std::invalid_argument>(code, {args}, text).
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.
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...