@luxalgo/vela 0.6.14 → 0.6.16

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 (51) hide show
  1. package/NOTICE +28 -28
  2. package/README.md +9 -2
  3. package/dist/{DataProvider-DhzstpQb.d.ts → DataProvider-XLdJB86x.d.ts} +1 -1
  4. package/dist/{DataProvider-DlMtrwqM.d.cts → DataProvider-xH2VBcFT.d.cts} +1 -1
  5. package/dist/{chunk-KG4YT3TI.js → chunk-6DPAYEGO.js} +8 -1
  6. package/dist/chunk-FE6V2YAQ.js +1 -0
  7. package/dist/{chunk-N2Q5EEFH.js → chunk-GIJYO4LO.js} +2596 -242
  8. package/dist/{chunk-F4M24ANM.js → chunk-I77F2SVE.js} +80 -4
  9. package/dist/{chunk-MHY7MVXH.js → chunk-MGU3USTP.js} +29 -16
  10. package/dist/{chunk-G77Y7LK2.js → chunk-Q3TFJNJW.js} +2 -2
  11. package/dist/{chunk-64TF7JAJ.js → chunk-SEOO6YG7.js} +408 -184
  12. package/dist/{contributions-lPojhTxI.d.cts → contributions-BEkxwA5F.d.cts} +40 -13
  13. package/dist/{contributions-37nni40G.d.ts → contributions-CoAX7_SF.d.ts} +40 -13
  14. package/dist/index.cjs +2755 -380
  15. package/dist/index.d.cts +20 -12
  16. package/dist/index.d.ts +20 -12
  17. package/dist/index.js +5 -5
  18. package/dist/{options-CYS5Wmlx.d.ts → options-bdSAtp7y.d.cts} +5 -0
  19. package/dist/{options-CYS5Wmlx.d.cts → options-bdSAtp7y.d.ts} +5 -0
  20. package/dist/{plugin-DHyaoMjW.d.ts → plugin-CNUgbRNq.d.ts} +11 -8
  21. package/dist/{plugin-Cwikpz1m.d.cts → plugin-Dxyq35Zf.d.cts} +11 -8
  22. package/dist/plugin.cjs +8 -0
  23. package/dist/plugin.d.cts +5 -5
  24. package/dist/plugin.d.ts +5 -5
  25. package/dist/plugin.js +3 -3
  26. package/dist/providers/binance.d.cts +2 -2
  27. package/dist/providers/binance.d.ts +2 -2
  28. package/dist/providers/coinbase.d.cts +2 -2
  29. package/dist/providers/coinbase.d.ts +2 -2
  30. package/dist/providers/hyperliquid.d.cts +2 -2
  31. package/dist/providers/hyperliquid.d.ts +2 -2
  32. package/dist/{side-panel-CT9ZwIGz.d.cts → side-panel-HF0IAzwf.d.cts} +17 -0
  33. package/dist/{side-panel-CT9ZwIGz.d.ts → side-panel-HF0IAzwf.d.ts} +17 -0
  34. package/dist/{statusline-model-D-q_OOx9.d.ts → statusline-model-CbPza-Wc.d.cts} +6 -4
  35. package/dist/{statusline-model-CiJm-riV.d.cts → statusline-model-D7cWkw8A.d.ts} +6 -4
  36. package/dist/ui.cjs +34 -15
  37. package/dist/ui.d.cts +1 -1
  38. package/dist/ui.d.ts +1 -1
  39. package/dist/ui.js +3 -3
  40. package/dist/vela.global.js +2675 -300
  41. package/dist/vela.global.min.js +58 -52
  42. package/dist/widget.cjs +3296 -622
  43. package/dist/widget.d.cts +15 -11
  44. package/dist/widget.d.ts +15 -11
  45. package/dist/widget.js +7 -7
  46. package/dist/workspace.cjs +3296 -622
  47. package/dist/workspace.d.cts +49 -19
  48. package/dist/workspace.d.ts +49 -19
  49. package/dist/workspace.js +6 -6
  50. package/package.json +1 -1
  51. package/dist/chunk-H26BEHF4.js +0 -7
package/dist/index.cjs CHANGED
@@ -135,7 +135,7 @@ var LiveSession = class {
135
135
 
136
136
  // src/core/engine/IndicatorHandleImpl.ts
137
137
  var IndicatorHandleImpl = class {
138
- constructor(id, title, controller, source) {
138
+ constructor(id, title, controller, source, nativeType) {
139
139
  this.controller = controller;
140
140
  this.schema = [];
141
141
  this.propsSchema = [];
@@ -144,6 +144,7 @@ var IndicatorHandleImpl = class {
144
144
  this.id = id;
145
145
  this.title = title;
146
146
  if (source !== void 0) this.source = source;
147
+ if (nativeType !== void 0) this.nativeType = nativeType;
147
148
  }
148
149
  get inputs() {
149
150
  return this.schema;
@@ -234,24 +235,24 @@ function summarizeModel(model) {
234
235
  }
235
236
  function inspectModels(models) {
236
237
  const indicators = models.map(summarizeModel);
237
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
238
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
238
239
  return {
239
240
  indicators,
240
241
  totals: {
241
242
  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)
243
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
244
+ fills: sum2((s) => s.fills),
245
+ backgrounds: sum2((s) => s.backgrounds),
246
+ priceLines: sum2((s) => s.priceLines),
247
+ lines: sum2((s) => s.lines),
248
+ boxes: sum2((s) => s.boxes),
249
+ labels: sum2((s) => s.labels),
250
+ polylines: sum2((s) => s.polylines),
251
+ linefills: sum2((s) => s.linefills),
252
+ tables: sum2((s) => s.tables),
253
+ barColors: sum2((s) => s.barColors),
254
+ trades: sum2((s) => s.trades),
255
+ forcedOverlay: sum2((s) => s.forcedOverlay)
255
256
  }
256
257
  };
257
258
  }
@@ -300,6 +301,7 @@ var SERIES_LINE = "#3b82f6";
300
301
  var CROSSHAIR = "#9aa0ad";
301
302
  var SLATE_DEEP = "#1e293b";
302
303
  var SLATE = "#475569";
304
+ var CHIP_PLATE = "#595959";
303
305
  var TRADE_LONG = ACCENT;
304
306
  var TRADE_SHORT = BEARISH;
305
307
  var TRADE_EXIT = "#d500f9";
@@ -527,6 +529,10 @@ registerIcon(
527
529
  "market-post",
528
530
  svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 14-4 4-4-4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
529
531
  );
532
+ registerIcon(
533
+ "market-extended",
534
+ svg24('<path d="M22 22H2"/><path d="M12 3a5 5 0 0 0 7.5 7.5A7.5 7.5 0 1 1 12 3Z"/>')
535
+ );
530
536
  registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
531
537
  registerIcon(
532
538
  "market-holiday",
@@ -540,6 +546,8 @@ registerIcon("chevrons-right", S('<path d="m4 3.5 4.5 4.5L4 12.5"/><path d="m8.5
540
546
  registerIcon("chevrons-left", S('<path d="M12 3.5 7.5 8l4.5 4.5"/><path d="M7.5 3.5 3 8l4.5 4.5"/>'));
541
547
  registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
542
548
  registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
549
+ registerIcon("pin", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/>'));
550
+ registerIcon("pin-filled", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/><path d="M6.4 2.5h3.2v3.6l2 2.3v.9H4.4v-.9l2-2.3z" fill="currentColor" stroke="none"/>'));
543
551
  registerIcon("eye", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
544
552
  registerIcon("eye-off", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
545
553
  registerIcon("lock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.8 0V7"/>'));
@@ -6724,8 +6732,8 @@ var DrawingController = class {
6724
6732
  this.port?.setExternalGhost?.(doc);
6725
6733
  }
6726
6734
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
6727
- setToolShortcuts(map) {
6728
- this.port?.setToolShortcuts?.(map);
6735
+ setToolShortcuts(map2) {
6736
+ this.port?.setToolShortcuts?.(map2);
6729
6737
  }
6730
6738
  // ── programmatic CRUD (facade-facing) ── each is one undo step
6731
6739
  add(type, init = {}) {
@@ -8449,16 +8457,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
8449
8457
  /**
8450
8458
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
8451
8459
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
8452
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
8453
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
8460
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
8461
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
8462
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
8463
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
8454
8464
  */
8455
8465
  addNativeIndicator(type, options = {}) {
8456
- const existing = this.registry.all().find((r) => r.native?.type === type);
8457
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
8458
8466
  const descriptor = getNativeIndicator(type);
8467
+ if (!descriptor?.multiInstance) {
8468
+ const existing = this.registry.all().find((r) => r.native?.type === type);
8469
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8470
+ }
8459
8471
  const id = this.registry.nextId("native");
8460
8472
  const title = descriptor?.title ?? type;
8461
- const handle = new IndicatorHandleImpl(id, title, this);
8473
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8462
8474
  this.handles.set(id, handle);
8463
8475
  if (!descriptor) {
8464
8476
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -8470,16 +8482,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
8470
8482
  void this.startNativeIndicator(id, handle);
8471
8483
  return handle;
8472
8484
  }
8473
- /**
8474
- * The catalog of registered native-indicator types with their live state on THIS chart: each
8475
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
8476
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
8477
- * because a descriptor's `isSupported` may probe the provider for a required capability.
8478
- */
8479
- /** The native types present on the chart — SYNC (registry state; no support probe). */
8485
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
8486
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
8480
8487
  presentNativeIndicators() {
8481
8488
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
8482
8489
  }
8490
+ /**
8491
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
8492
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
8493
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
8494
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
8495
+ * probe the provider for a required capability.
8496
+ */
8483
8497
  async availableNativeIndicators() {
8484
8498
  const symbol = this.qualifiedSymbol();
8485
8499
  const present = new Set(this.presentNativeIndicators());
@@ -8489,7 +8503,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
8489
8503
  title: d.title,
8490
8504
  supported: await this.isNativeSupported(d, symbol),
8491
8505
  present: present.has(d.type),
8492
- beta: d.beta
8506
+ beta: d.beta,
8507
+ ...d.multiInstance ? { multiInstance: true } : {}
8493
8508
  }))
8494
8509
  );
8495
8510
  }
@@ -9296,6 +9311,15 @@ var RendererControl = class {
9296
9311
  console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
9297
9312
  return null;
9298
9313
  }
9314
+ /**
9315
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
9316
+ * or null if the renderer has no canvas export. Silent — a host compositing
9317
+ * several charts skips a renderer that cannot contribute.
9318
+ */
9319
+ screenshotCanvas() {
9320
+ if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
9321
+ return null;
9322
+ }
9299
9323
  /**
9300
9324
  * The active renderer's full cosmetic config as a serializable, versioned JSON
9301
9325
  * document — persist it (templates, saved settings) and feed it back to
@@ -9764,7 +9788,7 @@ function attachForming(closed, forming) {
9764
9788
 
9765
9789
  // src/data/MultiProviderFeed.ts
9766
9790
  function intervalMs(timeframe) {
9767
- const map = {
9791
+ const map2 = {
9768
9792
  "1m": 6e4,
9769
9793
  "3m": 18e4,
9770
9794
  "5m": 3e5,
@@ -9786,7 +9810,7 @@ function intervalMs(timeframe) {
9786
9810
  W: 6048e5,
9787
9811
  M: 2592e6
9788
9812
  };
9789
- return map[timeframe] ?? 36e5;
9813
+ return map2[timeframe] ?? 36e5;
9790
9814
  }
9791
9815
  var MultiProviderFeed = class {
9792
9816
  constructor(store = sharedBarStore) {
@@ -10173,8 +10197,8 @@ var DrawingsControl = class {
10173
10197
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
10174
10198
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
10175
10199
  * platform formatting, so hints always match the host's actual bindings. */
10176
- setToolShortcuts(map) {
10177
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
10200
+ setToolShortcuts(map2) {
10201
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
10178
10202
  return this;
10179
10203
  }
10180
10204
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -10735,19 +10759,19 @@ var Surface = class _Surface {
10735
10759
  }
10736
10760
  setItems(items) {
10737
10761
  this.items = items;
10738
- for (const sub of this.subs.values()) sub.destroy();
10762
+ for (const sub2 of this.subs.values()) sub2.destroy();
10739
10763
  this.subs.clear();
10740
10764
  this.build();
10741
10765
  const api = this.api;
10742
- for (const sub of this.subs.values()) {
10743
- api.setChild(sub.handle.service);
10744
- sub.api.setParent(this.handle.service);
10766
+ for (const sub2 of this.subs.values()) {
10767
+ api.setChild(sub2.handle.service);
10768
+ sub2.api.setParent(this.handle.service);
10745
10769
  }
10746
10770
  this.handle.flush();
10747
10771
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
10748
10772
  }
10749
10773
  destroy() {
10750
- for (const sub of this.subs.values()) sub.destroy();
10774
+ for (const sub2 of this.subs.values()) sub2.destroy();
10751
10775
  this.subs.clear();
10752
10776
  this.handle.stop();
10753
10777
  this.positioner.remove();
@@ -10761,9 +10785,9 @@ var Surface = class _Surface {
10761
10785
  for (const li of this.list.children) {
10762
10786
  const id = li.dataset.veiId;
10763
10787
  if (!id) continue;
10764
- const sub = this.subs.get(id);
10765
- if (sub) {
10766
- vanilla.spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
10788
+ const sub2 = this.subs.get(id);
10789
+ if (sub2) {
10790
+ vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
10767
10791
  continue;
10768
10792
  }
10769
10793
  const item = this.byId(id);
@@ -10840,7 +10864,7 @@ var Surface = class _Surface {
10840
10864
  const arrow = iconEl("chevron-right", doc);
10841
10865
  arrow.classList.add("vela-menu-arrow");
10842
10866
  li.appendChild(arrow);
10843
- const sub = new _Surface(doc, {
10867
+ const sub2 = new _Surface(doc, {
10844
10868
  host: this.host,
10845
10869
  placement: "right-start",
10846
10870
  onSelect: this.onSelect,
@@ -10848,8 +10872,8 @@ var Surface = class _Surface {
10848
10872
  checkmarks: this.checkmarks,
10849
10873
  id: `${this.mid}--${item.id}`
10850
10874
  });
10851
- sub.setItems(item.submenu ?? []);
10852
- this.subs.set(item.id, sub);
10875
+ sub2.setItems(item.submenu ?? []);
10876
+ this.subs.set(item.id, sub2);
10853
10877
  }
10854
10878
  this.list.appendChild(li);
10855
10879
  }
@@ -10914,6 +10938,12 @@ ${STATIC_DECLS}
10914
10938
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
10915
10939
  /* Text ENTRY is the one exception: selection is part of editing. */
10916
10940
  .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
10941
+ /* iOS Safari zooms the page when a focused field is under 16px, and often
10942
+ keeps that zoom after the field blurs or a dialog closes. The shell's
10943
+ mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
10944
+ chart on a wide desktop still gets the rule when it is in the phone layout.
10945
+ Scoped to the kit root so a host page's own [data-layout] is never touched. */
10946
+ .vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
10917
10947
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
10918
10948
  .vela-icon svg { display: block; }
10919
10949
  `;
@@ -12609,8 +12639,21 @@ function buildColorPicker(color, theme, onChange) {
12609
12639
  };
12610
12640
  const recentRow = document.createElement("div");
12611
12641
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
12642
+ const recentSwatches = document.createElement("div");
12643
+ recentSwatches.style.cssText = "display:contents;";
12644
+ const add = document.createElement("label");
12645
+ 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;`;
12646
+ add.textContent = "+";
12647
+ const customInput = document.createElement("input");
12648
+ customInput.type = "color";
12649
+ customInput.value = curHex;
12650
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
12651
+ customInput.addEventListener("input", () => previewHex(customInput.value));
12652
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
12653
+ add.appendChild(customInput);
12654
+ recentRow.append(recentSwatches, add);
12612
12655
  const renderRecents = () => {
12613
- recentRow.replaceChildren();
12656
+ recentSwatches.replaceChildren();
12614
12657
  for (const c of recents) {
12615
12658
  const sw = document.createElement("button");
12616
12659
  sw.type = "button";
@@ -12619,18 +12662,8 @@ function buildColorPicker(color, theme, onChange) {
12619
12662
  e.stopPropagation();
12620
12663
  pickHex(c);
12621
12664
  });
12622
- recentRow.appendChild(sw);
12665
+ recentSwatches.appendChild(sw);
12623
12666
  }
12624
- const add = document.createElement("label");
12625
- 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;`;
12626
- add.textContent = "+";
12627
- const input = document.createElement("input");
12628
- input.type = "color";
12629
- input.value = curHex;
12630
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
12631
- input.addEventListener("input", () => pickHex(input.value, true));
12632
- add.appendChild(input);
12633
- recentRow.appendChild(add);
12634
12667
  };
12635
12668
  const opLabel = document.createElement("div");
12636
12669
  opLabel.textContent = "Opacity";
@@ -12670,14 +12703,18 @@ function buildColorPicker(color, theme, onChange) {
12670
12703
  function emit() {
12671
12704
  onChange(combineColor(curHex, curAlpha));
12672
12705
  }
12673
- function pickHex(hex, custom = false) {
12706
+ function previewHex(hex) {
12674
12707
  curHex = splitColor(hex).hex6;
12675
- addRecent(curHex);
12676
12708
  paintSelection();
12677
12709
  paintOpacity();
12678
- if (custom) renderRecents();
12679
12710
  emit();
12680
12711
  }
12712
+ function pickHex(hex, custom = false) {
12713
+ previewHex(hex);
12714
+ addRecent(curHex);
12715
+ customInput.value = curHex;
12716
+ if (custom) renderRecents();
12717
+ }
12681
12718
  renderRecents();
12682
12719
  paintSelection();
12683
12720
  paintOpacity();
@@ -14583,8 +14620,8 @@ var InputsUI = class {
14583
14620
  lg.style.top = `${top}px`;
14584
14621
  }
14585
14622
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
14586
- setCollapsedPanes(map) {
14587
- this.paneCollapse = map;
14623
+ setCollapsedPanes(map2) {
14624
+ this.paneCollapse = map2;
14588
14625
  }
14589
14626
  /**
14590
14627
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -15345,6 +15382,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
15345
15382
  center: (wickLeftDev + wickDev / 2) / dpr
15346
15383
  };
15347
15384
  }
15385
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
15386
+ const out = [];
15387
+ let col = NaN;
15388
+ let intervals = [];
15389
+ const flush = () => {
15390
+ if (intervals.length === 0) return;
15391
+ const merged = [intervals[0]];
15392
+ for (let k = 1; k < intervals.length; k += 1) {
15393
+ const prev2 = merged[merged.length - 1];
15394
+ const next = intervals[k];
15395
+ if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
15396
+ prev2.hi = next.hi;
15397
+ if (next.headIdx < prev2.headIdx) {
15398
+ prev2.headIdx = next.headIdx;
15399
+ prev2.headTime = next.headTime;
15400
+ prev2.open = next.open;
15401
+ }
15402
+ if (next.lastIdx > prev2.lastIdx) {
15403
+ prev2.lastIdx = next.lastIdx;
15404
+ prev2.close = next.close;
15405
+ }
15406
+ } else {
15407
+ merged.push(next);
15408
+ }
15409
+ }
15410
+ for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
15411
+ intervals = [];
15412
+ };
15413
+ for (let i = i0; i <= i1; i += 1) {
15414
+ const b = bars[i];
15415
+ if (!b || b.high <= b.low) continue;
15416
+ const x = Math.round(xOf(i));
15417
+ if (x !== col) {
15418
+ flush();
15419
+ col = x;
15420
+ }
15421
+ let lo = b.low;
15422
+ let hi = b.high;
15423
+ let headIdx = i;
15424
+ let headTime = b.time;
15425
+ let open2 = b.open;
15426
+ for (let k = intervals.length - 1; k >= 0; k -= 1) {
15427
+ const iv = intervals[k];
15428
+ if (iv.lo > hi || iv.hi < lo) continue;
15429
+ if (iv.lo < lo) lo = iv.lo;
15430
+ if (iv.hi > hi) hi = iv.hi;
15431
+ if (iv.headIdx < headIdx) {
15432
+ headIdx = iv.headIdx;
15433
+ headTime = iv.headTime;
15434
+ open2 = iv.open;
15435
+ }
15436
+ intervals.splice(k, 1);
15437
+ }
15438
+ let insertAt = 0;
15439
+ while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
15440
+ intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
15441
+ }
15442
+ flush();
15443
+ return out;
15444
+ }
15348
15445
 
15349
15446
  // src/renderers/native/core/chartConfig.ts
15350
15447
  var CHART_CONFIG_VERSION = 1;
@@ -15355,6 +15452,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
15355
15452
  var BASELINE_LEVEL_DEFAULT = 50;
15356
15453
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
15357
15454
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
15455
+ var EXTENDED_SHADE = POSTMARKET_SHADE;
15358
15456
  function defaultChartStyle() {
15359
15457
  return {
15360
15458
  chartTypes: {},
@@ -15363,7 +15461,7 @@ function defaultChartStyle() {
15363
15461
  gridHorz: { visible: true, color: null },
15364
15462
  borderColor: null,
15365
15463
  separatorColor: null,
15366
- crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: SLATE },
15464
+ crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
15367
15465
  candle: {
15368
15466
  bodyVisible: true,
15369
15467
  borderVisible: false,
@@ -15386,7 +15484,7 @@ function defaultChartStyle() {
15386
15484
  width: 2,
15387
15485
  baselineLevel: BASELINE_LEVEL_DEFAULT
15388
15486
  },
15389
- sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
15487
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
15390
15488
  };
15391
15489
  }
15392
15490
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -15486,9 +15584,9 @@ function factoryResetConfig(factory) {
15486
15584
  if (inst.enableKey) defaults2[inst.enableKey] = false;
15487
15585
  addRows(inst.rows);
15488
15586
  }
15489
- for (const sub of section.subsections ?? []) {
15490
- if (sub.enableKey) defaults2[sub.enableKey] = false;
15491
- addRows(sub.rows);
15587
+ for (const sub2 of section.subsections ?? []) {
15588
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
15589
+ addRows(sub2.rows);
15492
15590
  }
15493
15591
  if (!section.instances) addRows(section.rows);
15494
15592
  bag[t.id] = defaults2;
@@ -15615,7 +15713,8 @@ function mergeConfig(base, patch) {
15615
15713
  },
15616
15714
  sessions: {
15617
15715
  premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
15618
- postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
15716
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
15717
+ extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
15619
15718
  },
15620
15719
  stacking: {
15621
15720
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
@@ -16600,44 +16699,19 @@ var WebGL2Backend = class {
16600
16699
  }
16601
16700
  }
16602
16701
  /**
16603
- * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bucket bars
16604
- * sharing a rounded pixel column into one high-low stick → draw cost bounded by
16605
- * screen width, not bar count. Bucket color follows first-open→last-close.
16702
+ * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
16703
+ * rounded pixel column collapse into high-low sticks — one per contiguous
16704
+ * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
16705
+ * column stays a void. Draw cost stays bounded by screen width, not bar count;
16706
+ * stick color follows its own first-open→last-close.
16606
16707
  */
16607
16708
  emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
16608
- let px = NaN;
16609
- let lo = Infinity;
16610
- let hi = -Infinity;
16611
- let openV = 0;
16612
- let closeV = 0;
16613
- let headTime = 0;
16614
- let active = false;
16615
- const flush = () => {
16616
- if (!active) return;
16617
- const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
16618
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
16619
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
16620
- b.rect(px - 0.5, hY, 1, lY - hY, c);
16621
- };
16622
- for (let i = i0; i <= i1; i += 1) {
16623
- const bar = bars[i];
16624
- if (!bar || bar.high <= bar.low) continue;
16625
- const x = Math.round(coords.logicalToX(i));
16626
- if (!active || x !== px) {
16627
- flush();
16628
- px = x;
16629
- lo = bar.low;
16630
- hi = bar.high;
16631
- openV = bar.open;
16632
- headTime = bar.time;
16633
- active = true;
16634
- } else {
16635
- if (bar.low < lo) lo = bar.low;
16636
- if (bar.high > hi) hi = bar.high;
16637
- }
16638
- closeV = bar.close;
16709
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
16710
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
16711
+ const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
16712
+ const hY = yOf(s.hi);
16713
+ b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
16639
16714
  }
16640
- flush();
16641
16715
  }
16642
16716
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
16643
16717
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -16841,11 +16915,11 @@ function mergeBarColors(indicators) {
16841
16915
  }
16842
16916
  }
16843
16917
  if (!any) return EMPTY_BARCOLORS;
16844
- const map = /* @__PURE__ */ new Map();
16918
+ const map2 = /* @__PURE__ */ new Map();
16845
16919
  for (const m of indicators.values()) {
16846
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
16920
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
16847
16921
  }
16848
- return map;
16922
+ return map2;
16849
16923
  }
16850
16924
 
16851
16925
  // src/renderers/native/core/ViewportState.ts
@@ -17142,9 +17216,9 @@ function isHorizontalWheel(deltaX, deltaY) {
17142
17216
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
17143
17217
  return rightOffset + deltaX / barSpacing;
17144
17218
  }
17145
- function wheelPanDelta(deltaX, deltaY, shift) {
17219
+ function wheelPanDelta(deltaX, deltaY, shift2) {
17146
17220
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
17147
- if (shift) return deltaY;
17221
+ if (shift2) return deltaY;
17148
17222
  return null;
17149
17223
  }
17150
17224
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -17935,13 +18009,15 @@ var SceneGraph = class {
17935
18009
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
17936
18010
  return this.orderedCache;
17937
18011
  }
17938
- /** The session zones resolved into colored bands (pre/post-market washes from the
17939
- * config's session colors) — consumed by the same painting path as {@link highlights}. */
18012
+ /** The session zones resolved into colored bands (pre/post-market or extended-hours
18013
+ * washes from the config's session colors) — consumed by the same painting path as
18014
+ * {@link highlights}. */
17940
18015
  sessionHighlightBands() {
17941
18016
  if (!this.sessionZones) return [];
17942
18017
  const out = [];
17943
18018
  for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
17944
18019
  for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
18020
+ for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
17945
18021
  return out;
17946
18022
  }
17947
18023
  indicatorsForPane(paneId) {
@@ -18480,50 +18556,23 @@ var Canvas2dBackend = class {
18480
18556
  }
18481
18557
  }
18482
18558
  /**
18483
- * Sub-pixel LOD: bucket every bar sharing a rounded pixel column into ONE
18484
- * high-low stick, so draw cost is bounded by screen width (not bar count) when
18485
- * zoomed far out. The bucket's color follows its first-open→last-close
18486
- * direction (a barcolor() on the head bar still wins).
18559
+ * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
18560
+ * sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
18561
+ * so draw cost is bounded by screen width (not bar count) when zoomed far out
18562
+ * and a price gap inside the column stays a void. Each stick's color follows
18563
+ * its first-open→last-close direction (a barcolor() on the head bar still wins).
18487
18564
  */
18488
18565
  drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
18489
- let px = NaN;
18490
- let lo = Infinity;
18491
- let hi = -Infinity;
18492
- let openV = 0;
18493
- let closeV = 0;
18494
- let headTime = 0;
18495
- let active = false;
18496
- const flush = () => {
18497
- if (!active) return;
18498
- const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
18499
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
18500
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
18501
- ctx.strokeStyle = color;
18502
- ctx.lineWidth = 1;
18566
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
18567
+ ctx.lineWidth = 1;
18568
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
18569
+ const x = s.x + 0.5;
18570
+ ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
18503
18571
  ctx.beginPath();
18504
- ctx.moveTo(px, hY);
18505
- ctx.lineTo(px, lY);
18572
+ ctx.moveTo(x, yOf(s.hi));
18573
+ ctx.lineTo(x, yOf(s.lo));
18506
18574
  ctx.stroke();
18507
- };
18508
- for (let i = i0; i <= i1; i += 1) {
18509
- const b = bars[i];
18510
- if (!b || b.high <= b.low) continue;
18511
- const x = Math.round(coords.logicalToX(i)) + 0.5;
18512
- if (!active || x !== px) {
18513
- flush();
18514
- px = x;
18515
- lo = b.low;
18516
- hi = b.high;
18517
- openV = b.open;
18518
- headTime = b.time;
18519
- active = true;
18520
- } else {
18521
- if (b.low < lo) lo = b.low;
18522
- if (b.high > hi) hi = b.high;
18523
- }
18524
- closeV = b.close;
18525
18575
  }
18526
- flush();
18527
18576
  }
18528
18577
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
18529
18578
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -18791,11 +18840,11 @@ function mergeBarColors2(indicators) {
18791
18840
  }
18792
18841
  }
18793
18842
  if (!any) return EMPTY_BARCOLORS2;
18794
- const map = /* @__PURE__ */ new Map();
18843
+ const map2 = /* @__PURE__ */ new Map();
18795
18844
  for (const m of indicators.values()) {
18796
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
18845
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
18797
18846
  }
18798
- return map;
18847
+ return map2;
18799
18848
  }
18800
18849
 
18801
18850
  // src/renderers/shared/drawing-geometry.ts
@@ -19547,6 +19596,32 @@ var DrawingSceneRenderer = class {
19547
19596
  }
19548
19597
  };
19549
19598
 
19599
+ // src/renderers/native/chrome/tz.ts
19600
+ function tzOffsetMs(ms, timeZone) {
19601
+ if (!timeZone || timeZone === "UTC") return 0;
19602
+ try {
19603
+ const dtf = new Intl.DateTimeFormat("en-US", {
19604
+ timeZone,
19605
+ hourCycle: "h23",
19606
+ year: "numeric",
19607
+ month: "2-digit",
19608
+ day: "2-digit",
19609
+ hour: "2-digit",
19610
+ minute: "2-digit",
19611
+ second: "2-digit"
19612
+ });
19613
+ const parts = dtf.formatToParts(new Date(ms));
19614
+ const get = (t) => Number(parts.find((p) => p.type === t)?.value);
19615
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
19616
+ return asUTC - ms;
19617
+ } catch {
19618
+ return 0;
19619
+ }
19620
+ }
19621
+ function zonedDate(ms, timeZone) {
19622
+ return new Date(ms + tzOffsetMs(ms, timeZone));
19623
+ }
19624
+
19550
19625
  // src/renderers/native/chrome/ticks.ts
19551
19626
  function priceTicks(min, max, target = 6) {
19552
19627
  if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
@@ -19694,7 +19769,14 @@ var STEP_LADDER = [
19694
19769
  YEAR
19695
19770
  ];
19696
19771
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
19772
+ var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
19697
19773
  var pad2 = (n) => n < 10 ? `0${n}` : String(n);
19774
+ function formatTimeStamp(ms, timeZone, barIntervalMs) {
19775
+ const d = zonedDate(ms, timeZone);
19776
+ const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
19777
+ if (barIntervalMs >= DAY) return date;
19778
+ return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
19779
+ }
19698
19780
  function pickStep(targetMs) {
19699
19781
  for (const step of STEP_LADDER) if (step >= targetMs) return step;
19700
19782
  return STEP_LADDER[STEP_LADDER.length - 1];
@@ -19736,32 +19818,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
19736
19818
  return out;
19737
19819
  }
19738
19820
 
19739
- // src/renderers/native/chrome/tz.ts
19740
- function tzOffsetMs(ms, timeZone) {
19741
- if (!timeZone || timeZone === "UTC") return 0;
19742
- try {
19743
- const dtf = new Intl.DateTimeFormat("en-US", {
19744
- timeZone,
19745
- hourCycle: "h23",
19746
- year: "numeric",
19747
- month: "2-digit",
19748
- day: "2-digit",
19749
- hour: "2-digit",
19750
- minute: "2-digit",
19751
- second: "2-digit"
19752
- });
19753
- const parts = dtf.formatToParts(new Date(ms));
19754
- const get = (t) => Number(parts.find((p) => p.type === t)?.value);
19755
- const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
19756
- return asUTC - ms;
19757
- } catch {
19758
- return 0;
19759
- }
19760
- }
19761
- function zonedDate(ms, timeZone) {
19762
- return new Date(ms + tzOffsetMs(ms, timeZone));
19763
- }
19764
-
19765
19821
  // src/renderers/native/chrome/ChromeRenderer.ts
19766
19822
  var ChromeRenderer = class {
19767
19823
  constructor() {
@@ -20208,7 +20264,7 @@ var CrosshairRenderer = class {
20208
20264
  const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
20209
20265
  this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
20210
20266
  }
20211
- this.chip(ctx, x, dataH + 1, formatStamp(coords.logicalToTime(logical), scene.timezone), chipBg, "center", true, theme.background);
20267
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
20212
20268
  }
20213
20269
  destroy() {
20214
20270
  this.canvas = null;
@@ -20256,7 +20312,7 @@ var CrosshairRenderer = class {
20256
20312
  this.chip(ctx, dataW + 1, ext.y, formatAxisValue(pane.scale, pane.bounds.height, ext.price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
20257
20313
  }
20258
20314
  }
20259
- this.chip(ctx, x, dataH + 1, formatStamp(ext.time, scene.timezone), chipBg, "center", true, theme.background);
20315
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
20260
20316
  ctx.globalAlpha = 1;
20261
20317
  }
20262
20318
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
@@ -20289,11 +20345,6 @@ function setDash3(ctx, style) {
20289
20345
  else if (style === "dotted") ctx.setLineDash([2, 3]);
20290
20346
  else ctx.setLineDash([]);
20291
20347
  }
20292
- function formatStamp(ms, timeZone) {
20293
- const d = zonedDate(ms, timeZone);
20294
- const pad = (n) => n < 10 ? `0${n}` : String(n);
20295
- return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
20296
- }
20297
20348
 
20298
20349
  // src/core/timezones.ts
20299
20350
  var TIMEZONES = [
@@ -20509,9 +20560,9 @@ function settingsIdCatalog(hostSections) {
20509
20560
  };
20510
20561
  if (section.rows) addRows(section.rows);
20511
20562
  for (const inst of section.instances ?? []) addRows(inst.rows);
20512
- for (const sub of section.subsections ?? []) {
20513
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
20514
- addRows(sub.rows);
20563
+ for (const sub2 of section.subsections ?? []) {
20564
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
20565
+ addRows(sub2.rows);
20515
20566
  }
20516
20567
  }
20517
20568
  for (const hs of hostSections) {
@@ -21070,14 +21121,14 @@ var SettingsDialog = class {
21070
21121
  };
21071
21122
  if (section.rows) seed(section.rows);
21072
21123
  for (const inst of section.instances ?? []) seed(inst.rows);
21073
- for (const sub of section.subsections ?? []) seed(sub.rows);
21124
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
21074
21125
  for (const inst of section.instances ?? []) {
21075
21126
  if (!inst.enableKey || inst.enableKey in bag) continue;
21076
21127
  bag[inst.enableKey] = values[inst.enableKey] === true;
21077
21128
  }
21078
- for (const sub of section.subsections ?? []) {
21079
- if (!sub.enableKey || sub.enableKey in bag) continue;
21080
- bag[sub.enableKey] = values[sub.enableKey] === true;
21129
+ for (const sub2 of section.subsections ?? []) {
21130
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
21131
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
21081
21132
  }
21082
21133
  const refreshers = [];
21083
21134
  const put = (key, v) => {
@@ -21094,16 +21145,16 @@ var SettingsDialog = class {
21094
21145
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
21095
21146
  else this.flatTypeRows(rows, bag, put, refreshers, body);
21096
21147
  }
21097
- for (const sub of section.subsections ?? []) {
21098
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
21099
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
21148
+ for (const sub2 of section.subsections ?? []) {
21149
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
21150
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
21100
21151
  if (rows.length === 0) continue;
21101
- const subMarker = this.section(sub.title);
21152
+ const subMarker = this.section(sub2.title);
21102
21153
  subMarker.dataset.sdStyle = typeId;
21103
21154
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
21104
21155
  subMarker.dataset.sdSub = "1";
21105
21156
  body.append(subMarker);
21106
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
21157
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
21107
21158
  }
21108
21159
  for (const r of refreshers) r();
21109
21160
  }
@@ -23211,11 +23262,11 @@ var DrawingInteraction = class {
23211
23262
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
23212
23263
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
23213
23264
  * while placing a line tool it locks the segment angle to 45° steps. */
23214
- down(x, y, mode = "off", shift = false) {
23265
+ down(x, y, mode = "off", shift2 = false) {
23215
23266
  const proj = this.deps.projector();
23216
23267
  if (this.state.kind === "placing") {
23217
23268
  if (this.state.draft.placementMode() !== "click") return;
23218
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
23269
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
23219
23270
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
23220
23271
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
23221
23272
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -23234,7 +23285,7 @@ var DrawingInteraction = class {
23234
23285
  }
23235
23286
  const hit = this.hitAt(x, y);
23236
23287
  if (hit) {
23237
- if (shift) {
23288
+ if (shift2) {
23238
23289
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
23239
23290
  return;
23240
23291
  }
@@ -23244,7 +23295,7 @@ var DrawingInteraction = class {
23244
23295
  }
23245
23296
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
23246
23297
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
23247
- move(x, y, mode = "off", shift = false) {
23298
+ move(x, y, mode = "off", shift2 = false) {
23248
23299
  if (this.state.kind === "idle") {
23249
23300
  const tool = this.deps.activeTool();
23250
23301
  const had = this.snapAt != null;
@@ -23258,7 +23309,7 @@ var DrawingInteraction = class {
23258
23309
  this.captureFreehand(x, y);
23259
23310
  return;
23260
23311
  }
23261
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
23312
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
23262
23313
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
23263
23314
  this.deps.changed();
23264
23315
  return;
@@ -23271,7 +23322,7 @@ var DrawingInteraction = class {
23271
23322
  if (this.state.handle >= 0) {
23272
23323
  const a = d.anchors[this.state.handle];
23273
23324
  if (a) {
23274
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
23325
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
23275
23326
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
23276
23327
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
23277
23328
  }
@@ -23969,21 +24020,21 @@ var DrawingSettingsPopup = class {
23969
24020
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
23970
24021
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
23971
24022
  }
23972
- const vwap = drawing.vwap;
23973
- if (paths.has("vwap.midColor") && vwap) {
24023
+ const vwap2 = drawing.vwap;
24024
+ if (paths.has("vwap.midColor") && vwap2) {
23974
24025
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
23975
24026
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
23976
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
23977
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
24027
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
24028
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
23978
24029
  bar.appendChild(
23979
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
24030
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
23980
24031
  label: (v) => `${v}\u03C3`,
23981
24032
  labelInTrigger: true
23982
24033
  })
23983
24034
  );
23984
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
23985
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
23986
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
24035
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
24036
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
24037
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
23987
24038
  }
23988
24039
  if (paths.has("maxCurvature")) {
23989
24040
  const cur = drawing.maxCurvature ?? 24;
@@ -24729,8 +24780,8 @@ var DrawingToolbar = class {
24729
24780
  }
24730
24781
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
24731
24782
  * right edge of the flyout rows, beside the favorite star. */
24732
- setShortcuts(map) {
24733
- this.shortcuts = new Map(Object.entries(map));
24783
+ setShortcuts(map2) {
24784
+ this.shortcuts = new Map(Object.entries(map2));
24734
24785
  }
24735
24786
  /** Paint one star for the current favorite state (filled + gold, or outline). */
24736
24787
  paintStar(el, type) {
@@ -25632,8 +25683,8 @@ var UserDrawingController = class {
25632
25683
  this.toolbar.setFavorites(types);
25633
25684
  }
25634
25685
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
25635
- setToolShortcuts(map) {
25636
- this.toolbar.setShortcuts(map);
25686
+ setToolShortcuts(map2) {
25687
+ this.toolbar.setShortcuts(map2);
25637
25688
  }
25638
25689
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
25639
25690
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -25751,7 +25802,7 @@ var UserDrawingController = class {
25751
25802
  this.render();
25752
25803
  }
25753
25804
  }
25754
- pointerDown(x, y, snap = "off", shift = false) {
25805
+ pointerDown(x, y, snap = "off", shift2 = false) {
25755
25806
  if (this.eraserMode) {
25756
25807
  this.erasing = true;
25757
25808
  this.deleteAt(x, y);
@@ -25771,9 +25822,9 @@ var UserDrawingController = class {
25771
25822
  return;
25772
25823
  }
25773
25824
  }
25774
- this.interaction.down(x, y, snap, shift);
25825
+ this.interaction.down(x, y, snap, shift2);
25775
25826
  }
25776
- pointerMove(x, y, snap = "off", shift = false) {
25827
+ pointerMove(x, y, snap = "off", shift2 = false) {
25777
25828
  if (this.eraserMode) {
25778
25829
  if (this.erasing) this.deleteAt(x, y);
25779
25830
  return;
@@ -25784,7 +25835,7 @@ var UserDrawingController = class {
25784
25835
  this.render();
25785
25836
  return;
25786
25837
  }
25787
- this.interaction.move(x, y, snap, shift);
25838
+ this.interaction.move(x, y, snap, shift2);
25788
25839
  this.updateHover(x, y);
25789
25840
  }
25790
25841
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -26461,14 +26512,14 @@ function layoutTable(ctx, t, args) {
26461
26512
  }
26462
26513
  for (const { box, w: w2, h: h2 } of spanning) {
26463
26514
  if (box.cs > 1) {
26464
- let sum = 0;
26465
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
26466
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
26515
+ let sum2 = 0;
26516
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
26517
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
26467
26518
  }
26468
26519
  if (box.rs > 1) {
26469
- let sum = 0;
26470
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
26471
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
26520
+ let sum2 = 0;
26521
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
26522
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
26472
26523
  }
26473
26524
  }
26474
26525
  let w = 0;
@@ -26782,6 +26833,9 @@ function clipHighlightRect(x1, x2, left, right) {
26782
26833
  const end = Math.min(right, x2);
26783
26834
  return end > x ? { x, width: end - x } : null;
26784
26835
  }
26836
+ function bandEdgeSlot(logical) {
26837
+ return Math.ceil(logical) - 0.5;
26838
+ }
26785
26839
  var BackdropRenderer = class {
26786
26840
  constructor() {
26787
26841
  this.canvas = null;
@@ -26812,21 +26866,22 @@ var BackdropRenderer = class {
26812
26866
  this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
26813
26867
  }
26814
26868
  /** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
26815
- * Session-zone washes (pre/post-market) paint first, host highlights on top. */
26869
+ * Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
26870
+ * never cuts a candle in two), host highlights on top (continuous time mapping —
26871
+ * hosts may paint sub-bar spans). */
26816
26872
  drawHighlights(ctx, scene, coords) {
26817
26873
  const sessions = scene.sessionHighlightBands();
26818
26874
  if (sessions.length > 0) {
26819
26875
  const left = Math.max(0, coords.logicalToX(-0.5));
26820
26876
  const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
26821
- this.drawHighlightSet(ctx, sessions, coords, left, right);
26877
+ this.drawHighlightSet(ctx, sessions, coords, left, right, true);
26822
26878
  }
26823
- this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
26879
+ this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
26824
26880
  }
26825
- drawHighlightSet(ctx, bands, coords, left, right) {
26881
+ drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
26882
+ const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
26826
26883
  for (const band of bands) {
26827
- const x1 = coords.timeToX(band.from);
26828
- const x2 = coords.timeToX(band.to);
26829
- const rect = clipHighlightRect(x1, x2, left, right);
26884
+ const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
26830
26885
  if (!rect) continue;
26831
26886
  ctx.fillStyle = band.color;
26832
26887
  ctx.fillRect(rect.x, 0, rect.width, coords.height);
@@ -27085,6 +27140,7 @@ function createCustomMark(doc, html, background) {
27085
27140
  });
27086
27141
  el.style.color = attributionMarkColor(background);
27087
27142
  el.innerHTML = html;
27143
+ el.dataset.velaScreenshot = "1";
27088
27144
  return el;
27089
27145
  }
27090
27146
  function createAttributionMark(doc, background) {
@@ -27103,6 +27159,7 @@ function createAttributionMark(doc, background) {
27103
27159
  cursor: "pointer"
27104
27160
  });
27105
27161
  applyAttributionMarkTheme(a, background);
27162
+ a.dataset.velaScreenshot = "1";
27106
27163
  const symbol = doc.createElement("span");
27107
27164
  symbol.className = "vela-attr-symbol";
27108
27165
  symbol.setAttribute("aria-hidden", "true");
@@ -27185,24 +27242,24 @@ function rasterizeOverlay(ctx, root, frame) {
27185
27242
  drawSvgShapes(el, alpha);
27186
27243
  continue;
27187
27244
  }
27188
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
27245
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
27189
27246
  let path = null;
27190
27247
  if (tag === "path") {
27191
27248
  const d = el.getAttribute("d");
27192
27249
  if (d) path = new Path2D(d);
27193
27250
  } else if (tag === "circle") {
27194
27251
  path = new Path2D();
27195
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
27252
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
27196
27253
  } else if (tag === "ellipse") {
27197
27254
  path = new Path2D();
27198
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
27255
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
27199
27256
  } else if (tag === "rect") {
27200
27257
  path = new Path2D();
27201
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
27258
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
27202
27259
  } else if (tag === "line") {
27203
27260
  path = new Path2D();
27204
- path.moveTo(num3("x1"), num3("y1"));
27205
- path.lineTo(num3("x2"), num3("y2"));
27261
+ path.moveTo(num4("x1"), num4("y1"));
27262
+ path.lineTo(num4("x2"), num4("y2"));
27206
27263
  } else if (tag === "polyline" || tag === "polygon") {
27207
27264
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
27208
27265
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -28107,7 +28164,7 @@ var NativeRenderer = class {
28107
28164
  };
28108
28165
  })(),
28109
28166
  series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
28110
- sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
28167
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
28111
28168
  };
28112
28169
  }
28113
28170
  /**
@@ -28189,7 +28246,7 @@ var NativeRenderer = class {
28189
28246
  width: next.baseline.width,
28190
28247
  baselineLevel: next.baseline.baselineLevel
28191
28248
  };
28192
- s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
28249
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
28193
28250
  this.setPriceStyle(next.series.style);
28194
28251
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
28195
28252
  this.scene.baselineValue = next.series.baseline;
@@ -28411,9 +28468,10 @@ var NativeRenderer = class {
28411
28468
  * opts in with a `data-vela-screenshot` attribute on the mount container's
28412
28469
  * subtree (the widget marks its status line, and its symbol watermark with
28413
28470
  * `"under"` — drawn beneath the canvases, where it sits on screen). Only the
28414
- * crosshair (L2) is intentionally excluded.
28471
+ * crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
28472
+ * as a PNG data URL.
28415
28473
  */
28416
- screenshot() {
28474
+ screenshotCanvas() {
28417
28475
  if (!this.dataCanvas) return null;
28418
28476
  this.computeScales();
28419
28477
  this.paintData();
@@ -28441,7 +28499,10 @@ var NativeRenderer = class {
28441
28499
  if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
28442
28500
  }
28443
28501
  }
28444
- return out.toDataURL("image/png");
28502
+ return out;
28503
+ }
28504
+ screenshot() {
28505
+ return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
28445
28506
  }
28446
28507
  /**
28447
28508
  * The candles-first-appear reveal — animates the **candles themselves** (no extra
@@ -28615,8 +28676,8 @@ var NativeRenderer = class {
28615
28676
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
28616
28677
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
28617
28678
  drawingsSnapMode: () => this.snapMode,
28618
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
28619
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
28679
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
28680
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
28620
28681
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
28621
28682
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
28622
28683
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -28933,18 +28994,18 @@ var NativeRenderer = class {
28933
28994
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
28934
28995
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
28935
28996
  const newHeadTime = this.bars[0].time;
28936
- let shift = 0;
28997
+ let shift2 = 0;
28937
28998
  if (newHeadTime !== prevHeadTime) {
28938
28999
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
28939
- if (prepended > 0) shift = prepended;
29000
+ if (prepended > 0) shift2 = prepended;
28940
29001
  else {
28941
29002
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
28942
- if (trimmed > 0) shift = -trimmed;
29003
+ if (trimmed > 0) shift2 = -trimmed;
28943
29004
  }
28944
29005
  }
28945
- if (shift !== 0) {
28946
- this.zoomAnchorLogical += shift;
28947
- if (this.hoverLogical != null) this.hoverLogical += shift;
29006
+ if (shift2 !== 0) {
29007
+ this.zoomAnchorLogical += shift2;
29008
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
28948
29009
  }
28949
29010
  }
28950
29011
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -29836,7 +29897,7 @@ var NativeRenderer = class {
29836
29897
  const pricePane = this.dataWindowPricePane();
29837
29898
  const ohlc = this.dataWindowOHLC(bar, pricePane);
29838
29899
  const parts = [];
29839
- if (bar) parts.push(formatStamp2(bar.time, this.scene.timezone));
29900
+ if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
29840
29901
  if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
29841
29902
  for (const group of this.dataWindowGroups(idx, pricePane)) {
29842
29903
  for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
@@ -30550,15 +30611,15 @@ var NativeRenderer = class {
30550
30611
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
30551
30612
  * strip — merged own-scale indicators are hidden while collapsed. */
30552
30613
  collapsedMasterMap() {
30553
- const map = /* @__PURE__ */ new Map();
30614
+ const map2 = /* @__PURE__ */ new Map();
30554
30615
  for (const pane of this.scene.panes.values()) {
30555
30616
  if (!pane.collapsed) continue;
30556
30617
  const models = this.scene.orderedIndicatorsForPane(pane.id);
30557
30618
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
30558
30619
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
30559
- map.set(pane.id, master?.id ?? null);
30620
+ map2.set(pane.id, master?.id ?? null);
30560
30621
  }
30561
- return map;
30622
+ return map2;
30562
30623
  }
30563
30624
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
30564
30625
  setToolbarGutter(px) {
@@ -30723,7 +30784,7 @@ function formatStampParts(ms, timeZone) {
30723
30784
  time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
30724
30785
  };
30725
30786
  }
30726
- function formatStamp2(ms, timeZone) {
30787
+ function formatStamp(ms, timeZone) {
30727
30788
  const { date, time } = formatStampParts(ms, timeZone);
30728
30789
  return `${date} ${time}`;
30729
30790
  }
@@ -30754,7 +30815,7 @@ function sanitizeSessionZones(value) {
30754
30815
  }
30755
30816
  return out.sort((a, b) => a[0] - b[0]);
30756
30817
  };
30757
- return { pre: windows(v.pre), post: windows(v.post) };
30818
+ return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
30758
30819
  }
30759
30820
  function clamp012(v) {
30760
30821
  return Math.max(0, Math.min(1, v));
@@ -30954,130 +31015,2449 @@ function registerVpvr() {
30954
31015
  registerNativeIndicator(vpvrDescriptor);
30955
31016
  }
30956
31017
 
30957
- // src/Vela.ts
30958
- var Vela = class {
30959
- constructor(container, options = {}, deps = {}) {
30960
- registerBuiltinChartTypes();
30961
- registerVolume();
30962
- registerVpvr();
30963
- const element = resolveElement(container);
30964
- const theme = resolveTheme(options.theme);
30965
- let animZoom = true;
30966
- let animPan = true;
30967
- if (typeof options.animations === "boolean") {
30968
- animZoom = options.animations;
30969
- animPan = options.animations;
30970
- } else if (options.animations) {
30971
- animZoom = options.animations.zoom ?? true;
30972
- animPan = options.animations.pan ?? true;
30973
- }
30974
- const display = {
30975
- currentPriceLine: options.currentPriceLine ?? true,
30976
- logScale: options.logScale ?? false,
30977
- nativeBackend: options.nativeBackend ?? "auto",
30978
- animZoom,
30979
- animPan,
30980
- glow: options.glow ?? 0,
30981
- upColor: options.upColor ?? BULLISH,
30982
- downColor: options.downColor ?? BEARISH,
30983
- priceStyle: options.priceStyle ?? "candles"
30984
- };
30985
- const RendererClass = options.renderer ?? NativeRenderer;
30986
- if (typeof RendererClass !== "function") {
30987
- throw new Error(
30988
- "[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
30989
- );
30990
- }
30991
- const renderer = deps.renderer ?? new RendererClass(display);
30992
- const engines = deps.engines ?? [];
30993
- const feed = deps.dataFeed ?? new MultiProviderFeed();
30994
- const config = {
30995
- market: {
30996
- symbol: options.symbol,
30997
- timeframe: options.timeframe,
30998
- bars: options.bars,
30999
- session: options.session,
31000
- visibleRange: options.visibleRange,
31001
- data: options.data
31002
- },
31003
- live: options.live ?? false,
31004
- theme,
31005
- defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
31006
- drawings: options.drawings,
31007
- volume: options.volume ?? true
31008
- // the volume indicator is on by default
31009
- };
31010
- this.rendererControl = new RendererControl(renderer);
31011
- if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
31012
- this.dataControl = new DataControl(feed);
31013
- this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
31014
- const defaults2 = rendererDefaults();
31015
- if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
31016
- this.panesControl = new PanesControl(this.orchestrator);
31017
- this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
31018
+ // src/core/model/identity.ts
31019
+ function stableSeriesId(parts) {
31020
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
31021
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
31022
+ }
31023
+
31024
+ // src/core/native-indicators/classics/define.ts
31025
+ function num3(inputs, key, fallback) {
31026
+ const v = inputs[key];
31027
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
31028
+ }
31029
+ function str3(inputs, key, fallback) {
31030
+ const v = inputs[key];
31031
+ return typeof v === "string" && v !== "" ? v : fallback;
31032
+ }
31033
+ function toPoints(bars, values, colors) {
31034
+ const pts = new Array(bars.length);
31035
+ for (let i = 0; i < bars.length; i++) {
31036
+ const v = values[i];
31037
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
31038
+ const c = colors?.[i];
31039
+ if (c != null) p.color = c;
31040
+ pts[i] = p;
31018
31041
  }
31019
- /**
31020
- * Register a scripting engine so `addIndicator({ language })` can run that
31021
- * language. Vela ships NO engine — install the one you need (Pine Script:
31022
- * `@luxalgo/vela-pinets`) and register it, e.g.
31023
- * `chart.registerEngine('pine', new PineEngine())`; without one the chart
31024
- * displays candles, drawings and native indicators only. Re-registering a
31025
- * language replaces it.
31026
- */
31027
- registerEngine(language, engine) {
31028
- this.orchestrator.registerEngine(language, engine);
31029
- return this;
31042
+ return pts;
31043
+ }
31044
+ var ClassicIndicator = class {
31045
+ constructor(spec) {
31046
+ this.spec = spec;
31047
+ this.inputs = {};
31030
31048
  }
31031
- /** Run an indicator script on the chart's market data and render it. */
31032
- addIndicator(source, options) {
31033
- return this.orchestrator.addIndicator(source, options);
31049
+ start(ctx, inputs) {
31050
+ this.ctx = ctx;
31051
+ this.inputs = inputs;
31052
+ this.recompute();
31034
31053
  }
31035
- /**
31036
- * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
31037
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
31038
- * events) and is single-instance per type (a second call returns the existing handle). Returns
31039
- * a fail-soft handle for an unregistered type. Native renderer only.
31040
- */
31041
- addNativeIndicator(type, options) {
31042
- return this.orchestrator.addNativeIndicator(type, options);
31054
+ onBars() {
31055
+ this.recompute();
31043
31056
  }
31044
- /**
31045
- * The catalog of built-in native indicators with their live state on this chart — each entry's
31046
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
31047
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
31048
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
31049
- * because support may probe the provider (a type may need data the symbol lacks).
31050
- */
31051
- availableNativeIndicators() {
31052
- return this.orchestrator.availableNativeIndicators();
31057
+ onViewport() {
31053
31058
  }
31054
- /**
31055
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
31056
- * {@link availableNativeIndicators} (only support probing is async; presence never is).
31057
- * Persistence snapshots read this: an unload-time flush must see an add/remove that
31058
- * happened microseconds ago, which an async catalog mirror cannot guarantee.
31059
- */
31060
- presentNativeIndicators() {
31061
- return this.orchestrator.presentNativeIndicators();
31059
+ setInputs(inputs) {
31060
+ this.inputs = inputs;
31061
+ this.recompute();
31062
31062
  }
31063
- /** Live handles of every indicator currently on the chart (script + native) — drive
31064
- * host panels (object trees, indicator lists) with per-id visibility/removal. */
31065
- indicators() {
31066
- return this.orchestrator.listIndicators();
31063
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
31064
+ suspend() {
31067
31065
  }
31068
- /**
31069
- * Execute an indicator script and INJECT it only if the run succeeds — the seam for
31070
- * host editors and consoles. Resolves `{ ok: true, handle }` after the first
31071
- * successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
31072
- * in which case the failed indicator is removed again (no dead legend row).
31073
- * Never rejects.
31074
- */
31075
- /**
31076
- * Execute a script and resolve its FIRST computed run — the data-out door for host
31077
- * editors, consoles and dashboards. The script is injected only if it runs (a failure
31078
- * removes it again, leaving no dead legend row), and the result carries the run itself
31079
- * plus the controls for what it put on the chart: `onUpdate` to follow later runs,
31080
- * `remove` to take it off. Never rejects.
31066
+ resume() {
31067
+ this.recompute();
31068
+ }
31069
+ stop() {
31070
+ }
31071
+ recompute() {
31072
+ const bars = this.ctx.bars();
31073
+ const out = this.spec.compute(bars, this.inputs);
31074
+ const instanceId = this.spec.type;
31075
+ const series = [];
31076
+ const idsByKey = /* @__PURE__ */ new Map();
31077
+ out.plots.forEach((plot, ordinal) => {
31078
+ const kind = plot.kind ?? "line";
31079
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
31080
+ idsByKey.set(plot.key, id);
31081
+ series.push({
31082
+ id,
31083
+ title: plot.title,
31084
+ paneId: "",
31085
+ // stamped by the orchestrator
31086
+ kind,
31087
+ points: toPoints(bars, plot.values, plot.colors),
31088
+ style: {
31089
+ color: plot.color,
31090
+ width: plot.width ?? 1,
31091
+ lineStyle: plot.lineStyle ?? "solid",
31092
+ ...plot.base != null ? { base: plot.base } : {}
31093
+ },
31094
+ ...plot.overlay ? { overlay: true } : {}
31095
+ });
31096
+ });
31097
+ if (out.markers && out.markers.length > 0) {
31098
+ series.push({
31099
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
31100
+ title: this.spec.shortTitle ?? this.spec.title,
31101
+ paneId: "",
31102
+ kind: "markers",
31103
+ markers: out.markers
31104
+ });
31105
+ }
31106
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
31107
+ const from = idsByKey.get(band.from);
31108
+ const to = idsByKey.get(band.to);
31109
+ if (!from || !to) return [];
31110
+ return [
31111
+ {
31112
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
31113
+ paneId: "",
31114
+ fromSeriesId: from,
31115
+ toSeriesId: to,
31116
+ color: band.color
31117
+ }
31118
+ ];
31119
+ });
31120
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
31121
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
31122
+ paneId: "",
31123
+ price: level.price,
31124
+ ...level.color != null ? { color: level.color } : {},
31125
+ lineStyle: level.lineStyle ?? "dashed",
31126
+ width: 1,
31127
+ ...level.title != null ? { title: level.title } : {}
31128
+ }));
31129
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
31130
+ const { key, ...rest } = poly;
31131
+ return {
31132
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
31133
+ paneId: "",
31134
+ ...rest
31135
+ };
31136
+ });
31137
+ this.ctx.emit({ series, fills, priceLines, polylines });
31138
+ this.ctx.setStatus("idle");
31139
+ }
31140
+ };
31141
+ function classicDescriptor(spec) {
31142
+ return {
31143
+ type: spec.type,
31144
+ title: spec.title,
31145
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
31146
+ paneHint: spec.overlay ? "price" : "new",
31147
+ overlay: spec.overlay,
31148
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
31149
+ multiInstance: true,
31150
+ inputsSchema: () => spec.inputs,
31151
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
31152
+ create: () => new ClassicIndicator(spec)
31153
+ };
31154
+ }
31155
+
31156
+ // src/core/native-indicators/classics/math.ts
31157
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
31158
+ function sourceValues(bars, source) {
31159
+ const out = new Array(bars.length);
31160
+ for (let i = 0; i < bars.length; i++) {
31161
+ const b = bars[i];
31162
+ switch (source) {
31163
+ case "Open":
31164
+ out[i] = b.open;
31165
+ break;
31166
+ case "High":
31167
+ out[i] = b.high;
31168
+ break;
31169
+ case "Low":
31170
+ out[i] = b.low;
31171
+ break;
31172
+ case "HL2":
31173
+ out[i] = (b.high + b.low) / 2;
31174
+ break;
31175
+ case "HLC3":
31176
+ out[i] = (b.high + b.low + b.close) / 3;
31177
+ break;
31178
+ case "OHLC4":
31179
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
31180
+ break;
31181
+ case "HLCC4":
31182
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
31183
+ break;
31184
+ default:
31185
+ out[i] = b.close;
31186
+ }
31187
+ }
31188
+ return out;
31189
+ }
31190
+ function volumes(bars) {
31191
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
31192
+ }
31193
+ function highs(bars) {
31194
+ return bars.map((b) => b.high);
31195
+ }
31196
+ function lows(bars) {
31197
+ return bars.map((b) => b.low);
31198
+ }
31199
+ function closes(bars) {
31200
+ return bars.map((b) => b.close);
31201
+ }
31202
+ function opens(bars) {
31203
+ return bars.map((b) => b.open);
31204
+ }
31205
+ function zip(a, b, f) {
31206
+ const out = new Array(a.length);
31207
+ for (let i = 0; i < a.length; i++) {
31208
+ const x = a[i];
31209
+ const y = b[i];
31210
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
31211
+ }
31212
+ return out;
31213
+ }
31214
+ function map(a, f) {
31215
+ const out = new Array(a.length);
31216
+ for (let i = 0; i < a.length; i++) {
31217
+ const x = a[i];
31218
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
31219
+ }
31220
+ return out;
31221
+ }
31222
+ function sub(a, b) {
31223
+ return zip(a, b, (x, y) => x - y);
31224
+ }
31225
+ function windowed(v, len, fold) {
31226
+ const out = new Array(v.length).fill(Number.NaN);
31227
+ if (len <= 0) return out;
31228
+ let run = 0;
31229
+ for (let i = 0; i < v.length; i++) {
31230
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
31231
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
31232
+ }
31233
+ return out;
31234
+ }
31235
+ function sum(v, len) {
31236
+ return windowed(v, len, (w, from, to) => {
31237
+ let s = 0;
31238
+ for (let k = from; k <= to; k++) s += w[k];
31239
+ return s;
31240
+ });
31241
+ }
31242
+ function sma(v, len) {
31243
+ return windowed(v, len, (w, from, to) => {
31244
+ let s = 0;
31245
+ for (let k = from; k <= to; k++) s += w[k];
31246
+ return s / len;
31247
+ });
31248
+ }
31249
+ function wma(v, len) {
31250
+ const norm = len * (len + 1) / 2;
31251
+ return windowed(v, len, (w, from, to) => {
31252
+ let s = 0;
31253
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
31254
+ return s / norm;
31255
+ });
31256
+ }
31257
+ function vwma(src, vol, len) {
31258
+ const pv = zip(src, vol, (p, q) => p * q);
31259
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
31260
+ }
31261
+ function stdev(v, len) {
31262
+ return windowed(v, len, (w, from, to) => {
31263
+ let s = 0;
31264
+ for (let k = from; k <= to; k++) s += w[k];
31265
+ const mean = s / len;
31266
+ let sq = 0;
31267
+ for (let k = from; k <= to; k++) {
31268
+ const d = w[k] - mean;
31269
+ sq += d * d;
31270
+ }
31271
+ return Math.sqrt(sq / len);
31272
+ });
31273
+ }
31274
+ function highest(v, len) {
31275
+ return windowed(v, len, (w, from, to) => {
31276
+ let m = -Infinity;
31277
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
31278
+ return m;
31279
+ });
31280
+ }
31281
+ function lowest(v, len) {
31282
+ return windowed(v, len, (w, from, to) => {
31283
+ let m = Infinity;
31284
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
31285
+ return m;
31286
+ });
31287
+ }
31288
+ function barsSinceHighest(v, len) {
31289
+ return windowed(v, len, (w, from, to) => {
31290
+ let m = -Infinity;
31291
+ let at = to;
31292
+ for (let k = from; k <= to; k++) {
31293
+ if (w[k] >= m) {
31294
+ m = w[k];
31295
+ at = k;
31296
+ }
31297
+ }
31298
+ return to - at;
31299
+ });
31300
+ }
31301
+ function barsSinceLowest(v, len) {
31302
+ return windowed(v, len, (w, from, to) => {
31303
+ let m = Infinity;
31304
+ let at = to;
31305
+ for (let k = from; k <= to; k++) {
31306
+ if (w[k] <= m) {
31307
+ m = w[k];
31308
+ at = k;
31309
+ }
31310
+ }
31311
+ return to - at;
31312
+ });
31313
+ }
31314
+ function recursive(v, len, alpha) {
31315
+ const out = new Array(v.length).fill(Number.NaN);
31316
+ let run = 0;
31317
+ let prev2 = Number.NaN;
31318
+ for (let i = 0; i < v.length; i++) {
31319
+ const x = v[i];
31320
+ if (!Number.isFinite(x)) {
31321
+ run = 0;
31322
+ prev2 = Number.NaN;
31323
+ continue;
31324
+ }
31325
+ run += 1;
31326
+ if (Number.isFinite(prev2)) {
31327
+ prev2 = alpha * x + (1 - alpha) * prev2;
31328
+ out[i] = prev2;
31329
+ } else if (run >= len) {
31330
+ let s = 0;
31331
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
31332
+ prev2 = s / len;
31333
+ out[i] = prev2;
31334
+ }
31335
+ }
31336
+ return out;
31337
+ }
31338
+ function ema(v, len) {
31339
+ return recursive(v, len, 2 / (len + 1));
31340
+ }
31341
+ function rma(v, len) {
31342
+ return recursive(v, len, 1 / len);
31343
+ }
31344
+ function change(v, n = 1) {
31345
+ const out = new Array(v.length).fill(Number.NaN);
31346
+ for (let i = n; i < v.length; i++) {
31347
+ const a = v[i];
31348
+ const b = v[i - n];
31349
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
31350
+ }
31351
+ return out;
31352
+ }
31353
+ function roc(v, len) {
31354
+ const out = new Array(v.length).fill(Number.NaN);
31355
+ for (let i = len; i < v.length; i++) {
31356
+ const a = v[i];
31357
+ const b = v[i - len];
31358
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
31359
+ }
31360
+ return out;
31361
+ }
31362
+ function shift(v, n) {
31363
+ const out = new Array(v.length).fill(Number.NaN);
31364
+ for (let i = 0; i < v.length; i++) {
31365
+ const j = i - n;
31366
+ if (j >= 0 && j < v.length) out[i] = v[j];
31367
+ }
31368
+ return out;
31369
+ }
31370
+ function trueRange(bars) {
31371
+ const out = new Array(bars.length).fill(Number.NaN);
31372
+ for (let i = 0; i < bars.length; i++) {
31373
+ const b = bars[i];
31374
+ if (i === 0) {
31375
+ out[i] = b.high - b.low;
31376
+ continue;
31377
+ }
31378
+ const pc = bars[i - 1].close;
31379
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
31380
+ }
31381
+ return out;
31382
+ }
31383
+ function atr(bars, len) {
31384
+ return rma(trueRange(bars), len);
31385
+ }
31386
+ function rsi(v, len) {
31387
+ const d = change(v);
31388
+ const up = map(d, (x) => Math.max(x, 0));
31389
+ const dn = map(d, (x) => Math.max(-x, 0));
31390
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
31391
+ }
31392
+ function stoch(v, hi, lo, len) {
31393
+ const hh = highest(hi, len);
31394
+ const ll = lowest(lo, len);
31395
+ const out = new Array(v.length).fill(Number.NaN);
31396
+ for (let i = 0; i < v.length; i++) {
31397
+ const x = v[i];
31398
+ const h = hh[i];
31399
+ const l = ll[i];
31400
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
31401
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
31402
+ }
31403
+ return out;
31404
+ }
31405
+ function linreg(v, len) {
31406
+ const sx = (len - 1) * len / 2;
31407
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
31408
+ const denom = len * sxx - sx * sx;
31409
+ return windowed(v, len, (w, from, to) => {
31410
+ let sy = 0;
31411
+ let sxy = 0;
31412
+ for (let k = from; k <= to; k++) {
31413
+ const x = k - from;
31414
+ sy += w[k];
31415
+ sxy += x * w[k];
31416
+ }
31417
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
31418
+ const intercept = (sy - slope * sx) / len;
31419
+ return intercept + slope * (len - 1);
31420
+ });
31421
+ }
31422
+ function swma(v) {
31423
+ const out = new Array(v.length).fill(Number.NaN);
31424
+ for (let i = 3; i < v.length; i++) {
31425
+ const a = v[i - 3];
31426
+ const b = v[i - 2];
31427
+ const c = v[i - 1];
31428
+ const d = v[i];
31429
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
31430
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
31431
+ }
31432
+ }
31433
+ return out;
31434
+ }
31435
+ function percentRank(v, len) {
31436
+ const out = new Array(v.length).fill(Number.NaN);
31437
+ for (let i = len; i < v.length; i++) {
31438
+ const x = v[i];
31439
+ if (!Number.isFinite(x)) continue;
31440
+ let below = 0;
31441
+ let count = 0;
31442
+ for (let k = i - len; k < i; k++) {
31443
+ const y = v[k];
31444
+ if (!Number.isFinite(y)) {
31445
+ count = -1;
31446
+ break;
31447
+ }
31448
+ count += 1;
31449
+ if (y <= x) below += 1;
31450
+ }
31451
+ if (count === len) out[i] = 100 * below / len;
31452
+ }
31453
+ return out;
31454
+ }
31455
+ function cumSum(v) {
31456
+ const out = new Array(v.length).fill(Number.NaN);
31457
+ let acc = 0;
31458
+ let seen = false;
31459
+ for (let i = 0; i < v.length; i++) {
31460
+ const x = v[i];
31461
+ if (Number.isFinite(x)) {
31462
+ acc += x;
31463
+ seen = true;
31464
+ }
31465
+ if (seen) out[i] = acc;
31466
+ }
31467
+ return out;
31468
+ }
31469
+
31470
+ // src/core/native-indicators/classics/shared.ts
31471
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
31472
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
31473
+ }
31474
+ function sourceInput(defval = "Close") {
31475
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
31476
+ }
31477
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
31478
+ return { key, title, type: "color", defval };
31479
+ }
31480
+ function withAlpha2(hex, alpha = 0.08) {
31481
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
31482
+ if (!m) return hex;
31483
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
31484
+ return `#${m[1]}${a}`;
31485
+ }
31486
+
31487
+ // src/core/native-indicators/classics/averages.ts
31488
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
31489
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
31490
+ function fixedAverage(type, title, shortTitle, smooth) {
31491
+ return {
31492
+ type,
31493
+ title,
31494
+ shortTitle,
31495
+ overlay: true,
31496
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
31497
+ compute: (bars, inputs) => {
31498
+ const len = num3(inputs, "length", 20);
31499
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
31500
+ const offset = Math.trunc(num3(inputs, "offset", 0));
31501
+ if (offset !== 0) values = shift(values, offset);
31502
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31503
+ }
31504
+ };
31505
+ }
31506
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
31507
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
31508
+ var movingAverage = {
31509
+ type: "moving-average",
31510
+ title: "Moving Average",
31511
+ shortTitle: "MA",
31512
+ overlay: true,
31513
+ inputs: [
31514
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
31515
+ lengthInput(20),
31516
+ sourceInput(),
31517
+ offsetInput,
31518
+ colorInput()
31519
+ ],
31520
+ compute: (bars, inputs) => {
31521
+ const len = num3(inputs, "length", 20);
31522
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31523
+ const kind = str3(inputs, "maType", "SMA");
31524
+ let values;
31525
+ switch (kind) {
31526
+ case "EMA":
31527
+ values = ema(src, len);
31528
+ break;
31529
+ case "WMA":
31530
+ values = wma(src, len);
31531
+ break;
31532
+ case "RMA":
31533
+ values = rma(src, len);
31534
+ break;
31535
+ case "VWMA":
31536
+ values = vwma(src, volumes(bars), len);
31537
+ break;
31538
+ default:
31539
+ values = sma(src, len);
31540
+ }
31541
+ const offset = Math.trunc(num3(inputs, "offset", 0));
31542
+ if (offset !== 0) values = shift(values, offset);
31543
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31544
+ }
31545
+ };
31546
+ var smoothedMa = {
31547
+ type: "rma",
31548
+ title: "Smoothed Moving Average",
31549
+ shortTitle: "RMA",
31550
+ overlay: true,
31551
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31552
+ compute: (bars, inputs) => ({
31553
+ plots: [
31554
+ {
31555
+ key: "rma",
31556
+ title: "RMA",
31557
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
31558
+ color: str3(inputs, "color", SERIES_LINE),
31559
+ width: 2
31560
+ }
31561
+ ]
31562
+ })
31563
+ };
31564
+ var zlema = {
31565
+ type: "zlema",
31566
+ title: "Zero-Lag Exponential Moving Average",
31567
+ shortTitle: "ZLEMA",
31568
+ overlay: true,
31569
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31570
+ compute: (bars, inputs) => {
31571
+ const len = num3(inputs, "length", 14);
31572
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31573
+ const lag = Math.floor((len - 1) / 2);
31574
+ const lagged = shift(src, lag);
31575
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
31576
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31577
+ }
31578
+ };
31579
+ var vidya = {
31580
+ type: "vidya",
31581
+ title: "Variable Index Dynamic Average",
31582
+ shortTitle: "VIDYA",
31583
+ overlay: true,
31584
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
31585
+ compute: (bars, inputs) => {
31586
+ const len = num3(inputs, "length", 14);
31587
+ const cmoLen = num3(inputs, "cmoLength", 9);
31588
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31589
+ const d = change(src);
31590
+ const su = new Array(src.length).fill(Number.NaN);
31591
+ const sd = new Array(src.length).fill(Number.NaN);
31592
+ for (let i = cmoLen; i < src.length; i++) {
31593
+ let u = 0;
31594
+ let w = 0;
31595
+ let ok = true;
31596
+ for (let k = i - cmoLen + 1; k <= i; k++) {
31597
+ const x = d[k];
31598
+ if (!Number.isFinite(x)) {
31599
+ ok = false;
31600
+ break;
31601
+ }
31602
+ if (x > 0) u += x;
31603
+ else w -= x;
31604
+ }
31605
+ if (ok) {
31606
+ su[i] = u;
31607
+ sd[i] = w;
31608
+ }
31609
+ }
31610
+ const alpha = 2 / (len + 1);
31611
+ const out = new Array(src.length).fill(Number.NaN);
31612
+ let prev2 = Number.NaN;
31613
+ for (let i = 0; i < src.length; i++) {
31614
+ const x = src[i];
31615
+ const u = su[i];
31616
+ const w = sd[i];
31617
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
31618
+ const total = u + w;
31619
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
31620
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
31621
+ out[i] = prev2;
31622
+ }
31623
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31624
+ }
31625
+ };
31626
+ var maEnvelope = {
31627
+ type: "ma-envelope",
31628
+ title: "Moving Average Envelope",
31629
+ shortTitle: "MA Env",
31630
+ overlay: true,
31631
+ inputs: [
31632
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
31633
+ lengthInput(20),
31634
+ sourceInput(),
31635
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
31636
+ colorInput()
31637
+ ],
31638
+ compute: (bars, inputs) => {
31639
+ const len = num3(inputs, "length", 20);
31640
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31641
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
31642
+ const k = num3(inputs, "percent", 2.5) / 100;
31643
+ const ink = str3(inputs, "color", SERIES_LINE);
31644
+ return {
31645
+ plots: [
31646
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
31647
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
31648
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
31649
+ ],
31650
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
31651
+ };
31652
+ }
31653
+ };
31654
+ var linearRegression = {
31655
+ type: "linear-regression",
31656
+ title: "Linear Regression Curve",
31657
+ shortTitle: "LinReg",
31658
+ overlay: true,
31659
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31660
+ compute: (bars, inputs) => ({
31661
+ plots: [
31662
+ {
31663
+ key: "linreg",
31664
+ title: "LinReg",
31665
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
31666
+ color: str3(inputs, "color", SERIES_LINE),
31667
+ width: 2
31668
+ }
31669
+ ]
31670
+ })
31671
+ };
31672
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
31673
+
31674
+ // src/core/native-indicators/classics/bands.ts
31675
+ function bollinger(bars, inputs) {
31676
+ const len = num3(inputs, "length", 20);
31677
+ const mult = num3(inputs, "mult", 2);
31678
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31679
+ const basis = sma(src, len);
31680
+ const dev = map(stdev(src, len), (x) => x * mult);
31681
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
31682
+ }
31683
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
31684
+ var bollingerBands = {
31685
+ type: "bollinger-bands",
31686
+ title: "Bollinger Bands",
31687
+ shortTitle: "BB",
31688
+ overlay: true,
31689
+ inputs: [...BB_INPUTS, colorInput()],
31690
+ compute: (bars, inputs) => {
31691
+ const { basis, upper, lower } = bollinger(bars, inputs);
31692
+ const ink = str3(inputs, "color", SERIES_LINE);
31693
+ return {
31694
+ plots: [
31695
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
31696
+ { key: "upper", title: "Upper", values: upper, color: ink },
31697
+ { key: "lower", title: "Lower", values: lower, color: ink }
31698
+ ],
31699
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
31700
+ };
31701
+ }
31702
+ };
31703
+ var percentB = {
31704
+ type: "percent-b",
31705
+ title: "Bollinger Bands %B",
31706
+ shortTitle: "%B",
31707
+ overlay: false,
31708
+ inputs: [...BB_INPUTS, colorInput()],
31709
+ compute: (bars, inputs) => {
31710
+ const { upper, lower } = bollinger(bars, inputs);
31711
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
31712
+ const values = src.map((x, i) => {
31713
+ const u = upper[i];
31714
+ const l = lower[i];
31715
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
31716
+ });
31717
+ return {
31718
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
31719
+ levels: [
31720
+ { key: "upper", price: 1, color: NEUTRAL },
31721
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
31722
+ { key: "lower", price: 0, color: NEUTRAL }
31723
+ ]
31724
+ };
31725
+ }
31726
+ };
31727
+ var bandwidth = {
31728
+ type: "bandwidth",
31729
+ title: "Bollinger Bands Width",
31730
+ shortTitle: "BandWidth",
31731
+ overlay: false,
31732
+ inputs: [...BB_INPUTS, colorInput()],
31733
+ compute: (bars, inputs) => {
31734
+ const { basis, upper, lower } = bollinger(bars, inputs);
31735
+ const values = basis.map((b, i) => {
31736
+ const u = upper[i];
31737
+ const l = lower[i];
31738
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
31739
+ });
31740
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
31741
+ }
31742
+ };
31743
+ var keltner = {
31744
+ type: "keltner-channels",
31745
+ title: "Keltner Channels",
31746
+ shortTitle: "KC",
31747
+ overlay: true,
31748
+ inputs: [
31749
+ lengthInput(20),
31750
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
31751
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31752
+ sourceInput(),
31753
+ colorInput()
31754
+ ],
31755
+ compute: (bars, inputs) => {
31756
+ const len = num3(inputs, "length", 20);
31757
+ const mult = num3(inputs, "mult", 2);
31758
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
31759
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
31760
+ const ink = str3(inputs, "color", SERIES_LINE);
31761
+ return {
31762
+ plots: [
31763
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
31764
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
31765
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
31766
+ ],
31767
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
31768
+ };
31769
+ }
31770
+ };
31771
+ var donchian = {
31772
+ type: "donchian-channels",
31773
+ title: "Donchian Channels",
31774
+ shortTitle: "DC",
31775
+ overlay: true,
31776
+ inputs: [lengthInput(20), colorInput()],
31777
+ compute: (bars, inputs) => {
31778
+ const len = num3(inputs, "length", 20);
31779
+ const upper = highest(highs(bars), len);
31780
+ const lower = lowest(lows(bars), len);
31781
+ const ink = str3(inputs, "color", SERIES_LINE);
31782
+ return {
31783
+ plots: [
31784
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
31785
+ { key: "upper", title: "Upper", values: upper, color: ink },
31786
+ { key: "lower", title: "Lower", values: lower, color: ink }
31787
+ ],
31788
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
31789
+ };
31790
+ }
31791
+ };
31792
+ var chandelier = {
31793
+ type: "chandelier-exit",
31794
+ title: "Chandelier Exit",
31795
+ shortTitle: "CE",
31796
+ overlay: true,
31797
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
31798
+ compute: (bars, inputs) => {
31799
+ const len = num3(inputs, "length", 22);
31800
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
31801
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
31802
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
31803
+ return {
31804
+ plots: [
31805
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
31806
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
31807
+ ]
31808
+ };
31809
+ }
31810
+ };
31811
+ var chandeKroll = {
31812
+ type: "chande-kroll-stop",
31813
+ title: "Chande Kroll Stop",
31814
+ shortTitle: "CKS",
31815
+ overlay: true,
31816
+ inputs: [
31817
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31818
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
31819
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
31820
+ ],
31821
+ compute: (bars, inputs) => {
31822
+ const p = num3(inputs, "p", 10);
31823
+ const x = num3(inputs, "x", 1);
31824
+ const q = num3(inputs, "q", 9);
31825
+ const range = map(atr(bars, p), (v) => v * x);
31826
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
31827
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
31828
+ return {
31829
+ plots: [
31830
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
31831
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
31832
+ ]
31833
+ };
31834
+ }
31835
+ };
31836
+ var supertrend = {
31837
+ type: "supertrend",
31838
+ title: "SuperTrend",
31839
+ shortTitle: "SuperTrend",
31840
+ overlay: true,
31841
+ inputs: [
31842
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
31843
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
31844
+ ],
31845
+ compute: (bars, inputs) => {
31846
+ const len = num3(inputs, "atrLength", 10);
31847
+ const mult = num3(inputs, "mult", 3);
31848
+ const range = atr(bars, len);
31849
+ const n = bars.length;
31850
+ const st = new Array(n).fill(Number.NaN);
31851
+ const dir = new Array(n).fill(1);
31852
+ let up = Number.NaN;
31853
+ let dn = Number.NaN;
31854
+ let trend = 1;
31855
+ for (let i = 0; i < n; i++) {
31856
+ const r = range[i];
31857
+ if (!Number.isFinite(r)) continue;
31858
+ const b = bars[i];
31859
+ const mid = (b.high + b.low) / 2;
31860
+ const basicUp = mid - mult * r;
31861
+ const basicDn = mid + mult * r;
31862
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
31863
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
31864
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
31865
+ if (trend === 1 && b.close < up) trend = -1;
31866
+ else if (trend === -1 && b.close > dn) trend = 1;
31867
+ dir[i] = trend;
31868
+ st[i] = trend === 1 ? up : dn;
31869
+ }
31870
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
31871
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
31872
+ }
31873
+ };
31874
+ var alligator = {
31875
+ type: "williams-alligator",
31876
+ title: "Williams Alligator",
31877
+ shortTitle: "Alligator",
31878
+ overlay: true,
31879
+ inputs: [
31880
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
31881
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
31882
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
31883
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
31884
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
31885
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
31886
+ ],
31887
+ compute: (bars, inputs) => {
31888
+ const hl2 = sourceValues(bars, "HL2");
31889
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
31890
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
31891
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
31892
+ return {
31893
+ plots: [
31894
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
31895
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
31896
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
31897
+ ]
31898
+ };
31899
+ }
31900
+ };
31901
+ var gator = {
31902
+ type: "gator-oscillator",
31903
+ title: "Gator Oscillator",
31904
+ shortTitle: "Gator Oscillator",
31905
+ overlay: false,
31906
+ inputs: [],
31907
+ compute: (bars) => {
31908
+ const hl2 = sourceValues(bars, "HL2");
31909
+ const jaw = shift(rma(hl2, 13), 8);
31910
+ const teeth = shift(rma(hl2, 8), 5);
31911
+ const lips = shift(rma(hl2, 5), 3);
31912
+ const upper = map(sub(jaw, teeth), Math.abs);
31913
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
31914
+ const colorByExpansion = (v) => v.map((x, i) => {
31915
+ if (!Number.isFinite(x)) return null;
31916
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
31917
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
31918
+ });
31919
+ return {
31920
+ plots: [
31921
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
31922
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
31923
+ ]
31924
+ };
31925
+ }
31926
+ };
31927
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
31928
+
31929
+ // src/core/native-indicators/classics/oscillators.ts
31930
+ function guideLevels(low, high, mid) {
31931
+ const levels = [
31932
+ { key: "upper", price: high, color: NEUTRAL },
31933
+ { key: "lower", price: low, color: NEUTRAL }
31934
+ ];
31935
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
31936
+ return levels;
31937
+ }
31938
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
31939
+ function histColors(values) {
31940
+ return values.map((x, i) => {
31941
+ if (!Number.isFinite(x)) return null;
31942
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
31943
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
31944
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
31945
+ return rising ? `${BEARISH}80` : BEARISH;
31946
+ });
31947
+ }
31948
+ var rsiSpec = {
31949
+ type: "rsi",
31950
+ title: "Relative Strength Index",
31951
+ shortTitle: "RSI",
31952
+ overlay: false,
31953
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
31954
+ compute: (bars, inputs) => ({
31955
+ plots: [
31956
+ {
31957
+ key: "rsi",
31958
+ title: "RSI",
31959
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
31960
+ color: str3(inputs, "color", SERIES_LINE),
31961
+ width: 2
31962
+ }
31963
+ ],
31964
+ levels: guideLevels(30, 70, 50)
31965
+ })
31966
+ };
31967
+ var stochastic = {
31968
+ type: "stochastic",
31969
+ title: "Stochastic",
31970
+ shortTitle: "Stoch",
31971
+ overlay: false,
31972
+ inputs: [
31973
+ lengthInput(14, "kLength", "%K length"),
31974
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
31975
+ lengthInput(3, "dLength", "%D smoothing", 500)
31976
+ ],
31977
+ compute: (bars, inputs) => {
31978
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
31979
+ const d = sma(k, num3(inputs, "dLength", 3));
31980
+ return {
31981
+ plots: [
31982
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
31983
+ { key: "d", title: "%D", values: d, color: WARNING }
31984
+ ],
31985
+ levels: guideLevels(20, 80)
31986
+ };
31987
+ }
31988
+ };
31989
+ var stochasticRsi = {
31990
+ type: "stochastic-rsi",
31991
+ title: "Stochastic RSI",
31992
+ shortTitle: "Stoch RSI",
31993
+ overlay: false,
31994
+ inputs: [
31995
+ lengthInput(14, "rsiLength", "RSI length"),
31996
+ lengthInput(14, "stochLength", "Stochastic length"),
31997
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
31998
+ lengthInput(3, "dLength", "%D smoothing", 500),
31999
+ sourceInput()
32000
+ ],
32001
+ compute: (bars, inputs) => {
32002
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
32003
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
32004
+ const d = sma(k, num3(inputs, "dLength", 3));
32005
+ return {
32006
+ plots: [
32007
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
32008
+ { key: "d", title: "%D", values: d, color: WARNING }
32009
+ ],
32010
+ levels: guideLevels(20, 80)
32011
+ };
32012
+ }
32013
+ };
32014
+ var macd = {
32015
+ type: "macd",
32016
+ title: "MACD",
32017
+ overlay: false,
32018
+ inputs: [
32019
+ lengthInput(12, "fastLength", "Fast length"),
32020
+ lengthInput(26, "slowLength", "Slow length"),
32021
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
32022
+ sourceInput()
32023
+ ],
32024
+ compute: (bars, inputs) => {
32025
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32026
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
32027
+ const signal = ema(line, num3(inputs, "signalLength", 9));
32028
+ const hist = sub(line, signal);
32029
+ return {
32030
+ plots: [
32031
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
32032
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
32033
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32034
+ ],
32035
+ levels: ZERO_LEVEL
32036
+ };
32037
+ }
32038
+ };
32039
+ var ppo = {
32040
+ type: "ppo",
32041
+ title: "Percentage Price Oscillator",
32042
+ shortTitle: "PPO",
32043
+ overlay: false,
32044
+ inputs: [
32045
+ lengthInput(12, "fastLength", "Fast length"),
32046
+ lengthInput(26, "slowLength", "Slow length"),
32047
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
32048
+ sourceInput()
32049
+ ],
32050
+ compute: (bars, inputs) => {
32051
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32052
+ const fast = ema(src, num3(inputs, "fastLength", 12));
32053
+ const slow = ema(src, num3(inputs, "slowLength", 26));
32054
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
32055
+ const signal = ema(line, num3(inputs, "signalLength", 9));
32056
+ const hist = sub(line, signal);
32057
+ return {
32058
+ plots: [
32059
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
32060
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
32061
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32062
+ ],
32063
+ levels: ZERO_LEVEL
32064
+ };
32065
+ }
32066
+ };
32067
+ var awesome = {
32068
+ type: "awesome-oscillator",
32069
+ title: "Awesome Oscillator",
32070
+ shortTitle: "AO",
32071
+ overlay: false,
32072
+ inputs: [],
32073
+ compute: (bars) => {
32074
+ const hl2 = sourceValues(bars, "HL2");
32075
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
32076
+ const colors = ao.map((x, i) => {
32077
+ if (!Number.isFinite(x)) return null;
32078
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
32079
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
32080
+ });
32081
+ return {
32082
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
32083
+ levels: ZERO_LEVEL
32084
+ };
32085
+ }
32086
+ };
32087
+ var cci = {
32088
+ type: "commodity-channel-index",
32089
+ title: "Commodity Channel Index",
32090
+ shortTitle: "CCI",
32091
+ overlay: false,
32092
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
32093
+ compute: (bars, inputs) => {
32094
+ const len = num3(inputs, "length", 20);
32095
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
32096
+ const basis = sma(src, len);
32097
+ const dev = new Array(src.length).fill(Number.NaN);
32098
+ for (let i = len - 1; i < src.length; i++) {
32099
+ const mean = basis[i];
32100
+ if (!Number.isFinite(mean)) continue;
32101
+ let d = 0;
32102
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
32103
+ dev[i] = d / len;
32104
+ }
32105
+ const values = src.map((x, i) => {
32106
+ const b = basis[i];
32107
+ const d = dev[i];
32108
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
32109
+ });
32110
+ return {
32111
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32112
+ levels: guideLevels(-100, 100, 0)
32113
+ };
32114
+ }
32115
+ };
32116
+ var williamsR = {
32117
+ type: "williams-percent-r",
32118
+ title: "Williams %R",
32119
+ shortTitle: "%R",
32120
+ overlay: false,
32121
+ inputs: [lengthInput(14), colorInput()],
32122
+ compute: (bars, inputs) => {
32123
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
32124
+ return {
32125
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32126
+ levels: guideLevels(-80, -20)
32127
+ };
32128
+ }
32129
+ };
32130
+ var cmo = {
32131
+ type: "chande-momentum-oscillator",
32132
+ title: "Chande Momentum Oscillator",
32133
+ shortTitle: "CMO",
32134
+ overlay: false,
32135
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
32136
+ compute: (bars, inputs) => {
32137
+ const len = num3(inputs, "length", 9);
32138
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
32139
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
32140
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
32141
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
32142
+ return {
32143
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32144
+ levels: guideLevels(-50, 50, 0)
32145
+ };
32146
+ }
32147
+ };
32148
+ var connorsRsi = {
32149
+ type: "connors-rsi",
32150
+ title: "Connors RSI",
32151
+ shortTitle: "CRSI",
32152
+ overlay: false,
32153
+ inputs: [
32154
+ lengthInput(3, "rsiLength", "RSI length"),
32155
+ lengthInput(2, "streakLength", "Streak RSI length"),
32156
+ lengthInput(100, "rankLength", "Percent-rank length"),
32157
+ colorInput()
32158
+ ],
32159
+ compute: (bars, inputs) => {
32160
+ const c = closes(bars);
32161
+ const streak = new Array(c.length).fill(0);
32162
+ for (let i = 1; i < c.length; i++) {
32163
+ const d = c[i] - c[i - 1];
32164
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
32165
+ }
32166
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
32167
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
32168
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
32169
+ const values = c.map((_, i) => {
32170
+ const x = a[i];
32171
+ const y = b[i];
32172
+ const z = r[i];
32173
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
32174
+ });
32175
+ return {
32176
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32177
+ levels: guideLevels(20, 80, 50)
32178
+ };
32179
+ }
32180
+ };
32181
+ var fisher = {
32182
+ type: "fisher-transform",
32183
+ title: "Fisher Transform",
32184
+ shortTitle: "Fisher",
32185
+ overlay: false,
32186
+ inputs: [lengthInput(9)],
32187
+ compute: (bars, inputs) => {
32188
+ const len = num3(inputs, "length", 9);
32189
+ const hl2 = sourceValues(bars, "HL2");
32190
+ const hi = highest(hl2, len);
32191
+ const lo = lowest(hl2, len);
32192
+ const n = bars.length;
32193
+ const out = new Array(n).fill(Number.NaN);
32194
+ let value = 0;
32195
+ let fish = 0;
32196
+ for (let i = 0; i < n; i++) {
32197
+ const h = hi[i];
32198
+ const l = lo[i];
32199
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
32200
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
32201
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
32202
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
32203
+ out[i] = fish;
32204
+ }
32205
+ return {
32206
+ plots: [
32207
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
32208
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
32209
+ ],
32210
+ levels: ZERO_LEVEL
32211
+ };
32212
+ }
32213
+ };
32214
+ var trix = {
32215
+ type: "trix",
32216
+ title: "TRIX",
32217
+ overlay: false,
32218
+ inputs: [lengthInput(18), colorInput()],
32219
+ compute: (bars, inputs) => {
32220
+ const len = num3(inputs, "length", 18);
32221
+ const logClose = map(closes(bars), Math.log);
32222
+ const smooth = ema(ema(ema(logClose, len), len), len);
32223
+ const values = map(change(smooth), (x) => x * 1e4);
32224
+ return {
32225
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32226
+ levels: ZERO_LEVEL
32227
+ };
32228
+ }
32229
+ };
32230
+ function tsiLine(src, long, short) {
32231
+ const mom = change(src);
32232
+ const numer = ema(ema(mom, long), short);
32233
+ const denom = ema(ema(map(mom, Math.abs), long), short);
32234
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
32235
+ }
32236
+ var tsi = {
32237
+ type: "true-strength-index",
32238
+ title: "True Strength Index",
32239
+ shortTitle: "TSI",
32240
+ overlay: false,
32241
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
32242
+ compute: (bars, inputs) => {
32243
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
32244
+ return {
32245
+ plots: [
32246
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
32247
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
32248
+ ],
32249
+ levels: ZERO_LEVEL
32250
+ };
32251
+ }
32252
+ };
32253
+ var smiErgodic = {
32254
+ type: "smi-ergodic",
32255
+ title: "SMI Ergodic Oscillator",
32256
+ shortTitle: "SMIE",
32257
+ overlay: false,
32258
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
32259
+ compute: (bars, inputs) => {
32260
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
32261
+ const signal = ema(line, num3(inputs, "signalLength", 5));
32262
+ const osc = sub(line, signal);
32263
+ return {
32264
+ plots: [
32265
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
32266
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
32267
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32268
+ ],
32269
+ levels: ZERO_LEVEL
32270
+ };
32271
+ }
32272
+ };
32273
+ var stc = {
32274
+ type: "schaff-trend-cycle",
32275
+ title: "Schaff Trend Cycle",
32276
+ shortTitle: "Schaff Trend Cycle",
32277
+ overlay: false,
32278
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
32279
+ compute: (bars, inputs) => {
32280
+ const len = num3(inputs, "cycleLength", 10);
32281
+ const src = closes(bars);
32282
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
32283
+ const stochOf = (v) => {
32284
+ const lo = lowest(v, len);
32285
+ const hi = highest(v, len);
32286
+ const out = new Array(v.length).fill(Number.NaN);
32287
+ let prevRaw = Number.NaN;
32288
+ let smoothed = Number.NaN;
32289
+ for (let i = 0; i < v.length; i++) {
32290
+ const x = v[i];
32291
+ const l = lo[i];
32292
+ const h = hi[i];
32293
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
32294
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
32295
+ prevRaw = raw;
32296
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
32297
+ out[i] = smoothed;
32298
+ }
32299
+ return out;
32300
+ };
32301
+ const values = stochOf(stochOf(macdLine));
32302
+ return {
32303
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
32304
+ levels: guideLevels(25, 75)
32305
+ };
32306
+ }
32307
+ };
32308
+ var kst = {
32309
+ type: "know-sure-thing",
32310
+ title: "Know Sure Thing",
32311
+ shortTitle: "KST",
32312
+ overlay: false,
32313
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
32314
+ compute: (bars, inputs) => {
32315
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32316
+ const line = [
32317
+ sma(roc(src, 10), 10),
32318
+ sma(roc(src, 15), 10),
32319
+ sma(roc(src, 20), 10),
32320
+ sma(roc(src, 30), 15)
32321
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
32322
+ return {
32323
+ plots: [
32324
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
32325
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
32326
+ ],
32327
+ levels: ZERO_LEVEL
32328
+ };
32329
+ }
32330
+ };
32331
+ var coppock = {
32332
+ type: "coppock-curve",
32333
+ title: "Coppock Curve",
32334
+ shortTitle: "Coppock",
32335
+ overlay: false,
32336
+ inputs: [
32337
+ lengthInput(10, "wmaLength", "WMA length"),
32338
+ lengthInput(14, "longRoc", "Long RoC length"),
32339
+ lengthInput(11, "shortRoc", "Short RoC length"),
32340
+ sourceInput(),
32341
+ colorInput()
32342
+ ],
32343
+ compute: (bars, inputs) => {
32344
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32345
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
32346
+ return {
32347
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32348
+ levels: ZERO_LEVEL
32349
+ };
32350
+ }
32351
+ };
32352
+ var dpo = {
32353
+ type: "detrended-price-oscillator",
32354
+ title: "Detrended Price Oscillator",
32355
+ shortTitle: "DPO",
32356
+ overlay: false,
32357
+ inputs: [lengthInput(21), colorInput()],
32358
+ compute: (bars, inputs) => {
32359
+ const len = num3(inputs, "length", 21);
32360
+ const barsback = Math.floor(len / 2) + 1;
32361
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
32362
+ return {
32363
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32364
+ levels: ZERO_LEVEL
32365
+ };
32366
+ }
32367
+ };
32368
+ var rocSpec = {
32369
+ type: "rate-of-change",
32370
+ title: "Rate of Change",
32371
+ shortTitle: "ROC",
32372
+ overlay: false,
32373
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
32374
+ compute: (bars, inputs) => ({
32375
+ plots: [
32376
+ {
32377
+ key: "roc",
32378
+ title: "RoC",
32379
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
32380
+ color: str3(inputs, "color", SERIES_LINE),
32381
+ width: 2
32382
+ }
32383
+ ],
32384
+ levels: ZERO_LEVEL
32385
+ })
32386
+ };
32387
+ var ultimate = {
32388
+ type: "ultimate-oscillator",
32389
+ title: "Ultimate Oscillator",
32390
+ shortTitle: "UO",
32391
+ overlay: false,
32392
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
32393
+ compute: (bars, inputs) => {
32394
+ const n = bars.length;
32395
+ const bp = new Array(n).fill(Number.NaN);
32396
+ const tr = new Array(n).fill(Number.NaN);
32397
+ for (let i = 0; i < n; i++) {
32398
+ const b = bars[i];
32399
+ const pc = i > 0 ? bars[i - 1].close : b.close;
32400
+ const lo = Math.min(b.low, pc);
32401
+ const hi = Math.max(b.high, pc);
32402
+ bp[i] = b.close - lo;
32403
+ tr[i] = hi - lo;
32404
+ }
32405
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
32406
+ const a7 = avg(num3(inputs, "fastLength", 7));
32407
+ const a14 = avg(num3(inputs, "middleLength", 14));
32408
+ const a28 = avg(num3(inputs, "slowLength", 28));
32409
+ const values = a7.map((x, i) => {
32410
+ const y = a14[i];
32411
+ const z = a28[i];
32412
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
32413
+ });
32414
+ return {
32415
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32416
+ levels: guideLevels(30, 70, 50)
32417
+ };
32418
+ }
32419
+ };
32420
+ var rvgi = {
32421
+ type: "relative-vigor-index",
32422
+ title: "Relative Vigor Index",
32423
+ shortTitle: "RVGI",
32424
+ overlay: false,
32425
+ inputs: [lengthInput(10)],
32426
+ compute: (bars, inputs) => {
32427
+ const len = num3(inputs, "length", 10);
32428
+ const co = swma(sub(closes(bars), opens(bars)));
32429
+ const hl = swma(sub(highs(bars), lows(bars)));
32430
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
32431
+ return {
32432
+ plots: [
32433
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
32434
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
32435
+ ],
32436
+ levels: ZERO_LEVEL
32437
+ };
32438
+ }
32439
+ };
32440
+ var relVolatility = {
32441
+ type: "relative-volatility-index",
32442
+ title: "Relative Volatility Index",
32443
+ shortTitle: "RVI",
32444
+ overlay: false,
32445
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
32446
+ compute: (bars, inputs) => {
32447
+ const smoothLen = num3(inputs, "smoothLength", 14);
32448
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
32449
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
32450
+ const d = change(src);
32451
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
32452
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
32453
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
32454
+ return {
32455
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32456
+ levels: guideLevels(20, 80, 50)
32457
+ };
32458
+ }
32459
+ };
32460
+ var bop = {
32461
+ type: "balance-of-power",
32462
+ title: "Balance of Power",
32463
+ shortTitle: "BOP",
32464
+ overlay: false,
32465
+ inputs: [colorInput()],
32466
+ compute: (bars, inputs) => {
32467
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
32468
+ return {
32469
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32470
+ levels: ZERO_LEVEL
32471
+ };
32472
+ }
32473
+ };
32474
+ var elderRay = {
32475
+ type: "elder-ray",
32476
+ title: "Elder Ray",
32477
+ overlay: false,
32478
+ inputs: [lengthInput(13)],
32479
+ compute: (bars, inputs) => {
32480
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
32481
+ return {
32482
+ plots: [
32483
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
32484
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
32485
+ ],
32486
+ levels: ZERO_LEVEL
32487
+ };
32488
+ }
32489
+ };
32490
+ var ttmSqueeze = {
32491
+ type: "ttm-squeeze",
32492
+ title: "TTM Squeeze",
32493
+ overlay: false,
32494
+ inputs: [
32495
+ lengthInput(20),
32496
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
32497
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
32498
+ ],
32499
+ compute: (bars, inputs) => {
32500
+ const len = num3(inputs, "length", 20);
32501
+ const c = closes(bars);
32502
+ const basis = sma(c, len);
32503
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
32504
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
32505
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
32506
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
32507
+ const mom = linreg(sub(c, anchor), len);
32508
+ const squeezed = c.map((_, i) => {
32509
+ const d = dev[i];
32510
+ const r = kcRange[i];
32511
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
32512
+ });
32513
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
32514
+ return {
32515
+ plots: [
32516
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
32517
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
32518
+ ]
32519
+ };
32520
+ }
32521
+ };
32522
+ var oscillatorSpecs = [
32523
+ rsiSpec,
32524
+ stochastic,
32525
+ stochasticRsi,
32526
+ macd,
32527
+ ppo,
32528
+ awesome,
32529
+ cci,
32530
+ williamsR,
32531
+ cmo,
32532
+ connorsRsi,
32533
+ fisher,
32534
+ trix,
32535
+ tsi,
32536
+ smiErgodic,
32537
+ stc,
32538
+ kst,
32539
+ coppock,
32540
+ dpo,
32541
+ rocSpec,
32542
+ ultimate,
32543
+ rvgi,
32544
+ relVolatility,
32545
+ bop,
32546
+ elderRay,
32547
+ ttmSqueeze
32548
+ ];
32549
+
32550
+ // src/core/native-indicators/classics/trend.ts
32551
+ var aroon = {
32552
+ type: "aroon",
32553
+ title: "Aroon",
32554
+ overlay: false,
32555
+ inputs: [lengthInput(14)],
32556
+ compute: (bars, inputs) => {
32557
+ const len = num3(inputs, "length", 14);
32558
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
32559
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
32560
+ return {
32561
+ plots: [
32562
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
32563
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
32564
+ ],
32565
+ levels: [
32566
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
32567
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
32568
+ ]
32569
+ };
32570
+ }
32571
+ };
32572
+ var adx = {
32573
+ type: "average-directional-index",
32574
+ title: "Average Directional Index",
32575
+ shortTitle: "ADX",
32576
+ overlay: false,
32577
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
32578
+ compute: (bars, inputs) => {
32579
+ const diLen = num3(inputs, "diLength", 14);
32580
+ const adxLen = num3(inputs, "adxLength", 14);
32581
+ const n = bars.length;
32582
+ const plusDm = new Array(n).fill(Number.NaN);
32583
+ const minusDm = new Array(n).fill(Number.NaN);
32584
+ for (let i = 1; i < n; i++) {
32585
+ const up = bars[i].high - bars[i - 1].high;
32586
+ const dn = bars[i - 1].low - bars[i].low;
32587
+ plusDm[i] = up > dn && up > 0 ? up : 0;
32588
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
32589
+ }
32590
+ const atrLine = rma(trueRange(bars), diLen);
32591
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
32592
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
32593
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
32594
+ return {
32595
+ plots: [
32596
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
32597
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
32598
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
32599
+ ],
32600
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
32601
+ };
32602
+ }
32603
+ };
32604
+ var vortex = {
32605
+ type: "vortex-indicator",
32606
+ title: "Vortex Indicator",
32607
+ shortTitle: "VI",
32608
+ overlay: false,
32609
+ inputs: [lengthInput(14)],
32610
+ compute: (bars, inputs) => {
32611
+ const len = num3(inputs, "length", 14);
32612
+ const n = bars.length;
32613
+ const vmPlus = new Array(n).fill(Number.NaN);
32614
+ const vmMinus = new Array(n).fill(Number.NaN);
32615
+ for (let i = 1; i < n; i++) {
32616
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
32617
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
32618
+ }
32619
+ const trSum = sum(trueRange(bars), len);
32620
+ return {
32621
+ plots: [
32622
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
32623
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
32624
+ ],
32625
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
32626
+ };
32627
+ }
32628
+ };
32629
+ var trendSpecs = [aroon, adx, vortex];
32630
+
32631
+ // src/core/native-indicators/classics/volatility.ts
32632
+ var atrSpec = {
32633
+ type: "average-true-range",
32634
+ title: "Average True Range",
32635
+ shortTitle: "ATR",
32636
+ overlay: false,
32637
+ inputs: [lengthInput(14), colorInput()],
32638
+ compute: (bars, inputs) => ({
32639
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
32640
+ })
32641
+ };
32642
+ var historicalVolatility = {
32643
+ type: "historical-volatility",
32644
+ title: "Historical Volatility",
32645
+ shortTitle: "HV",
32646
+ overlay: false,
32647
+ inputs: [
32648
+ lengthInput(10),
32649
+ { 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)" },
32650
+ colorInput()
32651
+ ],
32652
+ compute: (bars, inputs) => {
32653
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
32654
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
32655
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32656
+ }
32657
+ };
32658
+ var chaikinVolatility = {
32659
+ type: "chaikin-volatility",
32660
+ title: "Chaikin Volatility",
32661
+ shortTitle: "CHV",
32662
+ overlay: false,
32663
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
32664
+ compute: (bars, inputs) => {
32665
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
32666
+ return {
32667
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32668
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32669
+ };
32670
+ }
32671
+ };
32672
+ var massIndex = {
32673
+ type: "mass-index",
32674
+ title: "Mass Index",
32675
+ shortTitle: "MI",
32676
+ overlay: false,
32677
+ inputs: [lengthInput(25), colorInput()],
32678
+ compute: (bars, inputs) => {
32679
+ const range = sub(highs(bars), lows(bars));
32680
+ const single = ema(range, 9);
32681
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
32682
+ return {
32683
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32684
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
32685
+ };
32686
+ }
32687
+ };
32688
+ var standardDeviation = {
32689
+ type: "standard-deviation",
32690
+ title: "Standard Deviation",
32691
+ shortTitle: "StdDev",
32692
+ overlay: false,
32693
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
32694
+ compute: (bars, inputs) => ({
32695
+ plots: [
32696
+ {
32697
+ key: "stdev",
32698
+ title: "StdDev",
32699
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
32700
+ color: str3(inputs, "color", SERIES_LINE),
32701
+ width: 2
32702
+ }
32703
+ ]
32704
+ })
32705
+ };
32706
+ var ulcerIndex = {
32707
+ type: "ulcer-index",
32708
+ title: "Ulcer Index",
32709
+ overlay: false,
32710
+ inputs: [lengthInput(14), colorInput()],
32711
+ compute: (bars, inputs) => {
32712
+ const len = num3(inputs, "length", 14);
32713
+ const c = closes(bars);
32714
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
32715
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
32716
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32717
+ }
32718
+ };
32719
+ var choppiness = {
32720
+ type: "choppiness-index",
32721
+ title: "Choppiness Index",
32722
+ shortTitle: "CHOP",
32723
+ overlay: false,
32724
+ inputs: [lengthInput(14), colorInput()],
32725
+ compute: (bars, inputs) => {
32726
+ const len = num3(inputs, "length", 14);
32727
+ const trSum = sum(trueRange(bars), len);
32728
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
32729
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
32730
+ return {
32731
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32732
+ levels: [
32733
+ { key: "upper", price: 61.8, color: NEUTRAL },
32734
+ { key: "lower", price: 38.2, color: NEUTRAL }
32735
+ ]
32736
+ };
32737
+ }
32738
+ };
32739
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
32740
+
32741
+ // src/core/native-indicators/classics/volume.ts
32742
+ function moneyFlowVolume(bars) {
32743
+ return bars.map((b) => {
32744
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
32745
+ if (b.high === b.low) return 0;
32746
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
32747
+ });
32748
+ }
32749
+ var obv = {
32750
+ type: "on-balance-volume",
32751
+ title: "On Balance Volume",
32752
+ shortTitle: "OBV",
32753
+ overlay: false,
32754
+ inputs: [colorInput()],
32755
+ compute: (bars, inputs) => {
32756
+ const vol = volumes(bars);
32757
+ const d = change(closes(bars));
32758
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
32759
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32760
+ }
32761
+ };
32762
+ var accDist = {
32763
+ type: "accumulation-distribution",
32764
+ title: "Accumulation / Distribution",
32765
+ shortTitle: "A/D",
32766
+ overlay: false,
32767
+ inputs: [colorInput()],
32768
+ compute: (bars, inputs) => ({
32769
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
32770
+ })
32771
+ };
32772
+ var cmf = {
32773
+ type: "chaikin-money-flow",
32774
+ title: "Chaikin Money Flow",
32775
+ shortTitle: "CMF",
32776
+ overlay: false,
32777
+ inputs: [lengthInput(20), colorInput()],
32778
+ compute: (bars, inputs) => {
32779
+ const len = num3(inputs, "length", 20);
32780
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
32781
+ return {
32782
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32783
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32784
+ };
32785
+ }
32786
+ };
32787
+ var chaikinOscillator = {
32788
+ type: "chaikin-oscillator",
32789
+ title: "Chaikin Oscillator",
32790
+ shortTitle: "Chaikin Osc",
32791
+ overlay: false,
32792
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
32793
+ compute: (bars, inputs) => {
32794
+ const ad = cumSum(moneyFlowVolume(bars));
32795
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
32796
+ return {
32797
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32798
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32799
+ };
32800
+ }
32801
+ };
32802
+ var eom = {
32803
+ type: "ease-of-movement",
32804
+ title: "Ease of Movement",
32805
+ shortTitle: "EOM",
32806
+ overlay: false,
32807
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
32808
+ compute: (bars, inputs) => {
32809
+ const div = num3(inputs, "divisor", 1e4);
32810
+ const hl2 = sourceValues(bars, "HL2");
32811
+ const move = change(hl2);
32812
+ const raw = bars.map((b, i) => {
32813
+ const m = move[i];
32814
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
32815
+ return div * m * (b.high - b.low) / b.volume;
32816
+ });
32817
+ return {
32818
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32819
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32820
+ };
32821
+ }
32822
+ };
32823
+ var forceIndex = {
32824
+ type: "force-index",
32825
+ title: "Force Index",
32826
+ shortTitle: "FI",
32827
+ overlay: false,
32828
+ inputs: [lengthInput(13), colorInput()],
32829
+ compute: (bars, inputs) => {
32830
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
32831
+ return {
32832
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32833
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32834
+ };
32835
+ }
32836
+ };
32837
+ var klinger = {
32838
+ type: "klinger-oscillator",
32839
+ title: "Klinger Oscillator",
32840
+ shortTitle: "KVO",
32841
+ overlay: false,
32842
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
32843
+ compute: (bars, inputs) => {
32844
+ const hlc3 = sourceValues(bars, "HLC3");
32845
+ const trend = change(hlc3);
32846
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
32847
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
32848
+ return {
32849
+ plots: [
32850
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
32851
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
32852
+ ],
32853
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32854
+ };
32855
+ }
32856
+ };
32857
+ var mfi = {
32858
+ type: "money-flow-index",
32859
+ title: "Money Flow Index",
32860
+ shortTitle: "MFI",
32861
+ overlay: false,
32862
+ inputs: [lengthInput(14), colorInput()],
32863
+ compute: (bars, inputs) => {
32864
+ const len = num3(inputs, "length", 14);
32865
+ const tp = sourceValues(bars, "HLC3");
32866
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
32867
+ const d = change(tp);
32868
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
32869
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
32870
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
32871
+ return {
32872
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32873
+ levels: [
32874
+ { key: "upper", price: 80, color: NEUTRAL },
32875
+ { key: "lower", price: 20, color: NEUTRAL }
32876
+ ]
32877
+ };
32878
+ }
32879
+ };
32880
+ function volumeIndex(bars, when) {
32881
+ const out = new Array(bars.length).fill(Number.NaN);
32882
+ let index = 1e3;
32883
+ let started = false;
32884
+ for (let i = 1; i < bars.length; i++) {
32885
+ const v = bars[i].volume;
32886
+ const pv = bars[i - 1].volume;
32887
+ const pc = bars[i - 1].close;
32888
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
32889
+ if (started) out[i] = index;
32890
+ continue;
32891
+ }
32892
+ started = true;
32893
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
32894
+ out[i] = index;
32895
+ }
32896
+ return out;
32897
+ }
32898
+ var nvi = {
32899
+ type: "negative-volume-index",
32900
+ title: "Negative Volume Index",
32901
+ shortTitle: "Negative Volume Index",
32902
+ overlay: false,
32903
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
32904
+ compute: (bars, inputs) => {
32905
+ const line = volumeIndex(bars, (v, pv) => v < pv);
32906
+ return {
32907
+ plots: [
32908
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
32909
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
32910
+ ]
32911
+ };
32912
+ }
32913
+ };
32914
+ var pvi = {
32915
+ type: "positive-volume-index",
32916
+ title: "Positive Volume Index",
32917
+ shortTitle: "Positive Volume Index",
32918
+ overlay: false,
32919
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
32920
+ compute: (bars, inputs) => {
32921
+ const line = volumeIndex(bars, (v, pv) => v > pv);
32922
+ return {
32923
+ plots: [
32924
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
32925
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
32926
+ ]
32927
+ };
32928
+ }
32929
+ };
32930
+ var pvt = {
32931
+ type: "price-volume-trend",
32932
+ title: "Price Volume Trend",
32933
+ shortTitle: "PVT",
32934
+ overlay: false,
32935
+ inputs: [colorInput()],
32936
+ compute: (bars, inputs) => {
32937
+ const c = closes(bars);
32938
+ const raw = bars.map((b, i) => {
32939
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
32940
+ const pc = c[i - 1];
32941
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
32942
+ });
32943
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
32944
+ }
32945
+ };
32946
+ var volumeOscillator = {
32947
+ type: "volume-oscillator",
32948
+ title: "Volume Oscillator",
32949
+ shortTitle: "Vol Osc",
32950
+ overlay: false,
32951
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
32952
+ compute: (bars, inputs) => {
32953
+ const vol = volumes(bars);
32954
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
32955
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
32956
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
32957
+ return {
32958
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
32959
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32960
+ };
32961
+ }
32962
+ };
32963
+ var pvo = {
32964
+ type: "pvo",
32965
+ title: "Percentage Volume Oscillator",
32966
+ shortTitle: "PVO",
32967
+ overlay: false,
32968
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
32969
+ compute: (bars, inputs) => {
32970
+ const vol = volumes(bars);
32971
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
32972
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
32973
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
32974
+ const signal = ema(line, num3(inputs, "signalLength", 9));
32975
+ const hist = sub(line, signal);
32976
+ return {
32977
+ plots: [
32978
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
32979
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
32980
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
32981
+ ],
32982
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
32983
+ };
32984
+ }
32985
+ };
32986
+ var vfi = {
32987
+ type: "volume-flow-indicator",
32988
+ title: "Volume Flow Indicator",
32989
+ shortTitle: "Volume Flow Indicator",
32990
+ overlay: false,
32991
+ inputs: [
32992
+ lengthInput(130),
32993
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
32994
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
32995
+ lengthInput(3, "smoothLength", "Smoothing length")
32996
+ ],
32997
+ compute: (bars, inputs) => {
32998
+ const len = num3(inputs, "length", 130);
32999
+ const coef = num3(inputs, "coef", 0.2);
33000
+ const volCoef = num3(inputs, "volCoef", 2.5);
33001
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
33002
+ const inter = change(typical);
33003
+ const vInter = stdev(inter, 30);
33004
+ const vol = volumes(bars);
33005
+ const vAve = sma(vol, len);
33006
+ const mf = change(sourceValues(bars, "HLC3"));
33007
+ const raw = bars.map((b, i) => {
33008
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
33009
+ const v = vol[i];
33010
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
33011
+ const m = mf[i];
33012
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
33013
+ const capped = Math.min(v, ave * volCoef);
33014
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
33015
+ });
33016
+ const values = bars.map((_, i) => {
33017
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
33018
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
33019
+ });
33020
+ const flowSum = sum(raw, len);
33021
+ const line = zip(flowSum, values, (f, a) => f / a);
33022
+ return {
33023
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
33024
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33025
+ };
33026
+ }
33027
+ };
33028
+ var intradayIntensity = {
33029
+ type: "intraday-intensity",
33030
+ title: "Intraday Intensity",
33031
+ shortTitle: "Intraday Intensity",
33032
+ overlay: false,
33033
+ inputs: [lengthInput(21), colorInput()],
33034
+ compute: (bars, inputs) => {
33035
+ const len = num3(inputs, "length", 21);
33036
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
33037
+ return {
33038
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
33039
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
33040
+ };
33041
+ }
33042
+ };
33043
+ var volumeSpecs = [
33044
+ obv,
33045
+ accDist,
33046
+ cmf,
33047
+ chaikinOscillator,
33048
+ eom,
33049
+ forceIndex,
33050
+ klinger,
33051
+ mfi,
33052
+ nvi,
33053
+ pvi,
33054
+ pvt,
33055
+ volumeOscillator,
33056
+ pvo,
33057
+ vfi,
33058
+ intradayIntensity
33059
+ ];
33060
+
33061
+ // src/core/native-indicators/classics/overlays.ts
33062
+ var DAY_MS2 = 864e5;
33063
+ function periodKey(anchor, time) {
33064
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
33065
+ if (anchor === "Month") {
33066
+ const d = new Date(time);
33067
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
33068
+ }
33069
+ return Math.floor(time / DAY_MS2);
33070
+ }
33071
+ var parabolicSar = {
33072
+ type: "parabolic-sar",
33073
+ title: "Parabolic SAR",
33074
+ shortTitle: "SAR",
33075
+ overlay: true,
33076
+ inputs: [
33077
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
33078
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
33079
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
33080
+ ],
33081
+ compute: (bars, inputs) => {
33082
+ const start = num3(inputs, "start", 0.02);
33083
+ const inc = num3(inputs, "increment", 0.02);
33084
+ const max = num3(inputs, "maximum", 0.2);
33085
+ const n = bars.length;
33086
+ const values = new Array(n).fill(Number.NaN);
33087
+ const colors = new Array(n).fill(null);
33088
+ if (n >= 2) {
33089
+ let long = bars[1].close >= bars[0].close;
33090
+ let sar = long ? bars[0].low : bars[0].high;
33091
+ let ep = long ? bars[0].high : bars[0].low;
33092
+ let af = start;
33093
+ for (let i = 1; i < n; i++) {
33094
+ const b = bars[i];
33095
+ sar += af * (ep - sar);
33096
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
33097
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
33098
+ const reversed = long ? b.low < sar : b.high > sar;
33099
+ if (reversed) {
33100
+ long = !long;
33101
+ sar = ep;
33102
+ ep = long ? b.high : b.low;
33103
+ af = start;
33104
+ } else if (long && b.high > ep) {
33105
+ ep = b.high;
33106
+ af = Math.min(max, af + inc);
33107
+ } else if (!long && b.low < ep) {
33108
+ ep = b.low;
33109
+ af = Math.min(max, af + inc);
33110
+ }
33111
+ values[i] = sar;
33112
+ colors[i] = long ? BULLISH : BEARISH;
33113
+ }
33114
+ }
33115
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
33116
+ }
33117
+ };
33118
+ var vwap = {
33119
+ type: "vwap",
33120
+ title: "Volume Weighted Average Price",
33121
+ shortTitle: "VWAP",
33122
+ overlay: true,
33123
+ inputs: [
33124
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
33125
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
33126
+ colorInput(INFO)
33127
+ ],
33128
+ compute: (bars, inputs) => {
33129
+ const anchor = str3(inputs, "anchor", "Day");
33130
+ const source = str3(inputs, "source", "HLC3");
33131
+ const values = new Array(bars.length).fill(Number.NaN);
33132
+ let period = Number.NaN;
33133
+ let cumPV = 0;
33134
+ let cumV = 0;
33135
+ for (let i = 0; i < bars.length; i++) {
33136
+ const b = bars[i];
33137
+ const key = periodKey(anchor, b.time);
33138
+ if (key !== period) {
33139
+ period = key;
33140
+ cumPV = 0;
33141
+ cumV = 0;
33142
+ }
33143
+ const v = b.volume;
33144
+ if (v != null && Number.isFinite(v) && v > 0) {
33145
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
33146
+ cumPV += tp * v;
33147
+ cumV += v;
33148
+ }
33149
+ if (cumV > 0) values[i] = cumPV / cumV;
33150
+ }
33151
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
33152
+ }
33153
+ };
33154
+ var pivotPoints = {
33155
+ type: "pivot-points",
33156
+ title: "Pivot Points",
33157
+ shortTitle: "Pivots",
33158
+ overlay: true,
33159
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
33160
+ compute: (bars, inputs) => {
33161
+ const anchor = str3(inputs, "anchor", "Day");
33162
+ const aggs = /* @__PURE__ */ new Map();
33163
+ for (const b of bars) {
33164
+ const key = periodKey(anchor, b.time);
33165
+ const a = aggs.get(key);
33166
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
33167
+ else {
33168
+ a.high = Math.max(a.high, b.high);
33169
+ a.low = Math.min(a.low, b.low);
33170
+ a.close = b.close;
33171
+ }
33172
+ }
33173
+ const n = bars.length;
33174
+ const mk = () => new Array(n).fill(Number.NaN);
33175
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
33176
+ for (let i = 0; i < n; i++) {
33177
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
33178
+ if (!prev2) continue;
33179
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
33180
+ levels.p[i] = p;
33181
+ levels.r1[i] = 2 * p - prev2.low;
33182
+ levels.s1[i] = 2 * p - prev2.high;
33183
+ levels.r2[i] = p + (prev2.high - prev2.low);
33184
+ levels.s2[i] = p - (prev2.high - prev2.low);
33185
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
33186
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
33187
+ }
33188
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
33189
+ return {
33190
+ plots: [
33191
+ plot("p", "P", WARNING),
33192
+ plot("r1", "R1", BEARISH),
33193
+ plot("s1", "S1", BULLISH),
33194
+ plot("r2", "R2", BEARISH),
33195
+ plot("s2", "S2", BULLISH),
33196
+ plot("r3", "R3", BEARISH),
33197
+ plot("s3", "S3", BULLISH)
33198
+ ]
33199
+ };
33200
+ }
33201
+ };
33202
+ var fiftyTwoWeek = {
33203
+ type: "52-week-high-low",
33204
+ title: "52 Week High/Low",
33205
+ shortTitle: "52W H/L",
33206
+ overlay: true,
33207
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
33208
+ compute: (bars, inputs) => {
33209
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
33210
+ const n = bars.length;
33211
+ const hi = new Array(n).fill(Number.NaN);
33212
+ const lo = new Array(n).fill(Number.NaN);
33213
+ const maxIdx = [];
33214
+ const minIdx = [];
33215
+ let from = 0;
33216
+ for (let i = 0; i < n; i++) {
33217
+ const b = bars[i];
33218
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
33219
+ maxIdx.push(i);
33220
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
33221
+ minIdx.push(i);
33222
+ while (bars[from].time < b.time - span) from += 1;
33223
+ while (maxIdx[0] < from) maxIdx.shift();
33224
+ while (minIdx[0] < from) minIdx.shift();
33225
+ hi[i] = bars[maxIdx[0]].high;
33226
+ lo[i] = bars[minIdx[0]].low;
33227
+ }
33228
+ return {
33229
+ plots: [
33230
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
33231
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
33232
+ ]
33233
+ };
33234
+ }
33235
+ };
33236
+ var zigzag = {
33237
+ type: "zigzag",
33238
+ title: "ZigZag",
33239
+ overlay: true,
33240
+ inputs: [
33241
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
33242
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
33243
+ colorInput()
33244
+ ],
33245
+ compute: (bars, inputs) => {
33246
+ const dev = num3(inputs, "deviation", 5) / 100;
33247
+ const depth = num3(inputs, "depth", 10);
33248
+ const pivots = [];
33249
+ if (bars.length > 1) {
33250
+ let up = bars[1].close >= bars[0].close;
33251
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
33252
+ for (let i = 1; i < bars.length; i++) {
33253
+ const b = bars[i];
33254
+ if (up) {
33255
+ if (b.high >= ext.price) ext = { i, price: b.high };
33256
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
33257
+ pivots.push(ext);
33258
+ up = false;
33259
+ ext = { i, price: b.low };
33260
+ }
33261
+ } else if (b.low <= ext.price) {
33262
+ ext = { i, price: b.low };
33263
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
33264
+ pivots.push(ext);
33265
+ up = true;
33266
+ ext = { i, price: b.high };
33267
+ }
33268
+ }
33269
+ pivots.push(ext);
33270
+ }
33271
+ return {
33272
+ plots: [],
33273
+ polylines: [
33274
+ {
33275
+ key: "zigzag",
33276
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
33277
+ curved: false,
33278
+ closed: false,
33279
+ lineColor: str3(inputs, "color", SERIES_LINE),
33280
+ lineWidth: 2,
33281
+ lineStyle: "solid",
33282
+ arrowLeft: false,
33283
+ arrowRight: false
33284
+ }
33285
+ ]
33286
+ };
33287
+ }
33288
+ };
33289
+ var williamsFractal = {
33290
+ type: "williams-fractal",
33291
+ title: "Williams Fractal",
33292
+ shortTitle: "Fractals",
33293
+ overlay: true,
33294
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
33295
+ compute: (bars, inputs) => {
33296
+ const p = num3(inputs, "periods", 2);
33297
+ const markers = [];
33298
+ const isExtreme = (i, pick, better) => {
33299
+ const v = pick(bars[i]);
33300
+ for (let k = i - p; k <= i + p; k++) {
33301
+ if (k === i) continue;
33302
+ if (!better(v, pick(bars[k]))) return false;
33303
+ }
33304
+ return true;
33305
+ };
33306
+ for (let i = p; i < bars.length - p; i++) {
33307
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
33308
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
33309
+ }
33310
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
33311
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
33312
+ }
33313
+ }
33314
+ return { plots: [], markers };
33315
+ }
33316
+ };
33317
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
33318
+
33319
+ // src/core/native-indicators/classics/index.ts
33320
+ var classicSpecs = [
33321
+ ...averageSpecs,
33322
+ ...bandSpecs,
33323
+ ...oscillatorSpecs,
33324
+ ...trendSpecs,
33325
+ ...volatilitySpecs,
33326
+ ...volumeSpecs,
33327
+ ...overlaySpecs
33328
+ ];
33329
+ function registerClassicIndicators() {
33330
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
33331
+ }
33332
+
33333
+ // src/Vela.ts
33334
+ var Vela = class {
33335
+ constructor(container, options = {}, deps = {}) {
33336
+ registerBuiltinChartTypes();
33337
+ registerVolume();
33338
+ registerVpvr();
33339
+ registerClassicIndicators();
33340
+ const element = resolveElement(container);
33341
+ const theme = resolveTheme(options.theme);
33342
+ let animZoom = true;
33343
+ let animPan = true;
33344
+ if (typeof options.animations === "boolean") {
33345
+ animZoom = options.animations;
33346
+ animPan = options.animations;
33347
+ } else if (options.animations) {
33348
+ animZoom = options.animations.zoom ?? true;
33349
+ animPan = options.animations.pan ?? true;
33350
+ }
33351
+ const display = {
33352
+ currentPriceLine: options.currentPriceLine ?? true,
33353
+ logScale: options.logScale ?? false,
33354
+ nativeBackend: options.nativeBackend ?? "auto",
33355
+ animZoom,
33356
+ animPan,
33357
+ glow: options.glow ?? 0,
33358
+ upColor: options.upColor ?? BULLISH,
33359
+ downColor: options.downColor ?? BEARISH,
33360
+ priceStyle: options.priceStyle ?? "candles"
33361
+ };
33362
+ const RendererClass = options.renderer ?? NativeRenderer;
33363
+ if (typeof RendererClass !== "function") {
33364
+ throw new Error(
33365
+ "[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
33366
+ );
33367
+ }
33368
+ const renderer = deps.renderer ?? new RendererClass(display);
33369
+ const engines = deps.engines ?? [];
33370
+ const feed = deps.dataFeed ?? new MultiProviderFeed();
33371
+ const config = {
33372
+ market: {
33373
+ symbol: options.symbol,
33374
+ timeframe: options.timeframe,
33375
+ bars: options.bars,
33376
+ session: options.session,
33377
+ visibleRange: options.visibleRange,
33378
+ data: options.data
33379
+ },
33380
+ live: options.live ?? false,
33381
+ theme,
33382
+ defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
33383
+ drawings: options.drawings,
33384
+ volume: options.volume ?? true
33385
+ // the volume indicator is on by default
33386
+ };
33387
+ this.rendererControl = new RendererControl(renderer);
33388
+ if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
33389
+ this.dataControl = new DataControl(feed);
33390
+ this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
33391
+ const defaults2 = rendererDefaults();
33392
+ if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
33393
+ this.panesControl = new PanesControl(this.orchestrator);
33394
+ this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
33395
+ }
33396
+ /**
33397
+ * Register a scripting engine so `addIndicator({ language })` can run that
33398
+ * language. Vela ships NO engine — install the one you need (Pine Script:
33399
+ * `@luxalgo/vela-pinets`) and register it, e.g.
33400
+ * `chart.registerEngine('pine', new PineEngine())`; without one the chart
33401
+ * displays candles, drawings and native indicators only. Re-registering a
33402
+ * language replaces it.
33403
+ */
33404
+ registerEngine(language, engine) {
33405
+ this.orchestrator.registerEngine(language, engine);
33406
+ return this;
33407
+ }
33408
+ /** Run an indicator script on the chart's market data and render it. */
33409
+ addIndicator(source, options) {
33410
+ return this.orchestrator.addIndicator(source, options);
33411
+ }
33412
+ /**
33413
+ * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
33414
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
33415
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
33416
+ * call returns the existing handle; the classic studies allow several instances, so each call
33417
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
33418
+ */
33419
+ addNativeIndicator(type, options) {
33420
+ return this.orchestrator.addNativeIndicator(type, options);
33421
+ }
33422
+ /**
33423
+ * The catalog of built-in native indicators with their live state on this chart — each entry's
33424
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
33425
+ * and whether it allows several instances (`multiInstance`; otherwise a second
33426
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
33427
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
33428
+ * provider (a type may need data the symbol lacks).
33429
+ */
33430
+ availableNativeIndicators() {
33431
+ return this.orchestrator.availableNativeIndicators();
33432
+ }
33433
+ /**
33434
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
33435
+ * multi-instance type repeats) — the synchronous slice of
33436
+ * {@link availableNativeIndicators} (only support probing is async; presence never is).
33437
+ * Persistence snapshots read this: an unload-time flush must see an add/remove that
33438
+ * happened microseconds ago, which an async catalog mirror cannot guarantee.
33439
+ */
33440
+ presentNativeIndicators() {
33441
+ return this.orchestrator.presentNativeIndicators();
33442
+ }
33443
+ /** Live handles of every indicator currently on the chart (script + native) — drive
33444
+ * host panels (object trees, indicator lists) with per-id visibility/removal. */
33445
+ indicators() {
33446
+ return this.orchestrator.listIndicators();
33447
+ }
33448
+ /**
33449
+ * Execute an indicator script and INJECT it only if the run succeeds — the seam for
33450
+ * host editors and consoles. Resolves `{ ok: true, handle }` after the first
33451
+ * successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
33452
+ * in which case the failed indicator is removed again (no dead legend row).
33453
+ * Never rejects.
33454
+ */
33455
+ /**
33456
+ * Execute a script and resolve its FIRST computed run — the data-out door for host
33457
+ * editors, consoles and dashboards. The script is injected only if it runs (a failure
33458
+ * removes it again, leaving no dead legend row), and the result carries the run itself
33459
+ * plus the controls for what it put on the chart: `onUpdate` to follow later runs,
33460
+ * `remove` to take it off. Never rejects.
31081
33461
  *
31082
33462
  * `runScript` is `runIndicator` with the run as its payload rather than a handle to go
31083
33463
  * fetch from — the same relationship `script:run` has to `context:changed`.
@@ -31419,18 +33799,13 @@ function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_
31419
33799
  return Math.round(Math.min(Math.max(px, lo), hi));
31420
33800
  }
31421
33801
 
31422
- // src/core/model/identity.ts
31423
- function stableSeriesId(parts) {
31424
- const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
31425
- return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
31426
- }
31427
-
31428
33802
  exports.ACCENT = ACCENT;
31429
33803
  exports.ACCENT_BRIGHT = ACCENT_BRIGHT;
31430
33804
  exports.BEARISH = BEARISH;
31431
33805
  exports.BULLISH = BULLISH;
31432
33806
  exports.BarStore = BarStore;
31433
33807
  exports.CATEGORICAL = CATEGORICAL;
33808
+ exports.CHIP_PLATE = CHIP_PLATE;
31434
33809
  exports.CROSSHAIR = CROSSHAIR;
31435
33810
  exports.CachingDataFeed = CachingDataFeed;
31436
33811
  exports.DARK_THEME = DARK_THEME;