react-native-vroom-chart 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/cpp/VroomChartHostObject.cpp +252 -1
  2. package/cpp/_core_include/vroom/vroom_chart.h +178 -4
  3. package/cpp/_core_src/atr.cpp +67 -0
  4. package/cpp/_core_src/atr.h +44 -0
  5. package/cpp/_core_src/atr_pane.cpp +162 -0
  6. package/cpp/_core_src/atr_pane.h +48 -0
  7. package/cpp/_core_src/candles.cpp +15 -14
  8. package/cpp/_core_src/chart.cpp +758 -200
  9. package/cpp/_core_src/chart.h +221 -3
  10. package/cpp/_core_src/chart_facade.cpp +236 -23
  11. package/cpp/_core_src/color_lerp.h +28 -0
  12. package/cpp/_core_src/fair_value_gaps.cpp +76 -0
  13. package/cpp/_core_src/fair_value_gaps.h +54 -0
  14. package/cpp/_core_src/fvg_overlay.cpp +262 -0
  15. package/cpp/_core_src/fvg_overlay.h +43 -0
  16. package/cpp/_core_src/ichimoku.cpp +65 -0
  17. package/cpp/_core_src/ichimoku.h +45 -0
  18. package/cpp/_core_src/labels.cpp +3 -0
  19. package/cpp/_core_src/line_morph.h +159 -0
  20. package/cpp/_core_src/loading_line.cpp +183 -0
  21. package/cpp/_core_src/loading_line.h +38 -0
  22. package/cpp/_core_src/loading_wave.h +140 -0
  23. package/cpp/_core_src/ma_overlay.cpp +273 -45
  24. package/cpp/_core_src/ma_overlay.h +64 -2
  25. package/cpp/_core_src/macd.cpp +24 -1
  26. package/cpp/_core_src/macd.h +16 -0
  27. package/cpp/_core_src/macd_pane.cpp +71 -62
  28. package/cpp/_core_src/macd_pane.h +13 -1
  29. package/cpp/_core_src/pane_series.h +169 -0
  30. package/cpp/_core_src/price_indicator.cpp +21 -7
  31. package/cpp/_core_src/price_indicator.h +11 -1
  32. package/cpp/_core_src/price_indicator_anim.h +81 -0
  33. package/cpp/_core_src/rsi.cpp +4 -0
  34. package/cpp/_core_src/rsi.h +7 -0
  35. package/cpp/_core_src/rsi_pane.cpp +85 -29
  36. package/cpp/_core_src/rsi_pane.h +11 -1
  37. package/cpp/_core_src/theme.cpp +5 -0
  38. package/cpp/_core_src/tip_geometry.h +55 -0
  39. package/cpp/_core_src/viewport.h +68 -0
  40. package/lib/index.d.mts +281 -3
  41. package/lib/index.d.ts +281 -3
  42. package/lib/index.js +292 -14
  43. package/lib/index.js.map +1 -1
  44. package/lib/index.mjs +292 -14
  45. package/lib/index.mjs.map +1 -1
  46. package/package.json +1 -1
  47. package/src/VroomChart.tsx +33 -3
  48. package/src/dataTransitions.ts +47 -0
  49. package/src/index.ts +5 -0
  50. package/src/jsi.d.ts +139 -1
  51. package/src/theme.ts +1 -0
  52. package/src/types.ts +5 -0
  53. package/src/useChartCore.ts +402 -8
@@ -57,6 +57,11 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
57
57
  out.push_back(jsi::PropNameID::forAscii(rt, "getVisiblePriceEnvelope"));
58
58
  out.push_back(jsi::PropNameID::forAscii(rt, "preservePriceEnvelope"));
59
59
  out.push_back(jsi::PropNameID::forAscii(rt, "beginIntervalMorph"));
60
+ out.push_back(jsi::PropNameID::forAscii(rt, "beginStreamMorph"));
61
+ out.push_back(jsi::PropNameID::forAscii(rt, "setLoading"));
62
+ out.push_back(jsi::PropNameID::forAscii(rt, "beginLoadingMorph"));
63
+ out.push_back(jsi::PropNameID::forAscii(rt, "setLoadingMorph"));
64
+ out.push_back(jsi::PropNameID::forAscii(rt, "beginLoadingReveal"));
60
65
  out.push_back(jsi::PropNameID::forAscii(rt, "setIntervalMorph"));
61
66
  out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
62
67
  out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
@@ -71,9 +76,12 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
71
76
  out.push_back(jsi::PropNameID::forAscii(rt, "getCrosshairInfo"));
72
77
  out.push_back(jsi::PropNameID::forAscii(rt, "setRSI"));
73
78
  out.push_back(jsi::PropNameID::forAscii(rt, "setMACD"));
79
+ out.push_back(jsi::PropNameID::forAscii(rt, "setATR"));
74
80
  out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
75
81
  out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
76
82
  out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
83
+ out.push_back(jsi::PropNameID::forAscii(rt, "setIchimoku"));
84
+ out.push_back(jsi::PropNameID::forAscii(rt, "setFairValueGaps"));
77
85
  out.push_back(jsi::PropNameID::forAscii(rt, "setVolume"));
78
86
  out.push_back(jsi::PropNameID::forAscii(rt, "setVolumeCollapse"));
79
87
  out.push_back(jsi::PropNameID::forAscii(rt, "setAxisCollapse"));
@@ -510,6 +518,101 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
510
518
  });
511
519
  }
512
520
 
521
+ if (name == "beginStreamMorph") {
522
+ // beginStreamMorph() — capture the visible geometry so the next setCandles
523
+ // can ease a live tick into place. Leaves the axes alone, and continues
524
+ // from the shape on screen when one is still animating. Call before
525
+ // setCandles.
526
+ return jsi::Function::createFromHostFunction(
527
+ rt,
528
+ jsi::PropNameID::forAscii(rt, "beginStreamMorph"),
529
+ 0,
530
+ [this](jsi::Runtime& /*rt2*/,
531
+ const jsi::Value& /*thisVal*/,
532
+ const jsi::Value* /*args*/,
533
+ size_t /*count*/) -> jsi::Value {
534
+ vroom_chart_begin_stream_morph(chart_);
535
+ return jsi::Value::undefined();
536
+ });
537
+ }
538
+
539
+ if (name == "setLoading") {
540
+ // setLoading(on, animate?) — show or hide the loading line. `on` must mean
541
+ // "loading *and* holding no data"; the core takes it at face value.
542
+ // `animate` defaults to true; pass false for reduced motion.
543
+ return jsi::Function::createFromHostFunction(
544
+ rt,
545
+ jsi::PropNameID::forAscii(rt, "setLoading"),
546
+ 2,
547
+ [this](jsi::Runtime& /*rt2*/,
548
+ const jsi::Value& /*thisVal*/,
549
+ const jsi::Value* args,
550
+ size_t count) -> jsi::Value {
551
+ if (count < 1) return jsi::Value::undefined();
552
+ const bool on = args[0].isBool() ? args[0].getBool()
553
+ : args[0].asNumber() != 0;
554
+ bool animate = true;
555
+ if (count >= 2 && !args[1].isUndefined() && !args[1].isNull()) {
556
+ animate = args[1].isBool() ? args[1].getBool()
557
+ : args[1].asNumber() != 0;
558
+ }
559
+ vroom_chart_set_loading(chart_, on ? 1 : 0, animate ? 1 : 0);
560
+ return jsi::Value::undefined();
561
+ });
562
+ }
563
+
564
+ if (name == "beginLoadingMorph") {
565
+ // beginLoadingMorph() — hand-off stage one: aim the loading line at the
566
+ // centres of the candles that just landed. Call *after* setCandles, then
567
+ // drive setLoadingMorph from 0 to 1.
568
+ return jsi::Function::createFromHostFunction(
569
+ rt,
570
+ jsi::PropNameID::forAscii(rt, "beginLoadingMorph"),
571
+ 0,
572
+ [this](jsi::Runtime& /*rt2*/,
573
+ const jsi::Value& /*thisVal*/,
574
+ const jsi::Value* /*args*/,
575
+ size_t /*count*/) -> jsi::Value {
576
+ vroom_chart_begin_loading_morph(chart_);
577
+ return jsi::Value::undefined();
578
+ });
579
+ }
580
+
581
+ if (name == "setLoadingMorph") {
582
+ // setLoadingMorph(t) — advance stage one. 0 = the frozen sine, 1 = the
583
+ // polyline through the candle centres.
584
+ return jsi::Function::createFromHostFunction(
585
+ rt,
586
+ jsi::PropNameID::forAscii(rt, "setLoadingMorph"),
587
+ 1,
588
+ [this](jsi::Runtime& /*rt2*/,
589
+ const jsi::Value& /*thisVal*/,
590
+ const jsi::Value* args,
591
+ size_t count) -> jsi::Value {
592
+ if (count < 1) return jsi::Value::undefined();
593
+ vroom_chart_set_loading_morph(
594
+ chart_, static_cast<float>(args[0].asNumber()));
595
+ return jsi::Value::undefined();
596
+ });
597
+ }
598
+
599
+ if (name == "beginLoadingReveal") {
600
+ // beginLoadingReveal() — hand-off stage two: collapse every candle onto the
601
+ // line at zero alpha so it grows outward from there. Call once stage one
602
+ // reaches 1, then drive setIntervalMorph from 0 to 1.
603
+ return jsi::Function::createFromHostFunction(
604
+ rt,
605
+ jsi::PropNameID::forAscii(rt, "beginLoadingReveal"),
606
+ 0,
607
+ [this](jsi::Runtime& /*rt2*/,
608
+ const jsi::Value& /*thisVal*/,
609
+ const jsi::Value* /*args*/,
610
+ size_t /*count*/) -> jsi::Value {
611
+ vroom_chart_begin_loading_reveal(chart_);
612
+ return jsi::Value::undefined();
613
+ });
614
+ }
615
+
513
616
  if (name == "setIntervalMorph") {
514
617
  // setIntervalMorph(t) — advance the capture toward the new candles. `t` is
515
618
  // pre-eased progress: 0 = the captured frame, 1 = settled (capture freed).
@@ -761,7 +864,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
761
864
  if (name == "setRSI") {
762
865
  // setRSI({enabled, period, upperBand, lowerBand, maPeriod, maKind,
763
866
  // maVisible, lineColor, lineWidth, lineVisible, maColor, maWidth,
764
- // bandColor, bandsVisible}) — configures the RSI pane. No render; the next
867
+ // bandColor, bandsVisible, extremeFill}) — configures the RSI pane. No
868
+ // render; the next
765
869
  // render() picks it up.
766
870
  return jsi::Function::createFromHostFunction(
767
871
  rt,
@@ -797,6 +901,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
797
901
  s.getProperty(rt2, "bandColor").asNumber());
798
902
  cfg.bands_visible =
799
903
  s.getProperty(rt2, "bandsVisible").asBool() ? 1 : 0;
904
+ cfg.extreme_fill =
905
+ s.getProperty(rt2, "extremeFill").asBool() ? 1 : 0;
800
906
  vroom_chart_set_rsi(chart_, &cfg);
801
907
  return jsi::Value::undefined();
802
908
  });
@@ -863,6 +969,34 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
863
969
  });
864
970
  }
865
971
 
972
+ if (name == "setATR") {
973
+ // setATR({enabled, period, smoothing, lineColor, lineWidth}) — configures
974
+ // the ATR pane. No render; the next render() picks it up.
975
+ return jsi::Function::createFromHostFunction(
976
+ rt,
977
+ jsi::PropNameID::forAscii(rt, "setATR"),
978
+ 1,
979
+ [this](jsi::Runtime& rt2,
980
+ const jsi::Value& /*thisVal*/,
981
+ const jsi::Value* args,
982
+ size_t count) -> jsi::Value {
983
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
984
+ auto s = args[0].asObject(rt2);
985
+ VroomATR cfg{};
986
+ cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
987
+ cfg.period = static_cast<int32_t>(
988
+ s.getProperty(rt2, "period").asNumber());
989
+ cfg.smoothing = static_cast<int32_t>(
990
+ s.getProperty(rt2, "smoothing").asNumber());
991
+ cfg.line_color = static_cast<uint32_t>(
992
+ s.getProperty(rt2, "lineColor").asNumber());
993
+ cfg.line_width = static_cast<float>(
994
+ s.getProperty(rt2, "lineWidth").asNumber());
995
+ vroom_chart_set_atr(chart_, &cfg);
996
+ return jsi::Value::undefined();
997
+ });
998
+ }
999
+
866
1000
  if (name == "setOverlays") {
867
1001
  // setOverlays([{ kind, period, source, color, width }, ...]) — replaces the
868
1002
  // full set of MA/EMA overlay lines. No render; the next render() picks it up.
@@ -972,6 +1106,123 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
972
1106
  });
973
1107
  }
974
1108
 
1109
+ if (name == "setIchimoku") {
1110
+ // setIchimoku({enabled, tenkanPeriod, kijunPeriod, senkouBPeriod,
1111
+ // displacement, <line>Color/<line>Width/<line>Enabled for tenkan, kijun,
1112
+ // senkouA, senkouB and chikou, cloudEnabled, bullishCloudColor,
1113
+ // bearishCloudColor, cloudOpacity}) — the Ichimoku overlay. No render; the
1114
+ // next render() picks it up.
1115
+ return jsi::Function::createFromHostFunction(
1116
+ rt,
1117
+ jsi::PropNameID::forAscii(rt, "setIchimoku"),
1118
+ 1,
1119
+ [this](jsi::Runtime& rt2,
1120
+ const jsi::Value& /*thisVal*/,
1121
+ const jsi::Value* args,
1122
+ size_t count) -> jsi::Value {
1123
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
1124
+ auto s = args[0].asObject(rt2);
1125
+ const auto num = [&](const char* k) {
1126
+ return s.getProperty(rt2, k).asNumber();
1127
+ };
1128
+ const auto flag = [&](const char* k) {
1129
+ return s.getProperty(rt2, k).asBool() ? 1 : 0;
1130
+ };
1131
+ VroomIchimoku cfg{};
1132
+ cfg.enabled = flag("enabled");
1133
+ cfg.tenkan_period = static_cast<int32_t>(num("tenkanPeriod"));
1134
+ cfg.kijun_period = static_cast<int32_t>(num("kijunPeriod"));
1135
+ cfg.senkou_b_period = static_cast<int32_t>(num("senkouBPeriod"));
1136
+ cfg.displacement = static_cast<int32_t>(num("displacement"));
1137
+ cfg.tenkan_color = static_cast<uint32_t>(num("tenkanColor"));
1138
+ cfg.tenkan_width = static_cast<float>(num("tenkanWidth"));
1139
+ cfg.tenkan_enabled = flag("tenkanEnabled");
1140
+ cfg.kijun_color = static_cast<uint32_t>(num("kijunColor"));
1141
+ cfg.kijun_width = static_cast<float>(num("kijunWidth"));
1142
+ cfg.kijun_enabled = flag("kijunEnabled");
1143
+ cfg.senkou_a_color = static_cast<uint32_t>(num("senkouAColor"));
1144
+ cfg.senkou_a_width = static_cast<float>(num("senkouAWidth"));
1145
+ cfg.senkou_a_enabled = flag("senkouAEnabled");
1146
+ cfg.senkou_b_color = static_cast<uint32_t>(num("senkouBColor"));
1147
+ cfg.senkou_b_width = static_cast<float>(num("senkouBWidth"));
1148
+ cfg.senkou_b_enabled = flag("senkouBEnabled");
1149
+ cfg.chikou_color = static_cast<uint32_t>(num("chikouColor"));
1150
+ cfg.chikou_width = static_cast<float>(num("chikouWidth"));
1151
+ cfg.chikou_enabled = flag("chikouEnabled");
1152
+ cfg.cloud_enabled = flag("cloudEnabled");
1153
+ cfg.bullish_cloud_color =
1154
+ static_cast<uint32_t>(num("bullishCloudColor"));
1155
+ cfg.bearish_cloud_color =
1156
+ static_cast<uint32_t>(num("bearishCloudColor"));
1157
+ cfg.cloud_opacity = static_cast<float>(num("cloudOpacity"));
1158
+ vroom_chart_set_ichimoku(chart_, &cfg);
1159
+ return jsi::Value::undefined();
1160
+ });
1161
+ }
1162
+
1163
+ if (name == "setFairValueGaps") {
1164
+ // setFairValueGaps({enabled, maxBarsBack, waitForClose, fillType,
1165
+ // deleteAfterFill, extendBoxes, boxLength, bullishColor, bearishColor,
1166
+ // opacity, borderEnabled, borderStyle, borderWidth, bullishBorderColor,
1167
+ // bearishBorderColor, labelsEnabled, label, labelDistance, labelColor,
1168
+ // labelFontSize, showInverse, inverseBullishColor, inverseBearishColor,
1169
+ // inverseLabel}) — the Fair Value Gap overlay. No render; the next
1170
+ // render() picks it up.
1171
+ return jsi::Function::createFromHostFunction(
1172
+ rt,
1173
+ jsi::PropNameID::forAscii(rt, "setFairValueGaps"),
1174
+ 1,
1175
+ [this](jsi::Runtime& rt2,
1176
+ const jsi::Value& /*thisVal*/,
1177
+ const jsi::Value* args,
1178
+ size_t count) -> jsi::Value {
1179
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
1180
+ auto s = args[0].asObject(rt2);
1181
+ const auto num = [&](const char* k) {
1182
+ return s.getProperty(rt2, k).asNumber();
1183
+ };
1184
+ const auto flag = [&](const char* k) {
1185
+ return s.getProperty(rt2, k).asBool() ? 1 : 0;
1186
+ };
1187
+ VroomFairValueGaps cfg{};
1188
+ cfg.enabled = flag("enabled");
1189
+ cfg.max_bars_back = static_cast<int32_t>(num("maxBarsBack"));
1190
+ cfg.wait_for_close = flag("waitForClose");
1191
+ cfg.fill_type = static_cast<int32_t>(num("fillType"));
1192
+ cfg.delete_after_fill = flag("deleteAfterFill");
1193
+ cfg.extend_boxes = flag("extendBoxes");
1194
+ cfg.box_length = static_cast<int32_t>(num("boxLength"));
1195
+ cfg.bullish_color = static_cast<uint32_t>(num("bullishColor"));
1196
+ cfg.bearish_color = static_cast<uint32_t>(num("bearishColor"));
1197
+ cfg.opacity = static_cast<float>(num("opacity"));
1198
+ cfg.border_enabled = flag("borderEnabled");
1199
+ cfg.border_style = static_cast<int32_t>(num("borderStyle"));
1200
+ cfg.border_width = static_cast<float>(num("borderWidth"));
1201
+ cfg.bullish_border_color =
1202
+ static_cast<uint32_t>(num("bullishBorderColor"));
1203
+ cfg.bearish_border_color =
1204
+ static_cast<uint32_t>(num("bearishBorderColor"));
1205
+ cfg.labels_enabled = flag("labelsEnabled");
1206
+ // Held alive until the setter, which copies them.
1207
+ const std::string label =
1208
+ s.getProperty(rt2, "label").asString(rt2).utf8(rt2);
1209
+ cfg.label = label.c_str();
1210
+ cfg.label_distance = static_cast<int32_t>(num("labelDistance"));
1211
+ cfg.label_color = static_cast<uint32_t>(num("labelColor"));
1212
+ cfg.label_font_size = static_cast<float>(num("labelFontSize"));
1213
+ cfg.show_inverse = flag("showInverse");
1214
+ cfg.inverse_bullish_color =
1215
+ static_cast<uint32_t>(num("inverseBullishColor"));
1216
+ cfg.inverse_bearish_color =
1217
+ static_cast<uint32_t>(num("inverseBearishColor"));
1218
+ const std::string inverse_label =
1219
+ s.getProperty(rt2, "inverseLabel").asString(rt2).utf8(rt2);
1220
+ cfg.inverse_label = inverse_label.c_str();
1221
+ vroom_chart_set_fair_value_gaps(chart_, &cfg);
1222
+ return jsi::Value::undefined();
1223
+ });
1224
+ }
1225
+
975
1226
  if (name == "setVolume") {
976
1227
  // setVolume({enabled, heightFrac, opacity, radiusPx, upColor, downColor})
977
1228
  // — the volume bars. Negative floats / zero colors inherit the theme. No
@@ -69,6 +69,80 @@ typedef struct VroomBollinger {
69
69
  float fill_opacity; // 0..1, multiplied into upper_color's alpha
70
70
  } VroomBollinger;
71
71
 
72
+ // Ichimoku Kinko Hyo overlay drawn on the price pane: five lines plus the cloud
73
+ // (kumo) shaded between the two leading spans. No pane is reserved.
74
+ //
75
+ // Three of the lines are drawn away from the bar they were computed on:
76
+ // senkou A/B lead by `displacement` slots (so the cloud runs past the newest
77
+ // candle into empty time) and chikou lags by the same. Displacement is applied
78
+ // at draw time, so changing it re-renders without recomputing the series.
79
+ typedef struct VroomIchimoku {
80
+ int32_t enabled; // 0/1
81
+ int32_t tenkan_period; // conversion lookback (clamped >= 1; default 9)
82
+ int32_t kijun_period; // base lookback (clamped >= 1; default 26)
83
+ int32_t senkou_b_period; // span B lookback (clamped >= 1; default 52)
84
+ int32_t displacement; // slots the cloud leads / chikou lags (>= 0; default 26)
85
+ uint32_t tenkan_color; // 0xAARRGGBB
86
+ float tenkan_width; // stroke px
87
+ int32_t tenkan_enabled; // 0/1
88
+ uint32_t kijun_color;
89
+ float kijun_width;
90
+ int32_t kijun_enabled;
91
+ uint32_t senkou_a_color;
92
+ float senkou_a_width;
93
+ int32_t senkou_a_enabled;
94
+ uint32_t senkou_b_color;
95
+ float senkou_b_width;
96
+ int32_t senkou_b_enabled;
97
+ uint32_t chikou_color;
98
+ float chikou_width;
99
+ int32_t chikou_enabled;
100
+ int32_t cloud_enabled; // 0/1: shade between the leading spans
101
+ uint32_t bullish_cloud_color; // where senkou A is above senkou B
102
+ uint32_t bearish_cloud_color; // where senkou A is below senkou B
103
+ float cloud_opacity; // 0..1, multiplied into the cloud color's alpha
104
+ } VroomIchimoku;
105
+
106
+ // Fair Value Gap overlay drawn on the price pane: shaded boxes over three-candle
107
+ // imbalances, where candle i-1 and i+1's wicks fail to overlap and leave a band
108
+ // of price that was skipped. Bullish when candles[i-1].high < candles[i+1].low,
109
+ // bearish when candles[i-1].low > candles[i+1].high. No pane is reserved.
110
+ //
111
+ // Each box is anchored to the middle bar's open time and spans the untouched
112
+ // price range; it runs `box_length` slots right, or to the pane edge when
113
+ // `extend_boxes`. Only enabled, max_bars_back, wait_for_close and fill_type
114
+ // recompute — geometry and style are applied at draw time.
115
+ //
116
+ // With `show_inverse`, a filled gap draws a second box of the opposite polarity
117
+ // starting where the first one ends, lasting until price reclaims the band.
118
+ typedef struct VroomFairValueGaps {
119
+ int32_t enabled; // 0/1
120
+ int32_t max_bars_back; // bars to scan (clamped >= 0; default 300)
121
+ int32_t wait_for_close; // 0/1: withhold a gap until its 3rd bar closes
122
+ int32_t fill_type; // 0 = close through the far edge, 1 = wick
123
+ int32_t delete_after_fill; // 0/1: hide a gap once filled (else truncate it)
124
+ int32_t extend_boxes; // 0/1: run boxes to the pane edge
125
+ int32_t box_length; // box width in slots (clamped >= 1; default 20)
126
+ uint32_t bullish_color; // 0xAARRGGBB fill where the gap is bullish
127
+ uint32_t bearish_color; // fill where the gap is bearish
128
+ float opacity; // 0..1, multiplied into the fill color's alpha
129
+ int32_t border_enabled; // 0/1: stroke the box outline
130
+ int32_t border_style; // 0 = solid, 1 = dotted, 2 = dashed
131
+ float border_width; // stroke px
132
+ uint32_t bullish_border_color;
133
+ uint32_t bearish_border_color;
134
+ int32_t labels_enabled; // 0/1: draw `label` on each box
135
+ const char* label; // UTF-8, copied by the setter; NULL = "FVG"
136
+ int32_t label_distance; // slots of clearance, extend_boxes only (>= 0)
137
+ uint32_t label_color; // alpha 0 falls back to the box's border color
138
+ float label_font_size; // px; <= 0 falls back to the axis font size
139
+ int32_t show_inverse; // 0/1: keep drawing a filled gap, polarity flipped
140
+ uint32_t inverse_bullish_color; // fill where the inverted zone is bullish,
141
+ // i.e. where a bearish gap was violated
142
+ uint32_t inverse_bearish_color; // fill where a bullish gap was violated
143
+ const char* inverse_label; // UTF-8, copied by the setter; NULL = "iFVG"
144
+ } VroomFairValueGaps;
145
+
72
146
  // MACD, drawn in its own pane below the candles: the difference between a fast
73
147
  // and a slow moving average of `source`, a signal line smoothing that
74
148
  // difference, and a histogram of the gap between the two.
@@ -128,8 +202,29 @@ typedef struct VroomRSI {
128
202
  float ma_width;
129
203
  uint32_t band_color; // both dashed rules; 0 inherits the default gray
130
204
  int32_t bands_visible; // 0/1
205
+ // Shade where the line sits past a band, fading out at the rule and
206
+ // deepening toward the end of the scale. Takes its colors from
207
+ // VROOM_COLOR_ACCENT_BULL / _BEAR, and never reaches full opacity. 0/1.
208
+ int32_t extreme_fill;
131
209
  } VroomRSI;
132
210
 
211
+ // Average True Range, drawn in its own pane below the candles: a single line
212
+ // measuring volatility in price units. True Range is the widest of the bar's
213
+ // own high-low span and the two gaps from its extremes to the previous close,
214
+ // smoothed over `period` bars.
215
+ //
216
+ // The style fields carry the same inherit sentinel as VroomMACD: a fully
217
+ // transparent color falls back to the built-in default and a non-positive width
218
+ // falls back to 1.5.
219
+ typedef struct VroomATR {
220
+ int32_t enabled; // 0/1
221
+ int32_t period; // lookback in candles (clamped >= 1; default 14)
222
+ int32_t smoothing; // 0 = Wilder's RMA, 1 = SMA, 2 = EMA
223
+
224
+ uint32_t line_color; // 0 inherits the default teal
225
+ float line_width; // stroke px; <= 0 inherits 1.5
226
+ } VroomATR;
227
+
133
228
  // Session VWAP, drawn as a single line on the price pane. The session resets
134
229
  // each UTC day shifted by `reset_offset_min` minutes, and the line lifts its pen
135
230
  // at each reset. Color 0 inherits the default cyan; a non-positive width
@@ -343,6 +438,9 @@ typedef enum {
343
438
  VROOM_COLOR_ACCENT_BULL, // generic up color: price indicator, volume, MACD
344
439
  VROOM_COLOR_ACCENT_BEAR, // generic down color
345
440
  VROOM_COLOR_LINE, // line-chart-mode close-price polyline
441
+ // The loading line. Its own alpha is honored, then scaled by the fade-in
442
+ // and the stage-3 fade-out, so an opaque color here is the usual choice.
443
+ VROOM_COLOR_SKELETON,
346
444
  VROOM_COLOR_COUNT_
347
445
  } VroomColorKey;
348
446
 
@@ -475,10 +573,64 @@ void vroom_chart_preserve_price_envelope(VroomChart* chart,
475
573
  // to 1. No-op when nothing is visible.
476
574
  void vroom_chart_begin_interval_morph(VroomChart* chart, int32_t mode);
477
575
 
478
- // Advances the interval morph started by vroom_chart_begin_interval_morph. `t`
479
- // (clamped to 0..1) is the eased progress: 0 renders the captured geometry
480
- // pixel-identically to the pre-swap frame, 1 renders the new candles and
481
- // releases the capture. Driven per-frame by the host animation loop.
576
+ // Captures the same geometry for a live update to the series already shown —
577
+ // a tick to the in-progress bar. Always a transform, and unlike the interval
578
+ // morph it leaves the axes alone: the interval hasn't changed, so the ticks
579
+ // between the labels are still the right ones and must not be faded.
580
+ //
581
+ // Restarting one that is still running continues from the shape on screen
582
+ // rather than from the data under it, so ticks arriving faster than the
583
+ // animation lands stay smooth instead of snapping back each time.
584
+ //
585
+ // Call before set_candles, then drive vroom_chart_set_interval_morph from 0 to
586
+ // 1. No-op when nothing is visible. Not for an update that appends a bar: slots
587
+ // pair from the right edge, so a new bar would shift every candle onto its
588
+ // neighbour's geometry — advance the visible range instead and let the series
589
+ // translate.
590
+ void vroom_chart_begin_stream_morph(VroomChart* chart);
591
+
592
+ // Shows or hides the loading line: a single stroke drawn across the plot in a
593
+ // slow travelling sine, for a chart that has been laid out but has no data yet.
594
+ // Suppresses the axis text, price badge, crosshair and indicator panes for as
595
+ // long as it's up.
596
+ //
597
+ // `on` must mean "loading *and* holding no data for the series being shown";
598
+ // the core takes it at face value rather than checking the candle buffer,
599
+ // because a host mid-asset-switch can still be holding the previous asset's
600
+ // bars. Pass `animate` 0 for reduced motion: the line draws still, and the
601
+ // chart is allowed to go idle instead of pinning a redraw loop.
602
+ //
603
+ // Passing 0 abandons any hand-off in flight, which is what a failed or
604
+ // superseded fetch wants. To hand off to data instead, use the two stages
605
+ // below.
606
+ void vroom_chart_set_loading(VroomChart* chart, int32_t on, int32_t animate);
607
+
608
+ // Hand-off stage one: reshapes the line into the series. Freezes the sine where
609
+ // it is and aims each vertex at the vertical centre of the candle that will
610
+ // occupy its column, so the line resolves into the silhouette of the data.
611
+ //
612
+ // Call *after* set_candles — it reads them to know where to aim — then drive
613
+ // vroom_chart_set_loading_morph from 0 to 1. The axes and the candles stay
614
+ // suppressed throughout; stage two brings them in. No-op if the line isn't up.
615
+ void vroom_chart_begin_loading_morph(VroomChart* chart);
616
+
617
+ // Advances stage one. `t` (clamped to 0..1) is the eased progress: 0 renders the
618
+ // frozen sine, 1 the polyline through the candle centres.
619
+ void vroom_chart_set_loading_morph(VroomChart* chart, float t);
620
+
621
+ // Hand-off stage two: the candles take over. Captures each one collapsed onto
622
+ // its own vertical centre at zero alpha and leaves the loading state, so the
623
+ // bars grow outward from the line and their color fades up as it fades out.
624
+ //
625
+ // Call once stage one has reached 1, then drive vroom_chart_set_interval_morph
626
+ // from 0 to 1 as usual — the line's fade-out rides that same clock, so the two
627
+ // finish together. No-op if the line isn't up.
628
+ void vroom_chart_begin_loading_reveal(VroomChart* chart);
629
+
630
+ // Advances the morph started by either begin_*_morph above. `t` (clamped to
631
+ // 0..1) is the eased progress: 0 renders the captured geometry pixel-identically
632
+ // to the pre-swap frame, 1 renders the new candles and releases the capture.
633
+ // Driven per-frame by the host animation loop.
482
634
  void vroom_chart_set_interval_morph(VroomChart* chart, float t);
483
635
 
484
636
  void vroom_chart_pan(VroomChart* chart, float dx_px, float dy_px);
@@ -581,6 +733,11 @@ void vroom_chart_set_rsi(VroomChart* chart, const VroomRSI* cfg);
581
733
  // re-render.
582
734
  void vroom_chart_set_macd(VroomChart* chart, const VroomMACD* cfg);
583
735
 
736
+ // Configures the ATR indicator (its own pane below the candles, stacking in
737
+ // enable order like RSI and MACD). Period and smoothing changes recompute the
738
+ // series; color and width changes only re-render.
739
+ void vroom_chart_set_atr(VroomChart* chart, const VroomATR* cfg);
740
+
584
741
  // Replaces the full set of moving-average overlay lines (SMA/EMA) drawn on the
585
742
  // price pane. Pass count 0 to clear them. Overlays don't reserve a pane.
586
743
  void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
@@ -596,6 +753,23 @@ void vroom_chart_set_vwap(VroomChart* chart, const VroomVWAP* cfg);
596
753
  // changes only re-render; enabled/period/mult/source/basis changes recompute.
597
754
  void vroom_chart_set_bollinger(VroomChart* chart, const VroomBollinger* cfg);
598
755
 
756
+ // Configures the Ichimoku overlay (five price-pane lines + the cloud between
757
+ // the leading spans; no pane is reserved). Only enabled and the three periods
758
+ // recompute; style, visibility and displacement changes just re-render.
759
+ //
760
+ // Enabling it also pulls the view forward so the leading spans, which sit past
761
+ // the newest candle, are on screen — the same future gap the default framing
762
+ // reserves when the indicator is already on at set_candles time.
763
+ void vroom_chart_set_ichimoku(VroomChart* chart, const VroomIchimoku* cfg);
764
+
765
+ // Configures the Fair Value Gap overlay (shaded imbalance boxes on the price
766
+ // pane; no pane is reserved). Only enabled, max_bars_back, wait_for_close and
767
+ // fill_type recompute; box geometry, style and labels just re-render.
768
+ //
769
+ // `cfg->label` is copied, so the caller may free it as soon as this returns.
770
+ void vroom_chart_set_fair_value_gaps(VroomChart* chart,
771
+ const VroomFairValueGaps* cfg);
772
+
599
773
  // Configures the volume bars. Render-only — the bars come straight off each
600
774
  // candle's volume, so nothing is recomputed. Bars are enabled by default; pass
601
775
  // a config with `enabled` 0 to hide them.
@@ -0,0 +1,67 @@
1
+ #include "atr.h"
2
+
3
+ #include <algorithm> // std::max
4
+ #include <cmath> // std::fabs, std::nan
5
+
6
+ #include "ma.h"
7
+ #include "series_ma.h"
8
+
9
+ namespace vroom::atr {
10
+
11
+ void compute(const ::VroomCandle* candles, std::size_t n, int period,
12
+ int smoothing, std::vector<double>& out) {
13
+ out.assign(n, std::nan(""));
14
+ if (!candles || period < 1) return;
15
+ const std::size_t P = static_cast<std::size_t>(period);
16
+ if (n < P) return;
17
+
18
+ std::vector<double> tr(n);
19
+ tr[0] = candles[0].high - candles[0].low; // no previous close to gap from
20
+ for (std::size_t i = 1; i < n; ++i) {
21
+ const double prev_close = candles[i - 1].close;
22
+ tr[i] = std::max({candles[i].high - candles[i].low,
23
+ std::fabs(candles[i].high - prev_close),
24
+ std::fabs(candles[i].low - prev_close)});
25
+ }
26
+
27
+ if (smoothing == kSma) {
28
+ vroom::series_ma::smooth(tr, vroom::ma::KIND_SMA, period, out);
29
+ return;
30
+ }
31
+ if (smoothing == kEma) {
32
+ vroom::series_ma::smooth(tr, vroom::ma::KIND_EMA, period, out);
33
+ return;
34
+ }
35
+
36
+ // Wilder's RMA: seeded with the simple average of the first P true ranges,
37
+ // then avg = (prevAvg * (P - 1) + current) / P.
38
+ double avg = 0.0;
39
+ for (std::size_t i = 0; i < P; ++i) avg += tr[i];
40
+ avg /= static_cast<double>(P);
41
+ out[P - 1] = avg;
42
+
43
+ const double pm1 = static_cast<double>(P - 1);
44
+ const double pd = static_cast<double>(P);
45
+ for (std::size_t i = P; i < n; ++i) {
46
+ avg = (avg * pm1 + tr[i]) / pd;
47
+ out[i] = avg;
48
+ }
49
+ }
50
+
51
+ double autoscale(const double* visible, std::size_t n) {
52
+ double scale = 0.0;
53
+ if (!visible) return scale;
54
+ for (std::size_t i = 0; i < n; ++i) {
55
+ if (std::isfinite(visible[i])) scale = std::max(scale, visible[i]);
56
+ }
57
+ return scale;
58
+ }
59
+
60
+ double band_fraction(double v, double scale, double y_scale) {
61
+ // Nothing on show yet — everything sits on the baseline rather than
62
+ // dividing by zero.
63
+ if (!(scale > 0.0)) return 0.0;
64
+ return (v / scale) * kBandPadFraction * y_scale;
65
+ }
66
+
67
+ } // namespace vroom::atr
@@ -0,0 +1,44 @@
1
+ // Average True Range — pure computation, no Skia. Kept Skia-free so it builds
2
+ // into the unit-test target.
3
+ //
4
+ // True Range is the widest of the bar's own high-low span and the two gaps from
5
+ // its extremes to the previous close, so an overnight jump the bar's range
6
+ // misses still counts. ATR is that series smoothed over `period` bars.
7
+
8
+ #pragma once
9
+
10
+ #include <cstddef>
11
+ #include <vector>
12
+
13
+ #include "vroom/vroom_chart.h" // ::VroomCandle
14
+
15
+ namespace vroom::atr {
16
+
17
+ // How the true-range series is smoothed. kRma is Wilder's original
18
+ // (alpha = 1/period) and the conventional default.
19
+ enum Smoothing { kRma = 0, kSma = 1, kEma = 2 };
20
+
21
+ // How much of the pane band the curve is allowed to fill, so a peak stops short
22
+ // of the separator above it instead of touching.
23
+ constexpr double kBandPadFraction = 0.85;
24
+
25
+ // Computes ATR over [candles, candles+n). `period` is clamped to >= 1.
26
+ // Fills `out` (resized to n): out[i] is the ATR at candle i in price units, or
27
+ // NaN before the first defined value. True range exists from bar 0 (which has
28
+ // no previous close, so it falls back to high - low), so the first ATR lands at
29
+ // index period - 1 — one earlier than RSI.
30
+ void compute(const ::VroomCandle* candles, std::size_t n, int period,
31
+ int smoothing, std::vector<double>& out);
32
+
33
+ // The value the pane band is fitted to: the largest finite ATR on show, or 0
34
+ // when there is nothing to plot. ATR is strictly positive, so the domain runs
35
+ // from 0 at the band's bottom edge up to this.
36
+ double autoscale(const double* visible, std::size_t n);
37
+
38
+ // Where a value sits in the pane band, as a fraction of its height — 0 at the
39
+ // bottom edge, 1 at the top. `y_scale` is the user's y-axis zoom (1 = the
40
+ // default fit). Split out of the renderer so the interval-morph capture maps
41
+ // its geometry through the same math the pane draws with.
42
+ double band_fraction(double v, double scale, double y_scale);
43
+
44
+ } // namespace vroom::atr