react-native-vroom-chart 0.6.0 → 0.8.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 +281 -33
- package/cpp/_core_include/vroom/vroom_chart.h +231 -36
- package/cpp/_core_src/bollinger.h +1 -1
- package/cpp/_core_src/candles.cpp +138 -41
- package/cpp/_core_src/candles.h +11 -1
- package/cpp/_core_src/chart.cpp +91 -40
- package/cpp/_core_src/chart.h +69 -29
- package/cpp/_core_src/chart_facade.cpp +247 -69
- package/cpp/_core_src/drawings.cpp +147 -4
- package/cpp/_core_src/drawings.h +10 -5
- package/cpp/_core_src/gradient.cpp +46 -0
- package/cpp/_core_src/gradient.h +31 -0
- package/cpp/_core_src/labels.cpp +49 -16
- package/cpp/_core_src/labels.h +33 -4
- package/cpp/_core_src/liquidity.cpp +3 -37
- package/cpp/_core_src/ma_overlay.cpp +174 -12
- package/cpp/_core_src/ma_overlay.h +55 -3
- package/cpp/_core_src/macd.cpp +13 -42
- package/cpp/_core_src/macd.h +11 -8
- package/cpp/_core_src/macd_pane.cpp +57 -27
- package/cpp/_core_src/price_line_layout.h +1 -1
- package/cpp/_core_src/rsi.cpp +4 -18
- package/cpp/_core_src/rsi.h +6 -6
- package/cpp/_core_src/rsi_pane.cpp +34 -19
- package/cpp/_core_src/series_ma.cpp +64 -0
- package/cpp/_core_src/series_ma.h +30 -0
- package/cpp/_core_src/style_inherit.h +35 -0
- package/cpp/_core_src/theme.cpp +2 -1
- package/cpp/_core_src/viewport.cpp +36 -12
- package/cpp/_core_src/viewport.h +50 -0
- package/cpp/_core_src/volume.cpp +33 -8
- package/cpp/_core_src/volume.h +12 -1
- package/cpp/_core_src/volume_anim.cpp +32 -0
- package/cpp/_core_src/volume_anim.h +41 -0
- package/lib/index.d.mts +206 -31
- package/lib/index.d.ts +206 -31
- package/lib/index.js +324 -44
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +329 -52
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +100 -17
- package/src/dataTransitions.ts +148 -0
- package/src/easing.ts +40 -0
- package/src/index.ts +9 -0
- package/src/jsi.d.ts +126 -19
- package/src/theme.ts +1 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +273 -30
package/cpp/_core_src/macd.cpp
CHANGED
|
@@ -2,61 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
#include <cmath> // std::nan, std::isfinite
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
namespace {
|
|
8
|
-
// SMA-seeded EMA over `src` (which may have a leading NaN run). Fills `out`
|
|
9
|
-
// (resized to src.size()): NaN until the seed index, the seed = SMA of the
|
|
10
|
-
// first `period` finite values, then ema = alpha*src + (1-alpha)*prev.
|
|
11
|
-
void ema_seeded(const std::vector<double>& src, int period,
|
|
12
|
-
std::vector<double>& out) {
|
|
13
|
-
const std::size_t n = src.size();
|
|
14
|
-
out.assign(n, std::nan(""));
|
|
15
|
-
if (period < 1) return;
|
|
16
|
-
const std::size_t P = static_cast<std::size_t>(period);
|
|
17
|
-
|
|
18
|
-
std::size_t f = 0;
|
|
19
|
-
while (f < n && !std::isfinite(src[f])) ++f;
|
|
20
|
-
if (f >= n || f + P > n) return; // not enough finite values to seed
|
|
21
|
-
|
|
22
|
-
const std::size_t seed = f + P - 1;
|
|
23
|
-
double sum = 0.0;
|
|
24
|
-
for (std::size_t k = f; k <= seed; ++k) sum += src[k];
|
|
25
|
-
double prev = sum / static_cast<double>(P);
|
|
26
|
-
out[seed] = prev;
|
|
5
|
+
#include "ma.h"
|
|
6
|
+
#include "series_ma.h"
|
|
27
7
|
|
|
28
|
-
|
|
29
|
-
for (std::size_t i = seed + 1; i < n; ++i) {
|
|
30
|
-
if (!std::isfinite(src[i])) break; // series are contiguous after f
|
|
31
|
-
prev = alpha * src[i] + (1.0 - alpha) * prev;
|
|
32
|
-
out[i] = prev;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
} // namespace
|
|
8
|
+
namespace vroom::macd {
|
|
36
9
|
|
|
37
10
|
void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
|
|
38
|
-
int signal,
|
|
39
|
-
std::vector<double>&
|
|
11
|
+
int signal, int source, int ma_kind, int signal_ma_kind,
|
|
12
|
+
std::vector<double>& macd_out, std::vector<double>& signal_out,
|
|
13
|
+
std::vector<double>& hist_out) {
|
|
40
14
|
macd_out.assign(n, std::nan(""));
|
|
41
15
|
signal_out.assign(n, std::nan(""));
|
|
42
16
|
hist_out.assign(n, std::nan(""));
|
|
43
17
|
if (!candles || fast < 1 || slow < 1 || signal < 1) return;
|
|
44
18
|
|
|
45
|
-
std::vector<double>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
std::vector<double> ema_slow;
|
|
50
|
-
ema_seeded(closes, fast, ema_fast);
|
|
51
|
-
ema_seeded(closes, slow, ema_slow);
|
|
19
|
+
std::vector<double> ma_fast;
|
|
20
|
+
std::vector<double> ma_slow;
|
|
21
|
+
vroom::ma::compute(candles, n, ma_kind, fast, source, ma_fast);
|
|
22
|
+
vroom::ma::compute(candles, n, ma_kind, slow, source, ma_slow);
|
|
52
23
|
|
|
53
24
|
for (std::size_t i = 0; i < n; ++i) {
|
|
54
|
-
if (std::isfinite(
|
|
55
|
-
macd_out[i] =
|
|
25
|
+
if (std::isfinite(ma_fast[i]) && std::isfinite(ma_slow[i])) {
|
|
26
|
+
macd_out[i] = ma_fast[i] - ma_slow[i];
|
|
56
27
|
}
|
|
57
28
|
}
|
|
58
29
|
|
|
59
|
-
|
|
30
|
+
vroom::series_ma::smooth(macd_out, signal_ma_kind, signal, signal_out);
|
|
60
31
|
|
|
61
32
|
for (std::size_t i = 0; i < n; ++i) {
|
|
62
33
|
if (std::isfinite(macd_out[i]) && std::isfinite(signal_out[i])) {
|
package/cpp/_core_src/macd.h
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// MACD — pure computation, no Skia. Kept Skia-free so it builds into the
|
|
2
2
|
// unit-test target. Mirrors rsi.cpp's NaN-warmup convention.
|
|
3
3
|
//
|
|
4
|
-
// MACD line =
|
|
5
|
-
// Signal line =
|
|
4
|
+
// MACD line = MA(fast) - MA(slow) of the chosen price source
|
|
5
|
+
// Signal line = MA(signal) of the MACD line
|
|
6
6
|
// Histogram = MACD - Signal
|
|
7
7
|
// EMAs are SMA-seeded: NaN until the first `period` values exist (the seed is
|
|
8
8
|
// their simple average), then ema = alpha*src + (1-alpha)*prev, alpha=2/(p+1).
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
|
|
17
17
|
namespace vroom::macd {
|
|
18
18
|
|
|
19
|
-
// Computes MACD over
|
|
20
|
-
//
|
|
19
|
+
// Computes MACD over [candles, candles+n). fast/slow/signal are clamped to >= 1
|
|
20
|
+
// (callers pass 12/26/9 by default; slow should exceed fast). `source` selects
|
|
21
|
+
// the price series (vroom::ma::Source); `ma_kind` picks the averaging for the
|
|
22
|
+
// fast/slow legs and `signal_ma_kind` for the signal line (vroom::ma::Kind).
|
|
21
23
|
// Fills three series (each resized to n, NaN where undefined):
|
|
22
|
-
// macd_out —
|
|
23
|
-
// signal_out —
|
|
24
|
+
// macd_out — MA(fast) - MA(slow), defined from index slow-1
|
|
25
|
+
// signal_out — MA(signal) of macd_out, defined from (slow-1)+(signal-1)
|
|
24
26
|
// hist_out — macd_out - signal_out, defined where both are
|
|
25
27
|
void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
|
|
26
|
-
int signal,
|
|
27
|
-
std::vector<double>&
|
|
28
|
+
int signal, int source, int ma_kind, int signal_ma_kind,
|
|
29
|
+
std::vector<double>& macd_out, std::vector<double>& signal_out,
|
|
30
|
+
std::vector<double>& hist_out);
|
|
28
31
|
|
|
29
32
|
} // namespace vroom::macd
|
|
@@ -20,17 +20,29 @@
|
|
|
20
20
|
|
|
21
21
|
#include "chart.h"
|
|
22
22
|
#include "fonts.h"
|
|
23
|
+
#include "style_inherit.h"
|
|
23
24
|
#include "theme.h"
|
|
24
25
|
#include "viewport.h"
|
|
25
26
|
|
|
26
27
|
namespace vroom::macd_pane {
|
|
27
28
|
|
|
28
29
|
namespace {
|
|
30
|
+
using vroom::style::color_or;
|
|
31
|
+
using vroom::style::width_or;
|
|
32
|
+
|
|
29
33
|
constexpr SkColor kMacdLine = 0xff2962ff; // blue MACD line
|
|
30
34
|
constexpr SkColor kSignalLine = 0xffff6d00; // orange signal line
|
|
31
35
|
constexpr SkColor kZeroLine = 0xff484f58; // zero reference
|
|
32
36
|
constexpr SkColor kDivider = 0xff21262d; // pane separator
|
|
33
37
|
constexpr float kPadFrac = 0.85f; // keep curves off the band edges
|
|
38
|
+
constexpr float kLineWidth = 1.5f; // default stroke for both lines
|
|
39
|
+
constexpr float kFadedAlpha = 0.5f; // easing histogram bars
|
|
40
|
+
|
|
41
|
+
// The easing histogram bars default to their own base color dimmed, which is
|
|
42
|
+
// how the pane read before the colors were configurable.
|
|
43
|
+
SkColor faded_or(uint32_t configured, SkColor base) {
|
|
44
|
+
return vroom::style::faded_or(configured, base, kFadedAlpha);
|
|
45
|
+
}
|
|
34
46
|
} // namespace
|
|
35
47
|
|
|
36
48
|
void draw(SkCanvas* canvas,
|
|
@@ -51,23 +63,25 @@ void draw(SkCanvas* canvas,
|
|
|
51
63
|
const float band_h = pane_bottom - pane_top;
|
|
52
64
|
if (band_h <= 0.f) return;
|
|
53
65
|
|
|
66
|
+
const VroomMACD& cfg = chart.macd;
|
|
54
67
|
const float mid = (pane_top + pane_bottom) * 0.5f;
|
|
55
68
|
// User y-zoom scales the amplitude about the zero line (mid). 1.0 = the
|
|
56
69
|
// default auto-fit; >1 zooms in, <1 zooms out.
|
|
57
70
|
const float half =
|
|
58
71
|
band_h * 0.5f * kPadFrac * static_cast<float>(chart.macd_y_scale);
|
|
59
72
|
|
|
60
|
-
// Auto-scale symmetric about zero across
|
|
73
|
+
// Auto-scale symmetric about zero across the finite values on show, so
|
|
74
|
+
// hiding a series lets the rest fill the pane.
|
|
61
75
|
double scale = 0.0;
|
|
62
|
-
auto track = [&](const double* s) {
|
|
63
|
-
if (!s) return;
|
|
76
|
+
auto track = [&](const double* s, bool shown) {
|
|
77
|
+
if (!s || !shown) return;
|
|
64
78
|
for (std::size_t i = 0; i < n; ++i) {
|
|
65
79
|
if (std::isfinite(s[i])) scale = std::max(scale, std::abs(s[i]));
|
|
66
80
|
}
|
|
67
81
|
};
|
|
68
|
-
track(macd_visible);
|
|
69
|
-
track(signal_visible);
|
|
70
|
-
track(hist_visible);
|
|
82
|
+
track(macd_visible, cfg.line_visible != 0);
|
|
83
|
+
track(signal_visible, cfg.signal_visible != 0);
|
|
84
|
+
track(hist_visible, cfg.hist_visible != 0);
|
|
71
85
|
|
|
72
86
|
auto y_for = [&](double v) -> float {
|
|
73
87
|
if (scale <= 0.0) return mid;
|
|
@@ -90,34 +104,44 @@ void draw(SkCanvas* canvas,
|
|
|
90
104
|
canvas->clipRect(SkRect::MakeLTRB(0.f, pane_top, candle_right, pane_bottom));
|
|
91
105
|
|
|
92
106
|
// Zero line.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
if (cfg.zero_visible) {
|
|
108
|
+
SkPaint zero;
|
|
109
|
+
zero.setAntiAlias(true);
|
|
110
|
+
zero.setColor(color_or(cfg.zero_color, kZeroLine));
|
|
111
|
+
zero.setStrokeWidth(1.f);
|
|
112
|
+
canvas->drawLine(0.f, mid, candle_right, mid, zero);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Histogram bars: from the zero line to y_for(hist), 4-color — above or
|
|
116
|
+
// below zero, each in a building and an easing shade. A bar is building
|
|
117
|
+
// while it grows away from zero and easing while it falls back toward it.
|
|
118
|
+
if (hist_visible && cfg.hist_visible) {
|
|
102
119
|
const float body_w =
|
|
103
120
|
vroom::candle_body_width(lay, window_ms, candle_duration_ms);
|
|
104
121
|
const float half_body = body_w * 0.5f;
|
|
105
122
|
const float zero_y = y_for(0.0);
|
|
106
|
-
const SkColor
|
|
107
|
-
|
|
123
|
+
const SkColor up =
|
|
124
|
+
color_or(cfg.hist_up_color, chart.theme.colors[VROOM_COLOR_ACCENT_BULL]);
|
|
125
|
+
const SkColor down =
|
|
126
|
+
color_or(cfg.hist_down_color, chart.theme.colors[VROOM_COLOR_ACCENT_BEAR]);
|
|
127
|
+
const SkColor up_easing = faded_or(cfg.hist_up_fading_color, up);
|
|
128
|
+
const SkColor down_easing = faded_or(cfg.hist_down_fading_color, down);
|
|
108
129
|
for (std::size_t i = 0; i < n; ++i) {
|
|
109
130
|
const double h = hist_visible[i];
|
|
110
131
|
if (!std::isfinite(h)) continue;
|
|
111
132
|
const bool have_prev = i > 0 && std::isfinite(hist_visible[i - 1]);
|
|
112
133
|
const double prev = have_prev ? hist_visible[i - 1] : h;
|
|
113
|
-
bool
|
|
134
|
+
bool building = true;
|
|
114
135
|
if (have_prev) {
|
|
115
|
-
|
|
136
|
+
building = h >= 0.0 ? h >= prev : h <= prev;
|
|
116
137
|
}
|
|
117
138
|
SkPaint bar;
|
|
118
139
|
bar.setAntiAlias(true);
|
|
119
|
-
|
|
120
|
-
|
|
140
|
+
if (h >= 0.0) {
|
|
141
|
+
bar.setColor(building ? up : up_easing);
|
|
142
|
+
} else {
|
|
143
|
+
bar.setColor(building ? down : down_easing);
|
|
144
|
+
}
|
|
121
145
|
const float cx = vroom::candle_center_x(
|
|
122
146
|
lay, visible[i].time_ms, candle_duration_ms, visible_start_ms,
|
|
123
147
|
window_ms);
|
|
@@ -130,7 +154,7 @@ void draw(SkCanvas* canvas,
|
|
|
130
154
|
}
|
|
131
155
|
|
|
132
156
|
// MACD + signal lines.
|
|
133
|
-
auto stroke_series = [&](const double* series, SkColor color) {
|
|
157
|
+
auto stroke_series = [&](const double* series, SkColor color, float width) {
|
|
134
158
|
if (!series) return;
|
|
135
159
|
SkPathBuilder path;
|
|
136
160
|
bool pen_down = false;
|
|
@@ -155,11 +179,17 @@ void draw(SkCanvas* canvas,
|
|
|
155
179
|
line.setAntiAlias(true);
|
|
156
180
|
line.setColor(color);
|
|
157
181
|
line.setStyle(SkPaint::kStroke_Style);
|
|
158
|
-
line.setStrokeWidth(
|
|
182
|
+
line.setStrokeWidth(width);
|
|
159
183
|
canvas->drawPath(path.detach(), line);
|
|
160
184
|
};
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
if (cfg.signal_visible) {
|
|
186
|
+
stroke_series(signal_visible, color_or(cfg.signal_color, kSignalLine),
|
|
187
|
+
width_or(cfg.signal_width, kLineWidth));
|
|
188
|
+
}
|
|
189
|
+
if (cfg.line_visible) { // MACD over signal
|
|
190
|
+
stroke_series(macd_visible, color_or(cfg.line_color, kMacdLine),
|
|
191
|
+
width_or(cfg.line_width, kLineWidth));
|
|
192
|
+
}
|
|
163
193
|
|
|
164
194
|
// Caption, top-left of the pane.
|
|
165
195
|
auto tf = vroom::axis_typeface();
|
|
@@ -168,8 +198,8 @@ void draw(SkCanvas* canvas,
|
|
|
168
198
|
font.setSubpixel(true);
|
|
169
199
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
170
200
|
char caption[40];
|
|
171
|
-
std::snprintf(caption, sizeof(caption), "MACD %d %d %d",
|
|
172
|
-
|
|
201
|
+
std::snprintf(caption, sizeof(caption), "MACD %d %d %d", cfg.fast,
|
|
202
|
+
cfg.slow, cfg.signal);
|
|
173
203
|
SkRect cb;
|
|
174
204
|
font.measureText(caption, std::strlen(caption), SkTextEncoding::kUTF8,
|
|
175
205
|
&cb);
|
|
@@ -17,7 +17,7 @@ constexpr float kPadV = 4.f; // above/below the text
|
|
|
17
17
|
constexpr float kCorner = 6.f; // rounded-pill corner radius
|
|
18
18
|
|
|
19
19
|
// Extra grab band above and below the stroke, so a 1px line is still an easy
|
|
20
|
-
// target. Matches the tolerance
|
|
20
|
+
// target. Matches the tolerance mainstream chart libraries settled on.
|
|
21
21
|
constexpr float kLineHitTolerance = 7.f;
|
|
22
22
|
|
|
23
23
|
// Gap between the label group and the price axis when right-aligned. Wide enough
|
package/cpp/_core_src/rsi.cpp
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
#include <cmath> // std::nan, std::isfinite
|
|
4
4
|
|
|
5
|
+
#include "series_ma.h"
|
|
6
|
+
|
|
5
7
|
namespace vroom::rsi {
|
|
6
8
|
|
|
7
9
|
namespace {
|
|
@@ -46,25 +48,9 @@ void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
void compute_ma(const std::vector<double>& rsi, int ma_period,
|
|
51
|
+
void compute_ma(const std::vector<double>& rsi, int ma_period, int kind,
|
|
50
52
|
std::vector<double>& out) {
|
|
51
|
-
|
|
52
|
-
out.assign(n, std::nan(""));
|
|
53
|
-
if (ma_period < 1) return;
|
|
54
|
-
const std::size_t P = static_cast<std::size_t>(ma_period);
|
|
55
|
-
if (n < P) return;
|
|
56
|
-
for (std::size_t i = P - 1; i < n; ++i) {
|
|
57
|
-
double sum = 0.0;
|
|
58
|
-
bool ok = true;
|
|
59
|
-
for (std::size_t k = i + 1 - P; k <= i; ++k) {
|
|
60
|
-
if (!std::isfinite(rsi[k])) {
|
|
61
|
-
ok = false;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
sum += rsi[k];
|
|
65
|
-
}
|
|
66
|
-
if (ok) out[i] = sum / static_cast<double>(P);
|
|
67
|
-
}
|
|
53
|
+
vroom::series_ma::smooth(rsi, kind, ma_period, out);
|
|
68
54
|
}
|
|
69
55
|
|
|
70
56
|
} // namespace vroom::rsi
|
package/cpp/_core_src/rsi.h
CHANGED
|
@@ -21,12 +21,12 @@ namespace vroom::rsi {
|
|
|
21
21
|
void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
22
22
|
std::vector<double>& out);
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// out
|
|
28
|
-
//
|
|
29
|
-
void compute_ma(const std::vector<double>& rsi, int ma_period,
|
|
24
|
+
// Moving average of an RSI series (the "RSI-based MA" / trendline that most RSI
|
|
25
|
+
// indicators overlay; crossovers of RSI vs. this line are the common signal).
|
|
26
|
+
// `kind` is a vroom::ma KIND_* value. `ma_period` is clamped to >= 1. Fills
|
|
27
|
+
// `out` (resized to rsi.size()), NaN until `ma_period` valid RSI values exist —
|
|
28
|
+
// either kind produces its first value at the same index.
|
|
29
|
+
void compute_ma(const std::vector<double>& rsi, int ma_period, int kind,
|
|
30
30
|
std::vector<double>& out);
|
|
31
31
|
|
|
32
32
|
} // namespace vroom::rsi
|
|
@@ -20,17 +20,22 @@
|
|
|
20
20
|
|
|
21
21
|
#include "chart.h"
|
|
22
22
|
#include "fonts.h"
|
|
23
|
+
#include "style_inherit.h"
|
|
23
24
|
#include "theme.h"
|
|
24
25
|
#include "viewport.h"
|
|
25
26
|
|
|
26
27
|
namespace vroom::rsi_pane {
|
|
27
28
|
|
|
28
29
|
namespace {
|
|
29
|
-
|
|
30
|
+
using vroom::style::color_or;
|
|
31
|
+
using vroom::style::width_or;
|
|
32
|
+
|
|
33
|
+
constexpr SkColor kRsiLine = 0xff8957e5; // violet RSI line
|
|
30
34
|
constexpr SkColor kRsiMaLine = 0xffd29922; // amber RSI moving-average trendline
|
|
31
35
|
constexpr SkColor kRefLine = 0xff30363d; // band reference lines
|
|
32
36
|
constexpr SkColor kDivider = 0xff21262d; // pane separator
|
|
33
37
|
constexpr SkScalar kDash[2] = {3.f, 3.f};
|
|
38
|
+
constexpr float kLineWidth = 1.5f; // default stroke for both lines
|
|
34
39
|
} // namespace
|
|
35
40
|
|
|
36
41
|
void draw(SkCanvas* canvas,
|
|
@@ -53,6 +58,7 @@ void draw(SkCanvas* canvas,
|
|
|
53
58
|
// Map 0..100 about the pane center so the user y-zoom scales symmetrically
|
|
54
59
|
// around 50. z == 1 reproduces the default fit (0 -> pane_bottom, 100 ->
|
|
55
60
|
// pane_top); z > 1 zooms in (taller), z < 1 zooms out (flatter).
|
|
61
|
+
const VroomRSI& cfg = chart.rsi;
|
|
56
62
|
const float center_y = (pane_top + pane_bottom) * 0.5f;
|
|
57
63
|
const float z = static_cast<float>(chart.rsi_y_scale);
|
|
58
64
|
auto y_for = [&](double v) -> float {
|
|
@@ -78,19 +84,21 @@ void draw(SkCanvas* canvas,
|
|
|
78
84
|
canvas->clipRect(SkRect::MakeLTRB(0.f, pane_top, candle_right, pane_bottom));
|
|
79
85
|
|
|
80
86
|
// Configurable band reference lines (overbought / oversold).
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
const float y_upper = y_for(cfg.upper_band);
|
|
88
|
+
const float y_lower = y_for(cfg.lower_band);
|
|
89
|
+
if (cfg.bands_visible) {
|
|
90
|
+
SkPaint ref;
|
|
91
|
+
ref.setAntiAlias(true);
|
|
92
|
+
ref.setColor(color_or(cfg.band_color, kRefLine));
|
|
93
|
+
ref.setStrokeWidth(1.f);
|
|
94
|
+
ref.setPathEffect(SkDashPathEffect::Make(kDash, 0.f));
|
|
95
|
+
canvas->drawLine(0.f, y_upper, candle_right, y_upper, ref);
|
|
96
|
+
canvas->drawLine(0.f, y_lower, candle_right, y_lower, ref);
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
// Strokes a value series as a polyline; a NaN lifts the pen so undefined
|
|
92
100
|
// leading values (i < period) leave a gap.
|
|
93
|
-
auto stroke_series = [&](const double* series, SkColor color) {
|
|
101
|
+
auto stroke_series = [&](const double* series, SkColor color, float width) {
|
|
94
102
|
if (!series) return;
|
|
95
103
|
SkPathBuilder path;
|
|
96
104
|
bool pen_down = false;
|
|
@@ -115,11 +123,17 @@ void draw(SkCanvas* canvas,
|
|
|
115
123
|
line.setAntiAlias(true);
|
|
116
124
|
line.setColor(color);
|
|
117
125
|
line.setStyle(SkPaint::kStroke_Style);
|
|
118
|
-
line.setStrokeWidth(
|
|
126
|
+
line.setStrokeWidth(width);
|
|
119
127
|
canvas->drawPath(path.detach(), line);
|
|
120
128
|
};
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
if (cfg.line_visible) {
|
|
130
|
+
stroke_series(rsi_visible, color_or(cfg.line_color, kRsiLine),
|
|
131
|
+
width_or(cfg.line_width, kLineWidth));
|
|
132
|
+
}
|
|
133
|
+
// Trendline on top. Its own visibility is handled upstream: a hidden
|
|
134
|
+
// trendline isn't computed, so the caller passes null.
|
|
135
|
+
stroke_series(rsi_ma_visible, color_or(cfg.ma_color, kRsiMaLine),
|
|
136
|
+
width_or(cfg.ma_width, kLineWidth));
|
|
123
137
|
|
|
124
138
|
// Caption, top-left of the pane.
|
|
125
139
|
auto tf = vroom::axis_typeface();
|
|
@@ -129,7 +143,7 @@ void draw(SkCanvas* canvas,
|
|
|
129
143
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
130
144
|
|
|
131
145
|
char caption[24];
|
|
132
|
-
std::snprintf(caption, sizeof(caption), "RSI %d",
|
|
146
|
+
std::snprintf(caption, sizeof(caption), "RSI %d", cfg.period);
|
|
133
147
|
SkRect cb;
|
|
134
148
|
font.measureText(caption, std::strlen(caption), SkTextEncoding::kUTF8,
|
|
135
149
|
&cb);
|
|
@@ -142,8 +156,9 @@ void draw(SkCanvas* canvas,
|
|
|
142
156
|
canvas->restore();
|
|
143
157
|
|
|
144
158
|
// 70 / 30 labels in the y-axis strip (drawn after the clip; the strip's
|
|
145
|
-
// background was already masked by draw_chart before this call).
|
|
146
|
-
|
|
159
|
+
// background was already masked by draw_chart before this call). They label
|
|
160
|
+
// the band rules, so they go with them.
|
|
161
|
+
if (tf && cfg.bands_visible) {
|
|
147
162
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
148
163
|
font.setSubpixel(true);
|
|
149
164
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
@@ -162,8 +177,8 @@ void draw(SkCanvas* canvas,
|
|
|
162
177
|
};
|
|
163
178
|
char ub[8];
|
|
164
179
|
char lb[8];
|
|
165
|
-
std::snprintf(ub, sizeof(ub), "%.0f",
|
|
166
|
-
std::snprintf(lb, sizeof(lb), "%.0f",
|
|
180
|
+
std::snprintf(ub, sizeof(ub), "%.0f", cfg.upper_band);
|
|
181
|
+
std::snprintf(lb, sizeof(lb), "%.0f", cfg.lower_band);
|
|
167
182
|
label(ub, y_upper);
|
|
168
183
|
label(lb, y_lower);
|
|
169
184
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#include "series_ma.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath> // std::nan, std::isfinite
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
|
|
6
|
+
#include "ma.h"
|
|
7
|
+
|
|
8
|
+
namespace vroom::series_ma {
|
|
9
|
+
|
|
10
|
+
void ema_seeded(const std::vector<double>& src, int period,
|
|
11
|
+
std::vector<double>& out) {
|
|
12
|
+
const std::size_t n = src.size();
|
|
13
|
+
out.assign(n, std::nan(""));
|
|
14
|
+
if (period < 1) return;
|
|
15
|
+
const std::size_t P = static_cast<std::size_t>(period);
|
|
16
|
+
|
|
17
|
+
std::size_t f = 0;
|
|
18
|
+
while (f < n && !std::isfinite(src[f])) ++f;
|
|
19
|
+
if (f >= n || f + P > n) return; // not enough finite values to seed
|
|
20
|
+
|
|
21
|
+
const std::size_t seed = f + P - 1;
|
|
22
|
+
double sum = 0.0;
|
|
23
|
+
for (std::size_t k = f; k <= seed; ++k) sum += src[k];
|
|
24
|
+
double prev = sum / static_cast<double>(P);
|
|
25
|
+
out[seed] = prev;
|
|
26
|
+
|
|
27
|
+
const double alpha = 2.0 / (static_cast<double>(P) + 1.0);
|
|
28
|
+
for (std::size_t i = seed + 1; i < n; ++i) {
|
|
29
|
+
if (!std::isfinite(src[i])) break; // series are contiguous after f
|
|
30
|
+
prev = alpha * src[i] + (1.0 - alpha) * prev;
|
|
31
|
+
out[i] = prev;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void sma_seeded(const std::vector<double>& src, int period,
|
|
36
|
+
std::vector<double>& out) {
|
|
37
|
+
const std::size_t n = src.size();
|
|
38
|
+
out.assign(n, std::nan(""));
|
|
39
|
+
if (period < 1) return;
|
|
40
|
+
const std::size_t P = static_cast<std::size_t>(period);
|
|
41
|
+
|
|
42
|
+
std::size_t f = 0;
|
|
43
|
+
while (f < n && !std::isfinite(src[f])) ++f;
|
|
44
|
+
if (f >= n || f + P > n) return;
|
|
45
|
+
|
|
46
|
+
double sum = 0.0;
|
|
47
|
+
for (std::size_t i = f; i < n; ++i) {
|
|
48
|
+
if (!std::isfinite(src[i])) break; // series are contiguous after f
|
|
49
|
+
sum += src[i];
|
|
50
|
+
if (i >= f + P) sum -= src[i - P];
|
|
51
|
+
if (i >= f + P - 1) out[i] = sum / static_cast<double>(P);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void smooth(const std::vector<double>& src, int kind, int period,
|
|
56
|
+
std::vector<double>& out) {
|
|
57
|
+
if (kind == vroom::ma::KIND_SMA) {
|
|
58
|
+
sma_seeded(src, period, out);
|
|
59
|
+
} else {
|
|
60
|
+
ema_seeded(src, period, out);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} // namespace vroom::series_ma
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Moving averages over an already-computed series — pure computation, no Skia.
|
|
2
|
+
//
|
|
3
|
+
// Distinct from ma.h, which averages a *price* source off the candles. These
|
|
4
|
+
// smooth a derived series that may open with a run of NaN warmup (the MACD
|
|
5
|
+
// difference, an RSI line), so they skip that run and start counting from the
|
|
6
|
+
// first finite value. Both smoothers therefore produce their first value at the
|
|
7
|
+
// same index, which is what lets the MA kind be a config toggle.
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
namespace vroom::series_ma {
|
|
14
|
+
|
|
15
|
+
// SMA-seeded EMA over `src`. Fills `out` (resized to src.size()): NaN until the
|
|
16
|
+
// seed index, the seed = SMA of the first `period` finite values, then
|
|
17
|
+
// ema = alpha*src + (1-alpha)*prev.
|
|
18
|
+
void ema_seeded(const std::vector<double>& src, int period,
|
|
19
|
+
std::vector<double>& out);
|
|
20
|
+
|
|
21
|
+
// Rolling SMA over `src`, skipping the same leading NaN run as ema_seeded.
|
|
22
|
+
void sma_seeded(const std::vector<double>& src, int period,
|
|
23
|
+
std::vector<double>& out);
|
|
24
|
+
|
|
25
|
+
// Dispatches on a vroom::ma KIND_* value: KIND_SMA picks sma_seeded, anything
|
|
26
|
+
// else ema_seeded.
|
|
27
|
+
void smooth(const std::vector<double>& src, int kind, int period,
|
|
28
|
+
std::vector<double>& out);
|
|
29
|
+
|
|
30
|
+
} // namespace vroom::series_ma
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Resolution of the indicator style "inherit sentinels".
|
|
2
|
+
//
|
|
3
|
+
// Every indicator style field is optional at the public API, so the marshalled
|
|
4
|
+
// struct needs a value that means "the host didn't set this". A fully
|
|
5
|
+
// transparent color and a non-positive width are those sentinels: they can't
|
|
6
|
+
// name anything drawable, so they're free to carry the meaning. The renderer
|
|
7
|
+
// swaps them for the theme value or the built-in default at draw time, which
|
|
8
|
+
// keeps the fallback in one place instead of freezing it into every bridge.
|
|
9
|
+
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#pragma clang diagnostic push
|
|
13
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
14
|
+
#include "include/core/SkColor.h"
|
|
15
|
+
#pragma clang diagnostic pop
|
|
16
|
+
|
|
17
|
+
namespace vroom::style {
|
|
18
|
+
|
|
19
|
+
inline SkColor color_or(uint32_t configured, SkColor fallback) {
|
|
20
|
+
return SkColorGetA(configured) == 0 ? fallback : configured;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// For an element whose default is another element's color dimmed — the easing
|
|
24
|
+
// MACD histogram bars, say.
|
|
25
|
+
inline SkColor faded_or(uint32_t configured, SkColor base, float alpha_frac) {
|
|
26
|
+
if (SkColorGetA(configured) != 0) return configured;
|
|
27
|
+
return SkColorSetA(base,
|
|
28
|
+
static_cast<U8CPU>(SkColorGetA(base) * alpha_frac));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
inline float width_or(float configured, float fallback) {
|
|
32
|
+
return configured > 0.f ? configured : fallback;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
} // namespace vroom::style
|
package/cpp/_core_src/theme.cpp
CHANGED
|
@@ -22,7 +22,7 @@ constexpr uint32_t kDefaultColors[VROOM_COLOR_COUNT_] = {
|
|
|
22
22
|
0x00000000, // WICK_BEAR — transparent sentinel: inherit BEAR fill
|
|
23
23
|
0xff26a69a, // ACCENT_BULL — classic teal-green (price indicator, volume, MACD)
|
|
24
24
|
0xffef5350, // ACCENT_BEAR — classic red
|
|
25
|
-
|
|
25
|
+
0xff8957e5, // LINE — line-chart close polyline; violet, matching the RSI line
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
constexpr float kDefaultFloats[VROOM_FLOAT_COUNT_] = {
|
|
@@ -38,6 +38,7 @@ constexpr float kDefaultFloats[VROOM_FLOAT_COUNT_] = {
|
|
|
38
38
|
0.f, // WICK_ROUND_CAP — butt caps by default
|
|
39
39
|
0.f, // VOLUME_RADIUS_PX — square by default
|
|
40
40
|
1.5f, // LINE_WIDTH_PX — line-chart polyline stroke width
|
|
41
|
+
0.28f, // LINE_GRADIENT_OPACITY — fill under the line, ramped to 0 at the pane bottom
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
} // namespace
|