react-native-vroom-chart 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/android/README.md +7 -0
  4. package/cpp/README.md +11 -0
  5. package/cpp/VroomChartHostObject.cpp +458 -0
  6. package/cpp/VroomChartHostObject.h +36 -0
  7. package/cpp/VroomJsiInstaller.cpp +69 -0
  8. package/cpp/VroomJsiInstaller.h +10 -0
  9. package/cpp/VroomSkiaContext.cpp +25 -0
  10. package/cpp/VroomSkiaContext.h +30 -0
  11. package/cpp/_core_include/vroom/vroom_chart.h +195 -0
  12. package/cpp/_core_src/candles.cpp +74 -0
  13. package/cpp/_core_src/candles.h +34 -0
  14. package/cpp/_core_src/chart.cpp +317 -0
  15. package/cpp/_core_src/chart.h +167 -0
  16. package/cpp/_core_src/chart_facade.cpp +570 -0
  17. package/cpp/_core_src/chart_internal.h +30 -0
  18. package/cpp/_core_src/crosshair.cpp +65 -0
  19. package/cpp/_core_src/crosshair.h +32 -0
  20. package/cpp/_core_src/fonts.cpp +22 -0
  21. package/cpp/_core_src/fonts.h +25 -0
  22. package/cpp/_core_src/labels.cpp +340 -0
  23. package/cpp/_core_src/labels.h +85 -0
  24. package/cpp/_core_src/ma.cpp +53 -0
  25. package/cpp/_core_src/ma.h +39 -0
  26. package/cpp/_core_src/ma_overlay.cpp +73 -0
  27. package/cpp/_core_src/ma_overlay.h +42 -0
  28. package/cpp/_core_src/macd.cpp +68 -0
  29. package/cpp/_core_src/macd.h +29 -0
  30. package/cpp/_core_src/macd_pane.cpp +199 -0
  31. package/cpp/_core_src/macd_pane.h +41 -0
  32. package/cpp/_core_src/price_indicator.cpp +106 -0
  33. package/cpp/_core_src/price_indicator.h +29 -0
  34. package/cpp/_core_src/rsi.cpp +70 -0
  35. package/cpp/_core_src/rsi.h +32 -0
  36. package/cpp/_core_src/rsi_pane.cpp +167 -0
  37. package/cpp/_core_src/rsi_pane.h +39 -0
  38. package/cpp/_core_src/theme.cpp +41 -0
  39. package/cpp/_core_src/theme.h +23 -0
  40. package/cpp/_core_src/ticks.cpp +49 -0
  41. package/cpp/_core_src/ticks.h +30 -0
  42. package/cpp/_core_src/viewport.cpp +141 -0
  43. package/cpp/_core_src/viewport.h +100 -0
  44. package/cpp/_core_src/volume.cpp +70 -0
  45. package/cpp/_core_src/volume.h +34 -0
  46. package/cpp/_core_src/vwap.cpp +51 -0
  47. package/cpp/_core_src/vwap.h +27 -0
  48. package/ios/README.md +7 -0
  49. package/ios/VroomChartModule.h +11 -0
  50. package/ios/VroomChartModule.mm +44 -0
  51. package/lib/index.d.mts +185 -0
  52. package/lib/index.d.ts +185 -0
  53. package/lib/index.js +429 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/index.mjs +396 -0
  56. package/lib/index.mjs.map +1 -0
  57. package/package.json +75 -0
  58. package/react-native-vroom-chart.podspec +79 -0
  59. package/src/NativeVroomChart.ts +12 -0
  60. package/src/VroomChart.tsx +382 -0
  61. package/src/index.ts +14 -0
  62. package/src/jsi.d.ts +128 -0
  63. package/src/packCandles.ts +24 -0
  64. package/src/theme.ts +43 -0
  65. package/src/types.ts +27 -0
  66. package/src/useChartCore.ts +135 -0
@@ -0,0 +1,42 @@
1
+ // Moving-average overlay line — a single SMA/EMA polyline drawn on the price
2
+ // pane (over the candles, on the candle price scale). One call per overlay line.
3
+
4
+ #pragma once
5
+
6
+ #include <cstddef>
7
+ #include <cstdint>
8
+
9
+ #include "vroom/vroom_chart.h" // ::VroomCandle
10
+
11
+ class SkCanvas;
12
+
13
+ namespace vroom {
14
+ struct Layout;
15
+ struct PriceBounds;
16
+ } // namespace vroom
17
+
18
+ namespace vroom::ma_overlay {
19
+
20
+ // Draws `values_visible` (aligned with `visible`, NaN where undefined) as a
21
+ // polyline on the price pane using candle_center_x + price_to_y. `color` is
22
+ // 0xAARRGGBB; `width` is the stroke px. Clipped to the candle area.
23
+ //
24
+ // `break_before` (optional, aligned with `visible`) forces a new subpath at any
25
+ // index where it is non-zero — used by VWAP to break the line at session
26
+ // resets. Pass nullptr for continuous lines (SMA/EMA).
27
+ void draw(SkCanvas* canvas,
28
+ const Layout& lay,
29
+ const PriceBounds& bounds,
30
+ const ::VroomCandle* visible,
31
+ std::size_t n,
32
+ const double* values_visible,
33
+ int64_t window_ms,
34
+ int64_t visible_start_ms,
35
+ int64_t candle_duration_ms,
36
+ float candle_right,
37
+ float candle_area_h,
38
+ uint32_t color,
39
+ float width,
40
+ const unsigned char* break_before = nullptr);
41
+
42
+ } // namespace vroom::ma_overlay
@@ -0,0 +1,68 @@
1
+ #include "macd.h"
2
+
3
+ #include <cmath> // std::nan, std::isfinite
4
+
5
+ namespace vroom::macd {
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;
27
+
28
+ const double alpha = 2.0 / (static_cast<double>(P) + 1.0);
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
36
+
37
+ void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
38
+ int signal, std::vector<double>& macd_out,
39
+ std::vector<double>& signal_out, std::vector<double>& hist_out) {
40
+ macd_out.assign(n, std::nan(""));
41
+ signal_out.assign(n, std::nan(""));
42
+ hist_out.assign(n, std::nan(""));
43
+ if (!candles || fast < 1 || slow < 1 || signal < 1) return;
44
+
45
+ std::vector<double> closes(n);
46
+ for (std::size_t i = 0; i < n; ++i) closes[i] = candles[i].close;
47
+
48
+ std::vector<double> ema_fast;
49
+ std::vector<double> ema_slow;
50
+ ema_seeded(closes, fast, ema_fast);
51
+ ema_seeded(closes, slow, ema_slow);
52
+
53
+ for (std::size_t i = 0; i < n; ++i) {
54
+ if (std::isfinite(ema_fast[i]) && std::isfinite(ema_slow[i])) {
55
+ macd_out[i] = ema_fast[i] - ema_slow[i];
56
+ }
57
+ }
58
+
59
+ ema_seeded(macd_out, signal, signal_out);
60
+
61
+ for (std::size_t i = 0; i < n; ++i) {
62
+ if (std::isfinite(macd_out[i]) && std::isfinite(signal_out[i])) {
63
+ hist_out[i] = macd_out[i] - signal_out[i];
64
+ }
65
+ }
66
+ }
67
+
68
+ } // namespace vroom::macd
@@ -0,0 +1,29 @@
1
+ // MACD — pure computation, no Skia. Kept Skia-free so it builds into the
2
+ // unit-test target. Mirrors rsi.cpp's NaN-warmup convention.
3
+ //
4
+ // MACD line = EMA(fast) - EMA(slow) of close
5
+ // Signal line = EMA(signal) of the MACD line
6
+ // Histogram = MACD - Signal
7
+ // EMAs are SMA-seeded: NaN until the first `period` values exist (the seed is
8
+ // their simple average), then ema = alpha*src + (1-alpha)*prev, alpha=2/(p+1).
9
+
10
+ #pragma once
11
+
12
+ #include <cstddef>
13
+ #include <vector>
14
+
15
+ #include "vroom/vroom_chart.h" // ::VroomCandle
16
+
17
+ namespace vroom::macd {
18
+
19
+ // Computes MACD over the closes of [candles, candles+n). fast/slow/signal are
20
+ // clamped to >= 1 (callers pass 12/26/9 by default; slow should exceed fast).
21
+ // Fills three series (each resized to n, NaN where undefined):
22
+ // macd_out — EMA(fast) - EMA(slow), defined from index slow-1
23
+ // signal_out — EMA(signal) of macd_out, defined from (slow-1)+(signal-1)
24
+ // hist_out — macd_out - signal_out, defined where both are
25
+ void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
26
+ int signal, std::vector<double>& macd_out,
27
+ std::vector<double>& signal_out, std::vector<double>& hist_out);
28
+
29
+ } // namespace vroom::macd
@@ -0,0 +1,199 @@
1
+ #include "macd_pane.h"
2
+
3
+ #pragma clang diagnostic push
4
+ #pragma clang diagnostic ignored "-Wdocumentation"
5
+ #include "include/core/SkCanvas.h"
6
+ #include "include/core/SkColor.h"
7
+ #include "include/core/SkFont.h"
8
+ #include "include/core/SkFontTypes.h"
9
+ #include "include/core/SkPaint.h"
10
+ #include "include/core/SkPath.h"
11
+ #include "include/core/SkPathBuilder.h"
12
+ #include "include/core/SkRect.h"
13
+ #include "include/core/SkTypeface.h"
14
+ #pragma clang diagnostic pop
15
+
16
+ #include <algorithm>
17
+ #include <cmath>
18
+ #include <cstdio>
19
+ #include <cstring>
20
+
21
+ #include "chart.h"
22
+ #include "fonts.h"
23
+ #include "theme.h"
24
+ #include "viewport.h"
25
+
26
+ namespace vroom::macd_pane {
27
+
28
+ namespace {
29
+ constexpr SkColor kMacdLine = 0xff2962ff; // blue MACD line
30
+ constexpr SkColor kSignalLine = 0xffff6d00; // orange signal line
31
+ constexpr SkColor kZeroLine = 0xff484f58; // zero reference
32
+ constexpr SkColor kDivider = 0xff21262d; // pane separator
33
+ constexpr float kPadFrac = 0.85f; // keep curves off the band edges
34
+ } // namespace
35
+
36
+ void draw(SkCanvas* canvas,
37
+ const VroomChart& chart,
38
+ const Layout& lay,
39
+ const ::VroomCandle* visible,
40
+ std::size_t n,
41
+ const double* macd_visible,
42
+ const double* signal_visible,
43
+ const double* hist_visible,
44
+ int64_t window_ms,
45
+ int64_t visible_start_ms,
46
+ int64_t candle_duration_ms,
47
+ float candle_right,
48
+ float pane_top,
49
+ float pane_bottom) {
50
+ if (!canvas || n == 0 || candle_right <= 0.f) return;
51
+ const float band_h = pane_bottom - pane_top;
52
+ if (band_h <= 0.f) return;
53
+
54
+ const float mid = (pane_top + pane_bottom) * 0.5f;
55
+ const float half = band_h * 0.5f * kPadFrac;
56
+
57
+ // Auto-scale symmetric about zero across all visible finite values.
58
+ double scale = 0.0;
59
+ auto track = [&](const double* s) {
60
+ if (!s) return;
61
+ for (std::size_t i = 0; i < n; ++i) {
62
+ if (std::isfinite(s[i])) scale = std::max(scale, std::abs(s[i]));
63
+ }
64
+ };
65
+ track(macd_visible);
66
+ track(signal_visible);
67
+ track(hist_visible);
68
+
69
+ auto y_for = [&](double v) -> float {
70
+ if (scale <= 0.0) return mid;
71
+ return mid - static_cast<float>(v / scale) * half;
72
+ };
73
+
74
+ // Mask the band (candles can overflow below the shortened price pane).
75
+ SkPaint bg;
76
+ bg.setColor(chart.theme.colors[VROOM_COLOR_BACKGROUND]);
77
+ canvas->drawRect(SkRect::MakeLTRB(0.f, pane_top, candle_right, pane_bottom),
78
+ bg);
79
+
80
+ // Pane separator (top edge).
81
+ SkPaint divider;
82
+ divider.setColor(kDivider);
83
+ divider.setStrokeWidth(1.f);
84
+ canvas->drawLine(0.f, pane_top, candle_right, pane_top, divider);
85
+
86
+ canvas->save();
87
+ canvas->clipRect(SkRect::MakeLTRB(0.f, pane_top, candle_right, pane_bottom));
88
+
89
+ // Zero line.
90
+ SkPaint zero;
91
+ zero.setAntiAlias(true);
92
+ zero.setColor(kZeroLine);
93
+ zero.setStrokeWidth(1.f);
94
+ canvas->drawLine(0.f, mid, candle_right, mid, zero);
95
+
96
+ // Histogram bars: from the zero line to y_for(hist), 4-color (green above /
97
+ // red below, full alpha when accelerating, half when decelerating).
98
+ if (hist_visible) {
99
+ const float body_w =
100
+ vroom::candle_body_width(lay, window_ms, candle_duration_ms);
101
+ const float half_body = body_w * 0.5f;
102
+ const float zero_y = y_for(0.0);
103
+ const SkColor bull = chart.theme.colors[VROOM_COLOR_BULL];
104
+ const SkColor bear = chart.theme.colors[VROOM_COLOR_BEAR];
105
+ for (std::size_t i = 0; i < n; ++i) {
106
+ const double h = hist_visible[i];
107
+ if (!std::isfinite(h)) continue;
108
+ const bool have_prev = i > 0 && std::isfinite(hist_visible[i - 1]);
109
+ const double prev = have_prev ? hist_visible[i - 1] : h;
110
+ bool accelerating = true;
111
+ if (have_prev) {
112
+ accelerating = h >= 0.0 ? h >= prev : h <= prev;
113
+ }
114
+ SkPaint bar;
115
+ bar.setAntiAlias(true);
116
+ bar.setColor(h >= 0.0 ? bull : bear);
117
+ bar.setAlphaf(accelerating ? 1.0f : 0.5f);
118
+ const float cx = vroom::candle_center_x(
119
+ lay, visible[i].time_ms, candle_duration_ms, visible_start_ms,
120
+ window_ms);
121
+ const float vy = y_for(h);
122
+ const float top = std::min(zero_y, vy);
123
+ const float bot = std::max(zero_y, vy);
124
+ canvas->drawRect(
125
+ SkRect::MakeLTRB(cx - half_body, top, cx + half_body, bot), bar);
126
+ }
127
+ }
128
+
129
+ // MACD + signal lines.
130
+ auto stroke_series = [&](const double* series, SkColor color) {
131
+ if (!series) return;
132
+ SkPathBuilder path;
133
+ bool pen_down = false;
134
+ for (std::size_t i = 0; i < n; ++i) {
135
+ const double v = series[i];
136
+ if (!std::isfinite(v)) {
137
+ pen_down = false;
138
+ continue;
139
+ }
140
+ const float x = vroom::candle_center_x(
141
+ lay, visible[i].time_ms, candle_duration_ms, visible_start_ms,
142
+ window_ms);
143
+ const float y = y_for(v);
144
+ if (pen_down) {
145
+ path.lineTo(x, y);
146
+ } else {
147
+ path.moveTo(x, y);
148
+ pen_down = true;
149
+ }
150
+ }
151
+ SkPaint line;
152
+ line.setAntiAlias(true);
153
+ line.setColor(color);
154
+ line.setStyle(SkPaint::kStroke_Style);
155
+ line.setStrokeWidth(1.5f);
156
+ canvas->drawPath(path.detach(), line);
157
+ };
158
+ stroke_series(signal_visible, kSignalLine);
159
+ stroke_series(macd_visible, kMacdLine); // MACD over signal
160
+
161
+ // Caption, top-left of the pane.
162
+ auto tf = vroom::axis_typeface();
163
+ if (tf) {
164
+ SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
165
+ font.setSubpixel(true);
166
+ font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
167
+ char caption[40];
168
+ std::snprintf(caption, sizeof(caption), "MACD %d %d %d", chart.macd_fast,
169
+ chart.macd_slow, chart.macd_signal);
170
+ SkRect cb;
171
+ font.measureText(caption, std::strlen(caption), SkTextEncoding::kUTF8,
172
+ &cb);
173
+ SkPaint cap_paint;
174
+ cap_paint.setAntiAlias(true);
175
+ cap_paint.setColor(chart.theme.colors[VROOM_COLOR_AXIS_TEXT]);
176
+ canvas->drawString(caption, 6.f, pane_top + 4.f - cb.fTop, font,
177
+ cap_paint);
178
+ }
179
+ canvas->restore();
180
+
181
+ // "0" label in the y-axis strip at the zero line.
182
+ if (tf) {
183
+ SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
184
+ font.setSubpixel(true);
185
+ font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
186
+ SkPaint text_paint;
187
+ text_paint.setAntiAlias(true);
188
+ text_paint.setColor(chart.theme.colors[VROOM_COLOR_AXIS_TEXT]);
189
+ SkRect tb;
190
+ const float tw =
191
+ font.measureText("0", 1, SkTextEncoding::kUTF8, &tb);
192
+ const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
193
+ const float text_x = axis_center_x - tw * 0.5f;
194
+ const float baseline_y = mid - (tb.fTop + tb.fBottom) * 0.5f;
195
+ canvas->drawString("0", text_x, baseline_y, font, text_paint);
196
+ }
197
+ }
198
+
199
+ } // namespace vroom::macd_pane
@@ -0,0 +1,41 @@
1
+ // MACD indicator pane — histogram bars, MACD + signal lines, and a zero line,
2
+ // drawn in a band below the candles. Its own module alongside rsi_pane so the
3
+ // chart orchestrator stays thin.
4
+
5
+ #pragma once
6
+
7
+ #include <cstddef>
8
+ #include <cstdint>
9
+
10
+ #include "vroom/vroom_chart.h" // ::VroomCandle
11
+
12
+ class SkCanvas;
13
+
14
+ namespace vroom {
15
+ struct Layout;
16
+ } // namespace vroom
17
+
18
+ struct VroomChart;
19
+
20
+ namespace vroom::macd_pane {
21
+
22
+ // Draws the MACD pane spanning [pane_top, pane_bottom]. The macd/signal/hist
23
+ // series are aligned with `visible` (NaN where undefined). The vertical scale
24
+ // auto-fits the visible values symmetrically about zero; shares the candles'
25
+ // horizontal mapping (candle_center_x) so it scrolls in lock-step.
26
+ void draw(SkCanvas* canvas,
27
+ const VroomChart& chart,
28
+ const Layout& lay,
29
+ const ::VroomCandle* visible,
30
+ std::size_t n,
31
+ const double* macd_visible,
32
+ const double* signal_visible,
33
+ const double* hist_visible,
34
+ int64_t window_ms,
35
+ int64_t visible_start_ms,
36
+ int64_t candle_duration_ms,
37
+ float candle_right,
38
+ float pane_top,
39
+ float pane_bottom);
40
+
41
+ } // namespace vroom::macd_pane
@@ -0,0 +1,106 @@
1
+ #include "price_indicator.h"
2
+
3
+ #pragma clang diagnostic push
4
+ #pragma clang diagnostic ignored "-Wdocumentation"
5
+ #include "include/core/SkCanvas.h"
6
+ #include "include/core/SkColor.h"
7
+ #include "include/core/SkFont.h"
8
+ #include "include/core/SkFontTypes.h"
9
+ #include "include/core/SkPaint.h"
10
+ #include "include/core/SkRRect.h"
11
+ #include "include/core/SkRect.h"
12
+ #include "include/core/SkTypeface.h"
13
+ #include "include/effects/SkDashPathEffect.h"
14
+ #pragma clang diagnostic pop
15
+
16
+ #include <cstdio>
17
+ #include <cstring>
18
+
19
+ #include "chart.h"
20
+ #include "fonts.h"
21
+ #include "theme.h"
22
+ #include "viewport.h"
23
+
24
+ namespace vroom::price_indicator {
25
+
26
+ namespace {
27
+ constexpr SkScalar kDash[2] = {2.f, 2.f};
28
+ constexpr float kPadV = 4.f; // box padding above/below the text
29
+ constexpr float kPadH = 8.f; // box padding left/right of the text
30
+ constexpr float kCorner = 6.f; // rounded-box corner radius
31
+ } // namespace
32
+
33
+ void draw(SkCanvas* canvas,
34
+ const VroomChart& chart,
35
+ const Layout& lay,
36
+ const PriceBounds& bounds,
37
+ float candle_right,
38
+ float candle_area_h) {
39
+ if (!canvas || chart.candles.empty()) return;
40
+
41
+ // The "current price" is the latest period's close, regardless of whether
42
+ // that candle is horizontally in view.
43
+ const ::VroomCandle& last = chart.candles.back();
44
+ const bool bull = last.close >= last.open;
45
+ const SkColor color =
46
+ chart.theme.colors[bull ? VROOM_COLOR_BULL : VROOM_COLOR_BEAR];
47
+
48
+ const float y = vroom::price_to_y(lay, bounds, last.close);
49
+ if (y < 0.f || y > candle_area_h) return; // price scrolled off-range
50
+
51
+ // Dotted line from the left edge to the y-axis separator.
52
+ SkPaint line;
53
+ line.setAntiAlias(true);
54
+ line.setColor(color);
55
+ line.setStrokeWidth(1.f);
56
+ line.setPathEffect(SkDashPathEffect::Make(kDash, 0.f));
57
+ canvas->drawLine(0.f, y, candle_right, y, line);
58
+
59
+ // Price box in the y-axis strip. Needs the axis typeface; if it isn't
60
+ // loaded yet, the line alone still conveys the level.
61
+ auto tf = vroom::axis_typeface();
62
+ if (!tf) return;
63
+
64
+ SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
65
+ font.setSubpixel(true);
66
+ font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
67
+
68
+ char buf[32];
69
+ std::snprintf(buf, sizeof(buf), "%.2f", last.close);
70
+ const size_t len = std::strlen(buf);
71
+
72
+ // Measure the tight glyph bounds (origin at the baseline) so we can center
73
+ // the actual rendered digits on `y` — robust to fonts whose cap-height
74
+ // metric doesn't match the digit extent.
75
+ SkRect tb;
76
+ const float text_w = font.measureText(buf, len, SkTextEncoding::kUTF8, &tb);
77
+ const float glyph_h = tb.height();
78
+
79
+ // Box wraps the digits (width follows the text), centered on the y-axis
80
+ // container's center so its text shares the same column as the y-axis
81
+ // labels, and vertically centered on the price y.
82
+ const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
83
+ const float box_h = glyph_h + 2.f * kPadV;
84
+ const float box_w = text_w + 2.f * kPadH;
85
+ const float box_left = axis_center_x - box_w * 0.5f;
86
+ const float top = y - box_h * 0.5f;
87
+ const SkRect rect =
88
+ SkRect::MakeLTRB(box_left, top, box_left + box_w, top + box_h);
89
+
90
+ SkPaint box;
91
+ box.setAntiAlias(true);
92
+ box.setColor(color);
93
+ canvas->drawRRect(SkRRect::MakeRectXY(rect, kCorner, kCorner), box);
94
+
95
+ // White price text, horizontally centered on the same axis center, and
96
+ // vertically centered: shift the baseline so the glyph bounds' midpoint
97
+ // lands on `y` (= the box center and the dotted line).
98
+ SkPaint text_paint;
99
+ text_paint.setAntiAlias(true);
100
+ text_paint.setColor(SK_ColorWHITE);
101
+ const float text_x = axis_center_x - text_w * 0.5f;
102
+ const float baseline_y = y - (tb.fTop + tb.fBottom) * 0.5f;
103
+ canvas->drawString(buf, text_x, baseline_y, font, text_paint);
104
+ }
105
+
106
+ } // namespace vroom::price_indicator
@@ -0,0 +1,29 @@
1
+ // Current-price indicator — a dotted horizontal line at the latest candle's
2
+ // close, spanning the candle area and ending in a rounded price tag in the
3
+ // y-axis strip. Red/green by whether the latest period closed up or down. Kept
4
+ // in its own module alongside candles / labels / crosshair so the chart
5
+ // orchestrator stays thin.
6
+
7
+ #pragma once
8
+
9
+ class SkCanvas;
10
+ struct VroomChart;
11
+
12
+ namespace vroom {
13
+ struct Layout;
14
+ struct PriceBounds;
15
+ } // namespace vroom
16
+
17
+ namespace vroom::price_indicator {
18
+
19
+ // Draws the line across [0, candle_right] at the latest close's y, plus the
20
+ // price box in the y-axis strip. `candle_area_h` is the y of the x-axis
21
+ // separator; the indicator is skipped if the close maps outside [0, candle_area_h].
22
+ void draw(SkCanvas* canvas,
23
+ const VroomChart& chart,
24
+ const Layout& lay,
25
+ const PriceBounds& bounds,
26
+ float candle_right,
27
+ float candle_area_h);
28
+
29
+ } // namespace vroom::price_indicator
@@ -0,0 +1,70 @@
1
+ #include "rsi.h"
2
+
3
+ #include <cmath> // std::nan, std::isfinite
4
+
5
+ namespace vroom::rsi {
6
+
7
+ namespace {
8
+ // Standard rule: avgLoss == 0 → 100 (no downside). When avgGain == 0 and
9
+ // avgLoss > 0, RS = 0 → RSI = 0 falls out naturally.
10
+ double rsi_from(double avg_gain, double avg_loss) {
11
+ if (avg_loss == 0.0) return 100.0;
12
+ const double rs = avg_gain / avg_loss;
13
+ return 100.0 - 100.0 / (1.0 + rs);
14
+ }
15
+ } // namespace
16
+
17
+ void compute(const ::VroomCandle* candles, std::size_t n, int period,
18
+ std::vector<double>& out) {
19
+ out.assign(n, std::nan(""));
20
+ if (!candles || period < 2) return;
21
+ const std::size_t P = static_cast<std::size_t>(period);
22
+ if (n <= P) return; // need `period` deltas to seed; first value at index P
23
+
24
+ // Seed: simple average of the first P gains/losses (deltas 1..P).
25
+ double avg_gain = 0.0;
26
+ double avg_loss = 0.0;
27
+ for (std::size_t i = 1; i <= P; ++i) {
28
+ const double d = candles[i].close - candles[i - 1].close;
29
+ if (d >= 0.0) avg_gain += d;
30
+ else avg_loss += -d;
31
+ }
32
+ avg_gain /= static_cast<double>(P);
33
+ avg_loss /= static_cast<double>(P);
34
+ out[P] = rsi_from(avg_gain, avg_loss);
35
+
36
+ // Wilder smoothing for the remaining candles.
37
+ const double pm1 = static_cast<double>(P - 1);
38
+ const double pd = static_cast<double>(P);
39
+ for (std::size_t i = P + 1; i < n; ++i) {
40
+ const double d = candles[i].close - candles[i - 1].close;
41
+ const double gain = d > 0.0 ? d : 0.0;
42
+ const double loss = d < 0.0 ? -d : 0.0;
43
+ avg_gain = (avg_gain * pm1 + gain) / pd;
44
+ avg_loss = (avg_loss * pm1 + loss) / pd;
45
+ out[i] = rsi_from(avg_gain, avg_loss);
46
+ }
47
+ }
48
+
49
+ void compute_ma(const std::vector<double>& rsi, int ma_period,
50
+ std::vector<double>& out) {
51
+ const std::size_t n = rsi.size();
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
+ }
68
+ }
69
+
70
+ } // namespace vroom::rsi
@@ -0,0 +1,32 @@
1
+ // Wilder's RSI — pure computation, no Skia. Kept Skia-free so it builds into
2
+ // the unit-test target.
3
+ //
4
+ // RSI = 100 - 100/(1+RS), RS = avgGain/avgLoss over `period` close-to-close
5
+ // changes. Seed = simple average of the first `period` gains/losses; subsequent
6
+ // values use Wilder smoothing avg = (prevAvg*(period-1) + current)/period.
7
+
8
+ #pragma once
9
+
10
+ #include <cstddef>
11
+ #include <vector>
12
+
13
+ #include "vroom/vroom_chart.h" // ::VroomCandle
14
+
15
+ namespace vroom::rsi {
16
+
17
+ // Computes RSI over the closes of [candles, candles+n). `period` is clamped to
18
+ // >= 2. Fills `out` (resized to n): out[i] is RSI in [0,100] at candle i, or
19
+ // NaN for i < period and for every index when n <= period (insufficient data).
20
+ // The first defined value is at index == period (the (period+1)-th candle).
21
+ void compute(const ::VroomCandle* candles, std::size_t n, int period,
22
+ std::vector<double>& out);
23
+
24
+ // Simple moving average of an RSI series (the "RSI-based MA" / trendline that
25
+ // most RSI indicators overlay; crossovers of RSI vs. this line are the common
26
+ // signal). `ma_period` is clamped to >= 1. Fills `out` (resized to rsi.size()):
27
+ // out[i] is the mean of rsi[i-ma_period+1 .. i] when all those are finite, else
28
+ // NaN (so it's only defined once `ma_period` valid RSI values exist).
29
+ void compute_ma(const std::vector<double>& rsi, int ma_period,
30
+ std::vector<double>& out);
31
+
32
+ } // namespace vroom::rsi