@luxalgo/vela 0.6.15 → 0.6.17

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 (48) hide show
  1. package/README.md +12 -1
  2. package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
  3. package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
  4. package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
  5. package/dist/chunk-FE6V2YAQ.js +1 -0
  6. package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
  7. package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
  8. package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
  9. package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
  10. package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
  11. package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
  12. package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
  13. package/dist/index.cjs +2746 -355
  14. package/dist/index.d.cts +15 -6
  15. package/dist/index.d.ts +15 -6
  16. package/dist/index.js +5 -5
  17. package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
  18. package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
  19. package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
  20. package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
  21. package/dist/plugin.cjs +1 -0
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +3 -3
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
  32. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
  33. package/dist/ui.cjs +23 -15
  34. package/dist/ui.d.cts +1 -1
  35. package/dist/ui.d.ts +1 -1
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +2600 -209
  38. package/dist/vela.global.min.js +52 -55
  39. package/dist/widget.cjs +2663 -241
  40. package/dist/widget.d.cts +6 -6
  41. package/dist/widget.d.ts +6 -6
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +2663 -241
  44. package/dist/workspace.d.cts +25 -14
  45. package/dist/workspace.d.ts +25 -14
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. package/dist/chunk-H26BEHF4.js +0 -7
package/dist/index.cjs CHANGED
@@ -25,6 +25,23 @@ function _interopNamespace(e) {
25
25
  var menu__namespace = /*#__PURE__*/_interopNamespace(menu);
26
26
  var dialog__namespace = /*#__PURE__*/_interopNamespace(dialog);
27
27
 
28
+ // src/core/options.ts
29
+ var LIVE_BAR_EASE_DEFAULT_MS = 90;
30
+ var LIVE_BAR_EASE_MAX_MS = 1e3;
31
+ function resolveLiveBarEaseMs(value) {
32
+ if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
33
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
34
+ return Math.min(value, LIVE_BAR_EASE_MAX_MS);
35
+ }
36
+ function resolveAnimations(animations) {
37
+ if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
38
+ return {
39
+ animZoom: animations?.zoom ?? true,
40
+ animPan: animations?.pan ?? true,
41
+ animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
42
+ };
43
+ }
44
+
28
45
  // src/core/model/series.ts
29
46
  function isLineLikeSeries(spec) {
30
47
  return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
@@ -135,7 +152,7 @@ var LiveSession = class {
135
152
 
136
153
  // src/core/engine/IndicatorHandleImpl.ts
137
154
  var IndicatorHandleImpl = class {
138
- constructor(id, title, controller, source) {
155
+ constructor(id, title, controller, source, nativeType) {
139
156
  this.controller = controller;
140
157
  this.schema = [];
141
158
  this.propsSchema = [];
@@ -144,6 +161,7 @@ var IndicatorHandleImpl = class {
144
161
  this.id = id;
145
162
  this.title = title;
146
163
  if (source !== void 0) this.source = source;
164
+ if (nativeType !== void 0) this.nativeType = nativeType;
147
165
  }
148
166
  get inputs() {
149
167
  return this.schema;
@@ -234,24 +252,24 @@ function summarizeModel(model) {
234
252
  }
235
253
  function inspectModels(models) {
236
254
  const indicators = models.map(summarizeModel);
237
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
255
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
238
256
  return {
239
257
  indicators,
240
258
  totals: {
241
259
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
242
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
243
- fills: sum((s) => s.fills),
244
- backgrounds: sum((s) => s.backgrounds),
245
- priceLines: sum((s) => s.priceLines),
246
- lines: sum((s) => s.lines),
247
- boxes: sum((s) => s.boxes),
248
- labels: sum((s) => s.labels),
249
- polylines: sum((s) => s.polylines),
250
- linefills: sum((s) => s.linefills),
251
- tables: sum((s) => s.tables),
252
- barColors: sum((s) => s.barColors),
253
- trades: sum((s) => s.trades),
254
- forcedOverlay: sum((s) => s.forcedOverlay)
260
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
261
+ fills: sum2((s) => s.fills),
262
+ backgrounds: sum2((s) => s.backgrounds),
263
+ priceLines: sum2((s) => s.priceLines),
264
+ lines: sum2((s) => s.lines),
265
+ boxes: sum2((s) => s.boxes),
266
+ labels: sum2((s) => s.labels),
267
+ polylines: sum2((s) => s.polylines),
268
+ linefills: sum2((s) => s.linefills),
269
+ tables: sum2((s) => s.tables),
270
+ barColors: sum2((s) => s.barColors),
271
+ trades: sum2((s) => s.trades),
272
+ forcedOverlay: sum2((s) => s.forcedOverlay)
255
273
  }
256
274
  };
257
275
  }
@@ -574,6 +592,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
574
592
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
575
593
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
576
594
  registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
595
+ registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
577
596
  registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
578
597
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
579
598
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
@@ -6731,8 +6750,8 @@ var DrawingController = class {
6731
6750
  this.port?.setExternalGhost?.(doc);
6732
6751
  }
6733
6752
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
6734
- setToolShortcuts(map) {
6735
- this.port?.setToolShortcuts?.(map);
6753
+ setToolShortcuts(map2) {
6754
+ this.port?.setToolShortcuts?.(map2);
6736
6755
  }
6737
6756
  // ── programmatic CRUD (facade-facing) ── each is one undo step
6738
6757
  add(type, init = {}) {
@@ -8456,16 +8475,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
8456
8475
  /**
8457
8476
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
8458
8477
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
8459
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
8460
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
8478
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
8479
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
8480
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
8481
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
8461
8482
  */
8462
8483
  addNativeIndicator(type, options = {}) {
8463
- const existing = this.registry.all().find((r) => r.native?.type === type);
8464
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
8465
8484
  const descriptor = getNativeIndicator(type);
8485
+ if (!descriptor?.multiInstance) {
8486
+ const existing = this.registry.all().find((r) => r.native?.type === type);
8487
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8488
+ }
8466
8489
  const id = this.registry.nextId("native");
8467
8490
  const title = descriptor?.title ?? type;
8468
- const handle = new IndicatorHandleImpl(id, title, this);
8491
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8469
8492
  this.handles.set(id, handle);
8470
8493
  if (!descriptor) {
8471
8494
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -8477,16 +8500,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
8477
8500
  void this.startNativeIndicator(id, handle);
8478
8501
  return handle;
8479
8502
  }
8480
- /**
8481
- * The catalog of registered native-indicator types with their live state on THIS chart: each
8482
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
8483
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
8484
- * because a descriptor's `isSupported` may probe the provider for a required capability.
8485
- */
8486
- /** The native types present on the chart — SYNC (registry state; no support probe). */
8503
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
8504
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
8487
8505
  presentNativeIndicators() {
8488
8506
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
8489
8507
  }
8508
+ /**
8509
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
8510
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
8511
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
8512
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
8513
+ * probe the provider for a required capability.
8514
+ */
8490
8515
  async availableNativeIndicators() {
8491
8516
  const symbol = this.qualifiedSymbol();
8492
8517
  const present = new Set(this.presentNativeIndicators());
@@ -8496,7 +8521,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
8496
8521
  title: d.title,
8497
8522
  supported: await this.isNativeSupported(d, symbol),
8498
8523
  present: present.has(d.type),
8499
- beta: d.beta
8524
+ beta: d.beta,
8525
+ ...d.multiInstance ? { multiInstance: true } : {}
8500
8526
  }))
8501
8527
  );
8502
8528
  }
@@ -9780,7 +9806,7 @@ function attachForming(closed, forming) {
9780
9806
 
9781
9807
  // src/data/MultiProviderFeed.ts
9782
9808
  function intervalMs(timeframe) {
9783
- const map = {
9809
+ const map2 = {
9784
9810
  "1m": 6e4,
9785
9811
  "3m": 18e4,
9786
9812
  "5m": 3e5,
@@ -9802,7 +9828,7 @@ function intervalMs(timeframe) {
9802
9828
  W: 6048e5,
9803
9829
  M: 2592e6
9804
9830
  };
9805
- return map[timeframe] ?? 36e5;
9831
+ return map2[timeframe] ?? 36e5;
9806
9832
  }
9807
9833
  var MultiProviderFeed = class {
9808
9834
  constructor(store = sharedBarStore) {
@@ -10189,8 +10215,8 @@ var DrawingsControl = class {
10189
10215
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
10190
10216
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
10191
10217
  * platform formatting, so hints always match the host's actual bindings. */
10192
- setToolShortcuts(map) {
10193
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
10218
+ setToolShortcuts(map2) {
10219
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
10194
10220
  return this;
10195
10221
  }
10196
10222
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -10517,6 +10543,23 @@ function attachChromeTooltip(anchor, opts) {
10517
10543
  clear();
10518
10544
  };
10519
10545
  }
10546
+ function chromeHint(text, opts) {
10547
+ const px = opts.size ?? 16;
10548
+ const el = document.createElement("span");
10549
+ el.className = "vela-hint";
10550
+ el.setAttribute("role", "img");
10551
+ el.setAttribute("aria-label", text);
10552
+ el.innerHTML = iconAt("info", px);
10553
+ el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
10554
+ const hover = (on) => {
10555
+ el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
10556
+ el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
10557
+ };
10558
+ el.addEventListener("pointerenter", () => hover(true));
10559
+ el.addEventListener("pointerleave", () => hover(false));
10560
+ const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
10561
+ return { el, dispose };
10562
+ }
10520
10563
  function runMachine(machine3, props, render) {
10521
10564
  const m = new vanilla.VanillaMachine(machine3, props);
10522
10565
  const unsub = m.subscribe(render);
@@ -10751,19 +10794,19 @@ var Surface = class _Surface {
10751
10794
  }
10752
10795
  setItems(items) {
10753
10796
  this.items = items;
10754
- for (const sub of this.subs.values()) sub.destroy();
10797
+ for (const sub2 of this.subs.values()) sub2.destroy();
10755
10798
  this.subs.clear();
10756
10799
  this.build();
10757
10800
  const api = this.api;
10758
- for (const sub of this.subs.values()) {
10759
- api.setChild(sub.handle.service);
10760
- sub.api.setParent(this.handle.service);
10801
+ for (const sub2 of this.subs.values()) {
10802
+ api.setChild(sub2.handle.service);
10803
+ sub2.api.setParent(this.handle.service);
10761
10804
  }
10762
10805
  this.handle.flush();
10763
10806
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
10764
10807
  }
10765
10808
  destroy() {
10766
- for (const sub of this.subs.values()) sub.destroy();
10809
+ for (const sub2 of this.subs.values()) sub2.destroy();
10767
10810
  this.subs.clear();
10768
10811
  this.handle.stop();
10769
10812
  this.positioner.remove();
@@ -10777,9 +10820,9 @@ var Surface = class _Surface {
10777
10820
  for (const li of this.list.children) {
10778
10821
  const id = li.dataset.veiId;
10779
10822
  if (!id) continue;
10780
- const sub = this.subs.get(id);
10781
- if (sub) {
10782
- vanilla.spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
10823
+ const sub2 = this.subs.get(id);
10824
+ if (sub2) {
10825
+ vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
10783
10826
  continue;
10784
10827
  }
10785
10828
  const item = this.byId(id);
@@ -10856,7 +10899,7 @@ var Surface = class _Surface {
10856
10899
  const arrow = iconEl("chevron-right", doc);
10857
10900
  arrow.classList.add("vela-menu-arrow");
10858
10901
  li.appendChild(arrow);
10859
- const sub = new _Surface(doc, {
10902
+ const sub2 = new _Surface(doc, {
10860
10903
  host: this.host,
10861
10904
  placement: "right-start",
10862
10905
  onSelect: this.onSelect,
@@ -10864,8 +10907,8 @@ var Surface = class _Surface {
10864
10907
  checkmarks: this.checkmarks,
10865
10908
  id: `${this.mid}--${item.id}`
10866
10909
  });
10867
- sub.setItems(item.submenu ?? []);
10868
- this.subs.set(item.id, sub);
10910
+ sub2.setItems(item.submenu ?? []);
10911
+ this.subs.set(item.id, sub2);
10869
10912
  }
10870
10913
  this.list.appendChild(li);
10871
10914
  }
@@ -12631,8 +12674,21 @@ function buildColorPicker(color, theme, onChange) {
12631
12674
  };
12632
12675
  const recentRow = document.createElement("div");
12633
12676
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
12677
+ const recentSwatches = document.createElement("div");
12678
+ recentSwatches.style.cssText = "display:contents;";
12679
+ const add = document.createElement("label");
12680
+ add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
12681
+ add.textContent = "+";
12682
+ const customInput = document.createElement("input");
12683
+ customInput.type = "color";
12684
+ customInput.value = curHex;
12685
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
12686
+ customInput.addEventListener("input", () => previewHex(customInput.value));
12687
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
12688
+ add.appendChild(customInput);
12689
+ recentRow.append(recentSwatches, add);
12634
12690
  const renderRecents = () => {
12635
- recentRow.replaceChildren();
12691
+ recentSwatches.replaceChildren();
12636
12692
  for (const c of recents) {
12637
12693
  const sw = document.createElement("button");
12638
12694
  sw.type = "button";
@@ -12641,18 +12697,8 @@ function buildColorPicker(color, theme, onChange) {
12641
12697
  e.stopPropagation();
12642
12698
  pickHex(c);
12643
12699
  });
12644
- recentRow.appendChild(sw);
12700
+ recentSwatches.appendChild(sw);
12645
12701
  }
12646
- const add = document.createElement("label");
12647
- add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
12648
- add.textContent = "+";
12649
- const input = document.createElement("input");
12650
- input.type = "color";
12651
- input.value = curHex;
12652
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
12653
- input.addEventListener("input", () => pickHex(input.value, true));
12654
- add.appendChild(input);
12655
- recentRow.appendChild(add);
12656
12702
  };
12657
12703
  const opLabel = document.createElement("div");
12658
12704
  opLabel.textContent = "Opacity";
@@ -12692,14 +12738,18 @@ function buildColorPicker(color, theme, onChange) {
12692
12738
  function emit() {
12693
12739
  onChange(combineColor(curHex, curAlpha));
12694
12740
  }
12695
- function pickHex(hex, custom = false) {
12741
+ function previewHex(hex) {
12696
12742
  curHex = splitColor(hex).hex6;
12697
- addRecent(curHex);
12698
12743
  paintSelection();
12699
12744
  paintOpacity();
12700
- if (custom) renderRecents();
12701
12745
  emit();
12702
12746
  }
12747
+ function pickHex(hex, custom = false) {
12748
+ previewHex(hex);
12749
+ addRecent(curHex);
12750
+ customInput.value = curHex;
12751
+ if (custom) renderRecents();
12752
+ }
12703
12753
  renderRecents();
12704
12754
  paintSelection();
12705
12755
  paintOpacity();
@@ -13651,14 +13701,11 @@ var IndicatorInputsDialog = class {
13651
13701
  buildTextField(id, current, emit) {
13652
13702
  return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
13653
13703
  }
13654
- /** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
13704
+ /** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
13655
13705
  infoButton(tooltip) {
13656
- const b = document.createElement("span");
13657
- b.textContent = "i";
13658
- this.dialogTips.push(attachChromeTooltip(b, { host: this.host.container, theme: () => this.host.theme(), text: () => tooltip, wrap: true, delayMs: 250 }));
13659
- b.className = "vela-ind-hint";
13660
- b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
13661
- return b;
13706
+ const { el, dispose } = chromeHint(tooltip, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
13707
+ this.dialogTips.push(dispose);
13708
+ return el;
13662
13709
  }
13663
13710
  /** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
13664
13711
  select(id, options, current, emit) {
@@ -14095,8 +14142,6 @@ function ensureDialogStyles() {
14095
14142
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
14096
14143
  .vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
14097
14144
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
14098
- .vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
14099
- .vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
14100
14145
  .vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
14101
14146
  .vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
14102
14147
  .vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
@@ -14605,8 +14650,8 @@ var InputsUI = class {
14605
14650
  lg.style.top = `${top}px`;
14606
14651
  }
14607
14652
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
14608
- setCollapsedPanes(map) {
14609
- this.paneCollapse = map;
14653
+ setCollapsedPanes(map2) {
14654
+ this.paneCollapse = map2;
14610
14655
  }
14611
14656
  /**
14612
14657
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -15569,9 +15614,9 @@ function factoryResetConfig(factory) {
15569
15614
  if (inst.enableKey) defaults2[inst.enableKey] = false;
15570
15615
  addRows(inst.rows);
15571
15616
  }
15572
- for (const sub of section.subsections ?? []) {
15573
- if (sub.enableKey) defaults2[sub.enableKey] = false;
15574
- addRows(sub.rows);
15617
+ for (const sub2 of section.subsections ?? []) {
15618
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
15619
+ addRows(sub2.rows);
15575
15620
  }
15576
15621
  if (!section.instances) addRows(section.rows);
15577
15622
  bag[t.id] = defaults2;
@@ -15640,7 +15685,8 @@ function mergeConfig(base, patch) {
15640
15685
  labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
15641
15686
  currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
15642
15687
  priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
15643
- countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
15688
+ countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
15689
+ animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
15644
15690
  },
15645
15691
  panes: {
15646
15692
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
@@ -16900,11 +16946,11 @@ function mergeBarColors(indicators) {
16900
16946
  }
16901
16947
  }
16902
16948
  if (!any) return EMPTY_BARCOLORS;
16903
- const map = /* @__PURE__ */ new Map();
16949
+ const map2 = /* @__PURE__ */ new Map();
16904
16950
  for (const m of indicators.values()) {
16905
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
16951
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
16906
16952
  }
16907
- return map;
16953
+ return map2;
16908
16954
  }
16909
16955
 
16910
16956
  // src/renderers/native/core/ViewportState.ts
@@ -17201,9 +17247,9 @@ function isHorizontalWheel(deltaX, deltaY) {
17201
17247
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
17202
17248
  return rightOffset + deltaX / barSpacing;
17203
17249
  }
17204
- function wheelPanDelta(deltaX, deltaY, shift) {
17250
+ function wheelPanDelta(deltaX, deltaY, shift2) {
17205
17251
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
17206
- if (shift) return deltaY;
17252
+ if (shift2) return deltaY;
17207
17253
  return null;
17208
17254
  }
17209
17255
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -18825,11 +18871,11 @@ function mergeBarColors2(indicators) {
18825
18871
  }
18826
18872
  }
18827
18873
  if (!any) return EMPTY_BARCOLORS2;
18828
- const map = /* @__PURE__ */ new Map();
18874
+ const map2 = /* @__PURE__ */ new Map();
18829
18875
  for (const m of indicators.values()) {
18830
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
18876
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
18831
18877
  }
18832
- return map;
18878
+ return map2;
18833
18879
  }
18834
18880
 
18835
18881
  // src/renderers/shared/drawing-geometry.ts
@@ -20504,6 +20550,8 @@ var BUILTIN_SETTINGS_IDS = [
20504
20550
  "symbol.style.baseline.fill-bottom",
20505
20551
  "symbol.style.baseline.base-level",
20506
20552
  "symbol.style.baseline.width",
20553
+ "symbol.animation",
20554
+ "symbol.animation.price-changes",
20507
20555
  "symbol.timezone",
20508
20556
  "scales",
20509
20557
  "scales.price-scale",
@@ -20545,9 +20593,9 @@ function settingsIdCatalog(hostSections) {
20545
20593
  };
20546
20594
  if (section.rows) addRows(section.rows);
20547
20595
  for (const inst of section.instances ?? []) addRows(inst.rows);
20548
- for (const sub of section.subsections ?? []) {
20549
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
20550
- addRows(sub.rows);
20596
+ for (const sub2 of section.subsections ?? []) {
20597
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
20598
+ addRows(sub2.rows);
20551
20599
  }
20552
20600
  }
20553
20601
  for (const hs of hostSections) {
@@ -20646,8 +20694,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
20646
20694
  .vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
20647
20695
  .vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
20648
20696
  .vela-sd-mobile .vela-sd-btn{height:38px;}
20649
- .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
20650
- `;
20697
+ .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
20651
20698
  if (!existing) document.head.appendChild(st);
20652
20699
  }
20653
20700
  var SettingsDialog = class {
@@ -20668,6 +20715,8 @@ var SettingsDialog = class {
20668
20715
  this.themeControl = null;
20669
20716
  /** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
20670
20717
  this.tabs = [];
20718
+ this.hintTips = [];
20719
+ // ⓘ tooltip disposers of the open build (see hint)
20671
20720
  /** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
20672
20721
  this.typeActiveInstance = /* @__PURE__ */ new Map();
20673
20722
  /** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
@@ -20856,6 +20905,13 @@ var SettingsDialog = class {
20856
20905
  body.append(g);
20857
20906
  }
20858
20907
  showActive(config.series.style);
20908
+ body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
20909
+ body.append(sid(this.boolRow(
20910
+ "Animate price changes",
20911
+ config.priceScale.animateLastPrice,
20912
+ (v) => this.emit({ priceScale: { animateLastPrice: v } }),
20913
+ this.hint("Glide the live bar to each new price instead of snapping.")
20914
+ ), "symbol.animation.price-changes"));
20859
20915
  body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
20860
20916
  body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
20861
20917
  const renderHostSections = (placement) => {
@@ -21063,6 +21119,8 @@ var SettingsDialog = class {
21063
21119
  this.ui = null;
21064
21120
  this.root = null;
21065
21121
  this.tabs = [];
21122
+ for (const dispose of this.hintTips) dispose();
21123
+ this.hintTips = [];
21066
21124
  ui?.destroy();
21067
21125
  }
21068
21126
  destroy() {
@@ -21106,14 +21164,14 @@ var SettingsDialog = class {
21106
21164
  };
21107
21165
  if (section.rows) seed(section.rows);
21108
21166
  for (const inst of section.instances ?? []) seed(inst.rows);
21109
- for (const sub of section.subsections ?? []) seed(sub.rows);
21167
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
21110
21168
  for (const inst of section.instances ?? []) {
21111
21169
  if (!inst.enableKey || inst.enableKey in bag) continue;
21112
21170
  bag[inst.enableKey] = values[inst.enableKey] === true;
21113
21171
  }
21114
- for (const sub of section.subsections ?? []) {
21115
- if (!sub.enableKey || sub.enableKey in bag) continue;
21116
- bag[sub.enableKey] = values[sub.enableKey] === true;
21172
+ for (const sub2 of section.subsections ?? []) {
21173
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
21174
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
21117
21175
  }
21118
21176
  const refreshers = [];
21119
21177
  const put = (key, v) => {
@@ -21130,16 +21188,16 @@ var SettingsDialog = class {
21130
21188
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
21131
21189
  else this.flatTypeRows(rows, bag, put, refreshers, body);
21132
21190
  }
21133
- for (const sub of section.subsections ?? []) {
21134
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
21135
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
21191
+ for (const sub2 of section.subsections ?? []) {
21192
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
21193
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
21136
21194
  if (rows.length === 0) continue;
21137
- const subMarker = this.section(sub.title);
21195
+ const subMarker = this.section(sub2.title);
21138
21196
  subMarker.dataset.sdStyle = typeId;
21139
21197
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
21140
21198
  subMarker.dataset.sdSub = "1";
21141
21199
  body.append(subMarker);
21142
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
21200
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
21143
21201
  }
21144
21202
  for (const r of refreshers) r();
21145
21203
  }
@@ -21476,23 +21534,31 @@ var SettingsDialog = class {
21476
21534
  }).el
21477
21535
  });
21478
21536
  }
21479
- /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area). */
21480
- boolRow(label, value, onChange) {
21481
- return this.toggleRow(label, value, onChange, []);
21537
+ /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
21538
+ * `info` (see {@link hint}) rides after the label. */
21539
+ boolRow(label, value, onChange, info) {
21540
+ return this.toggleRow(label, value, onChange, [], void 0, info);
21482
21541
  }
21483
21542
  /** An enable row: checkbox + label in the label column, dependent controls in the
21484
21543
  * shared control column; the control group dims and ignores input while the toggle
21485
21544
  * is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
21486
- toggleRow(label, value, onToggle, controls, get) {
21545
+ toggleRow(label, value, onToggle, controls, get, info) {
21487
21546
  return fieldRow({
21488
21547
  label,
21489
21548
  labelSize: "sm",
21490
21549
  bool: controls.length === 0,
21491
21550
  toggle: { checked: value, onChange: onToggle, get },
21492
21551
  control: controls,
21552
+ info,
21493
21553
  className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
21494
21554
  });
21495
21555
  }
21556
+ /** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
21557
+ hint(text) {
21558
+ const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
21559
+ this.hintTips.push(dispose);
21560
+ return el;
21561
+ }
21496
21562
  numberRow(label, value, min, max, step, onChange) {
21497
21563
  return fieldRow({
21498
21564
  label,
@@ -23247,11 +23313,11 @@ var DrawingInteraction = class {
23247
23313
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
23248
23314
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
23249
23315
  * while placing a line tool it locks the segment angle to 45° steps. */
23250
- down(x, y, mode = "off", shift = false) {
23316
+ down(x, y, mode = "off", shift2 = false) {
23251
23317
  const proj = this.deps.projector();
23252
23318
  if (this.state.kind === "placing") {
23253
23319
  if (this.state.draft.placementMode() !== "click") return;
23254
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
23320
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
23255
23321
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
23256
23322
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
23257
23323
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -23270,7 +23336,7 @@ var DrawingInteraction = class {
23270
23336
  }
23271
23337
  const hit = this.hitAt(x, y);
23272
23338
  if (hit) {
23273
- if (shift) {
23339
+ if (shift2) {
23274
23340
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
23275
23341
  return;
23276
23342
  }
@@ -23280,7 +23346,7 @@ var DrawingInteraction = class {
23280
23346
  }
23281
23347
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
23282
23348
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
23283
- move(x, y, mode = "off", shift = false) {
23349
+ move(x, y, mode = "off", shift2 = false) {
23284
23350
  if (this.state.kind === "idle") {
23285
23351
  const tool = this.deps.activeTool();
23286
23352
  const had = this.snapAt != null;
@@ -23294,7 +23360,7 @@ var DrawingInteraction = class {
23294
23360
  this.captureFreehand(x, y);
23295
23361
  return;
23296
23362
  }
23297
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
23363
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
23298
23364
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
23299
23365
  this.deps.changed();
23300
23366
  return;
@@ -23307,7 +23373,7 @@ var DrawingInteraction = class {
23307
23373
  if (this.state.handle >= 0) {
23308
23374
  const a = d.anchors[this.state.handle];
23309
23375
  if (a) {
23310
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
23376
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
23311
23377
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
23312
23378
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
23313
23379
  }
@@ -24005,21 +24071,21 @@ var DrawingSettingsPopup = class {
24005
24071
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
24006
24072
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
24007
24073
  }
24008
- const vwap = drawing.vwap;
24009
- if (paths.has("vwap.midColor") && vwap) {
24074
+ const vwap2 = drawing.vwap;
24075
+ if (paths.has("vwap.midColor") && vwap2) {
24010
24076
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
24011
24077
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
24012
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
24013
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
24078
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
24079
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
24014
24080
  bar.appendChild(
24015
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
24081
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
24016
24082
  label: (v) => `${v}\u03C3`,
24017
24083
  labelInTrigger: true
24018
24084
  })
24019
24085
  );
24020
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
24021
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
24022
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
24086
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
24087
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
24088
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
24023
24089
  }
24024
24090
  if (paths.has("maxCurvature")) {
24025
24091
  const cur = drawing.maxCurvature ?? 24;
@@ -24765,8 +24831,8 @@ var DrawingToolbar = class {
24765
24831
  }
24766
24832
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
24767
24833
  * right edge of the flyout rows, beside the favorite star. */
24768
- setShortcuts(map) {
24769
- this.shortcuts = new Map(Object.entries(map));
24834
+ setShortcuts(map2) {
24835
+ this.shortcuts = new Map(Object.entries(map2));
24770
24836
  }
24771
24837
  /** Paint one star for the current favorite state (filled + gold, or outline). */
24772
24838
  paintStar(el, type) {
@@ -25668,8 +25734,8 @@ var UserDrawingController = class {
25668
25734
  this.toolbar.setFavorites(types);
25669
25735
  }
25670
25736
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
25671
- setToolShortcuts(map) {
25672
- this.toolbar.setShortcuts(map);
25737
+ setToolShortcuts(map2) {
25738
+ this.toolbar.setShortcuts(map2);
25673
25739
  }
25674
25740
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
25675
25741
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -25787,7 +25853,7 @@ var UserDrawingController = class {
25787
25853
  this.render();
25788
25854
  }
25789
25855
  }
25790
- pointerDown(x, y, snap = "off", shift = false) {
25856
+ pointerDown(x, y, snap = "off", shift2 = false) {
25791
25857
  if (this.eraserMode) {
25792
25858
  this.erasing = true;
25793
25859
  this.deleteAt(x, y);
@@ -25807,9 +25873,9 @@ var UserDrawingController = class {
25807
25873
  return;
25808
25874
  }
25809
25875
  }
25810
- this.interaction.down(x, y, snap, shift);
25876
+ this.interaction.down(x, y, snap, shift2);
25811
25877
  }
25812
- pointerMove(x, y, snap = "off", shift = false) {
25878
+ pointerMove(x, y, snap = "off", shift2 = false) {
25813
25879
  if (this.eraserMode) {
25814
25880
  if (this.erasing) this.deleteAt(x, y);
25815
25881
  return;
@@ -25820,7 +25886,7 @@ var UserDrawingController = class {
25820
25886
  this.render();
25821
25887
  return;
25822
25888
  }
25823
- this.interaction.move(x, y, snap, shift);
25889
+ this.interaction.move(x, y, snap, shift2);
25824
25890
  this.updateHover(x, y);
25825
25891
  }
25826
25892
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -26497,14 +26563,14 @@ function layoutTable(ctx, t, args) {
26497
26563
  }
26498
26564
  for (const { box, w: w2, h: h2 } of spanning) {
26499
26565
  if (box.cs > 1) {
26500
- let sum = 0;
26501
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
26502
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
26566
+ let sum2 = 0;
26567
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
26568
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
26503
26569
  }
26504
26570
  if (box.rs > 1) {
26505
- let sum = 0;
26506
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
26507
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
26571
+ let sum2 = 0;
26572
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
26573
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
26508
26574
  }
26509
26575
  }
26510
26576
  let w = 0;
@@ -27227,24 +27293,24 @@ function rasterizeOverlay(ctx, root, frame) {
27227
27293
  drawSvgShapes(el, alpha);
27228
27294
  continue;
27229
27295
  }
27230
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
27296
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
27231
27297
  let path = null;
27232
27298
  if (tag === "path") {
27233
27299
  const d = el.getAttribute("d");
27234
27300
  if (d) path = new Path2D(d);
27235
27301
  } else if (tag === "circle") {
27236
27302
  path = new Path2D();
27237
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
27303
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
27238
27304
  } else if (tag === "ellipse") {
27239
27305
  path = new Path2D();
27240
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
27306
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
27241
27307
  } else if (tag === "rect") {
27242
27308
  path = new Path2D();
27243
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
27309
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
27244
27310
  } else if (tag === "line") {
27245
27311
  path = new Path2D();
27246
- path.moveTo(num3("x1"), num3("y1"));
27247
- path.lineTo(num3("x2"), num3("y2"));
27312
+ path.moveTo(num4("x1"), num4("y1"));
27313
+ path.lineTo(num4("x2"), num4("y2"));
27248
27314
  } else if (tag === "polyline" || tag === "polygon") {
27249
27315
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
27250
27316
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -27505,7 +27571,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
27505
27571
  var ZOOM_TAU_MS = 70;
27506
27572
  var SCALE_TAU_MS = 80;
27507
27573
  var FLING_TAU_MS = 110;
27508
- var LIVE_BAR_TAU_MS = 90;
27509
27574
  var SCROLL_TO_TAU_MS = 130;
27510
27575
  var FLING_STOP_PX = 0.02;
27511
27576
  var PRICE_SCALE_K = 4e-3;
@@ -27576,9 +27641,13 @@ var NativeRenderer = class {
27576
27641
  /** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
27577
27642
  this.historyChordsEnabled = true;
27578
27643
  this.liveRegion = null;
27579
- // ── animation state (eased zoom + inertial pan) ──
27644
+ // ── animation state (eased zoom + inertial pan + live-bar glide) ──
27580
27645
  this.animZoom = true;
27581
27646
  this.animPan = true;
27647
+ this.animLiveBarMs = 0;
27648
+ // forming-bar OHLC glide time-constant; 0 = each tick snaps
27649
+ this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
27650
+ // the duration the settings-dialog on/off toggle restores (last non-zero value configured)
27582
27651
  // Brand default candles.
27583
27652
  this.candleUp = BULLISH;
27584
27653
  this.candleDown = BEARISH;
@@ -27703,7 +27772,7 @@ var NativeRenderer = class {
27703
27772
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
27704
27773
  this.priceStyleCbs = /* @__PURE__ */ new Set();
27705
27774
  this.name = "native";
27706
- this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
27775
+ this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
27707
27776
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
27708
27777
  * so moving onto the button doesn't count as leaving). */
27709
27778
  this.onScrollProximityMove = (e) => {
@@ -27737,6 +27806,7 @@ var NativeRenderer = class {
27737
27806
  this.backendMode = opts.nativeBackend;
27738
27807
  this.animZoom = opts.animZoom;
27739
27808
  this.animPan = opts.animPan;
27809
+ this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
27740
27810
  this.glowAmount = opts.glow;
27741
27811
  this.candleUp = opts.upColor;
27742
27812
  this.candleDown = opts.downColor;
@@ -27786,6 +27856,10 @@ var NativeRenderer = class {
27786
27856
  case "animPan":
27787
27857
  this.animPan = Boolean(value);
27788
27858
  return;
27859
+ case "animLiveBar":
27860
+ this.setLiveBarEase(resolveLiveBarEaseMs(value));
27861
+ return;
27862
+ // affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
27789
27863
  case "intro": {
27790
27864
  const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
27791
27865
  this.introStyle = s;
@@ -27922,6 +27996,8 @@ var NativeRenderer = class {
27922
27996
  return this.animZoom;
27923
27997
  case "animPan":
27924
27998
  return this.animPan;
27999
+ case "animLiveBar":
28000
+ return this.animLiveBarMs;
27925
28001
  case "intro":
27926
28002
  return this.introStyle;
27927
28003
  case "zoomAnchor":
@@ -28090,7 +28166,8 @@ var NativeRenderer = class {
28090
28166
  labelsVisible: this.scene.showAxisLabels,
28091
28167
  currentPriceLine: this.scene.showPriceLine,
28092
28168
  priceLabel: this.scene.showPriceLabel,
28093
- countdown: this.scene.showCountdown
28169
+ countdown: this.scene.showCountdown,
28170
+ animateLastPrice: this.animLiveBarMs > 0
28094
28171
  },
28095
28172
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
28096
28173
  trades: {
@@ -28197,6 +28274,7 @@ var NativeRenderer = class {
28197
28274
  this.scene.showPriceLabel = next.priceScale.priceLabel;
28198
28275
  this.scene.showCountdown = next.priceScale.countdown;
28199
28276
  this.syncCountdownTimer();
28277
+ this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
28200
28278
  s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
28201
28279
  this.scene.tradeMarkers = {
28202
28280
  visible: next.trades.visible,
@@ -28661,8 +28739,8 @@ var NativeRenderer = class {
28661
28739
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
28662
28740
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
28663
28741
  drawingsSnapMode: () => this.snapMode,
28664
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
28665
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
28742
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
28743
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
28666
28744
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
28667
28745
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
28668
28746
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -28979,18 +29057,18 @@ var NativeRenderer = class {
28979
29057
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
28980
29058
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
28981
29059
  const newHeadTime = this.bars[0].time;
28982
- let shift = 0;
29060
+ let shift2 = 0;
28983
29061
  if (newHeadTime !== prevHeadTime) {
28984
29062
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
28985
- if (prepended > 0) shift = prepended;
29063
+ if (prepended > 0) shift2 = prepended;
28986
29064
  else {
28987
29065
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
28988
- if (trimmed > 0) shift = -trimmed;
29066
+ if (trimmed > 0) shift2 = -trimmed;
28989
29067
  }
28990
29068
  }
28991
- if (shift !== 0) {
28992
- this.zoomAnchorLogical += shift;
28993
- if (this.hoverLogical != null) this.hoverLogical += shift;
29069
+ if (shift2 !== 0) {
29070
+ this.zoomAnchorLogical += shift2;
29071
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
28994
29072
  }
28995
29073
  }
28996
29074
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -29013,8 +29091,11 @@ var NativeRenderer = class {
29013
29091
  const last = this.bars[n - 1];
29014
29092
  if (last && bar.time === last.time) {
29015
29093
  this.bars[n - 1] = bar;
29016
- if (this.liveEaseTime !== bar.time) this.syncLiveEase(bar);
29017
- this.animator.start();
29094
+ if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
29095
+ this.syncLiveEase(bar);
29096
+ } else {
29097
+ this.animator.start();
29098
+ }
29018
29099
  } else if (!last || bar.time > last.time) {
29019
29100
  this.bars.push(bar);
29020
29101
  this.syncLiveEase(bar);
@@ -29026,6 +29107,12 @@ var NativeRenderer = class {
29026
29107
  this.scene.bars = this.bars;
29027
29108
  this.scheduler.invalidate(4 /* Full */);
29028
29109
  }
29110
+ /** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
29111
+ * what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
29112
+ setLiveBarEase(ms) {
29113
+ this.animLiveBarMs = ms;
29114
+ if (ms > 0) this.animLiveBarOnMs = ms;
29115
+ }
29029
29116
  /** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
29030
29117
  syncLiveEase(bar) {
29031
29118
  this.liveEaseTime = bar.time;
@@ -29038,9 +29125,10 @@ var NativeRenderer = class {
29038
29125
  const target = this.bars[this.bars.length - 1];
29039
29126
  if (!target || this.liveEaseTime !== target.time) return false;
29040
29127
  const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
29041
- const nh = easeToward(this.liveEaseHigh, target.high, dtMs, LIVE_BAR_TAU_MS);
29042
- const nl = easeToward(this.liveEaseLow, target.low, dtMs, LIVE_BAR_TAU_MS);
29043
- const nc = easeToward(this.liveEaseClose, target.close, dtMs, LIVE_BAR_TAU_MS);
29128
+ const tau = this.animLiveBarMs;
29129
+ const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
29130
+ const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
29131
+ const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
29044
29132
  const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
29045
29133
  this.liveEaseHigh = active ? nh : target.high;
29046
29134
  this.liveEaseLow = active ? nl : target.low;
@@ -30596,15 +30684,15 @@ var NativeRenderer = class {
30596
30684
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
30597
30685
  * strip — merged own-scale indicators are hidden while collapsed. */
30598
30686
  collapsedMasterMap() {
30599
- const map = /* @__PURE__ */ new Map();
30687
+ const map2 = /* @__PURE__ */ new Map();
30600
30688
  for (const pane of this.scene.panes.values()) {
30601
30689
  if (!pane.collapsed) continue;
30602
30690
  const models = this.scene.orderedIndicatorsForPane(pane.id);
30603
30691
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
30604
30692
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
30605
- map.set(pane.id, master?.id ?? null);
30693
+ map2.set(pane.id, master?.id ?? null);
30606
30694
  }
30607
- return map;
30695
+ return map2;
30608
30696
  }
30609
30697
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
30610
30698
  setToolbarGutter(px) {
@@ -31000,213 +31088,2522 @@ function registerVpvr() {
31000
31088
  registerNativeIndicator(vpvrDescriptor);
31001
31089
  }
31002
31090
 
31003
- // src/Vela.ts
31004
- var Vela = class {
31005
- constructor(container, options = {}, deps = {}) {
31006
- registerBuiltinChartTypes();
31007
- registerVolume();
31008
- registerVpvr();
31009
- const element = resolveElement(container);
31010
- const theme = resolveTheme(options.theme);
31011
- let animZoom = true;
31012
- let animPan = true;
31013
- if (typeof options.animations === "boolean") {
31014
- animZoom = options.animations;
31015
- animPan = options.animations;
31016
- } else if (options.animations) {
31017
- animZoom = options.animations.zoom ?? true;
31018
- animPan = options.animations.pan ?? true;
31019
- }
31020
- const display = {
31021
- currentPriceLine: options.currentPriceLine ?? true,
31022
- logScale: options.logScale ?? false,
31023
- nativeBackend: options.nativeBackend ?? "auto",
31024
- animZoom,
31025
- animPan,
31026
- glow: options.glow ?? 0,
31027
- upColor: options.upColor ?? BULLISH,
31028
- downColor: options.downColor ?? BEARISH,
31029
- priceStyle: options.priceStyle ?? "candles"
31030
- };
31031
- const RendererClass = options.renderer ?? NativeRenderer;
31032
- if (typeof RendererClass !== "function") {
31033
- throw new Error(
31034
- "[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
31035
- );
31036
- }
31037
- const renderer = deps.renderer ?? new RendererClass(display);
31038
- const engines = deps.engines ?? [];
31039
- const feed = deps.dataFeed ?? new MultiProviderFeed();
31040
- const config = {
31041
- market: {
31042
- symbol: options.symbol,
31043
- timeframe: options.timeframe,
31044
- bars: options.bars,
31045
- session: options.session,
31046
- visibleRange: options.visibleRange,
31047
- data: options.data
31048
- },
31049
- live: options.live ?? false,
31050
- theme,
31051
- defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
31052
- drawings: options.drawings,
31053
- volume: options.volume ?? true
31054
- // the volume indicator is on by default
31055
- };
31056
- this.rendererControl = new RendererControl(renderer);
31057
- if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
31058
- this.dataControl = new DataControl(feed);
31059
- this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
31060
- const defaults2 = rendererDefaults();
31061
- if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
31062
- this.panesControl = new PanesControl(this.orchestrator);
31063
- this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
31091
+ // src/core/model/identity.ts
31092
+ function stableSeriesId(parts) {
31093
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
31094
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
31095
+ }
31096
+
31097
+ // src/core/native-indicators/classics/define.ts
31098
+ function num3(inputs, key, fallback) {
31099
+ const v = inputs[key];
31100
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
31101
+ }
31102
+ function str3(inputs, key, fallback) {
31103
+ const v = inputs[key];
31104
+ return typeof v === "string" && v !== "" ? v : fallback;
31105
+ }
31106
+ function toPoints(bars, values, colors) {
31107
+ const pts = new Array(bars.length);
31108
+ for (let i = 0; i < bars.length; i++) {
31109
+ const v = values[i];
31110
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
31111
+ const c = colors?.[i];
31112
+ if (c != null) p.color = c;
31113
+ pts[i] = p;
31064
31114
  }
31065
- /**
31066
- * Register a scripting engine so `addIndicator({ language })` can run that
31067
- * language. Vela ships NO engine — install the one you need (Pine Script:
31068
- * `@luxalgo/vela-pinets`) and register it, e.g.
31069
- * `chart.registerEngine('pine', new PineEngine())`; without one the chart
31070
- * displays candles, drawings and native indicators only. Re-registering a
31071
- * language replaces it.
31072
- */
31073
- registerEngine(language, engine) {
31074
- this.orchestrator.registerEngine(language, engine);
31075
- return this;
31115
+ return pts;
31116
+ }
31117
+ var ClassicIndicator = class {
31118
+ constructor(spec) {
31119
+ this.spec = spec;
31120
+ this.inputs = {};
31076
31121
  }
31077
- /** Run an indicator script on the chart's market data and render it. */
31078
- addIndicator(source, options) {
31079
- return this.orchestrator.addIndicator(source, options);
31122
+ start(ctx, inputs) {
31123
+ this.ctx = ctx;
31124
+ this.inputs = inputs;
31125
+ this.recompute();
31080
31126
  }
31081
- /**
31082
- * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
31083
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
31084
- * events) and is single-instance per type (a second call returns the existing handle). Returns
31085
- * a fail-soft handle for an unregistered type. Native renderer only.
31086
- */
31087
- addNativeIndicator(type, options) {
31088
- return this.orchestrator.addNativeIndicator(type, options);
31127
+ onBars() {
31128
+ this.recompute();
31089
31129
  }
31090
- /**
31091
- * The catalog of built-in native indicators with their live state on this chart — each entry's
31092
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
31093
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
31094
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
31095
- * because support may probe the provider (a type may need data the symbol lacks).
31096
- */
31097
- availableNativeIndicators() {
31098
- return this.orchestrator.availableNativeIndicators();
31130
+ onViewport() {
31099
31131
  }
31100
- /**
31101
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
31102
- * {@link availableNativeIndicators} (only support probing is async; presence never is).
31103
- * Persistence snapshots read this: an unload-time flush must see an add/remove that
31104
- * happened microseconds ago, which an async catalog mirror cannot guarantee.
31105
- */
31106
- presentNativeIndicators() {
31107
- return this.orchestrator.presentNativeIndicators();
31132
+ setInputs(inputs) {
31133
+ this.inputs = inputs;
31134
+ this.recompute();
31108
31135
  }
31109
- /** Live handles of every indicator currently on the chart (script + native) — drive
31110
- * host panels (object trees, indicator lists) with per-id visibility/removal. */
31111
- indicators() {
31112
- return this.orchestrator.listIndicators();
31136
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
31137
+ suspend() {
31113
31138
  }
31114
- /**
31115
- * Execute an indicator script and INJECT it only if the run succeeds — the seam for
31116
- * host editors and consoles. Resolves `{ ok: true, handle }` after the first
31117
- * successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
31118
- * in which case the failed indicator is removed again (no dead legend row).
31119
- * Never rejects.
31120
- */
31121
- /**
31122
- * Execute a script and resolve its FIRST computed run — the data-out door for host
31123
- * editors, consoles and dashboards. The script is injected only if it runs (a failure
31124
- * removes it again, leaving no dead legend row), and the result carries the run itself
31125
- * plus the controls for what it put on the chart: `onUpdate` to follow later runs,
31126
- * `remove` to take it off. Never rejects.
31127
- *
31128
- * `runScript` is `runIndicator` with the run as its payload rather than a handle to go
31129
- * fetch from — the same relationship `script:run` has to `context:changed`.
31130
- */
31131
- runScript(source, options) {
31132
- const handle = this.addIndicator(source, options);
31133
- const updates = /* @__PURE__ */ new Set();
31134
- const drop = () => {
31135
- try {
31136
- handle.remove();
31137
- } catch {
31138
- }
31139
- };
31140
- return new Promise((resolve) => {
31141
- let settled = false;
31142
- const offRun = this.on("script:run", (run) => {
31143
- if (run.id !== handle.id) return;
31144
- if (settled) {
31145
- for (const handler of updates) handler(run);
31146
- return;
31147
- }
31148
- settled = true;
31149
- offError();
31150
- resolve({
31151
- ok: true,
31152
- run,
31153
- error: null,
31154
- onUpdate: (handler) => {
31155
- updates.add(handler);
31156
- return () => updates.delete(handler);
31157
- },
31158
- remove: () => {
31159
- offRun();
31160
- updates.clear();
31161
- drop();
31162
- }
31163
- });
31164
- });
31165
- const offError = handle.on("error", ({ error }) => {
31166
- if (settled) return;
31167
- settled = true;
31168
- offRun();
31169
- offError();
31170
- drop();
31171
- resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
31172
- });
31173
- });
31139
+ resume() {
31140
+ this.recompute();
31174
31141
  }
31175
- runIndicator(source, options) {
31176
- const handle = this.addIndicator(source, options);
31177
- return new Promise((resolve) => {
31178
- const offReady = handle.on("ready", () => {
31179
- offReady();
31180
- offError();
31181
- resolve({ ok: true, handle, error: null, context: null });
31142
+ stop() {
31143
+ }
31144
+ recompute() {
31145
+ const bars = this.ctx.bars();
31146
+ const out = this.spec.compute(bars, this.inputs);
31147
+ const instanceId = this.spec.type;
31148
+ const series = [];
31149
+ const idsByKey = /* @__PURE__ */ new Map();
31150
+ out.plots.forEach((plot, ordinal) => {
31151
+ const kind = plot.kind ?? "line";
31152
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
31153
+ idsByKey.set(plot.key, id);
31154
+ series.push({
31155
+ id,
31156
+ title: plot.title,
31157
+ paneId: "",
31158
+ // stamped by the orchestrator
31159
+ kind,
31160
+ points: toPoints(bars, plot.values, plot.colors),
31161
+ style: {
31162
+ color: plot.color,
31163
+ width: plot.width ?? 1,
31164
+ lineStyle: plot.lineStyle ?? "solid",
31165
+ ...plot.base != null ? { base: plot.base } : {}
31166
+ },
31167
+ ...plot.overlay ? { overlay: true } : {}
31182
31168
  });
31183
- const offError = handle.on("error", ({ error }) => {
31184
- offReady();
31185
- offError();
31186
- void handle.context().catch(() => null).then((context) => {
31187
- try {
31188
- handle.remove();
31189
- } catch {
31190
- }
31191
- resolve({ ok: false, handle: null, error, context });
31192
- });
31169
+ });
31170
+ if (out.markers && out.markers.length > 0) {
31171
+ series.push({
31172
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
31173
+ title: this.spec.shortTitle ?? this.spec.title,
31174
+ paneId: "",
31175
+ kind: "markers",
31176
+ markers: out.markers
31193
31177
  });
31178
+ }
31179
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
31180
+ const from = idsByKey.get(band.from);
31181
+ const to = idsByKey.get(band.to);
31182
+ if (!from || !to) return [];
31183
+ return [
31184
+ {
31185
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
31186
+ paneId: "",
31187
+ fromSeriesId: from,
31188
+ toSeriesId: to,
31189
+ color: band.color
31190
+ }
31191
+ ];
31194
31192
  });
31193
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
31194
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
31195
+ paneId: "",
31196
+ price: level.price,
31197
+ ...level.color != null ? { color: level.color } : {},
31198
+ lineStyle: level.lineStyle ?? "dashed",
31199
+ width: 1,
31200
+ ...level.title != null ? { title: level.title } : {}
31201
+ }));
31202
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
31203
+ const { key, ...rest } = poly;
31204
+ return {
31205
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
31206
+ paneId: "",
31207
+ ...rest
31208
+ };
31209
+ });
31210
+ this.ctx.emit({ series, fills, priceLines, polylines });
31211
+ this.ctx.setStatus("idle");
31195
31212
  }
31196
- /**
31197
- * Switch the chart's market IN PLACE — symbol, provider, timeframe, depth, or offline
31198
- * data — WITHOUT destroying the chart: indicators re-execute over the new bars, native
31199
- * indicators restart, and panes, user drawings, renderer config and event
31200
- * subscriptions all survive. Resolves once the new market's history is painted (a
31201
- * deep backfill continues behind it — await {@link historyComplete}); a call
31202
- * superseded by a newer `setMarket` resolves silently. Emits `market:changed`
31203
- * (with the previous identity) when the market identity changed.
31204
- */
31205
- setMarket(next) {
31206
- return this.orchestrator.setMarket(next);
31213
+ };
31214
+ function classicDescriptor(spec) {
31215
+ return {
31216
+ type: spec.type,
31217
+ title: spec.title,
31218
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
31219
+ paneHint: spec.overlay ? "price" : "new",
31220
+ overlay: spec.overlay,
31221
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
31222
+ multiInstance: true,
31223
+ inputsSchema: () => spec.inputs,
31224
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
31225
+ create: () => new ClassicIndicator(spec)
31226
+ };
31227
+ }
31228
+
31229
+ // src/core/native-indicators/classics/math.ts
31230
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
31231
+ function sourceValues(bars, source) {
31232
+ const out = new Array(bars.length);
31233
+ for (let i = 0; i < bars.length; i++) {
31234
+ const b = bars[i];
31235
+ switch (source) {
31236
+ case "Open":
31237
+ out[i] = b.open;
31238
+ break;
31239
+ case "High":
31240
+ out[i] = b.high;
31241
+ break;
31242
+ case "Low":
31243
+ out[i] = b.low;
31244
+ break;
31245
+ case "HL2":
31246
+ out[i] = (b.high + b.low) / 2;
31247
+ break;
31248
+ case "HLC3":
31249
+ out[i] = (b.high + b.low + b.close) / 3;
31250
+ break;
31251
+ case "OHLC4":
31252
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
31253
+ break;
31254
+ case "HLCC4":
31255
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
31256
+ break;
31257
+ default:
31258
+ out[i] = b.close;
31259
+ }
31207
31260
  }
31208
- /** The current market identity — the read counterpart of {@link setMarket}. A snapshot
31209
- * of the REQUESTED market: it reflects an in-flight switch immediately (before the
31261
+ return out;
31262
+ }
31263
+ function volumes(bars) {
31264
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
31265
+ }
31266
+ function highs(bars) {
31267
+ return bars.map((b) => b.high);
31268
+ }
31269
+ function lows(bars) {
31270
+ return bars.map((b) => b.low);
31271
+ }
31272
+ function closes(bars) {
31273
+ return bars.map((b) => b.close);
31274
+ }
31275
+ function opens(bars) {
31276
+ return bars.map((b) => b.open);
31277
+ }
31278
+ function zip(a, b, f) {
31279
+ const out = new Array(a.length);
31280
+ for (let i = 0; i < a.length; i++) {
31281
+ const x = a[i];
31282
+ const y = b[i];
31283
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
31284
+ }
31285
+ return out;
31286
+ }
31287
+ function map(a, f) {
31288
+ const out = new Array(a.length);
31289
+ for (let i = 0; i < a.length; i++) {
31290
+ const x = a[i];
31291
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
31292
+ }
31293
+ return out;
31294
+ }
31295
+ function sub(a, b) {
31296
+ return zip(a, b, (x, y) => x - y);
31297
+ }
31298
+ function windowed(v, len, fold) {
31299
+ const out = new Array(v.length).fill(Number.NaN);
31300
+ if (len <= 0) return out;
31301
+ let run = 0;
31302
+ for (let i = 0; i < v.length; i++) {
31303
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
31304
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
31305
+ }
31306
+ return out;
31307
+ }
31308
+ function sum(v, len) {
31309
+ return windowed(v, len, (w, from, to) => {
31310
+ let s = 0;
31311
+ for (let k = from; k <= to; k++) s += w[k];
31312
+ return s;
31313
+ });
31314
+ }
31315
+ function sma(v, len) {
31316
+ return windowed(v, len, (w, from, to) => {
31317
+ let s = 0;
31318
+ for (let k = from; k <= to; k++) s += w[k];
31319
+ return s / len;
31320
+ });
31321
+ }
31322
+ function wma(v, len) {
31323
+ const norm = len * (len + 1) / 2;
31324
+ return windowed(v, len, (w, from, to) => {
31325
+ let s = 0;
31326
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
31327
+ return s / norm;
31328
+ });
31329
+ }
31330
+ function vwma(src, vol, len) {
31331
+ const pv = zip(src, vol, (p, q) => p * q);
31332
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
31333
+ }
31334
+ function stdev(v, len) {
31335
+ return windowed(v, len, (w, from, to) => {
31336
+ let s = 0;
31337
+ for (let k = from; k <= to; k++) s += w[k];
31338
+ const mean = s / len;
31339
+ let sq = 0;
31340
+ for (let k = from; k <= to; k++) {
31341
+ const d = w[k] - mean;
31342
+ sq += d * d;
31343
+ }
31344
+ return Math.sqrt(sq / len);
31345
+ });
31346
+ }
31347
+ function highest(v, len) {
31348
+ return windowed(v, len, (w, from, to) => {
31349
+ let m = -Infinity;
31350
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
31351
+ return m;
31352
+ });
31353
+ }
31354
+ function lowest(v, len) {
31355
+ return windowed(v, len, (w, from, to) => {
31356
+ let m = Infinity;
31357
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
31358
+ return m;
31359
+ });
31360
+ }
31361
+ function barsSinceHighest(v, len) {
31362
+ return windowed(v, len, (w, from, to) => {
31363
+ let m = -Infinity;
31364
+ let at = to;
31365
+ for (let k = from; k <= to; k++) {
31366
+ if (w[k] >= m) {
31367
+ m = w[k];
31368
+ at = k;
31369
+ }
31370
+ }
31371
+ return to - at;
31372
+ });
31373
+ }
31374
+ function barsSinceLowest(v, len) {
31375
+ return windowed(v, len, (w, from, to) => {
31376
+ let m = Infinity;
31377
+ let at = to;
31378
+ for (let k = from; k <= to; k++) {
31379
+ if (w[k] <= m) {
31380
+ m = w[k];
31381
+ at = k;
31382
+ }
31383
+ }
31384
+ return to - at;
31385
+ });
31386
+ }
31387
+ function recursive(v, len, alpha) {
31388
+ const out = new Array(v.length).fill(Number.NaN);
31389
+ let run = 0;
31390
+ let prev2 = Number.NaN;
31391
+ for (let i = 0; i < v.length; i++) {
31392
+ const x = v[i];
31393
+ if (!Number.isFinite(x)) {
31394
+ run = 0;
31395
+ prev2 = Number.NaN;
31396
+ continue;
31397
+ }
31398
+ run += 1;
31399
+ if (Number.isFinite(prev2)) {
31400
+ prev2 = alpha * x + (1 - alpha) * prev2;
31401
+ out[i] = prev2;
31402
+ } else if (run >= len) {
31403
+ let s = 0;
31404
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
31405
+ prev2 = s / len;
31406
+ out[i] = prev2;
31407
+ }
31408
+ }
31409
+ return out;
31410
+ }
31411
+ function ema(v, len) {
31412
+ return recursive(v, len, 2 / (len + 1));
31413
+ }
31414
+ function rma(v, len) {
31415
+ return recursive(v, len, 1 / len);
31416
+ }
31417
+ function change(v, n = 1) {
31418
+ const out = new Array(v.length).fill(Number.NaN);
31419
+ for (let i = n; i < v.length; i++) {
31420
+ const a = v[i];
31421
+ const b = v[i - n];
31422
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
31423
+ }
31424
+ return out;
31425
+ }
31426
+ function roc(v, len) {
31427
+ const out = new Array(v.length).fill(Number.NaN);
31428
+ for (let i = len; i < v.length; i++) {
31429
+ const a = v[i];
31430
+ const b = v[i - len];
31431
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
31432
+ }
31433
+ return out;
31434
+ }
31435
+ function shift(v, n) {
31436
+ const out = new Array(v.length).fill(Number.NaN);
31437
+ for (let i = 0; i < v.length; i++) {
31438
+ const j = i - n;
31439
+ if (j >= 0 && j < v.length) out[i] = v[j];
31440
+ }
31441
+ return out;
31442
+ }
31443
+ function trueRange(bars) {
31444
+ const out = new Array(bars.length).fill(Number.NaN);
31445
+ for (let i = 0; i < bars.length; i++) {
31446
+ const b = bars[i];
31447
+ if (i === 0) {
31448
+ out[i] = b.high - b.low;
31449
+ continue;
31450
+ }
31451
+ const pc = bars[i - 1].close;
31452
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
31453
+ }
31454
+ return out;
31455
+ }
31456
+ function atr(bars, len) {
31457
+ return rma(trueRange(bars), len);
31458
+ }
31459
+ function rsi(v, len) {
31460
+ const d = change(v);
31461
+ const up = map(d, (x) => Math.max(x, 0));
31462
+ const dn = map(d, (x) => Math.max(-x, 0));
31463
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
31464
+ }
31465
+ function stoch(v, hi, lo, len) {
31466
+ const hh = highest(hi, len);
31467
+ const ll = lowest(lo, len);
31468
+ const out = new Array(v.length).fill(Number.NaN);
31469
+ for (let i = 0; i < v.length; i++) {
31470
+ const x = v[i];
31471
+ const h = hh[i];
31472
+ const l = ll[i];
31473
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
31474
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
31475
+ }
31476
+ return out;
31477
+ }
31478
+ function linreg(v, len) {
31479
+ const sx = (len - 1) * len / 2;
31480
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
31481
+ const denom = len * sxx - sx * sx;
31482
+ return windowed(v, len, (w, from, to) => {
31483
+ let sy = 0;
31484
+ let sxy = 0;
31485
+ for (let k = from; k <= to; k++) {
31486
+ const x = k - from;
31487
+ sy += w[k];
31488
+ sxy += x * w[k];
31489
+ }
31490
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
31491
+ const intercept = (sy - slope * sx) / len;
31492
+ return intercept + slope * (len - 1);
31493
+ });
31494
+ }
31495
+ function swma(v) {
31496
+ const out = new Array(v.length).fill(Number.NaN);
31497
+ for (let i = 3; i < v.length; i++) {
31498
+ const a = v[i - 3];
31499
+ const b = v[i - 2];
31500
+ const c = v[i - 1];
31501
+ const d = v[i];
31502
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
31503
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
31504
+ }
31505
+ }
31506
+ return out;
31507
+ }
31508
+ function percentRank(v, len) {
31509
+ const out = new Array(v.length).fill(Number.NaN);
31510
+ for (let i = len; i < v.length; i++) {
31511
+ const x = v[i];
31512
+ if (!Number.isFinite(x)) continue;
31513
+ let below = 0;
31514
+ let count = 0;
31515
+ for (let k = i - len; k < i; k++) {
31516
+ const y = v[k];
31517
+ if (!Number.isFinite(y)) {
31518
+ count = -1;
31519
+ break;
31520
+ }
31521
+ count += 1;
31522
+ if (y <= x) below += 1;
31523
+ }
31524
+ if (count === len) out[i] = 100 * below / len;
31525
+ }
31526
+ return out;
31527
+ }
31528
+ function cumSum(v) {
31529
+ const out = new Array(v.length).fill(Number.NaN);
31530
+ let acc = 0;
31531
+ let seen = false;
31532
+ for (let i = 0; i < v.length; i++) {
31533
+ const x = v[i];
31534
+ if (Number.isFinite(x)) {
31535
+ acc += x;
31536
+ seen = true;
31537
+ }
31538
+ if (seen) out[i] = acc;
31539
+ }
31540
+ return out;
31541
+ }
31542
+
31543
+ // src/core/native-indicators/classics/shared.ts
31544
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
31545
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
31546
+ }
31547
+ function sourceInput(defval = "Close") {
31548
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
31549
+ }
31550
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
31551
+ return { key, title, type: "color", defval };
31552
+ }
31553
+ function withAlpha2(hex, alpha = 0.08) {
31554
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
31555
+ if (!m) return hex;
31556
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
31557
+ return `#${m[1]}${a}`;
31558
+ }
31559
+
31560
+ // src/core/native-indicators/classics/averages.ts
31561
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
31562
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
31563
+ function fixedAverage(type, title, shortTitle, smooth) {
31564
+ return {
31565
+ type,
31566
+ title,
31567
+ shortTitle,
31568
+ overlay: true,
31569
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
31570
+ compute: (bars, inputs) => {
31571
+ const len = num3(inputs, "length", 20);
31572
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
31573
+ const offset = Math.trunc(num3(inputs, "offset", 0));
31574
+ if (offset !== 0) values = shift(values, offset);
31575
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31576
+ }
31577
+ };
31578
+ }
31579
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
31580
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
31581
+ var movingAverage = {
31582
+ type: "moving-average",
31583
+ title: "Moving Average",
31584
+ shortTitle: "MA",
31585
+ overlay: true,
31586
+ inputs: [
31587
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
31588
+ lengthInput(20),
31589
+ sourceInput(),
31590
+ offsetInput,
31591
+ colorInput()
31592
+ ],
31593
+ compute: (bars, inputs) => {
31594
+ const len = num3(inputs, "length", 20);
31595
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31596
+ const kind = str3(inputs, "maType", "SMA");
31597
+ let values;
31598
+ switch (kind) {
31599
+ case "EMA":
31600
+ values = ema(src, len);
31601
+ break;
31602
+ case "WMA":
31603
+ values = wma(src, len);
31604
+ break;
31605
+ case "RMA":
31606
+ values = rma(src, len);
31607
+ break;
31608
+ case "VWMA":
31609
+ values = vwma(src, volumes(bars), len);
31610
+ break;
31611
+ default:
31612
+ values = sma(src, len);
31613
+ }
31614
+ const offset = Math.trunc(num3(inputs, "offset", 0));
31615
+ if (offset !== 0) values = shift(values, offset);
31616
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31617
+ }
31618
+ };
31619
+ var smoothedMa = {
31620
+ type: "rma",
31621
+ title: "Smoothed Moving Average",
31622
+ shortTitle: "RMA",
31623
+ overlay: true,
31624
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31625
+ compute: (bars, inputs) => ({
31626
+ plots: [
31627
+ {
31628
+ key: "rma",
31629
+ title: "RMA",
31630
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
31631
+ color: str3(inputs, "color", SERIES_LINE),
31632
+ width: 2
31633
+ }
31634
+ ]
31635
+ })
31636
+ };
31637
+ var zlema = {
31638
+ type: "zlema",
31639
+ title: "Zero-Lag Exponential Moving Average",
31640
+ shortTitle: "ZLEMA",
31641
+ overlay: true,
31642
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31643
+ compute: (bars, inputs) => {
31644
+ const len = num3(inputs, "length", 14);
31645
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31646
+ const lag = Math.floor((len - 1) / 2);
31647
+ const lagged = shift(src, lag);
31648
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
31649
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31650
+ }
31651
+ };
31652
+ var vidya = {
31653
+ type: "vidya",
31654
+ title: "Variable Index Dynamic Average",
31655
+ shortTitle: "VIDYA",
31656
+ overlay: true,
31657
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
31658
+ compute: (bars, inputs) => {
31659
+ const len = num3(inputs, "length", 14);
31660
+ const cmoLen = num3(inputs, "cmoLength", 9);
31661
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31662
+ const d = change(src);
31663
+ const su = new Array(src.length).fill(Number.NaN);
31664
+ const sd = new Array(src.length).fill(Number.NaN);
31665
+ for (let i = cmoLen; i < src.length; i++) {
31666
+ let u = 0;
31667
+ let w = 0;
31668
+ let ok = true;
31669
+ for (let k = i - cmoLen + 1; k <= i; k++) {
31670
+ const x = d[k];
31671
+ if (!Number.isFinite(x)) {
31672
+ ok = false;
31673
+ break;
31674
+ }
31675
+ if (x > 0) u += x;
31676
+ else w -= x;
31677
+ }
31678
+ if (ok) {
31679
+ su[i] = u;
31680
+ sd[i] = w;
31681
+ }
31682
+ }
31683
+ const alpha = 2 / (len + 1);
31684
+ const out = new Array(src.length).fill(Number.NaN);
31685
+ let prev2 = Number.NaN;
31686
+ for (let i = 0; i < src.length; i++) {
31687
+ const x = src[i];
31688
+ const u = su[i];
31689
+ const w = sd[i];
31690
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
31691
+ const total = u + w;
31692
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
31693
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
31694
+ out[i] = prev2;
31695
+ }
31696
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31697
+ }
31698
+ };
31699
+ var maEnvelope = {
31700
+ type: "ma-envelope",
31701
+ title: "Moving Average Envelope",
31702
+ shortTitle: "MA Env",
31703
+ overlay: true,
31704
+ inputs: [
31705
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
31706
+ lengthInput(20),
31707
+ sourceInput(),
31708
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
31709
+ colorInput()
31710
+ ],
31711
+ compute: (bars, inputs) => {
31712
+ const len = num3(inputs, "length", 20);
31713
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31714
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
31715
+ const k = num3(inputs, "percent", 2.5) / 100;
31716
+ const ink = str3(inputs, "color", SERIES_LINE);
31717
+ return {
31718
+ plots: [
31719
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
31720
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
31721
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
31722
+ ],
31723
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
31724
+ };
31725
+ }
31726
+ };
31727
+ var linearRegression = {
31728
+ type: "linear-regression",
31729
+ title: "Linear Regression Curve",
31730
+ shortTitle: "LinReg",
31731
+ overlay: true,
31732
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31733
+ compute: (bars, inputs) => ({
31734
+ plots: [
31735
+ {
31736
+ key: "linreg",
31737
+ title: "LinReg",
31738
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
31739
+ color: str3(inputs, "color", SERIES_LINE),
31740
+ width: 2
31741
+ }
31742
+ ]
31743
+ })
31744
+ };
31745
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
31746
+
31747
+ // src/core/native-indicators/classics/bands.ts
31748
+ function bollinger(bars, inputs) {
31749
+ const len = num3(inputs, "length", 20);
31750
+ const mult = num3(inputs, "mult", 2);
31751
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31752
+ const basis = sma(src, len);
31753
+ const dev = map(stdev(src, len), (x) => x * mult);
31754
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
31755
+ }
31756
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
31757
+ var bollingerBands = {
31758
+ type: "bollinger-bands",
31759
+ title: "Bollinger Bands",
31760
+ shortTitle: "BB",
31761
+ overlay: true,
31762
+ inputs: [...BB_INPUTS, colorInput()],
31763
+ compute: (bars, inputs) => {
31764
+ const { basis, upper, lower } = bollinger(bars, inputs);
31765
+ const ink = str3(inputs, "color", SERIES_LINE);
31766
+ return {
31767
+ plots: [
31768
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
31769
+ { key: "upper", title: "Upper", values: upper, color: ink },
31770
+ { key: "lower", title: "Lower", values: lower, color: ink }
31771
+ ],
31772
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
31773
+ };
31774
+ }
31775
+ };
31776
+ var percentB = {
31777
+ type: "percent-b",
31778
+ title: "Bollinger Bands %B",
31779
+ shortTitle: "%B",
31780
+ overlay: false,
31781
+ inputs: [...BB_INPUTS, colorInput()],
31782
+ compute: (bars, inputs) => {
31783
+ const { upper, lower } = bollinger(bars, inputs);
31784
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31785
+ const values = src.map((x, i) => {
31786
+ const u = upper[i];
31787
+ const l = lower[i];
31788
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
31789
+ });
31790
+ return {
31791
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
31792
+ levels: [
31793
+ { key: "upper", price: 1, color: NEUTRAL },
31794
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
31795
+ { key: "lower", price: 0, color: NEUTRAL }
31796
+ ]
31797
+ };
31798
+ }
31799
+ };
31800
+ var bandwidth = {
31801
+ type: "bandwidth",
31802
+ title: "Bollinger Bands Width",
31803
+ shortTitle: "BandWidth",
31804
+ overlay: false,
31805
+ inputs: [...BB_INPUTS, colorInput()],
31806
+ compute: (bars, inputs) => {
31807
+ const { basis, upper, lower } = bollinger(bars, inputs);
31808
+ const values = basis.map((b, i) => {
31809
+ const u = upper[i];
31810
+ const l = lower[i];
31811
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
31812
+ });
31813
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31814
+ }
31815
+ };
31816
+ var keltner = {
31817
+ type: "keltner-channels",
31818
+ title: "Keltner Channels",
31819
+ shortTitle: "KC",
31820
+ overlay: true,
31821
+ inputs: [
31822
+ lengthInput(20),
31823
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
31824
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31825
+ sourceInput(),
31826
+ colorInput()
31827
+ ],
31828
+ compute: (bars, inputs) => {
31829
+ const len = num3(inputs, "length", 20);
31830
+ const mult = num3(inputs, "mult", 2);
31831
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
31832
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
31833
+ const ink = str3(inputs, "color", SERIES_LINE);
31834
+ return {
31835
+ plots: [
31836
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
31837
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
31838
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
31839
+ ],
31840
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
31841
+ };
31842
+ }
31843
+ };
31844
+ var donchian = {
31845
+ type: "donchian-channels",
31846
+ title: "Donchian Channels",
31847
+ shortTitle: "DC",
31848
+ overlay: true,
31849
+ inputs: [lengthInput(20), colorInput()],
31850
+ compute: (bars, inputs) => {
31851
+ const len = num3(inputs, "length", 20);
31852
+ const upper = highest(highs(bars), len);
31853
+ const lower = lowest(lows(bars), len);
31854
+ const ink = str3(inputs, "color", SERIES_LINE);
31855
+ return {
31856
+ plots: [
31857
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
31858
+ { key: "upper", title: "Upper", values: upper, color: ink },
31859
+ { key: "lower", title: "Lower", values: lower, color: ink }
31860
+ ],
31861
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
31862
+ };
31863
+ }
31864
+ };
31865
+ var chandelier = {
31866
+ type: "chandelier-exit",
31867
+ title: "Chandelier Exit",
31868
+ shortTitle: "CE",
31869
+ overlay: true,
31870
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
31871
+ compute: (bars, inputs) => {
31872
+ const len = num3(inputs, "length", 22);
31873
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
31874
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
31875
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
31876
+ return {
31877
+ plots: [
31878
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
31879
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
31880
+ ]
31881
+ };
31882
+ }
31883
+ };
31884
+ var chandeKroll = {
31885
+ type: "chande-kroll-stop",
31886
+ title: "Chande Kroll Stop",
31887
+ shortTitle: "CKS",
31888
+ overlay: true,
31889
+ inputs: [
31890
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31891
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
31892
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
31893
+ ],
31894
+ compute: (bars, inputs) => {
31895
+ const p = num3(inputs, "p", 10);
31896
+ const x = num3(inputs, "x", 1);
31897
+ const q = num3(inputs, "q", 9);
31898
+ const range = map(atr(bars, p), (v) => v * x);
31899
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
31900
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
31901
+ return {
31902
+ plots: [
31903
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
31904
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
31905
+ ]
31906
+ };
31907
+ }
31908
+ };
31909
+ var supertrend = {
31910
+ type: "supertrend",
31911
+ title: "SuperTrend",
31912
+ shortTitle: "SuperTrend",
31913
+ overlay: true,
31914
+ inputs: [
31915
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31916
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
31917
+ ],
31918
+ compute: (bars, inputs) => {
31919
+ const len = num3(inputs, "atrLength", 10);
31920
+ const mult = num3(inputs, "mult", 3);
31921
+ const range = atr(bars, len);
31922
+ const n = bars.length;
31923
+ const st = new Array(n).fill(Number.NaN);
31924
+ const dir = new Array(n).fill(1);
31925
+ let up = Number.NaN;
31926
+ let dn = Number.NaN;
31927
+ let trend = 1;
31928
+ for (let i = 0; i < n; i++) {
31929
+ const r = range[i];
31930
+ if (!Number.isFinite(r)) continue;
31931
+ const b = bars[i];
31932
+ const mid = (b.high + b.low) / 2;
31933
+ const basicUp = mid - mult * r;
31934
+ const basicDn = mid + mult * r;
31935
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
31936
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
31937
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
31938
+ if (trend === 1 && b.close < up) trend = -1;
31939
+ else if (trend === -1 && b.close > dn) trend = 1;
31940
+ dir[i] = trend;
31941
+ st[i] = trend === 1 ? up : dn;
31942
+ }
31943
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
31944
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
31945
+ }
31946
+ };
31947
+ var alligator = {
31948
+ type: "williams-alligator",
31949
+ title: "Williams Alligator",
31950
+ shortTitle: "Alligator",
31951
+ overlay: true,
31952
+ inputs: [
31953
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
31954
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
31955
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
31956
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
31957
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
31958
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
31959
+ ],
31960
+ compute: (bars, inputs) => {
31961
+ const hl2 = sourceValues(bars, "HL2");
31962
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
31963
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
31964
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
31965
+ return {
31966
+ plots: [
31967
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
31968
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
31969
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
31970
+ ]
31971
+ };
31972
+ }
31973
+ };
31974
+ var gator = {
31975
+ type: "gator-oscillator",
31976
+ title: "Gator Oscillator",
31977
+ shortTitle: "Gator Oscillator",
31978
+ overlay: false,
31979
+ inputs: [],
31980
+ compute: (bars) => {
31981
+ const hl2 = sourceValues(bars, "HL2");
31982
+ const jaw = shift(rma(hl2, 13), 8);
31983
+ const teeth = shift(rma(hl2, 8), 5);
31984
+ const lips = shift(rma(hl2, 5), 3);
31985
+ const upper = map(sub(jaw, teeth), Math.abs);
31986
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
31987
+ const colorByExpansion = (v) => v.map((x, i) => {
31988
+ if (!Number.isFinite(x)) return null;
31989
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
31990
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
31991
+ });
31992
+ return {
31993
+ plots: [
31994
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
31995
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
31996
+ ]
31997
+ };
31998
+ }
31999
+ };
32000
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
32001
+
32002
+ // src/core/native-indicators/classics/oscillators.ts
32003
+ function guideLevels(low, high, mid) {
32004
+ const levels = [
32005
+ { key: "upper", price: high, color: NEUTRAL },
32006
+ { key: "lower", price: low, color: NEUTRAL }
32007
+ ];
32008
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
32009
+ return levels;
32010
+ }
32011
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
32012
+ function histColors(values) {
32013
+ return values.map((x, i) => {
32014
+ if (!Number.isFinite(x)) return null;
32015
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
32016
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
32017
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
32018
+ return rising ? `${BEARISH}80` : BEARISH;
32019
+ });
32020
+ }
32021
+ var rsiSpec = {
32022
+ type: "rsi",
32023
+ title: "Relative Strength Index",
32024
+ shortTitle: "RSI",
32025
+ overlay: false,
32026
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
32027
+ compute: (bars, inputs) => ({
32028
+ plots: [
32029
+ {
32030
+ key: "rsi",
32031
+ title: "RSI",
32032
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
32033
+ color: str3(inputs, "color", SERIES_LINE),
32034
+ width: 2
32035
+ }
32036
+ ],
32037
+ levels: guideLevels(30, 70, 50)
32038
+ })
32039
+ };
32040
+ var stochastic = {
32041
+ type: "stochastic",
32042
+ title: "Stochastic",
32043
+ shortTitle: "Stoch",
32044
+ overlay: false,
32045
+ inputs: [
32046
+ lengthInput(14, "kLength", "%K length"),
32047
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
32048
+ lengthInput(3, "dLength", "%D smoothing", 500)
32049
+ ],
32050
+ compute: (bars, inputs) => {
32051
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
32052
+ const d = sma(k, num3(inputs, "dLength", 3));
32053
+ return {
32054
+ plots: [
32055
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
32056
+ { key: "d", title: "%D", values: d, color: WARNING }
32057
+ ],
32058
+ levels: guideLevels(20, 80)
32059
+ };
32060
+ }
32061
+ };
32062
+ var stochasticRsi = {
32063
+ type: "stochastic-rsi",
32064
+ title: "Stochastic RSI",
32065
+ shortTitle: "Stoch RSI",
32066
+ overlay: false,
32067
+ inputs: [
32068
+ lengthInput(14, "rsiLength", "RSI length"),
32069
+ lengthInput(14, "stochLength", "Stochastic length"),
32070
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
32071
+ lengthInput(3, "dLength", "%D smoothing", 500),
32072
+ sourceInput()
32073
+ ],
32074
+ compute: (bars, inputs) => {
32075
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
32076
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
32077
+ const d = sma(k, num3(inputs, "dLength", 3));
32078
+ return {
32079
+ plots: [
32080
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
32081
+ { key: "d", title: "%D", values: d, color: WARNING }
32082
+ ],
32083
+ levels: guideLevels(20, 80)
32084
+ };
32085
+ }
32086
+ };
32087
+ var macd = {
32088
+ type: "macd",
32089
+ title: "MACD",
32090
+ overlay: false,
32091
+ inputs: [
32092
+ lengthInput(12, "fastLength", "Fast length"),
32093
+ lengthInput(26, "slowLength", "Slow length"),
32094
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
32095
+ sourceInput()
32096
+ ],
32097
+ compute: (bars, inputs) => {
32098
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32099
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
32100
+ const signal = ema(line, num3(inputs, "signalLength", 9));
32101
+ const hist = sub(line, signal);
32102
+ return {
32103
+ plots: [
32104
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
32105
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
32106
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32107
+ ],
32108
+ levels: ZERO_LEVEL
32109
+ };
32110
+ }
32111
+ };
32112
+ var ppo = {
32113
+ type: "ppo",
32114
+ title: "Percentage Price Oscillator",
32115
+ shortTitle: "PPO",
32116
+ overlay: false,
32117
+ inputs: [
32118
+ lengthInput(12, "fastLength", "Fast length"),
32119
+ lengthInput(26, "slowLength", "Slow length"),
32120
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
32121
+ sourceInput()
32122
+ ],
32123
+ compute: (bars, inputs) => {
32124
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32125
+ const fast = ema(src, num3(inputs, "fastLength", 12));
32126
+ const slow = ema(src, num3(inputs, "slowLength", 26));
32127
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
32128
+ const signal = ema(line, num3(inputs, "signalLength", 9));
32129
+ const hist = sub(line, signal);
32130
+ return {
32131
+ plots: [
32132
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
32133
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
32134
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32135
+ ],
32136
+ levels: ZERO_LEVEL
32137
+ };
32138
+ }
32139
+ };
32140
+ var awesome = {
32141
+ type: "awesome-oscillator",
32142
+ title: "Awesome Oscillator",
32143
+ shortTitle: "AO",
32144
+ overlay: false,
32145
+ inputs: [],
32146
+ compute: (bars) => {
32147
+ const hl2 = sourceValues(bars, "HL2");
32148
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
32149
+ const colors = ao.map((x, i) => {
32150
+ if (!Number.isFinite(x)) return null;
32151
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
32152
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
32153
+ });
32154
+ return {
32155
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
32156
+ levels: ZERO_LEVEL
32157
+ };
32158
+ }
32159
+ };
32160
+ var cci = {
32161
+ type: "commodity-channel-index",
32162
+ title: "Commodity Channel Index",
32163
+ shortTitle: "CCI",
32164
+ overlay: false,
32165
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
32166
+ compute: (bars, inputs) => {
32167
+ const len = num3(inputs, "length", 20);
32168
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
32169
+ const basis = sma(src, len);
32170
+ const dev = new Array(src.length).fill(Number.NaN);
32171
+ for (let i = len - 1; i < src.length; i++) {
32172
+ const mean = basis[i];
32173
+ if (!Number.isFinite(mean)) continue;
32174
+ let d = 0;
32175
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
32176
+ dev[i] = d / len;
32177
+ }
32178
+ const values = src.map((x, i) => {
32179
+ const b = basis[i];
32180
+ const d = dev[i];
32181
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
32182
+ });
32183
+ return {
32184
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32185
+ levels: guideLevels(-100, 100, 0)
32186
+ };
32187
+ }
32188
+ };
32189
+ var williamsR = {
32190
+ type: "williams-percent-r",
32191
+ title: "Williams %R",
32192
+ shortTitle: "%R",
32193
+ overlay: false,
32194
+ inputs: [lengthInput(14), colorInput()],
32195
+ compute: (bars, inputs) => {
32196
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
32197
+ return {
32198
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32199
+ levels: guideLevels(-80, -20)
32200
+ };
32201
+ }
32202
+ };
32203
+ var cmo = {
32204
+ type: "chande-momentum-oscillator",
32205
+ title: "Chande Momentum Oscillator",
32206
+ shortTitle: "CMO",
32207
+ overlay: false,
32208
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
32209
+ compute: (bars, inputs) => {
32210
+ const len = num3(inputs, "length", 9);
32211
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
32212
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
32213
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
32214
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
32215
+ return {
32216
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32217
+ levels: guideLevels(-50, 50, 0)
32218
+ };
32219
+ }
32220
+ };
32221
+ var connorsRsi = {
32222
+ type: "connors-rsi",
32223
+ title: "Connors RSI",
32224
+ shortTitle: "CRSI",
32225
+ overlay: false,
32226
+ inputs: [
32227
+ lengthInput(3, "rsiLength", "RSI length"),
32228
+ lengthInput(2, "streakLength", "Streak RSI length"),
32229
+ lengthInput(100, "rankLength", "Percent-rank length"),
32230
+ colorInput()
32231
+ ],
32232
+ compute: (bars, inputs) => {
32233
+ const c = closes(bars);
32234
+ const streak = new Array(c.length).fill(0);
32235
+ for (let i = 1; i < c.length; i++) {
32236
+ const d = c[i] - c[i - 1];
32237
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
32238
+ }
32239
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
32240
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
32241
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
32242
+ const values = c.map((_, i) => {
32243
+ const x = a[i];
32244
+ const y = b[i];
32245
+ const z = r[i];
32246
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
32247
+ });
32248
+ return {
32249
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32250
+ levels: guideLevels(20, 80, 50)
32251
+ };
32252
+ }
32253
+ };
32254
+ var fisher = {
32255
+ type: "fisher-transform",
32256
+ title: "Fisher Transform",
32257
+ shortTitle: "Fisher",
32258
+ overlay: false,
32259
+ inputs: [lengthInput(9)],
32260
+ compute: (bars, inputs) => {
32261
+ const len = num3(inputs, "length", 9);
32262
+ const hl2 = sourceValues(bars, "HL2");
32263
+ const hi = highest(hl2, len);
32264
+ const lo = lowest(hl2, len);
32265
+ const n = bars.length;
32266
+ const out = new Array(n).fill(Number.NaN);
32267
+ let value = 0;
32268
+ let fish = 0;
32269
+ for (let i = 0; i < n; i++) {
32270
+ const h = hi[i];
32271
+ const l = lo[i];
32272
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
32273
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
32274
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
32275
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
32276
+ out[i] = fish;
32277
+ }
32278
+ return {
32279
+ plots: [
32280
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
32281
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
32282
+ ],
32283
+ levels: ZERO_LEVEL
32284
+ };
32285
+ }
32286
+ };
32287
+ var trix = {
32288
+ type: "trix",
32289
+ title: "TRIX",
32290
+ overlay: false,
32291
+ inputs: [lengthInput(18), colorInput()],
32292
+ compute: (bars, inputs) => {
32293
+ const len = num3(inputs, "length", 18);
32294
+ const logClose = map(closes(bars), Math.log);
32295
+ const smooth = ema(ema(ema(logClose, len), len), len);
32296
+ const values = map(change(smooth), (x) => x * 1e4);
32297
+ return {
32298
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32299
+ levels: ZERO_LEVEL
32300
+ };
32301
+ }
32302
+ };
32303
+ function tsiLine(src, long, short) {
32304
+ const mom = change(src);
32305
+ const numer = ema(ema(mom, long), short);
32306
+ const denom = ema(ema(map(mom, Math.abs), long), short);
32307
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
32308
+ }
32309
+ var tsi = {
32310
+ type: "true-strength-index",
32311
+ title: "True Strength Index",
32312
+ shortTitle: "TSI",
32313
+ overlay: false,
32314
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
32315
+ compute: (bars, inputs) => {
32316
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
32317
+ return {
32318
+ plots: [
32319
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
32320
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
32321
+ ],
32322
+ levels: ZERO_LEVEL
32323
+ };
32324
+ }
32325
+ };
32326
+ var smiErgodic = {
32327
+ type: "smi-ergodic",
32328
+ title: "SMI Ergodic Oscillator",
32329
+ shortTitle: "SMIE",
32330
+ overlay: false,
32331
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
32332
+ compute: (bars, inputs) => {
32333
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
32334
+ const signal = ema(line, num3(inputs, "signalLength", 5));
32335
+ const osc = sub(line, signal);
32336
+ return {
32337
+ plots: [
32338
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
32339
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
32340
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32341
+ ],
32342
+ levels: ZERO_LEVEL
32343
+ };
32344
+ }
32345
+ };
32346
+ var stc = {
32347
+ type: "schaff-trend-cycle",
32348
+ title: "Schaff Trend Cycle",
32349
+ shortTitle: "Schaff Trend Cycle",
32350
+ overlay: false,
32351
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
32352
+ compute: (bars, inputs) => {
32353
+ const len = num3(inputs, "cycleLength", 10);
32354
+ const src = closes(bars);
32355
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
32356
+ const stochOf = (v) => {
32357
+ const lo = lowest(v, len);
32358
+ const hi = highest(v, len);
32359
+ const out = new Array(v.length).fill(Number.NaN);
32360
+ let prevRaw = Number.NaN;
32361
+ let smoothed = Number.NaN;
32362
+ for (let i = 0; i < v.length; i++) {
32363
+ const x = v[i];
32364
+ const l = lo[i];
32365
+ const h = hi[i];
32366
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
32367
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
32368
+ prevRaw = raw;
32369
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
32370
+ out[i] = smoothed;
32371
+ }
32372
+ return out;
32373
+ };
32374
+ const values = stochOf(stochOf(macdLine));
32375
+ return {
32376
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
32377
+ levels: guideLevels(25, 75)
32378
+ };
32379
+ }
32380
+ };
32381
+ var kst = {
32382
+ type: "know-sure-thing",
32383
+ title: "Know Sure Thing",
32384
+ shortTitle: "KST",
32385
+ overlay: false,
32386
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
32387
+ compute: (bars, inputs) => {
32388
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32389
+ const line = [
32390
+ sma(roc(src, 10), 10),
32391
+ sma(roc(src, 15), 10),
32392
+ sma(roc(src, 20), 10),
32393
+ sma(roc(src, 30), 15)
32394
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
32395
+ return {
32396
+ plots: [
32397
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
32398
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
32399
+ ],
32400
+ levels: ZERO_LEVEL
32401
+ };
32402
+ }
32403
+ };
32404
+ var coppock = {
32405
+ type: "coppock-curve",
32406
+ title: "Coppock Curve",
32407
+ shortTitle: "Coppock",
32408
+ overlay: false,
32409
+ inputs: [
32410
+ lengthInput(10, "wmaLength", "WMA length"),
32411
+ lengthInput(14, "longRoc", "Long RoC length"),
32412
+ lengthInput(11, "shortRoc", "Short RoC length"),
32413
+ sourceInput(),
32414
+ colorInput()
32415
+ ],
32416
+ compute: (bars, inputs) => {
32417
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32418
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
32419
+ return {
32420
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32421
+ levels: ZERO_LEVEL
32422
+ };
32423
+ }
32424
+ };
32425
+ var dpo = {
32426
+ type: "detrended-price-oscillator",
32427
+ title: "Detrended Price Oscillator",
32428
+ shortTitle: "DPO",
32429
+ overlay: false,
32430
+ inputs: [lengthInput(21), colorInput()],
32431
+ compute: (bars, inputs) => {
32432
+ const len = num3(inputs, "length", 21);
32433
+ const barsback = Math.floor(len / 2) + 1;
32434
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
32435
+ return {
32436
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32437
+ levels: ZERO_LEVEL
32438
+ };
32439
+ }
32440
+ };
32441
+ var rocSpec = {
32442
+ type: "rate-of-change",
32443
+ title: "Rate of Change",
32444
+ shortTitle: "ROC",
32445
+ overlay: false,
32446
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
32447
+ compute: (bars, inputs) => ({
32448
+ plots: [
32449
+ {
32450
+ key: "roc",
32451
+ title: "RoC",
32452
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
32453
+ color: str3(inputs, "color", SERIES_LINE),
32454
+ width: 2
32455
+ }
32456
+ ],
32457
+ levels: ZERO_LEVEL
32458
+ })
32459
+ };
32460
+ var ultimate = {
32461
+ type: "ultimate-oscillator",
32462
+ title: "Ultimate Oscillator",
32463
+ shortTitle: "UO",
32464
+ overlay: false,
32465
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
32466
+ compute: (bars, inputs) => {
32467
+ const n = bars.length;
32468
+ const bp = new Array(n).fill(Number.NaN);
32469
+ const tr = new Array(n).fill(Number.NaN);
32470
+ for (let i = 0; i < n; i++) {
32471
+ const b = bars[i];
32472
+ const pc = i > 0 ? bars[i - 1].close : b.close;
32473
+ const lo = Math.min(b.low, pc);
32474
+ const hi = Math.max(b.high, pc);
32475
+ bp[i] = b.close - lo;
32476
+ tr[i] = hi - lo;
32477
+ }
32478
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
32479
+ const a7 = avg(num3(inputs, "fastLength", 7));
32480
+ const a14 = avg(num3(inputs, "middleLength", 14));
32481
+ const a28 = avg(num3(inputs, "slowLength", 28));
32482
+ const values = a7.map((x, i) => {
32483
+ const y = a14[i];
32484
+ const z = a28[i];
32485
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
32486
+ });
32487
+ return {
32488
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32489
+ levels: guideLevels(30, 70, 50)
32490
+ };
32491
+ }
32492
+ };
32493
+ var rvgi = {
32494
+ type: "relative-vigor-index",
32495
+ title: "Relative Vigor Index",
32496
+ shortTitle: "RVGI",
32497
+ overlay: false,
32498
+ inputs: [lengthInput(10)],
32499
+ compute: (bars, inputs) => {
32500
+ const len = num3(inputs, "length", 10);
32501
+ const co = swma(sub(closes(bars), opens(bars)));
32502
+ const hl = swma(sub(highs(bars), lows(bars)));
32503
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
32504
+ return {
32505
+ plots: [
32506
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
32507
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
32508
+ ],
32509
+ levels: ZERO_LEVEL
32510
+ };
32511
+ }
32512
+ };
32513
+ var relVolatility = {
32514
+ type: "relative-volatility-index",
32515
+ title: "Relative Volatility Index",
32516
+ shortTitle: "RVI",
32517
+ overlay: false,
32518
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
32519
+ compute: (bars, inputs) => {
32520
+ const smoothLen = num3(inputs, "smoothLength", 14);
32521
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32522
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
32523
+ const d = change(src);
32524
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
32525
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
32526
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
32527
+ return {
32528
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32529
+ levels: guideLevels(20, 80, 50)
32530
+ };
32531
+ }
32532
+ };
32533
+ var bop = {
32534
+ type: "balance-of-power",
32535
+ title: "Balance of Power",
32536
+ shortTitle: "BOP",
32537
+ overlay: false,
32538
+ inputs: [colorInput()],
32539
+ compute: (bars, inputs) => {
32540
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
32541
+ return {
32542
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32543
+ levels: ZERO_LEVEL
32544
+ };
32545
+ }
32546
+ };
32547
+ var elderRay = {
32548
+ type: "elder-ray",
32549
+ title: "Elder Ray",
32550
+ overlay: false,
32551
+ inputs: [lengthInput(13)],
32552
+ compute: (bars, inputs) => {
32553
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
32554
+ return {
32555
+ plots: [
32556
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
32557
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
32558
+ ],
32559
+ levels: ZERO_LEVEL
32560
+ };
32561
+ }
32562
+ };
32563
+ var ttmSqueeze = {
32564
+ type: "ttm-squeeze",
32565
+ title: "TTM Squeeze",
32566
+ overlay: false,
32567
+ inputs: [
32568
+ lengthInput(20),
32569
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
32570
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
32571
+ ],
32572
+ compute: (bars, inputs) => {
32573
+ const len = num3(inputs, "length", 20);
32574
+ const c = closes(bars);
32575
+ const basis = sma(c, len);
32576
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
32577
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
32578
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
32579
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
32580
+ const mom = linreg(sub(c, anchor), len);
32581
+ const squeezed = c.map((_, i) => {
32582
+ const d = dev[i];
32583
+ const r = kcRange[i];
32584
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
32585
+ });
32586
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
32587
+ return {
32588
+ plots: [
32589
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
32590
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
32591
+ ]
32592
+ };
32593
+ }
32594
+ };
32595
+ var oscillatorSpecs = [
32596
+ rsiSpec,
32597
+ stochastic,
32598
+ stochasticRsi,
32599
+ macd,
32600
+ ppo,
32601
+ awesome,
32602
+ cci,
32603
+ williamsR,
32604
+ cmo,
32605
+ connorsRsi,
32606
+ fisher,
32607
+ trix,
32608
+ tsi,
32609
+ smiErgodic,
32610
+ stc,
32611
+ kst,
32612
+ coppock,
32613
+ dpo,
32614
+ rocSpec,
32615
+ ultimate,
32616
+ rvgi,
32617
+ relVolatility,
32618
+ bop,
32619
+ elderRay,
32620
+ ttmSqueeze
32621
+ ];
32622
+
32623
+ // src/core/native-indicators/classics/trend.ts
32624
+ var aroon = {
32625
+ type: "aroon",
32626
+ title: "Aroon",
32627
+ overlay: false,
32628
+ inputs: [lengthInput(14)],
32629
+ compute: (bars, inputs) => {
32630
+ const len = num3(inputs, "length", 14);
32631
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
32632
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
32633
+ return {
32634
+ plots: [
32635
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
32636
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
32637
+ ],
32638
+ levels: [
32639
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
32640
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
32641
+ ]
32642
+ };
32643
+ }
32644
+ };
32645
+ var adx = {
32646
+ type: "average-directional-index",
32647
+ title: "Average Directional Index",
32648
+ shortTitle: "ADX",
32649
+ overlay: false,
32650
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
32651
+ compute: (bars, inputs) => {
32652
+ const diLen = num3(inputs, "diLength", 14);
32653
+ const adxLen = num3(inputs, "adxLength", 14);
32654
+ const n = bars.length;
32655
+ const plusDm = new Array(n).fill(Number.NaN);
32656
+ const minusDm = new Array(n).fill(Number.NaN);
32657
+ for (let i = 1; i < n; i++) {
32658
+ const up = bars[i].high - bars[i - 1].high;
32659
+ const dn = bars[i - 1].low - bars[i].low;
32660
+ plusDm[i] = up > dn && up > 0 ? up : 0;
32661
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
32662
+ }
32663
+ const atrLine = rma(trueRange(bars), diLen);
32664
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
32665
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
32666
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
32667
+ return {
32668
+ plots: [
32669
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
32670
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
32671
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
32672
+ ],
32673
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
32674
+ };
32675
+ }
32676
+ };
32677
+ var vortex = {
32678
+ type: "vortex-indicator",
32679
+ title: "Vortex Indicator",
32680
+ shortTitle: "VI",
32681
+ overlay: false,
32682
+ inputs: [lengthInput(14)],
32683
+ compute: (bars, inputs) => {
32684
+ const len = num3(inputs, "length", 14);
32685
+ const n = bars.length;
32686
+ const vmPlus = new Array(n).fill(Number.NaN);
32687
+ const vmMinus = new Array(n).fill(Number.NaN);
32688
+ for (let i = 1; i < n; i++) {
32689
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
32690
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
32691
+ }
32692
+ const trSum = sum(trueRange(bars), len);
32693
+ return {
32694
+ plots: [
32695
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
32696
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
32697
+ ],
32698
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
32699
+ };
32700
+ }
32701
+ };
32702
+ var trendSpecs = [aroon, adx, vortex];
32703
+
32704
+ // src/core/native-indicators/classics/volatility.ts
32705
+ var atrSpec = {
32706
+ type: "average-true-range",
32707
+ title: "Average True Range",
32708
+ shortTitle: "ATR",
32709
+ overlay: false,
32710
+ inputs: [lengthInput(14), colorInput()],
32711
+ compute: (bars, inputs) => ({
32712
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
32713
+ })
32714
+ };
32715
+ var historicalVolatility = {
32716
+ type: "historical-volatility",
32717
+ title: "Historical Volatility",
32718
+ shortTitle: "HV",
32719
+ overlay: false,
32720
+ inputs: [
32721
+ lengthInput(10),
32722
+ { key: "annual", title: "Periods per year", type: "int", defval: 365, min: 1, max: 1e5, step: 1, tooltip: "Annualization factor (365 for daily crypto bars, 252 for stock sessions)" },
32723
+ colorInput()
32724
+ ],
32725
+ compute: (bars, inputs) => {
32726
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
32727
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
32728
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32729
+ }
32730
+ };
32731
+ var chaikinVolatility = {
32732
+ type: "chaikin-volatility",
32733
+ title: "Chaikin Volatility",
32734
+ shortTitle: "CHV",
32735
+ overlay: false,
32736
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
32737
+ compute: (bars, inputs) => {
32738
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
32739
+ return {
32740
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32741
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32742
+ };
32743
+ }
32744
+ };
32745
+ var massIndex = {
32746
+ type: "mass-index",
32747
+ title: "Mass Index",
32748
+ shortTitle: "MI",
32749
+ overlay: false,
32750
+ inputs: [lengthInput(25), colorInput()],
32751
+ compute: (bars, inputs) => {
32752
+ const range = sub(highs(bars), lows(bars));
32753
+ const single = ema(range, 9);
32754
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
32755
+ return {
32756
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32757
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
32758
+ };
32759
+ }
32760
+ };
32761
+ var standardDeviation = {
32762
+ type: "standard-deviation",
32763
+ title: "Standard Deviation",
32764
+ shortTitle: "StdDev",
32765
+ overlay: false,
32766
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
32767
+ compute: (bars, inputs) => ({
32768
+ plots: [
32769
+ {
32770
+ key: "stdev",
32771
+ title: "StdDev",
32772
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
32773
+ color: str3(inputs, "color", SERIES_LINE),
32774
+ width: 2
32775
+ }
32776
+ ]
32777
+ })
32778
+ };
32779
+ var ulcerIndex = {
32780
+ type: "ulcer-index",
32781
+ title: "Ulcer Index",
32782
+ overlay: false,
32783
+ inputs: [lengthInput(14), colorInput()],
32784
+ compute: (bars, inputs) => {
32785
+ const len = num3(inputs, "length", 14);
32786
+ const c = closes(bars);
32787
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
32788
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
32789
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32790
+ }
32791
+ };
32792
+ var choppiness = {
32793
+ type: "choppiness-index",
32794
+ title: "Choppiness Index",
32795
+ shortTitle: "CHOP",
32796
+ overlay: false,
32797
+ inputs: [lengthInput(14), colorInput()],
32798
+ compute: (bars, inputs) => {
32799
+ const len = num3(inputs, "length", 14);
32800
+ const trSum = sum(trueRange(bars), len);
32801
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
32802
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
32803
+ return {
32804
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32805
+ levels: [
32806
+ { key: "upper", price: 61.8, color: NEUTRAL },
32807
+ { key: "lower", price: 38.2, color: NEUTRAL }
32808
+ ]
32809
+ };
32810
+ }
32811
+ };
32812
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
32813
+
32814
+ // src/core/native-indicators/classics/volume.ts
32815
+ function moneyFlowVolume(bars) {
32816
+ return bars.map((b) => {
32817
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
32818
+ if (b.high === b.low) return 0;
32819
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
32820
+ });
32821
+ }
32822
+ var obv = {
32823
+ type: "on-balance-volume",
32824
+ title: "On Balance Volume",
32825
+ shortTitle: "OBV",
32826
+ overlay: false,
32827
+ inputs: [colorInput()],
32828
+ compute: (bars, inputs) => {
32829
+ const vol = volumes(bars);
32830
+ const d = change(closes(bars));
32831
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
32832
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32833
+ }
32834
+ };
32835
+ var accDist = {
32836
+ type: "accumulation-distribution",
32837
+ title: "Accumulation / Distribution",
32838
+ shortTitle: "A/D",
32839
+ overlay: false,
32840
+ inputs: [colorInput()],
32841
+ compute: (bars, inputs) => ({
32842
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
32843
+ })
32844
+ };
32845
+ var cmf = {
32846
+ type: "chaikin-money-flow",
32847
+ title: "Chaikin Money Flow",
32848
+ shortTitle: "CMF",
32849
+ overlay: false,
32850
+ inputs: [lengthInput(20), colorInput()],
32851
+ compute: (bars, inputs) => {
32852
+ const len = num3(inputs, "length", 20);
32853
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
32854
+ return {
32855
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32856
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32857
+ };
32858
+ }
32859
+ };
32860
+ var chaikinOscillator = {
32861
+ type: "chaikin-oscillator",
32862
+ title: "Chaikin Oscillator",
32863
+ shortTitle: "Chaikin Osc",
32864
+ overlay: false,
32865
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
32866
+ compute: (bars, inputs) => {
32867
+ const ad = cumSum(moneyFlowVolume(bars));
32868
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
32869
+ return {
32870
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32871
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32872
+ };
32873
+ }
32874
+ };
32875
+ var eom = {
32876
+ type: "ease-of-movement",
32877
+ title: "Ease of Movement",
32878
+ shortTitle: "EOM",
32879
+ overlay: false,
32880
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
32881
+ compute: (bars, inputs) => {
32882
+ const div = num3(inputs, "divisor", 1e4);
32883
+ const hl2 = sourceValues(bars, "HL2");
32884
+ const move = change(hl2);
32885
+ const raw = bars.map((b, i) => {
32886
+ const m = move[i];
32887
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
32888
+ return div * m * (b.high - b.low) / b.volume;
32889
+ });
32890
+ return {
32891
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32892
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32893
+ };
32894
+ }
32895
+ };
32896
+ var forceIndex = {
32897
+ type: "force-index",
32898
+ title: "Force Index",
32899
+ shortTitle: "FI",
32900
+ overlay: false,
32901
+ inputs: [lengthInput(13), colorInput()],
32902
+ compute: (bars, inputs) => {
32903
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
32904
+ return {
32905
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32906
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32907
+ };
32908
+ }
32909
+ };
32910
+ var klinger = {
32911
+ type: "klinger-oscillator",
32912
+ title: "Klinger Oscillator",
32913
+ shortTitle: "KVO",
32914
+ overlay: false,
32915
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
32916
+ compute: (bars, inputs) => {
32917
+ const hlc3 = sourceValues(bars, "HLC3");
32918
+ const trend = change(hlc3);
32919
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
32920
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
32921
+ return {
32922
+ plots: [
32923
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
32924
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
32925
+ ],
32926
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32927
+ };
32928
+ }
32929
+ };
32930
+ var mfi = {
32931
+ type: "money-flow-index",
32932
+ title: "Money Flow Index",
32933
+ shortTitle: "MFI",
32934
+ overlay: false,
32935
+ inputs: [lengthInput(14), colorInput()],
32936
+ compute: (bars, inputs) => {
32937
+ const len = num3(inputs, "length", 14);
32938
+ const tp = sourceValues(bars, "HLC3");
32939
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
32940
+ const d = change(tp);
32941
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
32942
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
32943
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
32944
+ return {
32945
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32946
+ levels: [
32947
+ { key: "upper", price: 80, color: NEUTRAL },
32948
+ { key: "lower", price: 20, color: NEUTRAL }
32949
+ ]
32950
+ };
32951
+ }
32952
+ };
32953
+ function volumeIndex(bars, when) {
32954
+ const out = new Array(bars.length).fill(Number.NaN);
32955
+ let index = 1e3;
32956
+ let started = false;
32957
+ for (let i = 1; i < bars.length; i++) {
32958
+ const v = bars[i].volume;
32959
+ const pv = bars[i - 1].volume;
32960
+ const pc = bars[i - 1].close;
32961
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
32962
+ if (started) out[i] = index;
32963
+ continue;
32964
+ }
32965
+ started = true;
32966
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
32967
+ out[i] = index;
32968
+ }
32969
+ return out;
32970
+ }
32971
+ var nvi = {
32972
+ type: "negative-volume-index",
32973
+ title: "Negative Volume Index",
32974
+ shortTitle: "Negative Volume Index",
32975
+ overlay: false,
32976
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
32977
+ compute: (bars, inputs) => {
32978
+ const line = volumeIndex(bars, (v, pv) => v < pv);
32979
+ return {
32980
+ plots: [
32981
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
32982
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
32983
+ ]
32984
+ };
32985
+ }
32986
+ };
32987
+ var pvi = {
32988
+ type: "positive-volume-index",
32989
+ title: "Positive Volume Index",
32990
+ shortTitle: "Positive Volume Index",
32991
+ overlay: false,
32992
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
32993
+ compute: (bars, inputs) => {
32994
+ const line = volumeIndex(bars, (v, pv) => v > pv);
32995
+ return {
32996
+ plots: [
32997
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
32998
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
32999
+ ]
33000
+ };
33001
+ }
33002
+ };
33003
+ var pvt = {
33004
+ type: "price-volume-trend",
33005
+ title: "Price Volume Trend",
33006
+ shortTitle: "PVT",
33007
+ overlay: false,
33008
+ inputs: [colorInput()],
33009
+ compute: (bars, inputs) => {
33010
+ const c = closes(bars);
33011
+ const raw = bars.map((b, i) => {
33012
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
33013
+ const pc = c[i - 1];
33014
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
33015
+ });
33016
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
33017
+ }
33018
+ };
33019
+ var volumeOscillator = {
33020
+ type: "volume-oscillator",
33021
+ title: "Volume Oscillator",
33022
+ shortTitle: "Vol Osc",
33023
+ overlay: false,
33024
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
33025
+ compute: (bars, inputs) => {
33026
+ const vol = volumes(bars);
33027
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
33028
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
33029
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
33030
+ return {
33031
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
33032
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33033
+ };
33034
+ }
33035
+ };
33036
+ var pvo = {
33037
+ type: "pvo",
33038
+ title: "Percentage Volume Oscillator",
33039
+ shortTitle: "PVO",
33040
+ overlay: false,
33041
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
33042
+ compute: (bars, inputs) => {
33043
+ const vol = volumes(bars);
33044
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
33045
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
33046
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
33047
+ const signal = ema(line, num3(inputs, "signalLength", 9));
33048
+ const hist = sub(line, signal);
33049
+ return {
33050
+ plots: [
33051
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
33052
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
33053
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
33054
+ ],
33055
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33056
+ };
33057
+ }
33058
+ };
33059
+ var vfi = {
33060
+ type: "volume-flow-indicator",
33061
+ title: "Volume Flow Indicator",
33062
+ shortTitle: "Volume Flow Indicator",
33063
+ overlay: false,
33064
+ inputs: [
33065
+ lengthInput(130),
33066
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
33067
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
33068
+ lengthInput(3, "smoothLength", "Smoothing length")
33069
+ ],
33070
+ compute: (bars, inputs) => {
33071
+ const len = num3(inputs, "length", 130);
33072
+ const coef = num3(inputs, "coef", 0.2);
33073
+ const volCoef = num3(inputs, "volCoef", 2.5);
33074
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
33075
+ const inter = change(typical);
33076
+ const vInter = stdev(inter, 30);
33077
+ const vol = volumes(bars);
33078
+ const vAve = sma(vol, len);
33079
+ const mf = change(sourceValues(bars, "HLC3"));
33080
+ const raw = bars.map((b, i) => {
33081
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
33082
+ const v = vol[i];
33083
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
33084
+ const m = mf[i];
33085
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
33086
+ const capped = Math.min(v, ave * volCoef);
33087
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
33088
+ });
33089
+ const values = bars.map((_, i) => {
33090
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
33091
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
33092
+ });
33093
+ const flowSum = sum(raw, len);
33094
+ const line = zip(flowSum, values, (f, a) => f / a);
33095
+ return {
33096
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
33097
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33098
+ };
33099
+ }
33100
+ };
33101
+ var intradayIntensity = {
33102
+ type: "intraday-intensity",
33103
+ title: "Intraday Intensity",
33104
+ shortTitle: "Intraday Intensity",
33105
+ overlay: false,
33106
+ inputs: [lengthInput(21), colorInput()],
33107
+ compute: (bars, inputs) => {
33108
+ const len = num3(inputs, "length", 21);
33109
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
33110
+ return {
33111
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
33112
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33113
+ };
33114
+ }
33115
+ };
33116
+ var volumeSpecs = [
33117
+ obv,
33118
+ accDist,
33119
+ cmf,
33120
+ chaikinOscillator,
33121
+ eom,
33122
+ forceIndex,
33123
+ klinger,
33124
+ mfi,
33125
+ nvi,
33126
+ pvi,
33127
+ pvt,
33128
+ volumeOscillator,
33129
+ pvo,
33130
+ vfi,
33131
+ intradayIntensity
33132
+ ];
33133
+
33134
+ // src/core/native-indicators/classics/overlays.ts
33135
+ var DAY_MS2 = 864e5;
33136
+ function periodKey(anchor, time) {
33137
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
33138
+ if (anchor === "Month") {
33139
+ const d = new Date(time);
33140
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
33141
+ }
33142
+ return Math.floor(time / DAY_MS2);
33143
+ }
33144
+ var parabolicSar = {
33145
+ type: "parabolic-sar",
33146
+ title: "Parabolic SAR",
33147
+ shortTitle: "SAR",
33148
+ overlay: true,
33149
+ inputs: [
33150
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
33151
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
33152
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
33153
+ ],
33154
+ compute: (bars, inputs) => {
33155
+ const start = num3(inputs, "start", 0.02);
33156
+ const inc = num3(inputs, "increment", 0.02);
33157
+ const max = num3(inputs, "maximum", 0.2);
33158
+ const n = bars.length;
33159
+ const values = new Array(n).fill(Number.NaN);
33160
+ const colors = new Array(n).fill(null);
33161
+ if (n >= 2) {
33162
+ let long = bars[1].close >= bars[0].close;
33163
+ let sar = long ? bars[0].low : bars[0].high;
33164
+ let ep = long ? bars[0].high : bars[0].low;
33165
+ let af = start;
33166
+ for (let i = 1; i < n; i++) {
33167
+ const b = bars[i];
33168
+ sar += af * (ep - sar);
33169
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
33170
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
33171
+ const reversed = long ? b.low < sar : b.high > sar;
33172
+ if (reversed) {
33173
+ long = !long;
33174
+ sar = ep;
33175
+ ep = long ? b.high : b.low;
33176
+ af = start;
33177
+ } else if (long && b.high > ep) {
33178
+ ep = b.high;
33179
+ af = Math.min(max, af + inc);
33180
+ } else if (!long && b.low < ep) {
33181
+ ep = b.low;
33182
+ af = Math.min(max, af + inc);
33183
+ }
33184
+ values[i] = sar;
33185
+ colors[i] = long ? BULLISH : BEARISH;
33186
+ }
33187
+ }
33188
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
33189
+ }
33190
+ };
33191
+ var vwap = {
33192
+ type: "vwap",
33193
+ title: "Volume Weighted Average Price",
33194
+ shortTitle: "VWAP",
33195
+ overlay: true,
33196
+ inputs: [
33197
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
33198
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
33199
+ colorInput(INFO)
33200
+ ],
33201
+ compute: (bars, inputs) => {
33202
+ const anchor = str3(inputs, "anchor", "Day");
33203
+ const source = str3(inputs, "source", "HLC3");
33204
+ const values = new Array(bars.length).fill(Number.NaN);
33205
+ let period = Number.NaN;
33206
+ let cumPV = 0;
33207
+ let cumV = 0;
33208
+ for (let i = 0; i < bars.length; i++) {
33209
+ const b = bars[i];
33210
+ const key = periodKey(anchor, b.time);
33211
+ if (key !== period) {
33212
+ period = key;
33213
+ cumPV = 0;
33214
+ cumV = 0;
33215
+ }
33216
+ const v = b.volume;
33217
+ if (v != null && Number.isFinite(v) && v > 0) {
33218
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
33219
+ cumPV += tp * v;
33220
+ cumV += v;
33221
+ }
33222
+ if (cumV > 0) values[i] = cumPV / cumV;
33223
+ }
33224
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
33225
+ }
33226
+ };
33227
+ var pivotPoints = {
33228
+ type: "pivot-points",
33229
+ title: "Pivot Points",
33230
+ shortTitle: "Pivots",
33231
+ overlay: true,
33232
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
33233
+ compute: (bars, inputs) => {
33234
+ const anchor = str3(inputs, "anchor", "Day");
33235
+ const aggs = /* @__PURE__ */ new Map();
33236
+ for (const b of bars) {
33237
+ const key = periodKey(anchor, b.time);
33238
+ const a = aggs.get(key);
33239
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
33240
+ else {
33241
+ a.high = Math.max(a.high, b.high);
33242
+ a.low = Math.min(a.low, b.low);
33243
+ a.close = b.close;
33244
+ }
33245
+ }
33246
+ const n = bars.length;
33247
+ const mk = () => new Array(n).fill(Number.NaN);
33248
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
33249
+ for (let i = 0; i < n; i++) {
33250
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
33251
+ if (!prev2) continue;
33252
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
33253
+ levels.p[i] = p;
33254
+ levels.r1[i] = 2 * p - prev2.low;
33255
+ levels.s1[i] = 2 * p - prev2.high;
33256
+ levels.r2[i] = p + (prev2.high - prev2.low);
33257
+ levels.s2[i] = p - (prev2.high - prev2.low);
33258
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
33259
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
33260
+ }
33261
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
33262
+ return {
33263
+ plots: [
33264
+ plot("p", "P", WARNING),
33265
+ plot("r1", "R1", BEARISH),
33266
+ plot("s1", "S1", BULLISH),
33267
+ plot("r2", "R2", BEARISH),
33268
+ plot("s2", "S2", BULLISH),
33269
+ plot("r3", "R3", BEARISH),
33270
+ plot("s3", "S3", BULLISH)
33271
+ ]
33272
+ };
33273
+ }
33274
+ };
33275
+ var fiftyTwoWeek = {
33276
+ type: "52-week-high-low",
33277
+ title: "52 Week High/Low",
33278
+ shortTitle: "52W H/L",
33279
+ overlay: true,
33280
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
33281
+ compute: (bars, inputs) => {
33282
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
33283
+ const n = bars.length;
33284
+ const hi = new Array(n).fill(Number.NaN);
33285
+ const lo = new Array(n).fill(Number.NaN);
33286
+ const maxIdx = [];
33287
+ const minIdx = [];
33288
+ let from = 0;
33289
+ for (let i = 0; i < n; i++) {
33290
+ const b = bars[i];
33291
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
33292
+ maxIdx.push(i);
33293
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
33294
+ minIdx.push(i);
33295
+ while (bars[from].time < b.time - span) from += 1;
33296
+ while (maxIdx[0] < from) maxIdx.shift();
33297
+ while (minIdx[0] < from) minIdx.shift();
33298
+ hi[i] = bars[maxIdx[0]].high;
33299
+ lo[i] = bars[minIdx[0]].low;
33300
+ }
33301
+ return {
33302
+ plots: [
33303
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
33304
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
33305
+ ]
33306
+ };
33307
+ }
33308
+ };
33309
+ var zigzag = {
33310
+ type: "zigzag",
33311
+ title: "ZigZag",
33312
+ overlay: true,
33313
+ inputs: [
33314
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
33315
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
33316
+ colorInput()
33317
+ ],
33318
+ compute: (bars, inputs) => {
33319
+ const dev = num3(inputs, "deviation", 5) / 100;
33320
+ const depth = num3(inputs, "depth", 10);
33321
+ const pivots = [];
33322
+ if (bars.length > 1) {
33323
+ let up = bars[1].close >= bars[0].close;
33324
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
33325
+ for (let i = 1; i < bars.length; i++) {
33326
+ const b = bars[i];
33327
+ if (up) {
33328
+ if (b.high >= ext.price) ext = { i, price: b.high };
33329
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
33330
+ pivots.push(ext);
33331
+ up = false;
33332
+ ext = { i, price: b.low };
33333
+ }
33334
+ } else if (b.low <= ext.price) {
33335
+ ext = { i, price: b.low };
33336
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
33337
+ pivots.push(ext);
33338
+ up = true;
33339
+ ext = { i, price: b.high };
33340
+ }
33341
+ }
33342
+ pivots.push(ext);
33343
+ }
33344
+ return {
33345
+ plots: [],
33346
+ polylines: [
33347
+ {
33348
+ key: "zigzag",
33349
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
33350
+ curved: false,
33351
+ closed: false,
33352
+ lineColor: str3(inputs, "color", SERIES_LINE),
33353
+ lineWidth: 2,
33354
+ lineStyle: "solid",
33355
+ arrowLeft: false,
33356
+ arrowRight: false
33357
+ }
33358
+ ]
33359
+ };
33360
+ }
33361
+ };
33362
+ var williamsFractal = {
33363
+ type: "williams-fractal",
33364
+ title: "Williams Fractal",
33365
+ shortTitle: "Fractals",
33366
+ overlay: true,
33367
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
33368
+ compute: (bars, inputs) => {
33369
+ const p = num3(inputs, "periods", 2);
33370
+ const markers = [];
33371
+ const isExtreme = (i, pick, better) => {
33372
+ const v = pick(bars[i]);
33373
+ for (let k = i - p; k <= i + p; k++) {
33374
+ if (k === i) continue;
33375
+ if (!better(v, pick(bars[k]))) return false;
33376
+ }
33377
+ return true;
33378
+ };
33379
+ for (let i = p; i < bars.length - p; i++) {
33380
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
33381
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
33382
+ }
33383
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
33384
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
33385
+ }
33386
+ }
33387
+ return { plots: [], markers };
33388
+ }
33389
+ };
33390
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
33391
+
33392
+ // src/core/native-indicators/classics/index.ts
33393
+ var classicSpecs = [
33394
+ ...averageSpecs,
33395
+ ...bandSpecs,
33396
+ ...oscillatorSpecs,
33397
+ ...trendSpecs,
33398
+ ...volatilitySpecs,
33399
+ ...volumeSpecs,
33400
+ ...overlaySpecs
33401
+ ];
33402
+ function registerClassicIndicators() {
33403
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
33404
+ }
33405
+
33406
+ // src/Vela.ts
33407
+ var Vela = class {
33408
+ constructor(container, options = {}, deps = {}) {
33409
+ registerBuiltinChartTypes();
33410
+ registerVolume();
33411
+ registerVpvr();
33412
+ registerClassicIndicators();
33413
+ const element = resolveElement(container);
33414
+ const theme = resolveTheme(options.theme);
33415
+ const display = {
33416
+ currentPriceLine: options.currentPriceLine ?? true,
33417
+ logScale: options.logScale ?? false,
33418
+ nativeBackend: options.nativeBackend ?? "auto",
33419
+ ...resolveAnimations(options.animations),
33420
+ glow: options.glow ?? 0,
33421
+ upColor: options.upColor ?? BULLISH,
33422
+ downColor: options.downColor ?? BEARISH,
33423
+ priceStyle: options.priceStyle ?? "candles"
33424
+ };
33425
+ const RendererClass = options.renderer ?? NativeRenderer;
33426
+ if (typeof RendererClass !== "function") {
33427
+ throw new Error(
33428
+ "[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
33429
+ );
33430
+ }
33431
+ const renderer = deps.renderer ?? new RendererClass(display);
33432
+ const engines = deps.engines ?? [];
33433
+ const feed = deps.dataFeed ?? new MultiProviderFeed();
33434
+ const config = {
33435
+ market: {
33436
+ symbol: options.symbol,
33437
+ timeframe: options.timeframe,
33438
+ bars: options.bars,
33439
+ session: options.session,
33440
+ visibleRange: options.visibleRange,
33441
+ data: options.data
33442
+ },
33443
+ live: options.live ?? false,
33444
+ theme,
33445
+ defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
33446
+ drawings: options.drawings,
33447
+ volume: options.volume ?? true
33448
+ // the volume indicator is on by default
33449
+ };
33450
+ this.rendererControl = new RendererControl(renderer);
33451
+ if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
33452
+ this.dataControl = new DataControl(feed);
33453
+ this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
33454
+ const defaults2 = rendererDefaults();
33455
+ if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
33456
+ this.panesControl = new PanesControl(this.orchestrator);
33457
+ this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
33458
+ }
33459
+ /**
33460
+ * Register a scripting engine so `addIndicator({ language })` can run that
33461
+ * language. Vela ships NO engine — install the one you need (Pine Script:
33462
+ * `@luxalgo/vela-pinets`) and register it, e.g.
33463
+ * `chart.registerEngine('pine', new PineEngine())`; without one the chart
33464
+ * displays candles, drawings and native indicators only. Re-registering a
33465
+ * language replaces it.
33466
+ */
33467
+ registerEngine(language, engine) {
33468
+ this.orchestrator.registerEngine(language, engine);
33469
+ return this;
33470
+ }
33471
+ /** Run an indicator script on the chart's market data and render it. */
33472
+ addIndicator(source, options) {
33473
+ return this.orchestrator.addIndicator(source, options);
33474
+ }
33475
+ /**
33476
+ * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
33477
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
33478
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
33479
+ * call returns the existing handle; the classic studies allow several instances, so each call
33480
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
33481
+ */
33482
+ addNativeIndicator(type, options) {
33483
+ return this.orchestrator.addNativeIndicator(type, options);
33484
+ }
33485
+ /**
33486
+ * The catalog of built-in native indicators with their live state on this chart — each entry's
33487
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
33488
+ * and whether it allows several instances (`multiInstance`; otherwise a second
33489
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
33490
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
33491
+ * provider (a type may need data the symbol lacks).
33492
+ */
33493
+ availableNativeIndicators() {
33494
+ return this.orchestrator.availableNativeIndicators();
33495
+ }
33496
+ /**
33497
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
33498
+ * multi-instance type repeats) — the synchronous slice of
33499
+ * {@link availableNativeIndicators} (only support probing is async; presence never is).
33500
+ * Persistence snapshots read this: an unload-time flush must see an add/remove that
33501
+ * happened microseconds ago, which an async catalog mirror cannot guarantee.
33502
+ */
33503
+ presentNativeIndicators() {
33504
+ return this.orchestrator.presentNativeIndicators();
33505
+ }
33506
+ /** Live handles of every indicator currently on the chart (script + native) — drive
33507
+ * host panels (object trees, indicator lists) with per-id visibility/removal. */
33508
+ indicators() {
33509
+ return this.orchestrator.listIndicators();
33510
+ }
33511
+ /**
33512
+ * Execute an indicator script and INJECT it only if the run succeeds — the seam for
33513
+ * host editors and consoles. Resolves `{ ok: true, handle }` after the first
33514
+ * successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
33515
+ * in which case the failed indicator is removed again (no dead legend row).
33516
+ * Never rejects.
33517
+ */
33518
+ /**
33519
+ * Execute a script and resolve its FIRST computed run — the data-out door for host
33520
+ * editors, consoles and dashboards. The script is injected only if it runs (a failure
33521
+ * removes it again, leaving no dead legend row), and the result carries the run itself
33522
+ * plus the controls for what it put on the chart: `onUpdate` to follow later runs,
33523
+ * `remove` to take it off. Never rejects.
33524
+ *
33525
+ * `runScript` is `runIndicator` with the run as its payload rather than a handle to go
33526
+ * fetch from — the same relationship `script:run` has to `context:changed`.
33527
+ */
33528
+ runScript(source, options) {
33529
+ const handle = this.addIndicator(source, options);
33530
+ const updates = /* @__PURE__ */ new Set();
33531
+ const drop = () => {
33532
+ try {
33533
+ handle.remove();
33534
+ } catch {
33535
+ }
33536
+ };
33537
+ return new Promise((resolve) => {
33538
+ let settled = false;
33539
+ const offRun = this.on("script:run", (run) => {
33540
+ if (run.id !== handle.id) return;
33541
+ if (settled) {
33542
+ for (const handler of updates) handler(run);
33543
+ return;
33544
+ }
33545
+ settled = true;
33546
+ offError();
33547
+ resolve({
33548
+ ok: true,
33549
+ run,
33550
+ error: null,
33551
+ onUpdate: (handler) => {
33552
+ updates.add(handler);
33553
+ return () => updates.delete(handler);
33554
+ },
33555
+ remove: () => {
33556
+ offRun();
33557
+ updates.clear();
33558
+ drop();
33559
+ }
33560
+ });
33561
+ });
33562
+ const offError = handle.on("error", ({ error }) => {
33563
+ if (settled) return;
33564
+ settled = true;
33565
+ offRun();
33566
+ offError();
33567
+ drop();
33568
+ resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
33569
+ });
33570
+ });
33571
+ }
33572
+ runIndicator(source, options) {
33573
+ const handle = this.addIndicator(source, options);
33574
+ return new Promise((resolve) => {
33575
+ const offReady = handle.on("ready", () => {
33576
+ offReady();
33577
+ offError();
33578
+ resolve({ ok: true, handle, error: null, context: null });
33579
+ });
33580
+ const offError = handle.on("error", ({ error }) => {
33581
+ offReady();
33582
+ offError();
33583
+ void handle.context().catch(() => null).then((context) => {
33584
+ try {
33585
+ handle.remove();
33586
+ } catch {
33587
+ }
33588
+ resolve({ ok: false, handle: null, error, context });
33589
+ });
33590
+ });
33591
+ });
33592
+ }
33593
+ /**
33594
+ * Switch the chart's market IN PLACE — symbol, provider, timeframe, depth, or offline
33595
+ * data — WITHOUT destroying the chart: indicators re-execute over the new bars, native
33596
+ * indicators restart, and panes, user drawings, renderer config and event
33597
+ * subscriptions all survive. Resolves once the new market's history is painted (a
33598
+ * deep backfill continues behind it — await {@link historyComplete}); a call
33599
+ * superseded by a newer `setMarket` resolves silently. Emits `market:changed`
33600
+ * (with the previous identity) when the market identity changed.
33601
+ */
33602
+ setMarket(next) {
33603
+ return this.orchestrator.setMarket(next);
33604
+ }
33605
+ /** The current market identity — the read counterpart of {@link setMarket}. A snapshot
33606
+ * of the REQUESTED market: it reflects an in-flight switch immediately (before the
31210
33607
  * new bars land). Listen to `market:changed` for committed identity changes. */
31211
33608
  get market() {
31212
33609
  return this.orchestrator.marketSnapshot();
@@ -31465,12 +33862,6 @@ function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_
31465
33862
  return Math.round(Math.min(Math.max(px, lo), hi));
31466
33863
  }
31467
33864
 
31468
- // src/core/model/identity.ts
31469
- function stableSeriesId(parts) {
31470
- const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
31471
- return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
31472
- }
31473
-
31474
33865
  exports.ACCENT = ACCENT;
31475
33866
  exports.ACCENT_BRIGHT = ACCENT_BRIGHT;
31476
33867
  exports.BEARISH = BEARISH;