react-native-vroom-chart 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cpp/VroomChartHostObject.cpp +252 -1
- package/cpp/_core_include/vroom/vroom_chart.h +178 -4
- package/cpp/_core_src/atr.cpp +67 -0
- package/cpp/_core_src/atr.h +44 -0
- package/cpp/_core_src/atr_pane.cpp +162 -0
- package/cpp/_core_src/atr_pane.h +48 -0
- package/cpp/_core_src/candles.cpp +15 -14
- package/cpp/_core_src/chart.cpp +758 -200
- package/cpp/_core_src/chart.h +221 -3
- package/cpp/_core_src/chart_facade.cpp +236 -23
- package/cpp/_core_src/color_lerp.h +28 -0
- package/cpp/_core_src/fair_value_gaps.cpp +76 -0
- package/cpp/_core_src/fair_value_gaps.h +54 -0
- package/cpp/_core_src/fvg_overlay.cpp +262 -0
- package/cpp/_core_src/fvg_overlay.h +43 -0
- package/cpp/_core_src/ichimoku.cpp +65 -0
- package/cpp/_core_src/ichimoku.h +45 -0
- package/cpp/_core_src/labels.cpp +3 -0
- package/cpp/_core_src/line_morph.h +159 -0
- package/cpp/_core_src/loading_line.cpp +183 -0
- package/cpp/_core_src/loading_line.h +38 -0
- package/cpp/_core_src/loading_wave.h +140 -0
- package/cpp/_core_src/ma_overlay.cpp +273 -45
- package/cpp/_core_src/ma_overlay.h +64 -2
- package/cpp/_core_src/macd.cpp +24 -1
- package/cpp/_core_src/macd.h +16 -0
- package/cpp/_core_src/macd_pane.cpp +71 -62
- package/cpp/_core_src/macd_pane.h +13 -1
- package/cpp/_core_src/pane_series.h +169 -0
- package/cpp/_core_src/price_indicator.cpp +21 -7
- package/cpp/_core_src/price_indicator.h +11 -1
- package/cpp/_core_src/price_indicator_anim.h +81 -0
- package/cpp/_core_src/rsi.cpp +4 -0
- package/cpp/_core_src/rsi.h +7 -0
- package/cpp/_core_src/rsi_pane.cpp +85 -29
- package/cpp/_core_src/rsi_pane.h +11 -1
- package/cpp/_core_src/theme.cpp +5 -0
- package/cpp/_core_src/tip_geometry.h +55 -0
- package/cpp/_core_src/viewport.h +68 -0
- package/lib/index.d.mts +281 -3
- package/lib/index.d.ts +281 -3
- package/lib/index.js +292 -14
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +292 -14
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +33 -3
- package/src/dataTransitions.ts +47 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +139 -1
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +402 -8
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#include "fvg_overlay.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include <cstddef>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
#pragma clang diagnostic push
|
|
9
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
10
|
+
#include "include/core/SkCanvas.h"
|
|
11
|
+
#include "include/core/SkColor.h"
|
|
12
|
+
#include "include/core/SkFont.h"
|
|
13
|
+
#include "include/core/SkPaint.h"
|
|
14
|
+
#include "include/core/SkRect.h"
|
|
15
|
+
#include "include/effects/SkDashPathEffect.h"
|
|
16
|
+
#pragma clang diagnostic pop
|
|
17
|
+
|
|
18
|
+
#include "chart.h"
|
|
19
|
+
#include "fonts.h"
|
|
20
|
+
|
|
21
|
+
namespace vroom::fvg_overlay {
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
constexpr SkScalar kDotted[2] = {2.f, 2.f};
|
|
26
|
+
constexpr SkScalar kDashed[2] = {6.f, 4.f};
|
|
27
|
+
constexpr float kLabelPadX = 4.f; // clearance between a label and a box edge
|
|
28
|
+
|
|
29
|
+
// Dash pattern for a VroomFairValueGaps::border_style. Null = solid.
|
|
30
|
+
sk_sp<SkPathEffect> dash_for(int32_t border_style) {
|
|
31
|
+
if (border_style == 1) return SkDashPathEffect::Make(kDotted, 0.f);
|
|
32
|
+
if (border_style == 2) return SkDashPathEffect::Make(kDashed, 0.f);
|
|
33
|
+
return nullptr;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A stretch of time one box covers. A gap draws one of these while it is open
|
|
37
|
+
// and, under show_inverse, a second of the opposite polarity once it has been
|
|
38
|
+
// violated.
|
|
39
|
+
struct Span {
|
|
40
|
+
int64_t start_ms;
|
|
41
|
+
int64_t end_ms;
|
|
42
|
+
bool bullish; // polarity of THIS span, already flipped for an inverse
|
|
43
|
+
bool inverse;
|
|
44
|
+
bool open; // still running at the newest bar rather than cut short
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// The spans `g` draws, written to `out` oldest first, returning how many. Zero
|
|
48
|
+
// when the gap is set to disappear on the fill and has no inversion to show.
|
|
49
|
+
std::size_t spans_for(const VroomChart& chart,
|
|
50
|
+
const vroom::fvg::Gap& g,
|
|
51
|
+
std::array<Span, 2>& out) {
|
|
52
|
+
const VroomFairValueGaps& cfg = chart.fvg;
|
|
53
|
+
const int64_t dur = chart.candle_duration_ms;
|
|
54
|
+
const int64_t newest_end = chart.candles.back().time_ms + dur;
|
|
55
|
+
const int64_t length = static_cast<int64_t>(cfg.box_length) * dur;
|
|
56
|
+
std::size_t n = 0;
|
|
57
|
+
|
|
58
|
+
// The original. It runs its configured length, or — when extended — to the
|
|
59
|
+
// end of the newest bar, and stops at the far side of the bar that filled
|
|
60
|
+
// it, so the shading covers exactly the span the gap was open for.
|
|
61
|
+
if (g.filled_ms == 0 || !cfg.delete_after_fill) {
|
|
62
|
+
int64_t end = cfg.extend_boxes ? newest_end : g.time_ms + length;
|
|
63
|
+
const bool open = g.filled_ms == 0;
|
|
64
|
+
if (!open) end = std::min(end, g.filled_ms + dur);
|
|
65
|
+
if (end > g.time_ms) out[n++] = Span{g.time_ms, end, g.bullish, false, open};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The inversion, anchored past the close of the breaking bar so it picks up
|
|
69
|
+
// exactly where the original stops and the two never overlap. It measures
|
|
70
|
+
// its own length from there and ends where price reclaimed the band.
|
|
71
|
+
if (cfg.show_inverse && g.filled_ms != 0) {
|
|
72
|
+
const int64_t start = g.filled_ms + dur;
|
|
73
|
+
int64_t end = cfg.extend_boxes ? newest_end : start + length;
|
|
74
|
+
const bool open = g.invalidated_ms == 0;
|
|
75
|
+
if (!open) end = std::min(end, g.invalidated_ms + dur);
|
|
76
|
+
if (end > start) out[n++] = Span{start, end, !g.bullish, true, open};
|
|
77
|
+
}
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// One span resolved to pixels, or false when it lands entirely off to one side.
|
|
82
|
+
bool box_for(const VroomChart& chart,
|
|
83
|
+
const vroom::Layout& lay,
|
|
84
|
+
const vroom::PriceBounds& bounds,
|
|
85
|
+
int64_t window_ms,
|
|
86
|
+
float candle_right,
|
|
87
|
+
const vroom::fvg::Gap& g,
|
|
88
|
+
const Span& s,
|
|
89
|
+
SkRect* out) {
|
|
90
|
+
const float left =
|
|
91
|
+
vroom::x_at_time(lay, chart.visible_start_ms, window_ms, s.start_ms);
|
|
92
|
+
const float right =
|
|
93
|
+
vroom::x_at_time(lay, chart.visible_start_ms, window_ms, s.end_ms);
|
|
94
|
+
if (right < 0.f || left > candle_right) return false;
|
|
95
|
+
|
|
96
|
+
*out = SkRect::MakeLTRB(left, vroom::price_to_y(lay, bounds, g.top), right,
|
|
97
|
+
vroom::price_to_y(lay, bounds, g.bottom));
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The fill color a span shades with, before opacity.
|
|
102
|
+
SkColor fill_color(const VroomFairValueGaps& cfg, const Span& s) {
|
|
103
|
+
if (s.inverse) {
|
|
104
|
+
return s.bullish ? cfg.inverse_bullish_color : cfg.inverse_bearish_color;
|
|
105
|
+
}
|
|
106
|
+
return s.bullish ? cfg.bullish_color : cfg.bearish_color;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The color a span's border and label take. Inverse spans have no paired border
|
|
110
|
+
// color of their own, so they reuse their fill at full alpha — the same
|
|
111
|
+
// relationship bullishBorderColor already has with bullishColor by default.
|
|
112
|
+
SkColor line_color(const VroomFairValueGaps& cfg, const Span& s) {
|
|
113
|
+
if (s.inverse) return SkColorSetA(fill_color(cfg, s), 0xff);
|
|
114
|
+
return s.bullish ? cfg.bullish_border_color : cfg.bearish_border_color;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Whether there is anything at all to draw, and the cache is safe to walk.
|
|
118
|
+
bool active(const VroomChart& chart, SkCanvas* canvas, float candle_right,
|
|
119
|
+
float candle_area_h) {
|
|
120
|
+
return canvas && candle_right > 0.f && candle_area_h > 0.f &&
|
|
121
|
+
chart.fvg.enabled && !chart.candles.empty() &&
|
|
122
|
+
!chart.fvg_cache.empty();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
SkColor with_opacity(SkColor c, float opacity) {
|
|
126
|
+
return SkColorSetA(c, static_cast<U8CPU>(SkColorGetA(c) * opacity));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
} // namespace
|
|
130
|
+
|
|
131
|
+
void draw_boxes(SkCanvas* canvas,
|
|
132
|
+
const VroomChart& chart,
|
|
133
|
+
const Layout& lay,
|
|
134
|
+
const PriceBounds& bounds,
|
|
135
|
+
int64_t window_ms,
|
|
136
|
+
float candle_right,
|
|
137
|
+
float candle_area_h) {
|
|
138
|
+
if (!active(chart, canvas, candle_right, candle_area_h)) return;
|
|
139
|
+
const VroomFairValueGaps& cfg = chart.fvg;
|
|
140
|
+
|
|
141
|
+
SkPaint fill;
|
|
142
|
+
fill.setAntiAlias(true);
|
|
143
|
+
fill.setStyle(SkPaint::kFill_Style);
|
|
144
|
+
|
|
145
|
+
SkPaint border;
|
|
146
|
+
border.setAntiAlias(true);
|
|
147
|
+
border.setStyle(SkPaint::kStroke_Style);
|
|
148
|
+
border.setStrokeWidth(cfg.border_width > 0.f ? cfg.border_width : 1.f);
|
|
149
|
+
border.setPathEffect(dash_for(cfg.border_style));
|
|
150
|
+
|
|
151
|
+
canvas->save();
|
|
152
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
153
|
+
std::array<Span, 2> spans;
|
|
154
|
+
for (const vroom::fvg::Gap& g : chart.fvg_cache) {
|
|
155
|
+
const std::size_t n = spans_for(chart, g, spans);
|
|
156
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
157
|
+
SkRect rect;
|
|
158
|
+
if (!box_for(chart, lay, bounds, window_ms, candle_right, g, spans[i],
|
|
159
|
+
&rect)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fill.setColor(with_opacity(fill_color(cfg, spans[i]), cfg.opacity));
|
|
164
|
+
canvas->drawRect(rect, fill);
|
|
165
|
+
|
|
166
|
+
if (cfg.border_enabled) {
|
|
167
|
+
border.setColor(line_color(cfg, spans[i]));
|
|
168
|
+
canvas->drawRect(rect, border);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
canvas->restore();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void draw_labels(SkCanvas* canvas,
|
|
176
|
+
const VroomChart& chart,
|
|
177
|
+
const Layout& lay,
|
|
178
|
+
const PriceBounds& bounds,
|
|
179
|
+
int64_t window_ms,
|
|
180
|
+
float candle_right,
|
|
181
|
+
float candle_area_h) {
|
|
182
|
+
if (!active(chart, canvas, candle_right, candle_area_h)) return;
|
|
183
|
+
const VroomFairValueGaps& cfg = chart.fvg;
|
|
184
|
+
const bool any_inverse = cfg.show_inverse && !chart.fvg_inverse_label.empty();
|
|
185
|
+
if (!cfg.labels_enabled || (chart.fvg_label.empty() && !any_inverse)) return;
|
|
186
|
+
|
|
187
|
+
auto tf = vroom::axis_typeface();
|
|
188
|
+
if (!tf) return;
|
|
189
|
+
const float size = cfg.label_font_size > 0.f
|
|
190
|
+
? cfg.label_font_size
|
|
191
|
+
: chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX];
|
|
192
|
+
SkFont font(tf, size);
|
|
193
|
+
font.setSubpixel(true);
|
|
194
|
+
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
195
|
+
|
|
196
|
+
// Indexed by Span::inverse, so a span picks its own text and metrics.
|
|
197
|
+
struct Label {
|
|
198
|
+
const std::string* text;
|
|
199
|
+
float width;
|
|
200
|
+
SkRect bounds;
|
|
201
|
+
};
|
|
202
|
+
Label labels[2] = {{&chart.fvg_label, 0.f, {}},
|
|
203
|
+
{&chart.fvg_inverse_label, 0.f, {}}};
|
|
204
|
+
for (Label& l : labels) {
|
|
205
|
+
l.width = font.measureText(l.text->data(), l.text->size(),
|
|
206
|
+
SkTextEncoding::kUTF8, &l.bounds);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Extended boxes all stop at the newest bar, so their labels go out into the
|
|
210
|
+
// empty slots past it rather than on top of the shading.
|
|
211
|
+
const float extend_x =
|
|
212
|
+
cfg.extend_boxes
|
|
213
|
+
? vroom::x_at_time(lay, chart.visible_start_ms, window_ms,
|
|
214
|
+
chart.candles.back().time_ms +
|
|
215
|
+
static_cast<int64_t>(cfg.label_distance + 1) *
|
|
216
|
+
chart.candle_duration_ms)
|
|
217
|
+
: 0.f;
|
|
218
|
+
|
|
219
|
+
SkPaint paint;
|
|
220
|
+
paint.setAntiAlias(true);
|
|
221
|
+
|
|
222
|
+
canvas->save();
|
|
223
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
224
|
+
std::array<Span, 2> spans;
|
|
225
|
+
for (const vroom::fvg::Gap& g : chart.fvg_cache) {
|
|
226
|
+
const std::size_t n = spans_for(chart, g, spans);
|
|
227
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
228
|
+
const Span& s = spans[i];
|
|
229
|
+
const Label& l = labels[s.inverse ? 1 : 0];
|
|
230
|
+
if (l.text->empty()) continue;
|
|
231
|
+
|
|
232
|
+
SkRect rect;
|
|
233
|
+
if (!box_for(chart, lay, bounds, window_ms, candle_right, g, s,
|
|
234
|
+
&rect)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// A box that was cut short stops early even under extend, so it
|
|
239
|
+
// keeps its label inside; only boxes still running to the newest
|
|
240
|
+
// bar move theirs out — and only while the pane has the room, since
|
|
241
|
+
// the newest candle sits flush right until the user pans.
|
|
242
|
+
const bool outside = cfg.extend_boxes && s.open &&
|
|
243
|
+
extend_x + l.width <= candle_right;
|
|
244
|
+
// A box whose right end runs past the pane would have its label
|
|
245
|
+
// sliced mid-glyph by the clip, so hold the text inside the plot.
|
|
246
|
+
const float x =
|
|
247
|
+
std::min(outside ? extend_x : rect.right() - kLabelPadX - l.width,
|
|
248
|
+
candle_right - kLabelPadX - l.width);
|
|
249
|
+
if (x + l.width < 0.f) continue;
|
|
250
|
+
|
|
251
|
+
paint.setColor(SkColorGetA(cfg.label_color) != 0 ? cfg.label_color
|
|
252
|
+
: line_color(cfg, s));
|
|
253
|
+
canvas->drawString(
|
|
254
|
+
l.text->c_str(), x,
|
|
255
|
+
rect.centerY() - (l.bounds.fTop + l.bounds.fBottom) * 0.5f, font,
|
|
256
|
+
paint);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
canvas->restore();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
} // namespace vroom::fvg_overlay
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Fair Value Gap boxes — the drawing half of the indicator, kept out of the
|
|
2
|
+
// chart orchestrator the way liquidity / drawings are. Detection lives in the
|
|
3
|
+
// Skia-free fair_value_gaps.{h,cpp}; this reads the cache it fills.
|
|
4
|
+
//
|
|
5
|
+
// Each gap becomes an axis-aligned rectangle spanning its untouched price range
|
|
6
|
+
// and running right from the middle bar. Split across two passes because they
|
|
7
|
+
// sit at different depths: the boxes go BEHIND the candles so bodies paint over
|
|
8
|
+
// them, the labels go in FRONT so they stay readable.
|
|
9
|
+
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
|
|
14
|
+
#include "viewport.h"
|
|
15
|
+
|
|
16
|
+
class SkCanvas;
|
|
17
|
+
struct VroomChart;
|
|
18
|
+
|
|
19
|
+
namespace vroom::fvg_overlay {
|
|
20
|
+
|
|
21
|
+
// Draws the box fills and their outlines. `candle_right` is the x of the
|
|
22
|
+
// price-axis strip and `candle_area_h` the price-pane bottom; geometry is
|
|
23
|
+
// clipped to that rectangle so a box never bleeds into the axis strips.
|
|
24
|
+
void draw_boxes(SkCanvas* canvas,
|
|
25
|
+
const VroomChart& chart,
|
|
26
|
+
const vroom::Layout& lay,
|
|
27
|
+
const vroom::PriceBounds& bounds,
|
|
28
|
+
int64_t window_ms,
|
|
29
|
+
float candle_right,
|
|
30
|
+
float candle_area_h);
|
|
31
|
+
|
|
32
|
+
// Draws each box's label: inside its right end normally, or out in the empty
|
|
33
|
+
// slots past the newest candle when the boxes are extended. No-op while no
|
|
34
|
+
// typeface is loaded.
|
|
35
|
+
void draw_labels(SkCanvas* canvas,
|
|
36
|
+
const VroomChart& chart,
|
|
37
|
+
const vroom::Layout& lay,
|
|
38
|
+
const vroom::PriceBounds& bounds,
|
|
39
|
+
int64_t window_ms,
|
|
40
|
+
float candle_right,
|
|
41
|
+
float candle_area_h);
|
|
42
|
+
|
|
43
|
+
} // namespace vroom::fvg_overlay
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#include "ichimoku.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm> // std::max, std::min
|
|
4
|
+
#include <cmath> // std::nan
|
|
5
|
+
|
|
6
|
+
namespace vroom::ichimoku {
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
// Midpoint of the high/low range over each trailing `period`-bar window — the
|
|
10
|
+
// shape all three of Ichimoku's averaged lines share. NaN over the warmup.
|
|
11
|
+
void midpoint_series(const ::VroomCandle* candles, std::size_t n, int period,
|
|
12
|
+
std::vector<double>& out) {
|
|
13
|
+
out.assign(n, std::nan(""));
|
|
14
|
+
if (!candles || period < 1) return;
|
|
15
|
+
const std::size_t P = static_cast<std::size_t>(period);
|
|
16
|
+
if (n < P) return;
|
|
17
|
+
|
|
18
|
+
for (std::size_t i = P - 1; i < n; ++i) {
|
|
19
|
+
const std::size_t s = i + 1 - P;
|
|
20
|
+
double hi = candles[s].high;
|
|
21
|
+
double lo = candles[s].low;
|
|
22
|
+
for (std::size_t j = s + 1; j <= i; ++j) {
|
|
23
|
+
hi = std::max(hi, candles[j].high);
|
|
24
|
+
lo = std::min(lo, candles[j].low);
|
|
25
|
+
}
|
|
26
|
+
out[i] = (hi + lo) * 0.5;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
32
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int tenkan_period,
|
|
33
|
+
int kijun_period, int senkou_b_period,
|
|
34
|
+
std::vector<double>& tenkan, std::vector<double>& kijun,
|
|
35
|
+
std::vector<double>& senkou_a, std::vector<double>& senkou_b,
|
|
36
|
+
std::vector<double>& chikou) {
|
|
37
|
+
midpoint_series(candles, n, tenkan_period, tenkan);
|
|
38
|
+
midpoint_series(candles, n, kijun_period, kijun);
|
|
39
|
+
midpoint_series(candles, n, senkou_b_period, senkou_b);
|
|
40
|
+
|
|
41
|
+
// NaN propagates through the average, so span A is only defined once both
|
|
42
|
+
// of its inputs are — no separate warmup bound to track.
|
|
43
|
+
senkou_a.assign(n, std::nan(""));
|
|
44
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
45
|
+
senkou_a[i] = (tenkan[i] + kijun[i]) * 0.5;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
chikou.assign(n, std::nan(""));
|
|
49
|
+
if (!candles) return;
|
|
50
|
+
for (std::size_t i = 0; i < n; ++i) chikou[i] = candles[i].close;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
IndexRange shifted_source_range(const ::VroomCandle* candles, std::size_t n,
|
|
54
|
+
int64_t start_ms, int64_t end_ms,
|
|
55
|
+
int64_t shift_ms) {
|
|
56
|
+
// 0/0 is visible_indices' "everything" sentinel (an unframed viewport).
|
|
57
|
+
// Shifting it would turn that into an empty range, so pass it through.
|
|
58
|
+
if (start_ms == 0 && end_ms == 0) {
|
|
59
|
+
return vroom::visible_indices(candles, n, 0, 0);
|
|
60
|
+
}
|
|
61
|
+
return vroom::visible_indices(candles, n, start_ms - shift_ms,
|
|
62
|
+
end_ms - shift_ms);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
} // namespace vroom::ichimoku
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Ichimoku Kinko Hyo over a candle series — pure, no Skia, so it builds into
|
|
2
|
+
// the unit-test target. Drawn as five price-pane overlay lines plus the cloud
|
|
3
|
+
// shaded between the two leading spans.
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <cstddef>
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include "viewport.h" // vroom::IndexRange
|
|
12
|
+
#include "vroom/vroom_chart.h" // ::VroomCandle
|
|
13
|
+
|
|
14
|
+
namespace vroom::ichimoku {
|
|
15
|
+
|
|
16
|
+
// Computes the five Ichimoku series over [candles, candles+n). Each period is
|
|
17
|
+
// clamped to >= 1.
|
|
18
|
+
//
|
|
19
|
+
// tenkan = midpoint of the high/low range over the trailing tenkan window
|
|
20
|
+
// kijun = same over the trailing kijun window
|
|
21
|
+
// senkou_a = (tenkan + kijun) / 2
|
|
22
|
+
// senkou_b = midpoint of the high/low range over the trailing senkou_b window
|
|
23
|
+
// chikou = close
|
|
24
|
+
//
|
|
25
|
+
// Every output is resized to n and indexed by the bar it was computed from —
|
|
26
|
+
// displacement is a drawing concern, so senkou_a/senkou_b/chikou are *not*
|
|
27
|
+
// shifted here. Values are NaN over each series' warmup and when n is shorter
|
|
28
|
+
// than the window a series needs.
|
|
29
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int tenkan_period,
|
|
30
|
+
int kijun_period, int senkou_b_period,
|
|
31
|
+
std::vector<double>& tenkan, std::vector<double>& kijun,
|
|
32
|
+
std::vector<double>& senkou_a, std::vector<double>& senkou_b,
|
|
33
|
+
std::vector<double>& chikou);
|
|
34
|
+
|
|
35
|
+
// Source indices whose *shifted* plot time lands in [start_ms, end_ms], for a
|
|
36
|
+
// series drawn at `candles[i].time_ms + shift_ms`.
|
|
37
|
+
//
|
|
38
|
+
// Shifting the query window back by the same amount is what lets the leading
|
|
39
|
+
// spans run past the newest candle: the range is found among bars that exist,
|
|
40
|
+
// while the x each one draws at does not have to.
|
|
41
|
+
IndexRange shifted_source_range(const ::VroomCandle* candles, std::size_t n,
|
|
42
|
+
int64_t start_ms, int64_t end_ms,
|
|
43
|
+
int64_t shift_ms);
|
|
44
|
+
|
|
45
|
+
} // namespace vroom::ichimoku
|
package/cpp/_core_src/labels.cpp
CHANGED
|
@@ -74,6 +74,9 @@ void apply_envelope(std::vector<Fade>& fades, float opacity) {
|
|
|
74
74
|
|
|
75
75
|
IntervalPhase interval_phase(const VroomChart& chart) {
|
|
76
76
|
if (chart.morph_from.empty() || chart.interval_morph_t >= 1.f) return {};
|
|
77
|
+
// A live update keeps the same interval, so its ticks are still the right
|
|
78
|
+
// ones — they stay put and keep their own per-label fades.
|
|
79
|
+
if (chart.morph_is_stream) return {};
|
|
77
80
|
const float t = chart.interval_morph_t;
|
|
78
81
|
constexpr float kMid = 0.5f;
|
|
79
82
|
if (t < kMid) return {true, true, 1.f - t / kMid};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Interval-morph capture for indicator series — the line-shaped counterpart to
|
|
2
|
+
// viewport.h's CandleSnapshot. Pure geometry, no Skia, so it builds into the
|
|
3
|
+
// unit-test target.
|
|
4
|
+
//
|
|
5
|
+
// A timeframe switch replaces every indicator cache at once (see
|
|
6
|
+
// vroom_chart_set_candles), so an outgoing shape has to be captured before the
|
|
7
|
+
// swap if it is going to reshape into the new one. Captures normalize the same
|
|
8
|
+
// way the candle one does — x as a fraction of the candle-area width, y as a
|
|
9
|
+
// fraction of the band the series was drawn in — which keeps them correct
|
|
10
|
+
// across a mid-morph resize, across the y-axis rescale the switch brings, and,
|
|
11
|
+
// for the indicator panes, across a change in the pane's own auto-fit.
|
|
12
|
+
|
|
13
|
+
#pragma once
|
|
14
|
+
|
|
15
|
+
#include <cstddef>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <vector>
|
|
18
|
+
|
|
19
|
+
#include "vroom/vroom_chart.h" // ::VroomOverlay
|
|
20
|
+
|
|
21
|
+
namespace vroom {
|
|
22
|
+
|
|
23
|
+
// Which series a capture belongs to. Pairing is by identity rather than
|
|
24
|
+
// position so an indicator toggled in the same commit as the timeframe can't
|
|
25
|
+
// reshape one line out of another's geometry.
|
|
26
|
+
enum class LineKind : uint8_t {
|
|
27
|
+
Overlay,
|
|
28
|
+
Vwap,
|
|
29
|
+
BollingerUpper,
|
|
30
|
+
BollingerMiddle,
|
|
31
|
+
BollingerLower,
|
|
32
|
+
IchimokuTenkan,
|
|
33
|
+
IchimokuKijun,
|
|
34
|
+
IchimokuSenkouA,
|
|
35
|
+
IchimokuSenkouB,
|
|
36
|
+
IchimokuChikou,
|
|
37
|
+
Rsi,
|
|
38
|
+
RsiMa,
|
|
39
|
+
Macd,
|
|
40
|
+
MacdSignal,
|
|
41
|
+
MacdHistogram,
|
|
42
|
+
Atr,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Identifies one captured series. The moving-average overlays are a
|
|
46
|
+
// user-ordered vector rather than a fixed slot, so `index` alone would pair two
|
|
47
|
+
// different MAs if one were added or removed during the switch; `tag` carries
|
|
48
|
+
// the overlay's kind, period and source, so a mismatch finds no capture and the
|
|
49
|
+
// line simply snaps. Both are 0 for every other series.
|
|
50
|
+
struct LineKey {
|
|
51
|
+
LineKind kind;
|
|
52
|
+
int32_t index = 0;
|
|
53
|
+
int32_t tag = 0;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
inline bool operator==(const LineKey& a, const LineKey& b) {
|
|
57
|
+
return a.kind == b.kind && a.index == b.index && a.tag == b.tag;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The key for the moving-average overlay at `index`, packing its kind, source
|
|
61
|
+
// and period into the tag. Periods above 65535 alias, which no usable lookback
|
|
62
|
+
// reaches.
|
|
63
|
+
inline LineKey overlay_line_key(const ::VroomOverlay& ov, std::size_t index) {
|
|
64
|
+
const int32_t tag =
|
|
65
|
+
(ov.kind << 24) ^ (ov.source << 16) ^ (ov.period & 0xffff);
|
|
66
|
+
return LineKey{LineKind::Overlay, static_cast<int32_t>(index), tag};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// One captured vertex. `x` is a fraction of the candle-area width and `y` a
|
|
70
|
+
// fraction of the band the series was drawn in (0 = bottom edge, 1 = top).
|
|
71
|
+
// `valid` is false where the source value was NaN, which is how a warmup gap
|
|
72
|
+
// survives the capture.
|
|
73
|
+
struct LineSnapshot {
|
|
74
|
+
float x = 0.f;
|
|
75
|
+
float y = 0.f;
|
|
76
|
+
bool valid = false;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// One captured series. Slot 0 is the newest vertex, matching the right-edge
|
|
80
|
+
// indexing candles::draw pairs on. `scale` is the auto-fit its pane band was
|
|
81
|
+
// sized to at capture time, which the pane's axis label reads back mid-morph;
|
|
82
|
+
// 0 for series on the price scale, whose band is the price bounds.
|
|
83
|
+
struct LineMorph {
|
|
84
|
+
LineKey key{LineKind::Overlay};
|
|
85
|
+
double scale = 0.0;
|
|
86
|
+
std::vector<LineSnapshot> pts;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// The capture for `key`, or null when nothing matching was captured — which is
|
|
90
|
+
// what an indicator enabled mid-morph gets, so it draws its new shape directly
|
|
91
|
+
// instead of reshaping out of geometry that was never on screen.
|
|
92
|
+
inline const LineMorph* find_line_morph(const std::vector<LineMorph>& lines,
|
|
93
|
+
const LineKey& key) {
|
|
94
|
+
for (const LineMorph& line : lines) {
|
|
95
|
+
if (line.key == key) return &line;
|
|
96
|
+
}
|
|
97
|
+
return nullptr;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// How many captured slots still contribute to a frame, mirroring
|
|
101
|
+
// morph_from_count: 0 once the morph lands, which collapses the draw path back
|
|
102
|
+
// to the new series alone. Drawing routines take max(n, this) as their slot
|
|
103
|
+
// count, pairing the new vertex at slot k with the captured one at pts[k].
|
|
104
|
+
inline std::size_t morph_line_count(const LineMorph* from, float morph_t) {
|
|
105
|
+
return (from && morph_t < 1.f) ? from->pts.size() : 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// One slot resolved to screen pixels. `valid` false means neither side defined
|
|
109
|
+
// the slot, and the polyline lifts its pen there.
|
|
110
|
+
struct MorphVertex {
|
|
111
|
+
float x = 0.f;
|
|
112
|
+
float y = 0.f;
|
|
113
|
+
bool valid = false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Blends a slot's captured position toward its new one. Both sides arrive in
|
|
117
|
+
// pixels: the caller converts the capture's fractions through whichever band
|
|
118
|
+
// the series lives in, which is the only part that differs between a price-pane
|
|
119
|
+
// overlay and an indicator pane.
|
|
120
|
+
//
|
|
121
|
+
// A slot only one side defines holds that side's position rather than lifting
|
|
122
|
+
// the pen. The warmup gap at a line's left end spans a different amount of time
|
|
123
|
+
// at each resolution, and sliding that end reads far better than blinking it.
|
|
124
|
+
inline MorphVertex morph_vertex(const MorphVertex& to, const MorphVertex& from,
|
|
125
|
+
float morph_t) {
|
|
126
|
+
if (!to.valid) return from;
|
|
127
|
+
if (!from.valid) return to;
|
|
128
|
+
return MorphVertex{from.x + (to.x - from.x) * morph_t,
|
|
129
|
+
from.y + (to.y - from.y) * morph_t, true};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The line counterpart to viewport.h's blend_candle_snapshots: rewrites a fresh
|
|
133
|
+
// capture to start from the shape on screen, so a live tick restarting the
|
|
134
|
+
// morph doesn't snap the indicators back off the candles they sit on.
|
|
135
|
+
//
|
|
136
|
+
// Series are matched by key, so one enabled or removed between two ticks simply
|
|
137
|
+
// finds no counterpart and keeps its fresh capture. A slot only one side
|
|
138
|
+
// defines keeps that side, mirroring morph_vertex — the warmup gap can move by
|
|
139
|
+
// a slot as bars arrive, and sliding that end reads better than blinking it.
|
|
140
|
+
inline void blend_line_morphs(std::vector<LineMorph>& dst,
|
|
141
|
+
const std::vector<LineMorph>& interrupted,
|
|
142
|
+
float morph_t) {
|
|
143
|
+
for (LineMorph& to : dst) {
|
|
144
|
+
const LineMorph* from = find_line_morph(interrupted, to.key);
|
|
145
|
+
if (!from) continue;
|
|
146
|
+
to.scale = from->scale + (to.scale - from->scale) * morph_t;
|
|
147
|
+
const std::size_t n =
|
|
148
|
+
to.pts.size() < from->pts.size() ? to.pts.size() : from->pts.size();
|
|
149
|
+
for (std::size_t k = 0; k < n; ++k) {
|
|
150
|
+
const LineSnapshot& a = from->pts[k];
|
|
151
|
+
LineSnapshot& b = to.pts[k];
|
|
152
|
+
if (!a.valid || !b.valid) continue;
|
|
153
|
+
b.x = a.x + (b.x - a.x) * morph_t;
|
|
154
|
+
b.y = a.y + (b.y - a.y) * morph_t;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
} // namespace vroom
|