react-native-vroom-chart 0.4.0 → 0.5.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 +47 -0
- package/cpp/_core_include/vroom/vroom_chart.h +25 -0
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/chart.cpp +49 -0
- package/cpp/_core_src/chart.h +18 -0
- package/cpp/_core_src/chart_facade.cpp +26 -0
- package/cpp/_core_src/ma_overlay.cpp +67 -0
- package/cpp/_core_src/ma_overlay.h +21 -0
- package/lib/index.d.mts +98 -1
- package/lib/index.d.ts +98 -1
- package/lib/index.js +27 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +27 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -7
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +2 -0
- package/src/index.ts +1 -0
- package/src/jsi.d.ts +20 -0
- package/src/types.ts +1 -0
- package/src/useChartCore.ts +36 -2
|
@@ -51,6 +51,7 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
51
51
|
out.push_back(jsi::PropNameID::forAscii(rt, "setMACD"));
|
|
52
52
|
out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
|
|
53
53
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
|
|
54
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
|
|
54
55
|
out.push_back(jsi::PropNameID::forAscii(rt, "render"));
|
|
55
56
|
return out;
|
|
56
57
|
}
|
|
@@ -551,6 +552,52 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
551
552
|
});
|
|
552
553
|
}
|
|
553
554
|
|
|
555
|
+
if (name == "setBollinger") {
|
|
556
|
+
// setBollinger({enabled, period, mult, source, basisKind, upperColor,
|
|
557
|
+
// upperWidth, middleColor, middleWidth, lowerColor, lowerWidth,
|
|
558
|
+
// fillEnabled, fillOpacity}) — Bollinger Bands overlay. No render; the
|
|
559
|
+
// next render() picks it up.
|
|
560
|
+
return jsi::Function::createFromHostFunction(
|
|
561
|
+
rt,
|
|
562
|
+
jsi::PropNameID::forAscii(rt, "setBollinger"),
|
|
563
|
+
1,
|
|
564
|
+
[this](jsi::Runtime& rt2,
|
|
565
|
+
const jsi::Value& /*thisVal*/,
|
|
566
|
+
const jsi::Value* args,
|
|
567
|
+
size_t count) -> jsi::Value {
|
|
568
|
+
if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
|
|
569
|
+
auto s = args[0].asObject(rt2);
|
|
570
|
+
VroomBollinger cfg{};
|
|
571
|
+
cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
|
|
572
|
+
cfg.period = static_cast<int32_t>(
|
|
573
|
+
s.getProperty(rt2, "period").asNumber());
|
|
574
|
+
cfg.mult = static_cast<float>(
|
|
575
|
+
s.getProperty(rt2, "mult").asNumber());
|
|
576
|
+
cfg.source = static_cast<int32_t>(
|
|
577
|
+
s.getProperty(rt2, "source").asNumber());
|
|
578
|
+
cfg.basis_kind = static_cast<int32_t>(
|
|
579
|
+
s.getProperty(rt2, "basisKind").asNumber());
|
|
580
|
+
cfg.upper_color = static_cast<uint32_t>(
|
|
581
|
+
s.getProperty(rt2, "upperColor").asNumber());
|
|
582
|
+
cfg.upper_width = static_cast<float>(
|
|
583
|
+
s.getProperty(rt2, "upperWidth").asNumber());
|
|
584
|
+
cfg.middle_color = static_cast<uint32_t>(
|
|
585
|
+
s.getProperty(rt2, "middleColor").asNumber());
|
|
586
|
+
cfg.middle_width = static_cast<float>(
|
|
587
|
+
s.getProperty(rt2, "middleWidth").asNumber());
|
|
588
|
+
cfg.lower_color = static_cast<uint32_t>(
|
|
589
|
+
s.getProperty(rt2, "lowerColor").asNumber());
|
|
590
|
+
cfg.lower_width = static_cast<float>(
|
|
591
|
+
s.getProperty(rt2, "lowerWidth").asNumber());
|
|
592
|
+
cfg.fill_enabled =
|
|
593
|
+
s.getProperty(rt2, "fillEnabled").asBool() ? 1 : 0;
|
|
594
|
+
cfg.fill_opacity = static_cast<float>(
|
|
595
|
+
s.getProperty(rt2, "fillOpacity").asNumber());
|
|
596
|
+
vroom_chart_set_bollinger(chart_, &cfg);
|
|
597
|
+
return jsi::Value::undefined();
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
554
601
|
if (name == "render") {
|
|
555
602
|
return jsi::Function::createFromHostFunction(
|
|
556
603
|
rt,
|
|
@@ -49,6 +49,26 @@ typedef struct VroomOverlay {
|
|
|
49
49
|
float width; // stroke width in px
|
|
50
50
|
} VroomOverlay;
|
|
51
51
|
|
|
52
|
+
// Bollinger Bands overlay drawn on the price pane: a basis MA of `source` over
|
|
53
|
+
// `period`, banded at ± `mult` × population standard deviation of the same
|
|
54
|
+
// window. Per TradingView semantics the stdev always uses the window's
|
|
55
|
+
// arithmetic mean, even when `basis_kind` selects an EMA basis line.
|
|
56
|
+
typedef struct VroomBollinger {
|
|
57
|
+
int32_t enabled; // 0/1
|
|
58
|
+
int32_t period; // lookback in candles (clamped >= 1; default 20)
|
|
59
|
+
float mult; // stdev multiplier (clamped >= 0; default 2)
|
|
60
|
+
int32_t source; // 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4
|
|
61
|
+
int32_t basis_kind; // 0 = SMA, 1 = EMA
|
|
62
|
+
uint32_t upper_color; // 0xAARRGGBB
|
|
63
|
+
float upper_width; // stroke px
|
|
64
|
+
uint32_t middle_color;
|
|
65
|
+
float middle_width;
|
|
66
|
+
uint32_t lower_color;
|
|
67
|
+
float lower_width;
|
|
68
|
+
int32_t fill_enabled; // 0/1: translucent fill between upper and lower
|
|
69
|
+
float fill_opacity; // 0..1, multiplied into upper_color's alpha
|
|
70
|
+
} VroomBollinger;
|
|
71
|
+
|
|
52
72
|
// A drawing anchor in data space (so a drawing tracks the candles on pan/zoom).
|
|
53
73
|
typedef struct VroomDrawPoint {
|
|
54
74
|
int64_t time_ms; // epoch milliseconds (not snapped to a candle slot)
|
|
@@ -321,6 +341,11 @@ void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
|
|
|
321
341
|
void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
322
342
|
uint32_t color, float width);
|
|
323
343
|
|
|
344
|
+
// Configures the Bollinger Bands overlay (three price-pane lines + an optional
|
|
345
|
+
// translucent fill between the bands; no pane is reserved). Color/width/fill
|
|
346
|
+
// changes only re-render; enabled/period/mult/source/basis changes recompute.
|
|
347
|
+
void vroom_chart_set_bollinger(VroomChart* chart, const VroomBollinger* cfg);
|
|
348
|
+
|
|
324
349
|
// ---- Drawings (line annotations) ------------------------------------------
|
|
325
350
|
|
|
326
351
|
// Replaces the full set of committed line drawings (data-anchored, so they track
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#include "bollinger.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath> // std::nan, std::sqrt
|
|
4
|
+
|
|
5
|
+
#include "ma.h"
|
|
6
|
+
|
|
7
|
+
namespace vroom::bollinger {
|
|
8
|
+
|
|
9
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
10
|
+
double mult, int source, int basis_kind,
|
|
11
|
+
std::vector<double>& middle, std::vector<double>& upper,
|
|
12
|
+
std::vector<double>& lower) {
|
|
13
|
+
vroom::ma::compute(candles, n, basis_kind, period, source, middle);
|
|
14
|
+
upper.assign(n, std::nan(""));
|
|
15
|
+
lower.assign(n, std::nan(""));
|
|
16
|
+
if (!candles || period < 1) return;
|
|
17
|
+
const std::size_t P = static_cast<std::size_t>(period);
|
|
18
|
+
if (n < P) return;
|
|
19
|
+
|
|
20
|
+
std::vector<double> src(n);
|
|
21
|
+
for (std::size_t i = 0; i < n; ++i) src[i] = vroom::ma::source_value(candles[i], source);
|
|
22
|
+
|
|
23
|
+
// Two-pass stdev around the true window mean per bar. O(n·period), but
|
|
24
|
+
// numerically stable — the rolling Σx²−n·mean² form cancels catastrophically
|
|
25
|
+
// on large prices with small deviations (e.g. BTC-scale values).
|
|
26
|
+
for (std::size_t i = P - 1; i < n; ++i) {
|
|
27
|
+
const std::size_t s = i + 1 - P;
|
|
28
|
+
double mean = 0.0;
|
|
29
|
+
for (std::size_t j = s; j <= i; ++j) mean += src[j];
|
|
30
|
+
mean /= static_cast<double>(P);
|
|
31
|
+
double var = 0.0;
|
|
32
|
+
for (std::size_t j = s; j <= i; ++j) {
|
|
33
|
+
const double d = src[j] - mean;
|
|
34
|
+
var += d * d;
|
|
35
|
+
}
|
|
36
|
+
var /= static_cast<double>(P);
|
|
37
|
+
const double band = mult * std::sqrt(var);
|
|
38
|
+
upper[i] = middle[i] + band;
|
|
39
|
+
lower[i] = middle[i] - band;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
} // namespace vroom::bollinger
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Bollinger Bands over a candle source series — pure, no Skia, so it builds
|
|
2
|
+
// into the unit-test target. Drawn as price-pane overlay lines + a band fill.
|
|
3
|
+
|
|
4
|
+
#pragma once
|
|
5
|
+
|
|
6
|
+
#include <cstddef>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "vroom/vroom_chart.h" // ::VroomCandle
|
|
10
|
+
|
|
11
|
+
namespace vroom::bollinger {
|
|
12
|
+
|
|
13
|
+
// Computes the three band series over [candles, candles+n). `period` is clamped
|
|
14
|
+
// to >= 1; `mult` is the standard-deviation multiplier. `source` is a
|
|
15
|
+
// vroom::ma::Source index and `basis_kind` a vroom::ma::Kind (SMA/EMA).
|
|
16
|
+
//
|
|
17
|
+
// middle = ma::compute(basis_kind, period, source)
|
|
18
|
+
// upper/lower = middle ± mult * population stdev of source over the trailing
|
|
19
|
+
// period window
|
|
20
|
+
//
|
|
21
|
+
// The stdev always uses the window's arithmetic mean, even when basis_kind
|
|
22
|
+
// selects an EMA basis line (TradingView semantics).
|
|
23
|
+
//
|
|
24
|
+
// Each output is resized to n; values are NaN for i < period-1 and when
|
|
25
|
+
// n < period.
|
|
26
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
27
|
+
double mult, int source, int basis_kind,
|
|
28
|
+
std::vector<double>& middle, std::vector<double>& upper,
|
|
29
|
+
std::vector<double>& lower);
|
|
30
|
+
|
|
31
|
+
} // namespace vroom::bollinger
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include "include/core/SkRect.h"
|
|
19
19
|
#pragma clang diagnostic pop
|
|
20
20
|
|
|
21
|
+
#include "bollinger.h"
|
|
21
22
|
#include "candles.h"
|
|
22
23
|
#include "chart_internal.h"
|
|
23
24
|
#include "crosshair.h"
|
|
@@ -100,6 +101,15 @@ void VroomChart::ensure_vwap() {
|
|
|
100
101
|
vwap_dirty = false;
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
void VroomChart::ensure_bollinger() {
|
|
105
|
+
if (!bollinger.enabled || !bollinger_dirty) return;
|
|
106
|
+
vroom::bollinger::compute(candles.data(), candles.size(), bollinger.period,
|
|
107
|
+
bollinger.mult, bollinger.source,
|
|
108
|
+
bollinger.basis_kind, bb_middle_cache,
|
|
109
|
+
bb_upper_cache, bb_lower_cache);
|
|
110
|
+
bollinger_dirty = false;
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
104
114
|
const auto lay = layout();
|
|
105
115
|
|
|
@@ -149,6 +159,23 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
149
159
|
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
150
160
|
candle_area_h);
|
|
151
161
|
|
|
162
|
+
// 4.7. Bollinger Band fill — the translucent region between the upper and
|
|
163
|
+
// lower bands, behind the candles so their bull/bear colors stay
|
|
164
|
+
// untinted. The band lines themselves draw above the candles (5.65).
|
|
165
|
+
if (bollinger.enabled) {
|
|
166
|
+
ensure_bollinger();
|
|
167
|
+
if (bollinger.fill_enabled &&
|
|
168
|
+
bb_upper_cache.size() == candles.size() &&
|
|
169
|
+
bb_lower_cache.size() == candles.size()) {
|
|
170
|
+
vroom::ma_overlay::fill_between(
|
|
171
|
+
canvas, lay, bounds, visible, n,
|
|
172
|
+
bb_upper_cache.data() + range.start,
|
|
173
|
+
bb_lower_cache.data() + range.start, window_ms,
|
|
174
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
175
|
+
candle_area_h, bollinger.upper_color, bollinger.fill_opacity);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
152
179
|
// 5. Price series — candles, a close-price line, or a blend of the two during
|
|
153
180
|
// the candle↔line morph. `morph_fade` crossfades candles→line and
|
|
154
181
|
// `morph_collapse` folds each candle toward its close (the line vertex).
|
|
@@ -202,6 +229,28 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
202
229
|
}
|
|
203
230
|
}
|
|
204
231
|
|
|
232
|
+
// 5.65. Bollinger Band lines — upper, lower, then the basis last so it
|
|
233
|
+
// reads on top where the bands pinch. Same price scale as the
|
|
234
|
+
// candles; the fill went down in 4.7.
|
|
235
|
+
if (bollinger.enabled) {
|
|
236
|
+
ensure_bollinger();
|
|
237
|
+
const std::size_t sz = candles.size();
|
|
238
|
+
if (bb_upper_cache.size() == sz && bb_lower_cache.size() == sz &&
|
|
239
|
+
bb_middle_cache.size() == sz) {
|
|
240
|
+
const auto stroke = [&](const std::vector<double>& cache,
|
|
241
|
+
uint32_t color, float width) {
|
|
242
|
+
vroom::ma_overlay::draw(canvas, lay, bounds, visible, n,
|
|
243
|
+
cache.data() + range.start, window_ms,
|
|
244
|
+
visible_start_ms, candle_duration_ms,
|
|
245
|
+
candle_right, candle_area_h, color,
|
|
246
|
+
width);
|
|
247
|
+
};
|
|
248
|
+
stroke(bb_upper_cache, bollinger.upper_color, bollinger.upper_width);
|
|
249
|
+
stroke(bb_lower_cache, bollinger.lower_color, bollinger.lower_width);
|
|
250
|
+
stroke(bb_middle_cache, bollinger.middle_color, bollinger.middle_width);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
205
254
|
// 5.7. Drawing annotations (committed line tools + the in-progress draft).
|
|
206
255
|
// On the price pane above the candles/overlays, below the axis labels.
|
|
207
256
|
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -135,6 +135,20 @@ struct VroomChart {
|
|
|
135
135
|
std::vector<unsigned char> vwap_breaks;
|
|
136
136
|
bool vwap_dirty = true;
|
|
137
137
|
|
|
138
|
+
// Bollinger Bands overlay (price pane; no pane reserved). Caches aligned to
|
|
139
|
+
// `candles` (NaN warmup), recomputed lazily by ensure_bollinger() when
|
|
140
|
+
// bollinger_dirty. Defaults: 20-period SMA of close, ±2σ, blue bands /
|
|
141
|
+
// orange basis, 10% fill.
|
|
142
|
+
VroomBollinger bollinger{0, 20, 2.f, 0, 0,
|
|
143
|
+
0xff2962ff, 1.f, // upper: blue
|
|
144
|
+
0xffff6d00, 1.f, // middle: orange
|
|
145
|
+
0xff2962ff, 1.f, // lower: blue
|
|
146
|
+
1, 0.1f};
|
|
147
|
+
std::vector<double> bb_middle_cache;
|
|
148
|
+
std::vector<double> bb_upper_cache;
|
|
149
|
+
std::vector<double> bb_lower_cache;
|
|
150
|
+
bool bollinger_dirty = true;
|
|
151
|
+
|
|
138
152
|
// --- drawings (annotations) --------------------------------------------
|
|
139
153
|
// Committed drawings, anchored in data space so they track the candles on
|
|
140
154
|
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
@@ -222,6 +236,10 @@ struct VroomChart {
|
|
|
222
236
|
// Recomputes the VWAP cache when vwap_dirty and VWAP is enabled.
|
|
223
237
|
void ensure_vwap();
|
|
224
238
|
|
|
239
|
+
// Recomputes the Bollinger Band caches when bollinger_dirty and the
|
|
240
|
+
// indicator is enabled.
|
|
241
|
+
void ensure_bollinger();
|
|
242
|
+
|
|
225
243
|
// The main drawing pass. Calls into the labels and candles modules.
|
|
226
244
|
void draw_chart(SkCanvas* canvas);
|
|
227
245
|
|
|
@@ -131,6 +131,7 @@ extern "C" void vroom_chart_set_candles(VroomChart* chart, const VroomCandle* da
|
|
|
131
131
|
chart->macd_dirty = true;
|
|
132
132
|
chart->overlays_dirty = true;
|
|
133
133
|
chart->vwap_dirty = true;
|
|
134
|
+
chart->bollinger_dirty = true;
|
|
134
135
|
|
|
135
136
|
// Infer the candle period from the first interval. Robust enough for
|
|
136
137
|
// uniform-duration series (the only kind we model today).
|
|
@@ -158,6 +159,7 @@ extern "C" void vroom_chart_append_candle(VroomChart* chart, const VroomCandle*
|
|
|
158
159
|
chart->macd_dirty = true;
|
|
159
160
|
chart->overlays_dirty = true;
|
|
160
161
|
chart->vwap_dirty = true;
|
|
162
|
+
chart->bollinger_dirty = true;
|
|
161
163
|
chart->mark_dirty();
|
|
162
164
|
}
|
|
163
165
|
|
|
@@ -168,6 +170,7 @@ extern "C" void vroom_chart_update_last(VroomChart* chart, const VroomCandle* c)
|
|
|
168
170
|
chart->macd_dirty = true;
|
|
169
171
|
chart->overlays_dirty = true;
|
|
170
172
|
chart->vwap_dirty = true;
|
|
173
|
+
chart->bollinger_dirty = true;
|
|
171
174
|
chart->mark_dirty();
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -1049,6 +1052,29 @@ extern "C" void vroom_chart_set_vwap(VroomChart* chart, bool enabled,
|
|
|
1049
1052
|
chart->mark_dirty();
|
|
1050
1053
|
}
|
|
1051
1054
|
|
|
1055
|
+
extern "C" void vroom_chart_set_bollinger(VroomChart* chart,
|
|
1056
|
+
const VroomBollinger* cfg) {
|
|
1057
|
+
if (!chart || !cfg) return;
|
|
1058
|
+
VroomBollinger next = *cfg;
|
|
1059
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1060
|
+
next.fill_enabled = next.fill_enabled ? 1 : 0;
|
|
1061
|
+
if (next.period < 1) next.period = 1;
|
|
1062
|
+
if (!(next.mult >= 0.f)) next.mult = 0.f;
|
|
1063
|
+
next.fill_opacity = std::clamp(next.fill_opacity, 0.f, 1.f);
|
|
1064
|
+
|
|
1065
|
+
// Only the series-affecting fields force a recompute; style and fill
|
|
1066
|
+
// changes are render-only.
|
|
1067
|
+
const VroomBollinger& cur = chart->bollinger;
|
|
1068
|
+
const bool recompute = cur.enabled != next.enabled ||
|
|
1069
|
+
cur.period != next.period ||
|
|
1070
|
+
cur.mult != next.mult ||
|
|
1071
|
+
cur.source != next.source ||
|
|
1072
|
+
cur.basis_kind != next.basis_kind;
|
|
1073
|
+
chart->bollinger = next;
|
|
1074
|
+
if (recompute) chart->bollinger_dirty = true;
|
|
1075
|
+
chart->mark_dirty();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1052
1078
|
// ---- Direct draw (used by hosts that don't need the SkPicture cache) ------
|
|
1053
1079
|
|
|
1054
1080
|
extern "C" void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas) {
|
|
@@ -78,4 +78,71 @@ void draw(SkCanvas* canvas,
|
|
|
78
78
|
canvas->restore();
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
void fill_between(SkCanvas* canvas,
|
|
82
|
+
const Layout& lay,
|
|
83
|
+
const PriceBounds& bounds,
|
|
84
|
+
const ::VroomCandle* visible,
|
|
85
|
+
std::size_t n,
|
|
86
|
+
const double* upper_visible,
|
|
87
|
+
const double* lower_visible,
|
|
88
|
+
int64_t window_ms,
|
|
89
|
+
int64_t visible_start_ms,
|
|
90
|
+
int64_t candle_duration_ms,
|
|
91
|
+
float candle_right,
|
|
92
|
+
float candle_area_h,
|
|
93
|
+
uint32_t color,
|
|
94
|
+
float opacity) {
|
|
95
|
+
if (!canvas || !upper_visible || !lower_visible || n == 0 ||
|
|
96
|
+
candle_right <= 0.f || candle_area_h <= 0.f) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
100
|
+
if (opacity <= 0.f) return;
|
|
101
|
+
|
|
102
|
+
// One closed contour per maximal run where both series are finite; a
|
|
103
|
+
// single-point run has no area. Multiple runs (e.g. around a data gap)
|
|
104
|
+
// become multiple contours in one path.
|
|
105
|
+
SkPathBuilder path;
|
|
106
|
+
std::size_t i = 0;
|
|
107
|
+
while (i < n) {
|
|
108
|
+
if (!std::isfinite(upper_visible[i]) ||
|
|
109
|
+
!std::isfinite(lower_visible[i])) {
|
|
110
|
+
++i;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
std::size_t e = i;
|
|
114
|
+
while (e + 1 < n && std::isfinite(upper_visible[e + 1]) &&
|
|
115
|
+
std::isfinite(lower_visible[e + 1])) {
|
|
116
|
+
++e;
|
|
117
|
+
}
|
|
118
|
+
if (e > i) {
|
|
119
|
+
const auto x_at = [&](std::size_t k) {
|
|
120
|
+
return vroom::candle_center_x(lay, visible[k].time_ms,
|
|
121
|
+
candle_duration_ms,
|
|
122
|
+
visible_start_ms, window_ms);
|
|
123
|
+
};
|
|
124
|
+
path.moveTo(x_at(i), vroom::price_to_y(lay, bounds, upper_visible[i]));
|
|
125
|
+
for (std::size_t k = i + 1; k <= e; ++k) {
|
|
126
|
+
path.lineTo(x_at(k), vroom::price_to_y(lay, bounds, upper_visible[k]));
|
|
127
|
+
}
|
|
128
|
+
for (std::size_t k = e + 1; k-- > i;) {
|
|
129
|
+
path.lineTo(x_at(k), vroom::price_to_y(lay, bounds, lower_visible[k]));
|
|
130
|
+
}
|
|
131
|
+
path.close();
|
|
132
|
+
}
|
|
133
|
+
i = e + 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
SkPaint fill;
|
|
137
|
+
fill.setAntiAlias(true);
|
|
138
|
+
fill.setColor(static_cast<SkColor>(color));
|
|
139
|
+
fill.setAlphaf(fill.getAlphaf() * opacity);
|
|
140
|
+
fill.setStyle(SkPaint::kFill_Style);
|
|
141
|
+
|
|
142
|
+
canvas->save();
|
|
143
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
144
|
+
canvas->drawPath(path.detach(), fill);
|
|
145
|
+
canvas->restore();
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
} // namespace vroom::ma_overlay
|
|
@@ -40,4 +40,25 @@ void draw(SkCanvas* canvas,
|
|
|
40
40
|
const unsigned char* break_before = nullptr,
|
|
41
41
|
float opacity = 1.f);
|
|
42
42
|
|
|
43
|
+
// Fills the closed region between two aligned series (NaN where undefined)
|
|
44
|
+
// with `color` at its alpha × `opacity` — used for the Bollinger Band fill.
|
|
45
|
+
// Runs where either series is NaN are skipped, so the fill never bridges the
|
|
46
|
+
// warmup gap. Plain-alpha SkPaint fill, no gradient shader (the Skia gradient
|
|
47
|
+
// APIs diverge across our pinned versions; see liquidity.cpp). Clipped to the
|
|
48
|
+
// candle area like draw().
|
|
49
|
+
void fill_between(SkCanvas* canvas,
|
|
50
|
+
const Layout& lay,
|
|
51
|
+
const PriceBounds& bounds,
|
|
52
|
+
const ::VroomCandle* visible,
|
|
53
|
+
std::size_t n,
|
|
54
|
+
const double* upper_visible,
|
|
55
|
+
const double* lower_visible,
|
|
56
|
+
int64_t window_ms,
|
|
57
|
+
int64_t visible_start_ms,
|
|
58
|
+
int64_t candle_duration_ms,
|
|
59
|
+
float candle_right,
|
|
60
|
+
float candle_area_h,
|
|
61
|
+
uint32_t color,
|
|
62
|
+
float opacity);
|
|
63
|
+
|
|
43
64
|
} // namespace vroom::ma_overlay
|
package/lib/index.d.mts
CHANGED
|
@@ -201,6 +201,31 @@ type DrawingStore = {
|
|
|
201
201
|
*/
|
|
202
202
|
save: (marketId: string, data: string) => void | Promise<void>;
|
|
203
203
|
};
|
|
204
|
+
/**
|
|
205
|
+
* Whether undo/redo are currently available for the chart's drawings — the
|
|
206
|
+
* payload of `onHistoryChange`, for binding toolbar button enabled-states.
|
|
207
|
+
*/
|
|
208
|
+
type UndoRedoState = {
|
|
209
|
+
canUndo: boolean;
|
|
210
|
+
canRedo: boolean;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Programmatic undo/redo controls, published through the `historyRef` prop in
|
|
214
|
+
* managed mode — for toolbar buttons and other UI outside the chart. The
|
|
215
|
+
* keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
|
|
216
|
+
*/
|
|
217
|
+
type UndoRedoControls = {
|
|
218
|
+
/** Roll back the most recent committed drawing action. No-op when empty. */
|
|
219
|
+
undo: () => void;
|
|
220
|
+
/** Re-apply the most recently undone action. No-op when empty. */
|
|
221
|
+
redo: () => void;
|
|
222
|
+
/**
|
|
223
|
+
* Drop the undo/redo stacks without touching the drawings. Rarely needed —
|
|
224
|
+
* users find a cleared history surprising — but useful when reusing a mounted
|
|
225
|
+
* chart for what the user perceives as a brand-new context.
|
|
226
|
+
*/
|
|
227
|
+
clearHistory: () => void;
|
|
228
|
+
};
|
|
204
229
|
/** RSI indicator config. Rendered in a pane below the candles when enabled. */
|
|
205
230
|
type RSIConfig = {
|
|
206
231
|
enabled?: boolean;
|
|
@@ -246,6 +271,42 @@ type VWAPConfig = {
|
|
|
246
271
|
/** Stroke width in px. Default 1.5. */
|
|
247
272
|
width?: number;
|
|
248
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Bollinger Bands overlay config. A basis moving average of `source` over
|
|
276
|
+
* `period`, banded at ± `stdDev` × population standard deviation of the same
|
|
277
|
+
* window, drawn as three lines on the price pane with an optional translucent
|
|
278
|
+
* fill between the bands. No pane is reserved.
|
|
279
|
+
*/
|
|
280
|
+
type BollingerBandsConfig = {
|
|
281
|
+
enabled?: boolean;
|
|
282
|
+
/** Lookback in candles. Default 20, clamped to >= 1. */
|
|
283
|
+
period?: number;
|
|
284
|
+
/** Standard-deviation multiplier. Default 2. */
|
|
285
|
+
stdDev?: number;
|
|
286
|
+
/** Price source. Default 'close'. */
|
|
287
|
+
source?: MASource;
|
|
288
|
+
/**
|
|
289
|
+
* Basis (middle) line type. Default 'sma'. The stdev always uses the
|
|
290
|
+
* window's arithmetic mean, even with an EMA basis (TradingView semantics).
|
|
291
|
+
*/
|
|
292
|
+
basis?: 'sma' | 'ema';
|
|
293
|
+
/** Upper band color (hex string or packed ARGB number). Default blue. */
|
|
294
|
+
upperColor?: string | number;
|
|
295
|
+
/** Upper band stroke width in px. Default 1. */
|
|
296
|
+
upperWidth?: number;
|
|
297
|
+
/** Basis (middle) line color. Default orange. */
|
|
298
|
+
middleColor?: string | number;
|
|
299
|
+
/** Basis line stroke width in px. Default 1. */
|
|
300
|
+
middleWidth?: number;
|
|
301
|
+
/** Lower band color. Default blue. */
|
|
302
|
+
lowerColor?: string | number;
|
|
303
|
+
/** Lower band stroke width in px. Default 1. */
|
|
304
|
+
lowerWidth?: number;
|
|
305
|
+
/** Translucent fill between the bands. Default true. */
|
|
306
|
+
fill?: boolean;
|
|
307
|
+
/** Fill opacity 0..1, applied to the upper band color. Default 0.1. */
|
|
308
|
+
fillOpacity?: number;
|
|
309
|
+
};
|
|
249
310
|
/**
|
|
250
311
|
* A single resting-liquidity band: a price interval carrying a total order size
|
|
251
312
|
* on one side of the book. Consolidate raw L2 levels into these buckets before
|
|
@@ -355,6 +416,8 @@ type VroomChartCoreProps = {
|
|
|
355
416
|
movingAverages?: MovingAverageOverlay[];
|
|
356
417
|
/** VWAP overlay (session anchor, configurable reset). */
|
|
357
418
|
vwap?: VWAPConfig;
|
|
419
|
+
/** Bollinger Bands overlay (three lines + fill on the price pane). */
|
|
420
|
+
bollingerBands?: BollingerBandsConfig;
|
|
358
421
|
/** Resting-order / order-book liquidity bands drawn behind the candles. */
|
|
359
422
|
liquidity?: LiquidityConfig;
|
|
360
423
|
/**
|
|
@@ -417,6 +480,40 @@ type VroomChartCoreProps = {
|
|
|
417
480
|
* host should apply the requested mode.
|
|
418
481
|
*/
|
|
419
482
|
onModeChange?: (mode: ChartMode) => void;
|
|
483
|
+
/**
|
|
484
|
+
* Max drawing undo depth in managed mode (one step = one committed drawing
|
|
485
|
+
* action). Oldest steps are evicted beyond this. Default 100. History is
|
|
486
|
+
* in-memory and per-`seriesKey`: it resets on market switch and is never
|
|
487
|
+
* persisted — only the drawings themselves are saved. Web only.
|
|
488
|
+
*/
|
|
489
|
+
historyLimit?: number;
|
|
490
|
+
/**
|
|
491
|
+
* Fired when drawing undo/redo availability changes in managed mode — bind
|
|
492
|
+
* toolbar undo/redo buttons' enabled-state to it. (In controlled mode you own
|
|
493
|
+
* the history, so track availability yourself.) Web only.
|
|
494
|
+
*/
|
|
495
|
+
onHistoryChange?: (state: UndoRedoState) => void;
|
|
496
|
+
/**
|
|
497
|
+
* Receives programmatic `undo`/`redo`/`clearHistory` controls in managed mode
|
|
498
|
+
* (e.g. `useRef<UndoRedoControls | null>(null)` passed here, then
|
|
499
|
+
* `historyRef.current?.undo()` from a toolbar button). Set to `null` while
|
|
500
|
+
* unmounted or when no `drawingStore` is present. Web only.
|
|
501
|
+
*/
|
|
502
|
+
historyRef?: {
|
|
503
|
+
current: UndoRedoControls | null;
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Fired when the user presses the undo shortcut (⌘Z / Ctrl+Z) in controlled
|
|
507
|
+
* mode — apply the undo to your own drawings state. Ignored when
|
|
508
|
+
* `drawingStore` is set (managed mode undoes internally). Web only.
|
|
509
|
+
*/
|
|
510
|
+
onUndo?: () => void;
|
|
511
|
+
/**
|
|
512
|
+
* Fired when the user presses the redo shortcut (⇧⌘Z / Ctrl+Shift+Z /
|
|
513
|
+
* Ctrl+Y) in controlled mode — apply the redo to your own drawings state.
|
|
514
|
+
* Ignored when `drawingStore` is set. Web only.
|
|
515
|
+
*/
|
|
516
|
+
onRedo?: () => void;
|
|
420
517
|
onCrosshair?: (e: CrosshairEvent) => void;
|
|
421
518
|
onViewportChange?: (startMs: number, endMs: number) => void;
|
|
422
519
|
};
|
|
@@ -450,4 +547,4 @@ declare global {
|
|
|
450
547
|
*/
|
|
451
548
|
declare function VroomChart(props: VroomChartProps): React.JSX.Element;
|
|
452
549
|
|
|
453
|
-
export { type Candle, type ChartType, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
|
|
550
|
+
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
|