react-native-vroom-chart 0.2.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 +85 -1
- package/cpp/_core_include/vroom/vroom_chart.h +111 -11
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/candles.cpp +32 -5
- package/cpp/_core_src/candles.h +8 -1
- package/cpp/_core_src/chart.cpp +70 -3
- package/cpp/_core_src/chart.h +53 -4
- package/cpp/_core_src/chart_facade.cpp +183 -2
- package/cpp/_core_src/drawings.cpp +313 -15
- package/cpp/_core_src/drawings.h +23 -0
- package/cpp/_core_src/ma_overlay.cpp +76 -1
- package/cpp/_core_src/ma_overlay.h +23 -1
- package/cpp/_core_src/theme.cpp +2 -0
- package/lib/index.d.mts +211 -13
- package/lib/index.d.ts +211 -13
- package/lib/index.js +92 -5
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +92 -5
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -7
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +71 -0
- package/src/index.ts +2 -0
- package/src/jsi.d.ts +24 -0
- package/src/theme.ts +2 -0
- package/src/types.ts +2 -0
- package/src/useChartCore.ts +40 -2
|
@@ -27,13 +27,15 @@ ChartHostObject::~ChartHostObject() {
|
|
|
27
27
|
std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
28
28
|
jsi::Runtime& rt) {
|
|
29
29
|
std::vector<jsi::PropNameID> out;
|
|
30
|
-
out.reserve(
|
|
30
|
+
out.reserve(24);
|
|
31
31
|
out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
|
|
32
32
|
out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
|
|
33
33
|
out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
|
|
34
34
|
out.push_back(jsi::PropNameID::forAscii(rt, "setFloat"));
|
|
35
35
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVisibleRange"));
|
|
36
36
|
out.push_back(jsi::PropNameID::forAscii(rt, "setDefaultCandleWidth"));
|
|
37
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setChartType"));
|
|
38
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setMorph"));
|
|
37
39
|
out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
|
|
38
40
|
out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
|
|
39
41
|
out.push_back(jsi::PropNameID::forAscii(rt, "zoom"));
|
|
@@ -49,6 +51,7 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
49
51
|
out.push_back(jsi::PropNameID::forAscii(rt, "setMACD"));
|
|
50
52
|
out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
|
|
51
53
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
|
|
54
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
|
|
52
55
|
out.push_back(jsi::PropNameID::forAscii(rt, "render"));
|
|
53
56
|
return out;
|
|
54
57
|
}
|
|
@@ -188,6 +191,41 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
188
191
|
});
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
if (name == "setChartType") {
|
|
195
|
+
// setChartType(mode) — 0 = candlesticks (default), 1 = line chart.
|
|
196
|
+
return jsi::Function::createFromHostFunction(
|
|
197
|
+
rt,
|
|
198
|
+
jsi::PropNameID::forAscii(rt, "setChartType"),
|
|
199
|
+
1,
|
|
200
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
201
|
+
const jsi::Value& /*thisVal*/,
|
|
202
|
+
const jsi::Value* args,
|
|
203
|
+
size_t count) -> jsi::Value {
|
|
204
|
+
if (count < 1) return jsi::Value::undefined();
|
|
205
|
+
vroom_chart_set_chart_type(
|
|
206
|
+
chart_, static_cast<int32_t>(args[0].asNumber()));
|
|
207
|
+
return jsi::Value::undefined();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (name == "setMorph") {
|
|
212
|
+
// setMorph(collapse, fade) — candle↔line blend for animated transitions.
|
|
213
|
+
return jsi::Function::createFromHostFunction(
|
|
214
|
+
rt,
|
|
215
|
+
jsi::PropNameID::forAscii(rt, "setMorph"),
|
|
216
|
+
2,
|
|
217
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
218
|
+
const jsi::Value& /*thisVal*/,
|
|
219
|
+
const jsi::Value* args,
|
|
220
|
+
size_t count) -> jsi::Value {
|
|
221
|
+
if (count < 2) return jsi::Value::undefined();
|
|
222
|
+
vroom_chart_set_morph(chart_,
|
|
223
|
+
static_cast<float>(args[0].asNumber()),
|
|
224
|
+
static_cast<float>(args[1].asNumber()));
|
|
225
|
+
return jsi::Value::undefined();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
191
229
|
if (name == "pan") {
|
|
192
230
|
// pan(dx, dy) -> JsiSkPicture
|
|
193
231
|
// Mutates the visible range and renders in one JSI call so gesture
|
|
@@ -514,6 +552,52 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
514
552
|
});
|
|
515
553
|
}
|
|
516
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
|
+
|
|
517
601
|
if (name == "render") {
|
|
518
602
|
return jsi::Function::createFromHostFunction(
|
|
519
603
|
rt,
|
|
@@ -49,18 +49,50 @@ 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)
|
|
55
75
|
double price;
|
|
56
76
|
} VroomDrawPoint;
|
|
57
77
|
|
|
58
|
-
// A committed
|
|
78
|
+
// A committed drawing on the price pane. `kind` selects the geometry:
|
|
79
|
+
// 0 = line — a two-point trendline from `a` to `b`.
|
|
80
|
+
// 1 = box — an axis-aligned rectangle whose two opposite corners are `a`
|
|
81
|
+
// and `b`; the other two corners (a.x,b.y) and (b.x,a.y) derive.
|
|
82
|
+
// 2 = pencil — a freehand path through `points` (in draw order). `a`/`b` mirror
|
|
83
|
+
// the first/last point so bounds and handle code stay uniform.
|
|
84
|
+
//
|
|
85
|
+
// `points`/`point_count` are only read for kind 2; line and box leave them
|
|
86
|
+
// null/0. Like the rest of this API the points are copied internally, so the
|
|
87
|
+
// caller may free them as soon as the call returns.
|
|
59
88
|
typedef struct VroomDrawing {
|
|
60
|
-
VroomDrawPoint
|
|
61
|
-
VroomDrawPoint
|
|
62
|
-
uint32_t
|
|
63
|
-
float
|
|
89
|
+
VroomDrawPoint a;
|
|
90
|
+
VroomDrawPoint b;
|
|
91
|
+
uint32_t color; // 0xAARRGGBB
|
|
92
|
+
float width; // stroke width in px
|
|
93
|
+
int32_t kind; // 0 = line, 1 = box, 2 = pencil
|
|
94
|
+
const VroomDrawPoint* points; // pencil path (kind 2), else null
|
|
95
|
+
int32_t point_count; // number of `points`, else 0
|
|
64
96
|
} VroomDrawing;
|
|
65
97
|
|
|
66
98
|
// A resting-liquidity band: a price interval carrying a total order size on one
|
|
@@ -112,6 +144,7 @@ typedef enum {
|
|
|
112
144
|
VROOM_COLOR_WICK_BEAR, // bear wick; 0 alpha => inherit BEAR fill
|
|
113
145
|
VROOM_COLOR_ACCENT_BULL, // generic up color: price indicator, volume, MACD
|
|
114
146
|
VROOM_COLOR_ACCENT_BEAR, // generic down color
|
|
147
|
+
VROOM_COLOR_LINE, // line-chart-mode close-price polyline
|
|
115
148
|
VROOM_COLOR_COUNT_
|
|
116
149
|
} VroomColorKey;
|
|
117
150
|
|
|
@@ -127,6 +160,7 @@ typedef enum {
|
|
|
127
160
|
VROOM_FLOAT_CANDLE_RADIUS_PX, // candle body corner radius px (0 = square)
|
|
128
161
|
VROOM_FLOAT_WICK_ROUND_CAP, // 0/1: round the wick end caps
|
|
129
162
|
VROOM_FLOAT_VOLUME_RADIUS_PX, // volume bar top-corner radius px (0 = square)
|
|
163
|
+
VROOM_FLOAT_LINE_WIDTH_PX, // line-chart-mode polyline stroke width px
|
|
130
164
|
VROOM_FLOAT_COUNT_
|
|
131
165
|
} VroomFloatKey;
|
|
132
166
|
|
|
@@ -163,6 +197,17 @@ void vroom_chart_set_visible_range(VroomChart* chart, int64_t start_ms, int64_t
|
|
|
163
197
|
// set_visible_range still overrides it.
|
|
164
198
|
void vroom_chart_set_default_candle_width(VroomChart* chart, float px);
|
|
165
199
|
|
|
200
|
+
// Chart render mode: 0 = candlesticks (default), 1 = line chart (a polyline
|
|
201
|
+
// through each candle's close). Other layers (volume, indicators, overlays,
|
|
202
|
+
// crosshair, drawings) are unaffected.
|
|
203
|
+
void vroom_chart_set_chart_type(VroomChart* chart, int32_t mode);
|
|
204
|
+
|
|
205
|
+
// Candle↔line morph blend for animated transitions. `collapse` folds candles
|
|
206
|
+
// toward their close price; `fade` crossfades candles→line. Both 0 = candles,
|
|
207
|
+
// both 1 = line. Driven per-frame by the host animation loop; set_chart_type
|
|
208
|
+
// snaps both to the target.
|
|
209
|
+
void vroom_chart_set_morph(VroomChart* chart, float collapse, float fade);
|
|
210
|
+
|
|
166
211
|
// Reads the current visible time window. Either out pointer may be null.
|
|
167
212
|
// Both are 0 when the window is still uninitialized.
|
|
168
213
|
void vroom_chart_get_visible_range(VroomChart* chart,
|
|
@@ -296,6 +341,11 @@ void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
|
|
|
296
341
|
void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
297
342
|
uint32_t color, float width);
|
|
298
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
|
+
|
|
299
349
|
// ---- Drawings (line annotations) ------------------------------------------
|
|
300
350
|
|
|
301
351
|
// Replaces the full set of committed line drawings (data-anchored, so they track
|
|
@@ -304,6 +354,34 @@ void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
|
304
354
|
void vroom_chart_set_drawings(VroomChart* chart, const VroomDrawing* drawings,
|
|
305
355
|
size_t count);
|
|
306
356
|
|
|
357
|
+
// Hit-tests pixel (x_px, y_px) against the committed drawings. On a hit, fills
|
|
358
|
+
// *out_index with the drawing index, *out_part with 0 (endpoint A), 1
|
|
359
|
+
// (endpoint B), or 2 (line body), *out_t with the 0..1 grab position along the
|
|
360
|
+
// segment (A→B; 0/1 for handle hits), and returns true. Endpoint hits are only
|
|
361
|
+
// reported for the currently selected drawing (whose handles are visible).
|
|
362
|
+
// Returns false on a miss (out params untouched). Any out pointer may be null.
|
|
363
|
+
bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px, float y_px,
|
|
364
|
+
int32_t* out_index, int32_t* out_part,
|
|
365
|
+
float* out_t);
|
|
366
|
+
|
|
367
|
+
// Selects a committed drawing (renders its endpoint handles). `index` -1 clears
|
|
368
|
+
// the selection. `grabbed_endpoint` 0/1 renders that handle 50% larger while it's
|
|
369
|
+
// being dragged; -1 for none. An out-of-range index clears the selection.
|
|
370
|
+
void vroom_chart_set_selected_drawing(VroomChart* chart, int32_t index,
|
|
371
|
+
int32_t grabbed_endpoint);
|
|
372
|
+
|
|
373
|
+
// Moves one endpoint of a committed drawing to a new data-space anchor (for live
|
|
374
|
+
// handle dragging). `endpoint` is 0 (A) or 1 (B). No-op for an out-of-range index.
|
|
375
|
+
void vroom_chart_move_drawing_endpoint(VroomChart* chart, int32_t index,
|
|
376
|
+
int32_t endpoint, int64_t time_ms,
|
|
377
|
+
double price);
|
|
378
|
+
|
|
379
|
+
// Shifts a whole committed drawing by a *relative* data-space delta — `a`, `b`,
|
|
380
|
+
// and (for a pencil) every path point. Used for live body dragging of shapes
|
|
381
|
+
// whose points can't be restated cheaply. No-op for an out-of-range index.
|
|
382
|
+
void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
383
|
+
int64_t d_time_ms, double d_price);
|
|
384
|
+
|
|
307
385
|
// ---- Liquidity bands (order-book depth overlay) ---------------------------
|
|
308
386
|
|
|
309
387
|
// Replaces the full set of resting-liquidity bands and their shared style.
|
|
@@ -314,14 +392,29 @@ void vroom_chart_set_liquidity(VroomChart* chart, const VroomBand* bands,
|
|
|
314
392
|
|
|
315
393
|
// Sets the transient in-progress "draft" the drawing tool shows while the user
|
|
316
394
|
// places points. Node A is always shown; when `has_b`, node B is shown too.
|
|
317
|
-
// `guide != 0` also draws the
|
|
318
|
-
// draws node dots only (the committed
|
|
319
|
-
//
|
|
395
|
+
// `guide != 0` also draws the live preview (a guideline for a line, or a preview
|
|
396
|
+
// rectangle for a box); `guide == 0` draws node dots only (the committed shape
|
|
397
|
+
// already renders via set_drawings). `kind` matches VroomDrawing (0 = line,
|
|
398
|
+
// 1 = box) so the preview geometry matches the eventual shape. `color`/`width`
|
|
399
|
+
// style the preview to match the eventual drawing.
|
|
320
400
|
void vroom_chart_set_draft(VroomChart* chart, int64_t a_time, double a_price,
|
|
321
401
|
bool has_b, int64_t b_time, double b_price,
|
|
322
|
-
bool guide, uint32_t color, float width
|
|
323
|
-
|
|
324
|
-
|
|
402
|
+
bool guide, uint32_t color, float width,
|
|
403
|
+
int32_t kind);
|
|
404
|
+
|
|
405
|
+
// Begins a freehand (pencil) draft stroke, clearing any previous draft points.
|
|
406
|
+
// Follow with vroom_chart_append_draft_point per captured sample; the growing
|
|
407
|
+
// path renders live. `color`/`width` style it to match the eventual stroke.
|
|
408
|
+
void vroom_chart_start_draft_stroke(VroomChart* chart, uint32_t color,
|
|
409
|
+
float width);
|
|
410
|
+
|
|
411
|
+
// Appends one point to the in-progress freehand draft. Cheap (O(1) amortized) so
|
|
412
|
+
// it can be called on every pointer move without restating the whole path.
|
|
413
|
+
// No-op unless a draft stroke was started.
|
|
414
|
+
void vroom_chart_append_draft_point(VroomChart* chart, int64_t time_ms,
|
|
415
|
+
double price);
|
|
416
|
+
|
|
417
|
+
// Clears the draft (hides the in-progress node dots / guideline / stroke).
|
|
325
418
|
void vroom_chart_clear_draft(VroomChart* chart);
|
|
326
419
|
|
|
327
420
|
// Fills *out with the continuous data coordinate (time_ms, price) at pixel
|
|
@@ -331,6 +424,13 @@ void vroom_chart_clear_draft(VroomChart* chart);
|
|
|
331
424
|
bool vroom_chart_coord_at(VroomChart* chart, float x_px, float y_px,
|
|
332
425
|
VroomCoord* out);
|
|
333
426
|
|
|
427
|
+
// Projects a data coordinate (time_ms, price) to its pixel position, filling
|
|
428
|
+
// *out_x / *out_y (either may be null) with the free (non-snapped) mapping — the
|
|
429
|
+
// inverse of vroom_chart_coord_at, matching the rendered drawing geometry.
|
|
430
|
+
// Returns false (out params untouched) when there are no candles / degenerate.
|
|
431
|
+
bool vroom_chart_project(VroomChart* chart, int64_t time_ms, double price,
|
|
432
|
+
float* out_x, float* out_y);
|
|
433
|
+
|
|
334
434
|
// ---- Rendering ------------------------------------------------------------
|
|
335
435
|
|
|
336
436
|
void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas);
|
|
@@ -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
|
|
@@ -30,11 +30,28 @@ void draw(SkCanvas* canvas,
|
|
|
30
30
|
const PriceBounds& bounds,
|
|
31
31
|
int64_t window_ms,
|
|
32
32
|
int64_t visible_start_ms,
|
|
33
|
-
int64_t candle_duration_ms
|
|
33
|
+
int64_t candle_duration_ms,
|
|
34
|
+
float collapse,
|
|
35
|
+
float opacity) {
|
|
34
36
|
if (!canvas || n == 0) return;
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
collapse = std::clamp(collapse, 0.f, 1.f);
|
|
39
|
+
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
40
|
+
if (opacity <= 0.f) return;
|
|
41
|
+
|
|
42
|
+
// During the candle→line morph, fade the entire candle layer as one unit so
|
|
43
|
+
// overlapping bars/wicks composite cleanly instead of stacking alpha.
|
|
44
|
+
const bool fade_layer = opacity < 0.999f;
|
|
45
|
+
if (fade_layer) {
|
|
46
|
+
canvas->saveLayerAlpha(nullptr,
|
|
47
|
+
static_cast<U8CPU>(opacity * 255.f + 0.5f));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const float full_body_w = vroom::candle_body_width(
|
|
37
51
|
lay, window_ms, candle_duration_ms);
|
|
52
|
+
// Thin the body toward the line stroke width as candles collapse to the line.
|
|
53
|
+
const float line_w = theme.floats[VROOM_FLOAT_LINE_WIDTH_PX];
|
|
54
|
+
const float body_w = full_body_w + (line_w - full_body_w) * collapse;
|
|
38
55
|
|
|
39
56
|
const uint32_t fill_bull = theme.colors[VROOM_COLOR_BULL];
|
|
40
57
|
const uint32_t fill_bear = theme.colors[VROOM_COLOR_BEAR];
|
|
@@ -94,10 +111,18 @@ void draw(SkCanvas* canvas,
|
|
|
94
111
|
const float cx = vroom::candle_center_x(
|
|
95
112
|
lay, c.time_ms, candle_duration_ms,
|
|
96
113
|
visible_start_ms, window_ms);
|
|
97
|
-
const float y_high = vroom::price_to_y(lay, bounds, c.high);
|
|
98
|
-
const float y_low = vroom::price_to_y(lay, bounds, c.low);
|
|
99
|
-
const float y_open = vroom::price_to_y(lay, bounds, c.open);
|
|
100
114
|
const float y_close = vroom::price_to_y(lay, bounds, c.close);
|
|
115
|
+
// Collapse high/low/open toward the close so the candle folds into its
|
|
116
|
+
// close point (the line vertex) as `collapse` → 1.
|
|
117
|
+
const float y_high =
|
|
118
|
+
vroom::price_to_y(lay, bounds, c.high) * (1.f - collapse) +
|
|
119
|
+
y_close * collapse;
|
|
120
|
+
const float y_low =
|
|
121
|
+
vroom::price_to_y(lay, bounds, c.low) * (1.f - collapse) +
|
|
122
|
+
y_close * collapse;
|
|
123
|
+
const float y_open =
|
|
124
|
+
vroom::price_to_y(lay, bounds, c.open) * (1.f - collapse) +
|
|
125
|
+
y_close * collapse;
|
|
101
126
|
|
|
102
127
|
canvas->drawLine(cx, y_high, cx, y_low,
|
|
103
128
|
bull ? wick_bull : wick_bear);
|
|
@@ -133,6 +158,8 @@ void draw(SkCanvas* canvas,
|
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
}
|
|
161
|
+
|
|
162
|
+
if (fade_layer) canvas->restore();
|
|
136
163
|
}
|
|
137
164
|
|
|
138
165
|
} // namespace vroom::candles
|
package/cpp/_core_src/candles.h
CHANGED
|
@@ -21,6 +21,11 @@ namespace vroom::candles {
|
|
|
21
21
|
|
|
22
22
|
// Draws every candle in [visible, visible + n). The visible slice should
|
|
23
23
|
// already be filtered by `visible_indices` in viewport.h.
|
|
24
|
+
//
|
|
25
|
+
// `collapse` (0..1) morphs each candle vertically toward its close price for the
|
|
26
|
+
// candle→line transition: 0 = normal candle, 1 = a flat point at the close (the
|
|
27
|
+
// body/wick heights and width shrink to the line). `opacity` (0..1) fades the
|
|
28
|
+
// whole candle layer out as the line fades in. Both default to a no-op.
|
|
24
29
|
void draw(SkCanvas* canvas,
|
|
25
30
|
const ::VroomCandle* visible,
|
|
26
31
|
std::size_t n,
|
|
@@ -29,6 +34,8 @@ void draw(SkCanvas* canvas,
|
|
|
29
34
|
const PriceBounds& bounds,
|
|
30
35
|
int64_t window_ms,
|
|
31
36
|
int64_t visible_start_ms,
|
|
32
|
-
int64_t candle_duration_ms
|
|
37
|
+
int64_t candle_duration_ms,
|
|
38
|
+
float collapse = 0.f,
|
|
39
|
+
float opacity = 1.f);
|
|
33
40
|
|
|
34
41
|
} // namespace vroom::candles
|
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,9 +159,44 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
149
159
|
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
150
160
|
candle_area_h);
|
|
151
161
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
|
|
179
|
+
// 5. Price series — candles, a close-price line, or a blend of the two during
|
|
180
|
+
// the candle↔line morph. `morph_fade` crossfades candles→line and
|
|
181
|
+
// `morph_collapse` folds each candle toward its close (the line vertex).
|
|
182
|
+
// fade 0 = pure candles, fade 1 = pure line. The line reuses the MA-overlay
|
|
183
|
+
// polyline routine, fed the visible closes and styled by theme.LINE.
|
|
184
|
+
const float fade = morph_fade;
|
|
185
|
+
const float collapse = morph_collapse;
|
|
186
|
+
if (fade < 1.f) {
|
|
187
|
+
vroom::candles::draw(canvas, visible, n, lay, theme, bounds, window_ms,
|
|
188
|
+
visible_start_ms, candle_duration_ms, collapse,
|
|
189
|
+
1.f - fade);
|
|
190
|
+
}
|
|
191
|
+
if (fade > 0.f) {
|
|
192
|
+
std::vector<double> closes(n);
|
|
193
|
+
for (std::size_t i = 0; i < n; ++i) closes[i] = visible[i].close;
|
|
194
|
+
vroom::ma_overlay::draw(
|
|
195
|
+
canvas, lay, bounds, visible, n, closes.data(), window_ms,
|
|
196
|
+
visible_start_ms, candle_duration_ms, candle_right, candle_area_h,
|
|
197
|
+
theme.colors[VROOM_COLOR_LINE], theme.floats[VROOM_FLOAT_LINE_WIDTH_PX],
|
|
198
|
+
nullptr, fade);
|
|
199
|
+
}
|
|
155
200
|
|
|
156
201
|
// 5.5. Moving-average overlay lines (SMA/EMA) on the price pane, over the
|
|
157
202
|
// candles. They share the candle price scale and don't reserve a pane.
|
|
@@ -184,6 +229,28 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
184
229
|
}
|
|
185
230
|
}
|
|
186
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
|
+
|
|
187
254
|
// 5.7. Drawing annotations (committed line tools + the in-progress draft).
|
|
188
255
|
// On the price pane above the candles/overlays, below the axis labels.
|
|
189
256
|
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -54,6 +54,15 @@ struct VroomChart {
|
|
|
54
54
|
// initial zoom). 0 = legacy "last ~80 candles" behavior.
|
|
55
55
|
float default_candle_px = 0.f;
|
|
56
56
|
|
|
57
|
+
// Render mode: 0 = candlesticks (default), 1 = line chart (close polyline).
|
|
58
|
+
int chart_type = 0;
|
|
59
|
+
|
|
60
|
+
// Candle↔line morph blend, driven per-frame by the JS animation loop.
|
|
61
|
+
// `morph_collapse` folds candles toward their close; `morph_fade` crossfades
|
|
62
|
+
// candles→line. Both 0 = candles, both 1 = line. set_chart_type snaps them.
|
|
63
|
+
float morph_collapse = 0.f;
|
|
64
|
+
float morph_fade = 0.f;
|
|
65
|
+
|
|
57
66
|
// Cached y-axis width in pixels, sized to fit the widest formatted price
|
|
58
67
|
// label. 0 = uncomputed; layout() falls back to a width ratio.
|
|
59
68
|
float axis_width_px = 0.f;
|
|
@@ -126,10 +135,36 @@ struct VroomChart {
|
|
|
126
135
|
std::vector<unsigned char> vwap_breaks;
|
|
127
136
|
bool vwap_dirty = true;
|
|
128
137
|
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
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
|
+
|
|
152
|
+
// --- drawings (annotations) --------------------------------------------
|
|
153
|
+
// Committed drawings, anchored in data space so they track the candles on
|
|
154
|
+
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
155
|
+
//
|
|
156
|
+
// Mirrors the public VroomDrawing but *owns* its points, so a pencil path
|
|
157
|
+
// (kind 2) can carry a variable number of them. For line/box `points` is
|
|
158
|
+
// empty and only a/b are used; for pencil a/b mirror the first/last point.
|
|
159
|
+
struct StoredDrawing {
|
|
160
|
+
VroomDrawPoint a{};
|
|
161
|
+
VroomDrawPoint b{};
|
|
162
|
+
uint32_t color = 0xff2962ff;
|
|
163
|
+
float width = 2.f;
|
|
164
|
+
int32_t kind = 0;
|
|
165
|
+
std::vector<VroomDrawPoint> points; // pencil path (kind 2)
|
|
166
|
+
};
|
|
167
|
+
std::vector<StoredDrawing> drawings;
|
|
133
168
|
|
|
134
169
|
// Transient in-progress "draft" the drawing tool shows while placing points.
|
|
135
170
|
// draft_a is always drawn (node dot); draft_b is drawn when draft_has_b.
|
|
@@ -143,6 +178,16 @@ struct VroomChart {
|
|
|
143
178
|
bool draft_guide = false;
|
|
144
179
|
uint32_t draft_color = 0xff2962ff;
|
|
145
180
|
float draft_width = 2.f;
|
|
181
|
+
int32_t draft_kind = 0; // 0 = line, 1 = box, 2 = pencil (VroomDrawing)
|
|
182
|
+
// Freehand stroke in progress (draft_kind 2), grown one point at a time.
|
|
183
|
+
std::vector<VroomDrawPoint> draft_points;
|
|
184
|
+
|
|
185
|
+
// Selection/editing state for committed drawings. selected_drawing indexes
|
|
186
|
+
// `drawings` (or -1); its endpoints render as handles. grabbed_endpoint is
|
|
187
|
+
// 0 (A) or 1 (B) while that handle is being dragged (rendered 50% larger),
|
|
188
|
+
// else -1.
|
|
189
|
+
int32_t selected_drawing = -1;
|
|
190
|
+
int32_t grabbed_endpoint = -1;
|
|
146
191
|
|
|
147
192
|
// --- liquidity bands (order-book depth overlay) ------------------------
|
|
148
193
|
// Resting-order bands anchored in price space, drawn behind the candles and
|
|
@@ -191,6 +236,10 @@ struct VroomChart {
|
|
|
191
236
|
// Recomputes the VWAP cache when vwap_dirty and VWAP is enabled.
|
|
192
237
|
void ensure_vwap();
|
|
193
238
|
|
|
239
|
+
// Recomputes the Bollinger Band caches when bollinger_dirty and the
|
|
240
|
+
// indicator is enabled.
|
|
241
|
+
void ensure_bollinger();
|
|
242
|
+
|
|
194
243
|
// The main drawing pass. Calls into the labels and candles modules.
|
|
195
244
|
void draw_chart(SkCanvas* canvas);
|
|
196
245
|
|