@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
@@ -111,7 +111,7 @@ var Vela = (function (exports) {
111
111
 
112
112
  // src/core/engine/IndicatorHandleImpl.ts
113
113
  var IndicatorHandleImpl = class {
114
- constructor(id, title, controller, source) {
114
+ constructor(id, title, controller, source, nativeType) {
115
115
  this.controller = controller;
116
116
  this.schema = [];
117
117
  this.propsSchema = [];
@@ -120,6 +120,7 @@ var Vela = (function (exports) {
120
120
  this.id = id;
121
121
  this.title = title;
122
122
  if (source !== void 0) this.source = source;
123
+ if (nativeType !== void 0) this.nativeType = nativeType;
123
124
  }
124
125
  get inputs() {
125
126
  return this.schema;
@@ -210,24 +211,24 @@ var Vela = (function (exports) {
210
211
  }
211
212
  function inspectModels(models) {
212
213
  const indicators = models.map(summarizeModel);
213
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
214
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
214
215
  return {
215
216
  indicators,
216
217
  totals: {
217
218
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
218
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
219
- fills: sum((s) => s.fills),
220
- backgrounds: sum((s) => s.backgrounds),
221
- priceLines: sum((s) => s.priceLines),
222
- lines: sum((s) => s.lines),
223
- boxes: sum((s) => s.boxes),
224
- labels: sum((s) => s.labels),
225
- polylines: sum((s) => s.polylines),
226
- linefills: sum((s) => s.linefills),
227
- tables: sum((s) => s.tables),
228
- barColors: sum((s) => s.barColors),
229
- trades: sum((s) => s.trades),
230
- forcedOverlay: sum((s) => s.forcedOverlay)
219
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
220
+ fills: sum2((s) => s.fills),
221
+ backgrounds: sum2((s) => s.backgrounds),
222
+ priceLines: sum2((s) => s.priceLines),
223
+ lines: sum2((s) => s.lines),
224
+ boxes: sum2((s) => s.boxes),
225
+ labels: sum2((s) => s.labels),
226
+ polylines: sum2((s) => s.polylines),
227
+ linefills: sum2((s) => s.linefills),
228
+ tables: sum2((s) => s.tables),
229
+ barColors: sum2((s) => s.barColors),
230
+ trades: sum2((s) => s.trades),
231
+ forcedOverlay: sum2((s) => s.forcedOverlay)
231
232
  }
232
233
  };
233
234
  }
@@ -276,6 +277,7 @@ var Vela = (function (exports) {
276
277
  var CROSSHAIR = "#9aa0ad";
277
278
  var SLATE_DEEP = "#1e293b";
278
279
  var SLATE = "#475569";
280
+ var CHIP_PLATE = "#595959";
279
281
  var TRADE_LONG = ACCENT;
280
282
  var TRADE_SHORT = BEARISH;
281
283
  var TRADE_EXIT = "#d500f9";
@@ -503,6 +505,10 @@ var Vela = (function (exports) {
503
505
  "market-post",
504
506
  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"/>')
505
507
  );
508
+ registerIcon(
509
+ "market-extended",
510
+ 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"/>')
511
+ );
506
512
  registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
507
513
  registerIcon(
508
514
  "market-holiday",
@@ -516,6 +522,8 @@ var Vela = (function (exports) {
516
522
  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"/>'));
517
523
  registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
518
524
  registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
525
+ 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"/>'));
526
+ 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"/>'));
519
527
  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"/>'));
520
528
  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"/>'));
521
529
  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"/>'));
@@ -6700,8 +6708,8 @@ var Vela = (function (exports) {
6700
6708
  this.port?.setExternalGhost?.(doc);
6701
6709
  }
6702
6710
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
6703
- setToolShortcuts(map) {
6704
- this.port?.setToolShortcuts?.(map);
6711
+ setToolShortcuts(map2) {
6712
+ this.port?.setToolShortcuts?.(map2);
6705
6713
  }
6706
6714
  // ── programmatic CRUD (facade-facing) ── each is one undo step
6707
6715
  add(type, init = {}) {
@@ -8425,16 +8433,20 @@ var Vela = (function (exports) {
8425
8433
  /**
8426
8434
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
8427
8435
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
8428
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
8429
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
8436
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
8437
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
8438
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
8439
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
8430
8440
  */
8431
8441
  addNativeIndicator(type, options = {}) {
8432
- const existing = this.registry.all().find((r) => r.native?.type === type);
8433
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
8434
8442
  const descriptor = getNativeIndicator(type);
8443
+ if (!descriptor?.multiInstance) {
8444
+ const existing = this.registry.all().find((r) => r.native?.type === type);
8445
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8446
+ }
8435
8447
  const id = this.registry.nextId("native");
8436
8448
  const title = descriptor?.title ?? type;
8437
- const handle = new IndicatorHandleImpl(id, title, this);
8449
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8438
8450
  this.handles.set(id, handle);
8439
8451
  if (!descriptor) {
8440
8452
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -8446,16 +8458,18 @@ var Vela = (function (exports) {
8446
8458
  void this.startNativeIndicator(id, handle);
8447
8459
  return handle;
8448
8460
  }
8449
- /**
8450
- * The catalog of registered native-indicator types with their live state on THIS chart: each
8451
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
8452
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
8453
- * because a descriptor's `isSupported` may probe the provider for a required capability.
8454
- */
8455
- /** The native types present on the chart — SYNC (registry state; no support probe). */
8461
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
8462
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
8456
8463
  presentNativeIndicators() {
8457
8464
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
8458
8465
  }
8466
+ /**
8467
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
8468
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
8469
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
8470
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
8471
+ * probe the provider for a required capability.
8472
+ */
8459
8473
  async availableNativeIndicators() {
8460
8474
  const symbol = this.qualifiedSymbol();
8461
8475
  const present = new Set(this.presentNativeIndicators());
@@ -8465,7 +8479,8 @@ var Vela = (function (exports) {
8465
8479
  title: d.title,
8466
8480
  supported: await this.isNativeSupported(d, symbol),
8467
8481
  present: present.has(d.type),
8468
- beta: d.beta
8482
+ beta: d.beta,
8483
+ ...d.multiInstance ? { multiInstance: true } : {}
8469
8484
  }))
8470
8485
  );
8471
8486
  }
@@ -9272,6 +9287,15 @@ var Vela = (function (exports) {
9272
9287
  console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
9273
9288
  return null;
9274
9289
  }
9290
+ /**
9291
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
9292
+ * or null if the renderer has no canvas export. Silent — a host compositing
9293
+ * several charts skips a renderer that cannot contribute.
9294
+ */
9295
+ screenshotCanvas() {
9296
+ if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
9297
+ return null;
9298
+ }
9275
9299
  /**
9276
9300
  * The active renderer's full cosmetic config as a serializable, versioned JSON
9277
9301
  * document — persist it (templates, saved settings) and feed it back to
@@ -9740,7 +9764,7 @@ var Vela = (function (exports) {
9740
9764
 
9741
9765
  // src/data/MultiProviderFeed.ts
9742
9766
  function intervalMs(timeframe) {
9743
- const map = {
9767
+ const map2 = {
9744
9768
  "1m": 6e4,
9745
9769
  "3m": 18e4,
9746
9770
  "5m": 3e5,
@@ -9762,7 +9786,7 @@ var Vela = (function (exports) {
9762
9786
  W: 6048e5,
9763
9787
  M: 2592e6
9764
9788
  };
9765
- return map[timeframe] ?? 36e5;
9789
+ return map2[timeframe] ?? 36e5;
9766
9790
  }
9767
9791
  var MultiProviderFeed = class {
9768
9792
  constructor(store = sharedBarStore) {
@@ -10149,8 +10173,8 @@ var Vela = (function (exports) {
10149
10173
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
10150
10174
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
10151
10175
  * platform formatting, so hints always match the host's actual bindings. */
10152
- setToolShortcuts(map) {
10153
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
10176
+ setToolShortcuts(map2) {
10177
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
10154
10178
  return this;
10155
10179
  }
10156
10180
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -10615,7 +10639,7 @@ var Vela = (function (exports) {
10615
10639
  }
10616
10640
 
10617
10641
  // node_modules/@zag-js/core/dist/merge-props.mjs
10618
- var clsx = (...args) => args.map((str3) => str3?.trim?.()).filter(Boolean).join(" ");
10642
+ var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
10619
10643
  var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
10620
10644
  var serialize = (style) => {
10621
10645
  const res = {};
@@ -10839,12 +10863,12 @@ var Vela = (function (exports) {
10839
10863
  return {
10840
10864
  and: (...guards) => {
10841
10865
  return function andGuard(params) {
10842
- return guards.every((str3) => params.guard(str3));
10866
+ return guards.every((str4) => params.guard(str4));
10843
10867
  };
10844
10868
  },
10845
10869
  or: (...guards) => {
10846
10870
  return function orGuard(params) {
10847
- return guards.some((str3) => params.guard(str3));
10871
+ return guards.some((str4) => params.guard(str4));
10848
10872
  };
10849
10873
  },
10850
10874
  not: (guard) => {
@@ -11405,9 +11429,9 @@ var Vela = (function (exports) {
11405
11429
  const { attributes, callback: fn } = options;
11406
11430
  const win = node.ownerDocument.defaultView || window;
11407
11431
  const obs = new win.MutationObserver((changes) => {
11408
- for (const change of changes) {
11409
- if (change.type === "attributes" && change.attributeName && attributes.includes(change.attributeName)) {
11410
- fn(change);
11432
+ for (const change2 of changes) {
11433
+ if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
11434
+ fn(change2);
11411
11435
  }
11412
11436
  }
11413
11437
  });
@@ -11481,7 +11505,7 @@ var Vela = (function (exports) {
11481
11505
  }
11482
11506
 
11483
11507
  // node_modules/@zag-js/dom-query/dist/searchable.mjs
11484
- var sanitize = (str3) => str3.split("").map((char) => {
11508
+ var sanitize = (str4) => str4.split("").map((char) => {
11485
11509
  const code = char.charCodeAt(0);
11486
11510
  if (code > 0 && code < 128) return char;
11487
11511
  if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
@@ -12181,10 +12205,10 @@ var Vela = (function (exports) {
12181
12205
  fn?.(this.getParams());
12182
12206
  }
12183
12207
  });
12184
- __publicField(this, "guard", (str3) => {
12185
- if (isFunction(str3)) return str3(this.getParams());
12186
- const fn = this.machine.implementations?.guards?.[str3];
12187
- if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str3)}"`);
12208
+ __publicField(this, "guard", (str4) => {
12209
+ if (isFunction(str4)) return str4(this.getParams());
12210
+ const fn = this.machine.implementations?.guards?.[str4];
12211
+ if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
12188
12212
  return fn?.(this.getParams());
12189
12213
  });
12190
12214
  __publicField(this, "effect", (keys) => {
@@ -16962,19 +16986,19 @@ var Vela = (function (exports) {
16962
16986
  }
16963
16987
  setItems(items) {
16964
16988
  this.items = items;
16965
- for (const sub of this.subs.values()) sub.destroy();
16989
+ for (const sub2 of this.subs.values()) sub2.destroy();
16966
16990
  this.subs.clear();
16967
16991
  this.build();
16968
16992
  const api = this.api;
16969
- for (const sub of this.subs.values()) {
16970
- api.setChild(sub.handle.service);
16971
- sub.api.setParent(this.handle.service);
16993
+ for (const sub2 of this.subs.values()) {
16994
+ api.setChild(sub2.handle.service);
16995
+ sub2.api.setParent(this.handle.service);
16972
16996
  }
16973
16997
  this.handle.flush();
16974
16998
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
16975
16999
  }
16976
17000
  destroy() {
16977
- for (const sub of this.subs.values()) sub.destroy();
17001
+ for (const sub2 of this.subs.values()) sub2.destroy();
16978
17002
  this.subs.clear();
16979
17003
  this.handle.stop();
16980
17004
  this.positioner.remove();
@@ -16988,9 +17012,9 @@ var Vela = (function (exports) {
16988
17012
  for (const li of this.list.children) {
16989
17013
  const id = li.dataset.veiId;
16990
17014
  if (!id) continue;
16991
- const sub = this.subs.get(id);
16992
- if (sub) {
16993
- spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
17015
+ const sub2 = this.subs.get(id);
17016
+ if (sub2) {
17017
+ spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
16994
17018
  continue;
16995
17019
  }
16996
17020
  const item = this.byId(id);
@@ -17067,7 +17091,7 @@ var Vela = (function (exports) {
17067
17091
  const arrow3 = iconEl("chevron-right", doc);
17068
17092
  arrow3.classList.add("vela-menu-arrow");
17069
17093
  li.appendChild(arrow3);
17070
- const sub = new _Surface(doc, {
17094
+ const sub2 = new _Surface(doc, {
17071
17095
  host: this.host,
17072
17096
  placement: "right-start",
17073
17097
  onSelect: this.onSelect,
@@ -17075,8 +17099,8 @@ var Vela = (function (exports) {
17075
17099
  checkmarks: this.checkmarks,
17076
17100
  id: `${this.mid}--${item.id}`
17077
17101
  });
17078
- sub.setItems(item.submenu ?? []);
17079
- this.subs.set(item.id, sub);
17102
+ sub2.setItems(item.submenu ?? []);
17103
+ this.subs.set(item.id, sub2);
17080
17104
  }
17081
17105
  this.list.appendChild(li);
17082
17106
  }
@@ -17141,6 +17165,12 @@ ${STATIC_DECLS}
17141
17165
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
17142
17166
  /* Text ENTRY is the one exception: selection is part of editing. */
17143
17167
  .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
17168
+ /* iOS Safari zooms the page when a focused field is under 16px, and often
17169
+ keeps that zoom after the field blurs or a dialog closes. The shell's
17170
+ mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
17171
+ chart on a wide desktop still gets the rule when it is in the phone layout.
17172
+ Scoped to the kit root so a host page's own [data-layout] is never touched. */
17173
+ .vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
17144
17174
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
17145
17175
  .vela-icon svg { display: block; }
17146
17176
  `;
@@ -20025,8 +20055,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
20025
20055
  };
20026
20056
  const recentRow = document.createElement("div");
20027
20057
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
20058
+ const recentSwatches = document.createElement("div");
20059
+ recentSwatches.style.cssText = "display:contents;";
20060
+ const add = document.createElement("label");
20061
+ 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;`;
20062
+ add.textContent = "+";
20063
+ const customInput = document.createElement("input");
20064
+ customInput.type = "color";
20065
+ customInput.value = curHex;
20066
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
20067
+ customInput.addEventListener("input", () => previewHex(customInput.value));
20068
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
20069
+ add.appendChild(customInput);
20070
+ recentRow.append(recentSwatches, add);
20028
20071
  const renderRecents = () => {
20029
- recentRow.replaceChildren();
20072
+ recentSwatches.replaceChildren();
20030
20073
  for (const c of recents) {
20031
20074
  const sw = document.createElement("button");
20032
20075
  sw.type = "button";
@@ -20035,18 +20078,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20035
20078
  e.stopPropagation();
20036
20079
  pickHex(c);
20037
20080
  });
20038
- recentRow.appendChild(sw);
20081
+ recentSwatches.appendChild(sw);
20039
20082
  }
20040
- const add = document.createElement("label");
20041
- 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;`;
20042
- add.textContent = "+";
20043
- const input = document.createElement("input");
20044
- input.type = "color";
20045
- input.value = curHex;
20046
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
20047
- input.addEventListener("input", () => pickHex(input.value, true));
20048
- add.appendChild(input);
20049
- recentRow.appendChild(add);
20050
20083
  };
20051
20084
  const opLabel = document.createElement("div");
20052
20085
  opLabel.textContent = "Opacity";
@@ -20086,14 +20119,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
20086
20119
  function emit() {
20087
20120
  onChange(combineColor(curHex, curAlpha));
20088
20121
  }
20089
- function pickHex(hex, custom = false) {
20122
+ function previewHex(hex) {
20090
20123
  curHex = splitColor(hex).hex6;
20091
- addRecent(curHex);
20092
20124
  paintSelection();
20093
20125
  paintOpacity();
20094
- if (custom) renderRecents();
20095
20126
  emit();
20096
20127
  }
20128
+ function pickHex(hex, custom = false) {
20129
+ previewHex(hex);
20130
+ addRecent(curHex);
20131
+ customInput.value = curHex;
20132
+ if (custom) renderRecents();
20133
+ }
20097
20134
  renderRecents();
20098
20135
  paintSelection();
20099
20136
  paintOpacity();
@@ -21999,8 +22036,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21999
22036
  lg.style.top = `${top}px`;
22000
22037
  }
22001
22038
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22002
- setCollapsedPanes(map) {
22003
- this.paneCollapse = map;
22039
+ setCollapsedPanes(map2) {
22040
+ this.paneCollapse = map2;
22004
22041
  }
22005
22042
  /**
22006
22043
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -22761,6 +22798,66 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22761
22798
  center: (wickLeftDev + wickDev / 2) / dpr
22762
22799
  };
22763
22800
  }
22801
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
22802
+ const out = [];
22803
+ let col = NaN;
22804
+ let intervals = [];
22805
+ const flush = () => {
22806
+ if (intervals.length === 0) return;
22807
+ const merged = [intervals[0]];
22808
+ for (let k = 1; k < intervals.length; k += 1) {
22809
+ const prev3 = merged[merged.length - 1];
22810
+ const next2 = intervals[k];
22811
+ if (Math.abs(yOf(prev3.hi) - yOf(next2.lo)) < 1) {
22812
+ prev3.hi = next2.hi;
22813
+ if (next2.headIdx < prev3.headIdx) {
22814
+ prev3.headIdx = next2.headIdx;
22815
+ prev3.headTime = next2.headTime;
22816
+ prev3.open = next2.open;
22817
+ }
22818
+ if (next2.lastIdx > prev3.lastIdx) {
22819
+ prev3.lastIdx = next2.lastIdx;
22820
+ prev3.close = next2.close;
22821
+ }
22822
+ } else {
22823
+ merged.push(next2);
22824
+ }
22825
+ }
22826
+ for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
22827
+ intervals = [];
22828
+ };
22829
+ for (let i = i0; i <= i1; i += 1) {
22830
+ const b = bars[i];
22831
+ if (!b || b.high <= b.low) continue;
22832
+ const x = Math.round(xOf(i));
22833
+ if (x !== col) {
22834
+ flush();
22835
+ col = x;
22836
+ }
22837
+ let lo = b.low;
22838
+ let hi = b.high;
22839
+ let headIdx = i;
22840
+ let headTime = b.time;
22841
+ let open2 = b.open;
22842
+ for (let k = intervals.length - 1; k >= 0; k -= 1) {
22843
+ const iv = intervals[k];
22844
+ if (iv.lo > hi || iv.hi < lo) continue;
22845
+ if (iv.lo < lo) lo = iv.lo;
22846
+ if (iv.hi > hi) hi = iv.hi;
22847
+ if (iv.headIdx < headIdx) {
22848
+ headIdx = iv.headIdx;
22849
+ headTime = iv.headTime;
22850
+ open2 = iv.open;
22851
+ }
22852
+ intervals.splice(k, 1);
22853
+ }
22854
+ let insertAt = 0;
22855
+ while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
22856
+ intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
22857
+ }
22858
+ flush();
22859
+ return out;
22860
+ }
22764
22861
 
22765
22862
  // src/renderers/native/core/chartConfig.ts
22766
22863
  var CHART_CONFIG_VERSION = 1;
@@ -22771,6 +22868,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22771
22868
  var BASELINE_LEVEL_DEFAULT = 50;
22772
22869
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
22773
22870
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
22871
+ var EXTENDED_SHADE = POSTMARKET_SHADE;
22774
22872
  function defaultChartStyle() {
22775
22873
  return {
22776
22874
  chartTypes: {},
@@ -22779,7 +22877,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22779
22877
  gridHorz: { visible: true, color: null },
22780
22878
  borderColor: null,
22781
22879
  separatorColor: null,
22782
- crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: SLATE },
22880
+ crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
22783
22881
  candle: {
22784
22882
  bodyVisible: true,
22785
22883
  borderVisible: false,
@@ -22802,7 +22900,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22802
22900
  width: 2,
22803
22901
  baselineLevel: BASELINE_LEVEL_DEFAULT
22804
22902
  },
22805
- sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
22903
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
22806
22904
  };
22807
22905
  }
22808
22906
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -22902,9 +23000,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22902
23000
  if (inst.enableKey) defaults2[inst.enableKey] = false;
22903
23001
  addRows(inst.rows);
22904
23002
  }
22905
- for (const sub of section.subsections ?? []) {
22906
- if (sub.enableKey) defaults2[sub.enableKey] = false;
22907
- addRows(sub.rows);
23003
+ for (const sub2 of section.subsections ?? []) {
23004
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
23005
+ addRows(sub2.rows);
22908
23006
  }
22909
23007
  if (!section.instances) addRows(section.rows);
22910
23008
  bag[t.id] = defaults2;
@@ -23031,7 +23129,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23031
23129
  },
23032
23130
  sessions: {
23033
23131
  premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
23034
- postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
23132
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
23133
+ extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
23035
23134
  },
23036
23135
  stacking: {
23037
23136
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
@@ -24016,44 +24115,19 @@ void main() {
24016
24115
  }
24017
24116
  }
24018
24117
  /**
24019
- * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bucket bars
24020
- * sharing a rounded pixel column into one high-low stick → draw cost bounded by
24021
- * screen width, not bar count. Bucket color follows first-open→last-close.
24118
+ * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
24119
+ * rounded pixel column collapse into high-low sticks — one per contiguous
24120
+ * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
24121
+ * column stays a void. Draw cost stays bounded by screen width, not bar count;
24122
+ * stick color follows its own first-open→last-close.
24022
24123
  */
24023
24124
  emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
24024
- let px = NaN;
24025
- let lo = Infinity;
24026
- let hi = -Infinity;
24027
- let openV = 0;
24028
- let closeV = 0;
24029
- let headTime = 0;
24030
- let active = false;
24031
- const flush = () => {
24032
- if (!active) return;
24033
- const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
24034
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
24035
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
24036
- b.rect(px - 0.5, hY, 1, lY - hY, c);
24037
- };
24038
- for (let i = i0; i <= i1; i += 1) {
24039
- const bar = bars[i];
24040
- if (!bar || bar.high <= bar.low) continue;
24041
- const x = Math.round(coords.logicalToX(i));
24042
- if (!active || x !== px) {
24043
- flush();
24044
- px = x;
24045
- lo = bar.low;
24046
- hi = bar.high;
24047
- openV = bar.open;
24048
- headTime = bar.time;
24049
- active = true;
24050
- } else {
24051
- if (bar.low < lo) lo = bar.low;
24052
- if (bar.high > hi) hi = bar.high;
24053
- }
24054
- closeV = bar.close;
24125
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
24126
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
24127
+ const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
24128
+ const hY = yOf(s.hi);
24129
+ b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
24055
24130
  }
24056
- flush();
24057
24131
  }
24058
24132
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
24059
24133
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -24257,11 +24331,11 @@ void main() {
24257
24331
  }
24258
24332
  }
24259
24333
  if (!any) return EMPTY_BARCOLORS;
24260
- const map = /* @__PURE__ */ new Map();
24334
+ const map2 = /* @__PURE__ */ new Map();
24261
24335
  for (const m of indicators.values()) {
24262
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24336
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24263
24337
  }
24264
- return map;
24338
+ return map2;
24265
24339
  }
24266
24340
 
24267
24341
  // src/renderers/native/core/ViewportState.ts
@@ -24558,9 +24632,9 @@ void main() {
24558
24632
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24559
24633
  return rightOffset + deltaX / barSpacing;
24560
24634
  }
24561
- function wheelPanDelta(deltaX, deltaY, shift3) {
24635
+ function wheelPanDelta(deltaX, deltaY, shift4) {
24562
24636
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24563
- if (shift3) return deltaY;
24637
+ if (shift4) return deltaY;
24564
24638
  return null;
24565
24639
  }
24566
24640
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -25351,13 +25425,15 @@ void main() {
25351
25425
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
25352
25426
  return this.orderedCache;
25353
25427
  }
25354
- /** The session zones resolved into colored bands (pre/post-market washes from the
25355
- * config's session colors) — consumed by the same painting path as {@link highlights}. */
25428
+ /** The session zones resolved into colored bands (pre/post-market or extended-hours
25429
+ * washes from the config's session colors) — consumed by the same painting path as
25430
+ * {@link highlights}. */
25356
25431
  sessionHighlightBands() {
25357
25432
  if (!this.sessionZones) return [];
25358
25433
  const out = [];
25359
25434
  for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
25360
25435
  for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
25436
+ for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
25361
25437
  return out;
25362
25438
  }
25363
25439
  indicatorsForPane(paneId) {
@@ -25896,50 +25972,23 @@ void main() {
25896
25972
  }
25897
25973
  }
25898
25974
  /**
25899
- * Sub-pixel LOD: bucket every bar sharing a rounded pixel column into ONE
25900
- * high-low stick, so draw cost is bounded by screen width (not bar count) when
25901
- * zoomed far out. The bucket's color follows its first-open→last-close
25902
- * direction (a barcolor() on the head bar still wins).
25975
+ * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
25976
+ * sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
25977
+ * so draw cost is bounded by screen width (not bar count) when zoomed far out
25978
+ * and a price gap inside the column stays a void. Each stick's color follows
25979
+ * its first-open→last-close direction (a barcolor() on the head bar still wins).
25903
25980
  */
25904
25981
  drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
25905
- let px = NaN;
25906
- let lo = Infinity;
25907
- let hi = -Infinity;
25908
- let openV = 0;
25909
- let closeV = 0;
25910
- let headTime = 0;
25911
- let active = false;
25912
- const flush = () => {
25913
- if (!active) return;
25914
- const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
25915
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
25916
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
25917
- ctx.strokeStyle = color;
25918
- ctx.lineWidth = 1;
25982
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
25983
+ ctx.lineWidth = 1;
25984
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
25985
+ const x = s.x + 0.5;
25986
+ ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
25919
25987
  ctx.beginPath();
25920
- ctx.moveTo(px, hY);
25921
- ctx.lineTo(px, lY);
25988
+ ctx.moveTo(x, yOf(s.hi));
25989
+ ctx.lineTo(x, yOf(s.lo));
25922
25990
  ctx.stroke();
25923
- };
25924
- for (let i = i0; i <= i1; i += 1) {
25925
- const b = bars[i];
25926
- if (!b || b.high <= b.low) continue;
25927
- const x = Math.round(coords.logicalToX(i)) + 0.5;
25928
- if (!active || x !== px) {
25929
- flush();
25930
- px = x;
25931
- lo = b.low;
25932
- hi = b.high;
25933
- openV = b.open;
25934
- headTime = b.time;
25935
- active = true;
25936
- } else {
25937
- if (b.low < lo) lo = b.low;
25938
- if (b.high > hi) hi = b.high;
25939
- }
25940
- closeV = b.close;
25941
25991
  }
25942
- flush();
25943
25992
  }
25944
25993
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
25945
25994
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -26207,11 +26256,11 @@ void main() {
26207
26256
  }
26208
26257
  }
26209
26258
  if (!any) return EMPTY_BARCOLORS2;
26210
- const map = /* @__PURE__ */ new Map();
26259
+ const map2 = /* @__PURE__ */ new Map();
26211
26260
  for (const m of indicators.values()) {
26212
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26261
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
26213
26262
  }
26214
- return map;
26263
+ return map2;
26215
26264
  }
26216
26265
 
26217
26266
  // src/renderers/shared/drawing-geometry.ts
@@ -26963,6 +27012,32 @@ void main() {
26963
27012
  }
26964
27013
  };
26965
27014
 
27015
+ // src/renderers/native/chrome/tz.ts
27016
+ function tzOffsetMs(ms, timeZone) {
27017
+ if (!timeZone || timeZone === "UTC") return 0;
27018
+ try {
27019
+ const dtf = new Intl.DateTimeFormat("en-US", {
27020
+ timeZone,
27021
+ hourCycle: "h23",
27022
+ year: "numeric",
27023
+ month: "2-digit",
27024
+ day: "2-digit",
27025
+ hour: "2-digit",
27026
+ minute: "2-digit",
27027
+ second: "2-digit"
27028
+ });
27029
+ const parts3 = dtf.formatToParts(new Date(ms));
27030
+ const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
27031
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
27032
+ return asUTC - ms;
27033
+ } catch {
27034
+ return 0;
27035
+ }
27036
+ }
27037
+ function zonedDate(ms, timeZone) {
27038
+ return new Date(ms + tzOffsetMs(ms, timeZone));
27039
+ }
27040
+
26966
27041
  // src/renderers/native/chrome/ticks.ts
26967
27042
  function priceTicks(min2, max2, target = 6) {
26968
27043
  if (!(max2 > min2) || !Number.isFinite(min2) || !Number.isFinite(max2)) return [];
@@ -27110,7 +27185,14 @@ void main() {
27110
27185
  YEAR
27111
27186
  ];
27112
27187
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
27188
+ var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
27113
27189
  var pad2 = (n) => n < 10 ? `0${n}` : String(n);
27190
+ function formatTimeStamp(ms, timeZone, barIntervalMs) {
27191
+ const d = zonedDate(ms, timeZone);
27192
+ const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
27193
+ if (barIntervalMs >= DAY) return date;
27194
+ return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
27195
+ }
27114
27196
  function pickStep(targetMs) {
27115
27197
  for (const step of STEP_LADDER) if (step >= targetMs) return step;
27116
27198
  return STEP_LADDER[STEP_LADDER.length - 1];
@@ -27152,32 +27234,6 @@ void main() {
27152
27234
  return out;
27153
27235
  }
27154
27236
 
27155
- // src/renderers/native/chrome/tz.ts
27156
- function tzOffsetMs(ms, timeZone) {
27157
- if (!timeZone || timeZone === "UTC") return 0;
27158
- try {
27159
- const dtf = new Intl.DateTimeFormat("en-US", {
27160
- timeZone,
27161
- hourCycle: "h23",
27162
- year: "numeric",
27163
- month: "2-digit",
27164
- day: "2-digit",
27165
- hour: "2-digit",
27166
- minute: "2-digit",
27167
- second: "2-digit"
27168
- });
27169
- const parts3 = dtf.formatToParts(new Date(ms));
27170
- const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
27171
- const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
27172
- return asUTC - ms;
27173
- } catch {
27174
- return 0;
27175
- }
27176
- }
27177
- function zonedDate(ms, timeZone) {
27178
- return new Date(ms + tzOffsetMs(ms, timeZone));
27179
- }
27180
-
27181
27237
  // src/renderers/native/chrome/ChromeRenderer.ts
27182
27238
  var ChromeRenderer = class {
27183
27239
  constructor() {
@@ -27624,7 +27680,7 @@ void main() {
27624
27680
  const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
27625
27681
  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);
27626
27682
  }
27627
- this.chip(ctx, x, dataH + 1, formatStamp(coords.logicalToTime(logical), scene.timezone), chipBg, "center", true, theme.background);
27683
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
27628
27684
  }
27629
27685
  destroy() {
27630
27686
  this.canvas = null;
@@ -27672,7 +27728,7 @@ void main() {
27672
27728
  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);
27673
27729
  }
27674
27730
  }
27675
- this.chip(ctx, x, dataH + 1, formatStamp(ext.time, scene.timezone), chipBg, "center", true, theme.background);
27731
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
27676
27732
  ctx.globalAlpha = 1;
27677
27733
  }
27678
27734
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
@@ -27705,11 +27761,6 @@ void main() {
27705
27761
  else if (style === "dotted") ctx.setLineDash([2, 3]);
27706
27762
  else ctx.setLineDash([]);
27707
27763
  }
27708
- function formatStamp(ms, timeZone) {
27709
- const d = zonedDate(ms, timeZone);
27710
- const pad = (n) => n < 10 ? `0${n}` : String(n);
27711
- return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
27712
- }
27713
27764
 
27714
27765
  // src/core/timezones.ts
27715
27766
  var TIMEZONES = [
@@ -27925,9 +27976,9 @@ void main() {
27925
27976
  };
27926
27977
  if (section.rows) addRows(section.rows);
27927
27978
  for (const inst of section.instances ?? []) addRows(inst.rows);
27928
- for (const sub of section.subsections ?? []) {
27929
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
27930
- addRows(sub.rows);
27979
+ for (const sub2 of section.subsections ?? []) {
27980
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27981
+ addRows(sub2.rows);
27931
27982
  }
27932
27983
  }
27933
27984
  for (const hs of hostSections) {
@@ -28486,14 +28537,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28486
28537
  };
28487
28538
  if (section.rows) seed(section.rows);
28488
28539
  for (const inst of section.instances ?? []) seed(inst.rows);
28489
- for (const sub of section.subsections ?? []) seed(sub.rows);
28540
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28490
28541
  for (const inst of section.instances ?? []) {
28491
28542
  if (!inst.enableKey || inst.enableKey in bag) continue;
28492
28543
  bag[inst.enableKey] = values[inst.enableKey] === true;
28493
28544
  }
28494
- for (const sub of section.subsections ?? []) {
28495
- if (!sub.enableKey || sub.enableKey in bag) continue;
28496
- bag[sub.enableKey] = values[sub.enableKey] === true;
28545
+ for (const sub2 of section.subsections ?? []) {
28546
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28547
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28497
28548
  }
28498
28549
  const refreshers = [];
28499
28550
  const put = (key, v) => {
@@ -28510,16 +28561,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28510
28561
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28511
28562
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28512
28563
  }
28513
- for (const sub of section.subsections ?? []) {
28514
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
28515
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
28564
+ for (const sub2 of section.subsections ?? []) {
28565
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28566
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28516
28567
  if (rows.length === 0) continue;
28517
- const subMarker = this.section(sub.title);
28568
+ const subMarker = this.section(sub2.title);
28518
28569
  subMarker.dataset.sdStyle = typeId;
28519
28570
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28520
28571
  subMarker.dataset.sdSub = "1";
28521
28572
  body.append(subMarker);
28522
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28573
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28523
28574
  }
28524
28575
  for (const r of refreshers) r();
28525
28576
  }
@@ -30627,11 +30678,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30627
30678
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30628
30679
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30629
30680
  * while placing a line tool it locks the segment angle to 45° steps. */
30630
- down(x, y, mode = "off", shift3 = false) {
30681
+ down(x, y, mode = "off", shift4 = false) {
30631
30682
  const proj = this.deps.projector();
30632
30683
  if (this.state.kind === "placing") {
30633
30684
  if (this.state.draft.placementMode() !== "click") return;
30634
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30685
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30635
30686
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30636
30687
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30637
30688
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30650,7 +30701,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30650
30701
  }
30651
30702
  const hit = this.hitAt(x, y);
30652
30703
  if (hit) {
30653
- if (shift3) {
30704
+ if (shift4) {
30654
30705
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30655
30706
  return;
30656
30707
  }
@@ -30660,7 +30711,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30660
30711
  }
30661
30712
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30662
30713
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30663
- move(x, y, mode = "off", shift3 = false) {
30714
+ move(x, y, mode = "off", shift4 = false) {
30664
30715
  if (this.state.kind === "idle") {
30665
30716
  const tool = this.deps.activeTool();
30666
30717
  const had = this.snapAt != null;
@@ -30674,7 +30725,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30674
30725
  this.captureFreehand(x, y);
30675
30726
  return;
30676
30727
  }
30677
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30728
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30678
30729
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30679
30730
  this.deps.changed();
30680
30731
  return;
@@ -30687,7 +30738,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30687
30738
  if (this.state.handle >= 0) {
30688
30739
  const a = d.anchors[this.state.handle];
30689
30740
  if (a) {
30690
- const pivot = shift3 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30741
+ const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30691
30742
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30692
30743
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30693
30744
  }
@@ -31385,21 +31436,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
31385
31436
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31386
31437
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31387
31438
  }
31388
- const vwap = drawing.vwap;
31389
- if (paths.has("vwap.midColor") && vwap) {
31439
+ const vwap2 = drawing.vwap;
31440
+ if (paths.has("vwap.midColor") && vwap2) {
31390
31441
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31391
31442
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
31392
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31393
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31443
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31444
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31394
31445
  bar.appendChild(
31395
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31446
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31396
31447
  label: (v) => `${v}\u03C3`,
31397
31448
  labelInTrigger: true
31398
31449
  })
31399
31450
  );
31400
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31401
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31402
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31451
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31452
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31453
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31403
31454
  }
31404
31455
  if (paths.has("maxCurvature")) {
31405
31456
  const cur = drawing.maxCurvature ?? 24;
@@ -32145,8 +32196,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32145
32196
  }
32146
32197
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
32147
32198
  * right edge of the flyout rows, beside the favorite star. */
32148
- setShortcuts(map) {
32149
- this.shortcuts = new Map(Object.entries(map));
32199
+ setShortcuts(map2) {
32200
+ this.shortcuts = new Map(Object.entries(map2));
32150
32201
  }
32151
32202
  /** Paint one star for the current favorite state (filled + gold, or outline). */
32152
32203
  paintStar(el, type) {
@@ -33048,8 +33099,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33048
33099
  this.toolbar.setFavorites(types);
33049
33100
  }
33050
33101
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
33051
- setToolShortcuts(map) {
33052
- this.toolbar.setShortcuts(map);
33102
+ setToolShortcuts(map2) {
33103
+ this.toolbar.setShortcuts(map2);
33053
33104
  }
33054
33105
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
33055
33106
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -33167,7 +33218,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33167
33218
  this.render();
33168
33219
  }
33169
33220
  }
33170
- pointerDown(x, y, snap = "off", shift3 = false) {
33221
+ pointerDown(x, y, snap = "off", shift4 = false) {
33171
33222
  if (this.eraserMode) {
33172
33223
  this.erasing = true;
33173
33224
  this.deleteAt(x, y);
@@ -33187,9 +33238,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33187
33238
  return;
33188
33239
  }
33189
33240
  }
33190
- this.interaction.down(x, y, snap, shift3);
33241
+ this.interaction.down(x, y, snap, shift4);
33191
33242
  }
33192
- pointerMove(x, y, snap = "off", shift3 = false) {
33243
+ pointerMove(x, y, snap = "off", shift4 = false) {
33193
33244
  if (this.eraserMode) {
33194
33245
  if (this.erasing) this.deleteAt(x, y);
33195
33246
  return;
@@ -33200,7 +33251,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33200
33251
  this.render();
33201
33252
  return;
33202
33253
  }
33203
- this.interaction.move(x, y, snap, shift3);
33254
+ this.interaction.move(x, y, snap, shift4);
33204
33255
  this.updateHover(x, y);
33205
33256
  }
33206
33257
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -33877,14 +33928,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33877
33928
  }
33878
33929
  for (const { box, w: w2, h: h2 } of spanning) {
33879
33930
  if (box.cs > 1) {
33880
- let sum = 0;
33881
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
33882
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
33931
+ let sum2 = 0;
33932
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
33933
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
33883
33934
  }
33884
33935
  if (box.rs > 1) {
33885
- let sum = 0;
33886
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
33887
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
33936
+ let sum2 = 0;
33937
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
33938
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
33888
33939
  }
33889
33940
  }
33890
33941
  let w = 0;
@@ -34198,6 +34249,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34198
34249
  const end = Math.min(right, x2);
34199
34250
  return end > x ? { x, width: end - x } : null;
34200
34251
  }
34252
+ function bandEdgeSlot(logical) {
34253
+ return Math.ceil(logical) - 0.5;
34254
+ }
34201
34255
  var BackdropRenderer = class {
34202
34256
  constructor() {
34203
34257
  this.canvas = null;
@@ -34228,21 +34282,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34228
34282
  this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
34229
34283
  }
34230
34284
  /** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
34231
- * Session-zone washes (pre/post-market) paint first, host highlights on top. */
34285
+ * Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
34286
+ * never cuts a candle in two), host highlights on top (continuous time mapping —
34287
+ * hosts may paint sub-bar spans). */
34232
34288
  drawHighlights(ctx, scene, coords) {
34233
34289
  const sessions = scene.sessionHighlightBands();
34234
34290
  if (sessions.length > 0) {
34235
34291
  const left = Math.max(0, coords.logicalToX(-0.5));
34236
34292
  const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
34237
- this.drawHighlightSet(ctx, sessions, coords, left, right);
34293
+ this.drawHighlightSet(ctx, sessions, coords, left, right, true);
34238
34294
  }
34239
- this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
34295
+ this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
34240
34296
  }
34241
- drawHighlightSet(ctx, bands, coords, left, right) {
34297
+ drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
34298
+ const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
34242
34299
  for (const band of bands) {
34243
- const x1 = coords.timeToX(band.from);
34244
- const x2 = coords.timeToX(band.to);
34245
- const rect = clipHighlightRect(x1, x2, left, right);
34300
+ const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
34246
34301
  if (!rect) continue;
34247
34302
  ctx.fillStyle = band.color;
34248
34303
  ctx.fillRect(rect.x, 0, rect.width, coords.height);
@@ -34501,6 +34556,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34501
34556
  });
34502
34557
  el.style.color = attributionMarkColor(background);
34503
34558
  el.innerHTML = html;
34559
+ el.dataset.velaScreenshot = "1";
34504
34560
  return el;
34505
34561
  }
34506
34562
  function createAttributionMark(doc, background) {
@@ -34519,6 +34575,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34519
34575
  cursor: "pointer"
34520
34576
  });
34521
34577
  applyAttributionMarkTheme(a, background);
34578
+ a.dataset.velaScreenshot = "1";
34522
34579
  const symbol = doc.createElement("span");
34523
34580
  symbol.className = "vela-attr-symbol";
34524
34581
  symbol.setAttribute("aria-hidden", "true");
@@ -34601,24 +34658,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34601
34658
  drawSvgShapes(el, alpha);
34602
34659
  continue;
34603
34660
  }
34604
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34661
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34605
34662
  let path = null;
34606
34663
  if (tag === "path") {
34607
34664
  const d = el.getAttribute("d");
34608
34665
  if (d) path = new Path2D(d);
34609
34666
  } else if (tag === "circle") {
34610
34667
  path = new Path2D();
34611
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
34668
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
34612
34669
  } else if (tag === "ellipse") {
34613
34670
  path = new Path2D();
34614
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
34671
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
34615
34672
  } else if (tag === "rect") {
34616
34673
  path = new Path2D();
34617
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
34674
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
34618
34675
  } else if (tag === "line") {
34619
34676
  path = new Path2D();
34620
- path.moveTo(num3("x1"), num3("y1"));
34621
- path.lineTo(num3("x2"), num3("y2"));
34677
+ path.moveTo(num4("x1"), num4("y1"));
34678
+ path.lineTo(num4("x2"), num4("y2"));
34622
34679
  } else if (tag === "polyline" || tag === "polygon") {
34623
34680
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
34624
34681
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -35523,7 +35580,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35523
35580
  };
35524
35581
  })(),
35525
35582
  series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
35526
- sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
35583
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
35527
35584
  };
35528
35585
  }
35529
35586
  /**
@@ -35605,7 +35662,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35605
35662
  width: next2.baseline.width,
35606
35663
  baselineLevel: next2.baseline.baselineLevel
35607
35664
  };
35608
- s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor };
35665
+ s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor, extendedColor: next2.sessions.extendedColor };
35609
35666
  this.setPriceStyle(next2.series.style);
35610
35667
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
35611
35668
  this.scene.baselineValue = next2.series.baseline;
@@ -35827,9 +35884,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35827
35884
  * opts in with a `data-vela-screenshot` attribute on the mount container's
35828
35885
  * subtree (the widget marks its status line, and its symbol watermark with
35829
35886
  * `"under"` — drawn beneath the canvases, where it sits on screen). Only the
35830
- * crosshair (L2) is intentionally excluded.
35887
+ * crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
35888
+ * as a PNG data URL.
35831
35889
  */
35832
- screenshot() {
35890
+ screenshotCanvas() {
35833
35891
  if (!this.dataCanvas) return null;
35834
35892
  this.computeScales();
35835
35893
  this.paintData();
@@ -35857,7 +35915,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35857
35915
  if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
35858
35916
  }
35859
35917
  }
35860
- return out.toDataURL("image/png");
35918
+ return out;
35919
+ }
35920
+ screenshot() {
35921
+ return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
35861
35922
  }
35862
35923
  /**
35863
35924
  * The candles-first-appear reveal — animates the **candles themselves** (no extra
@@ -36031,8 +36092,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36031
36092
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
36032
36093
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
36033
36094
  drawingsSnapMode: () => this.snapMode,
36034
- drawingsPointerDown: (x, y, snap, shift3) => this.userDrawings?.pointerDown(x, y, snap, shift3),
36035
- drawingsPointerMove: (x, y, snap, shift3) => this.userDrawings?.pointerMove(x, y, snap, shift3),
36095
+ drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
36096
+ drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
36036
36097
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
36037
36098
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
36038
36099
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -36349,18 +36410,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36349
36410
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
36350
36411
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
36351
36412
  const newHeadTime = this.bars[0].time;
36352
- let shift3 = 0;
36413
+ let shift4 = 0;
36353
36414
  if (newHeadTime !== prevHeadTime) {
36354
36415
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
36355
- if (prepended > 0) shift3 = prepended;
36416
+ if (prepended > 0) shift4 = prepended;
36356
36417
  else {
36357
36418
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
36358
- if (trimmed > 0) shift3 = -trimmed;
36419
+ if (trimmed > 0) shift4 = -trimmed;
36359
36420
  }
36360
36421
  }
36361
- if (shift3 !== 0) {
36362
- this.zoomAnchorLogical += shift3;
36363
- if (this.hoverLogical != null) this.hoverLogical += shift3;
36422
+ if (shift4 !== 0) {
36423
+ this.zoomAnchorLogical += shift4;
36424
+ if (this.hoverLogical != null) this.hoverLogical += shift4;
36364
36425
  }
36365
36426
  }
36366
36427
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -37252,7 +37313,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37252
37313
  const pricePane = this.dataWindowPricePane();
37253
37314
  const ohlc = this.dataWindowOHLC(bar, pricePane);
37254
37315
  const parts3 = [];
37255
- if (bar) parts3.push(formatStamp2(bar.time, this.scene.timezone));
37316
+ if (bar) parts3.push(formatStamp(bar.time, this.scene.timezone));
37256
37317
  if (ohlc) parts3.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
37257
37318
  for (const group of this.dataWindowGroups(idx, pricePane)) {
37258
37319
  for (const row of group.rows) parts3.push(`${row.label} ${row.value}`);
@@ -37966,15 +38027,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37966
38027
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
37967
38028
  * strip — merged own-scale indicators are hidden while collapsed. */
37968
38029
  collapsedMasterMap() {
37969
- const map = /* @__PURE__ */ new Map();
38030
+ const map2 = /* @__PURE__ */ new Map();
37970
38031
  for (const pane of this.scene.panes.values()) {
37971
38032
  if (!pane.collapsed) continue;
37972
38033
  const models = this.scene.orderedIndicatorsForPane(pane.id);
37973
38034
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
37974
38035
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
37975
- map.set(pane.id, master?.id ?? null);
38036
+ map2.set(pane.id, master?.id ?? null);
37976
38037
  }
37977
- return map;
38038
+ return map2;
37978
38039
  }
37979
38040
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
37980
38041
  setToolbarGutter(px) {
@@ -38139,7 +38200,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38139
38200
  time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
38140
38201
  };
38141
38202
  }
38142
- function formatStamp2(ms, timeZone) {
38203
+ function formatStamp(ms, timeZone) {
38143
38204
  const { date, time } = formatStampParts(ms, timeZone);
38144
38205
  return `${date} ${time}`;
38145
38206
  }
@@ -38170,7 +38231,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38170
38231
  }
38171
38232
  return out.sort((a, b) => a[0] - b[0]);
38172
38233
  };
38173
- return { pre: windows(v.pre), post: windows(v.post) };
38234
+ return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
38174
38235
  }
38175
38236
  function clamp012(v) {
38176
38237
  return Math.max(0, Math.min(1, v));
@@ -38370,12 +38431,2328 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38370
38431
  registerNativeIndicator(vpvrDescriptor);
38371
38432
  }
38372
38433
 
38434
+ // src/core/model/identity.ts
38435
+ function stableSeriesId(parts3) {
38436
+ const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
38437
+ return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
38438
+ }
38439
+
38440
+ // src/core/native-indicators/classics/define.ts
38441
+ function num3(inputs, key, fallback) {
38442
+ const v = inputs[key];
38443
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
38444
+ }
38445
+ function str3(inputs, key, fallback) {
38446
+ const v = inputs[key];
38447
+ return typeof v === "string" && v !== "" ? v : fallback;
38448
+ }
38449
+ function toPoints(bars, values, colors) {
38450
+ const pts = new Array(bars.length);
38451
+ for (let i = 0; i < bars.length; i++) {
38452
+ const v = values[i];
38453
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
38454
+ const c = colors?.[i];
38455
+ if (c != null) p.color = c;
38456
+ pts[i] = p;
38457
+ }
38458
+ return pts;
38459
+ }
38460
+ var ClassicIndicator = class {
38461
+ constructor(spec) {
38462
+ this.spec = spec;
38463
+ this.inputs = {};
38464
+ }
38465
+ start(ctx, inputs) {
38466
+ this.ctx = ctx;
38467
+ this.inputs = inputs;
38468
+ this.recompute();
38469
+ }
38470
+ onBars() {
38471
+ this.recompute();
38472
+ }
38473
+ onViewport() {
38474
+ }
38475
+ setInputs(inputs) {
38476
+ this.inputs = inputs;
38477
+ this.recompute();
38478
+ }
38479
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
38480
+ suspend() {
38481
+ }
38482
+ resume() {
38483
+ this.recompute();
38484
+ }
38485
+ stop() {
38486
+ }
38487
+ recompute() {
38488
+ const bars = this.ctx.bars();
38489
+ const out = this.spec.compute(bars, this.inputs);
38490
+ const instanceId = this.spec.type;
38491
+ const series = [];
38492
+ const idsByKey = /* @__PURE__ */ new Map();
38493
+ out.plots.forEach((plot, ordinal) => {
38494
+ const kind = plot.kind ?? "line";
38495
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
38496
+ idsByKey.set(plot.key, id);
38497
+ series.push({
38498
+ id,
38499
+ title: plot.title,
38500
+ paneId: "",
38501
+ // stamped by the orchestrator
38502
+ kind,
38503
+ points: toPoints(bars, plot.values, plot.colors),
38504
+ style: {
38505
+ color: plot.color,
38506
+ width: plot.width ?? 1,
38507
+ lineStyle: plot.lineStyle ?? "solid",
38508
+ ...plot.base != null ? { base: plot.base } : {}
38509
+ },
38510
+ ...plot.overlay ? { overlay: true } : {}
38511
+ });
38512
+ });
38513
+ if (out.markers && out.markers.length > 0) {
38514
+ series.push({
38515
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
38516
+ title: this.spec.shortTitle ?? this.spec.title,
38517
+ paneId: "",
38518
+ kind: "markers",
38519
+ markers: out.markers
38520
+ });
38521
+ }
38522
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
38523
+ const from = idsByKey.get(band.from);
38524
+ const to = idsByKey.get(band.to);
38525
+ if (!from || !to) return [];
38526
+ return [
38527
+ {
38528
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
38529
+ paneId: "",
38530
+ fromSeriesId: from,
38531
+ toSeriesId: to,
38532
+ color: band.color
38533
+ }
38534
+ ];
38535
+ });
38536
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
38537
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
38538
+ paneId: "",
38539
+ price: level.price,
38540
+ ...level.color != null ? { color: level.color } : {},
38541
+ lineStyle: level.lineStyle ?? "dashed",
38542
+ width: 1,
38543
+ ...level.title != null ? { title: level.title } : {}
38544
+ }));
38545
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
38546
+ const { key, ...rest } = poly;
38547
+ return {
38548
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
38549
+ paneId: "",
38550
+ ...rest
38551
+ };
38552
+ });
38553
+ this.ctx.emit({ series, fills, priceLines, polylines });
38554
+ this.ctx.setStatus("idle");
38555
+ }
38556
+ };
38557
+ function classicDescriptor(spec) {
38558
+ return {
38559
+ type: spec.type,
38560
+ title: spec.title,
38561
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
38562
+ paneHint: spec.overlay ? "price" : "new",
38563
+ overlay: spec.overlay,
38564
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
38565
+ multiInstance: true,
38566
+ inputsSchema: () => spec.inputs,
38567
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
38568
+ create: () => new ClassicIndicator(spec)
38569
+ };
38570
+ }
38571
+
38572
+ // src/core/native-indicators/classics/math.ts
38573
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
38574
+ function sourceValues(bars, source) {
38575
+ const out = new Array(bars.length);
38576
+ for (let i = 0; i < bars.length; i++) {
38577
+ const b = bars[i];
38578
+ switch (source) {
38579
+ case "Open":
38580
+ out[i] = b.open;
38581
+ break;
38582
+ case "High":
38583
+ out[i] = b.high;
38584
+ break;
38585
+ case "Low":
38586
+ out[i] = b.low;
38587
+ break;
38588
+ case "HL2":
38589
+ out[i] = (b.high + b.low) / 2;
38590
+ break;
38591
+ case "HLC3":
38592
+ out[i] = (b.high + b.low + b.close) / 3;
38593
+ break;
38594
+ case "OHLC4":
38595
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
38596
+ break;
38597
+ case "HLCC4":
38598
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
38599
+ break;
38600
+ default:
38601
+ out[i] = b.close;
38602
+ }
38603
+ }
38604
+ return out;
38605
+ }
38606
+ function volumes(bars) {
38607
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
38608
+ }
38609
+ function highs(bars) {
38610
+ return bars.map((b) => b.high);
38611
+ }
38612
+ function lows(bars) {
38613
+ return bars.map((b) => b.low);
38614
+ }
38615
+ function closes(bars) {
38616
+ return bars.map((b) => b.close);
38617
+ }
38618
+ function opens(bars) {
38619
+ return bars.map((b) => b.open);
38620
+ }
38621
+ function zip(a, b, f) {
38622
+ const out = new Array(a.length);
38623
+ for (let i = 0; i < a.length; i++) {
38624
+ const x = a[i];
38625
+ const y = b[i];
38626
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
38627
+ }
38628
+ return out;
38629
+ }
38630
+ function map(a, f) {
38631
+ const out = new Array(a.length);
38632
+ for (let i = 0; i < a.length; i++) {
38633
+ const x = a[i];
38634
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
38635
+ }
38636
+ return out;
38637
+ }
38638
+ function sub(a, b) {
38639
+ return zip(a, b, (x, y) => x - y);
38640
+ }
38641
+ function windowed(v, len, fold) {
38642
+ const out = new Array(v.length).fill(Number.NaN);
38643
+ if (len <= 0) return out;
38644
+ let run = 0;
38645
+ for (let i = 0; i < v.length; i++) {
38646
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
38647
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
38648
+ }
38649
+ return out;
38650
+ }
38651
+ function sum(v, len) {
38652
+ return windowed(v, len, (w, from, to) => {
38653
+ let s = 0;
38654
+ for (let k = from; k <= to; k++) s += w[k];
38655
+ return s;
38656
+ });
38657
+ }
38658
+ function sma(v, len) {
38659
+ return windowed(v, len, (w, from, to) => {
38660
+ let s = 0;
38661
+ for (let k = from; k <= to; k++) s += w[k];
38662
+ return s / len;
38663
+ });
38664
+ }
38665
+ function wma(v, len) {
38666
+ const norm = len * (len + 1) / 2;
38667
+ return windowed(v, len, (w, from, to) => {
38668
+ let s = 0;
38669
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
38670
+ return s / norm;
38671
+ });
38672
+ }
38673
+ function vwma(src, vol, len) {
38674
+ const pv = zip(src, vol, (p, q) => p * q);
38675
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
38676
+ }
38677
+ function stdev(v, len) {
38678
+ return windowed(v, len, (w, from, to) => {
38679
+ let s = 0;
38680
+ for (let k = from; k <= to; k++) s += w[k];
38681
+ const mean = s / len;
38682
+ let sq = 0;
38683
+ for (let k = from; k <= to; k++) {
38684
+ const d = w[k] - mean;
38685
+ sq += d * d;
38686
+ }
38687
+ return Math.sqrt(sq / len);
38688
+ });
38689
+ }
38690
+ function highest(v, len) {
38691
+ return windowed(v, len, (w, from, to) => {
38692
+ let m = -Infinity;
38693
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
38694
+ return m;
38695
+ });
38696
+ }
38697
+ function lowest(v, len) {
38698
+ return windowed(v, len, (w, from, to) => {
38699
+ let m = Infinity;
38700
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
38701
+ return m;
38702
+ });
38703
+ }
38704
+ function barsSinceHighest(v, len) {
38705
+ return windowed(v, len, (w, from, to) => {
38706
+ let m = -Infinity;
38707
+ let at = to;
38708
+ for (let k = from; k <= to; k++) {
38709
+ if (w[k] >= m) {
38710
+ m = w[k];
38711
+ at = k;
38712
+ }
38713
+ }
38714
+ return to - at;
38715
+ });
38716
+ }
38717
+ function barsSinceLowest(v, len) {
38718
+ return windowed(v, len, (w, from, to) => {
38719
+ let m = Infinity;
38720
+ let at = to;
38721
+ for (let k = from; k <= to; k++) {
38722
+ if (w[k] <= m) {
38723
+ m = w[k];
38724
+ at = k;
38725
+ }
38726
+ }
38727
+ return to - at;
38728
+ });
38729
+ }
38730
+ function recursive(v, len, alpha) {
38731
+ const out = new Array(v.length).fill(Number.NaN);
38732
+ let run = 0;
38733
+ let prev3 = Number.NaN;
38734
+ for (let i = 0; i < v.length; i++) {
38735
+ const x = v[i];
38736
+ if (!Number.isFinite(x)) {
38737
+ run = 0;
38738
+ prev3 = Number.NaN;
38739
+ continue;
38740
+ }
38741
+ run += 1;
38742
+ if (Number.isFinite(prev3)) {
38743
+ prev3 = alpha * x + (1 - alpha) * prev3;
38744
+ out[i] = prev3;
38745
+ } else if (run >= len) {
38746
+ let s = 0;
38747
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
38748
+ prev3 = s / len;
38749
+ out[i] = prev3;
38750
+ }
38751
+ }
38752
+ return out;
38753
+ }
38754
+ function ema(v, len) {
38755
+ return recursive(v, len, 2 / (len + 1));
38756
+ }
38757
+ function rma(v, len) {
38758
+ return recursive(v, len, 1 / len);
38759
+ }
38760
+ function change(v, n = 1) {
38761
+ const out = new Array(v.length).fill(Number.NaN);
38762
+ for (let i = n; i < v.length; i++) {
38763
+ const a = v[i];
38764
+ const b = v[i - n];
38765
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
38766
+ }
38767
+ return out;
38768
+ }
38769
+ function roc(v, len) {
38770
+ const out = new Array(v.length).fill(Number.NaN);
38771
+ for (let i = len; i < v.length; i++) {
38772
+ const a = v[i];
38773
+ const b = v[i - len];
38774
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
38775
+ }
38776
+ return out;
38777
+ }
38778
+ function shift3(v, n) {
38779
+ const out = new Array(v.length).fill(Number.NaN);
38780
+ for (let i = 0; i < v.length; i++) {
38781
+ const j = i - n;
38782
+ if (j >= 0 && j < v.length) out[i] = v[j];
38783
+ }
38784
+ return out;
38785
+ }
38786
+ function trueRange(bars) {
38787
+ const out = new Array(bars.length).fill(Number.NaN);
38788
+ for (let i = 0; i < bars.length; i++) {
38789
+ const b = bars[i];
38790
+ if (i === 0) {
38791
+ out[i] = b.high - b.low;
38792
+ continue;
38793
+ }
38794
+ const pc = bars[i - 1].close;
38795
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
38796
+ }
38797
+ return out;
38798
+ }
38799
+ function atr(bars, len) {
38800
+ return rma(trueRange(bars), len);
38801
+ }
38802
+ function rsi(v, len) {
38803
+ const d = change(v);
38804
+ const up = map(d, (x) => Math.max(x, 0));
38805
+ const dn = map(d, (x) => Math.max(-x, 0));
38806
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
38807
+ }
38808
+ function stoch(v, hi, lo, len) {
38809
+ const hh = highest(hi, len);
38810
+ const ll = lowest(lo, len);
38811
+ const out = new Array(v.length).fill(Number.NaN);
38812
+ for (let i = 0; i < v.length; i++) {
38813
+ const x = v[i];
38814
+ const h = hh[i];
38815
+ const l = ll[i];
38816
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
38817
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
38818
+ }
38819
+ return out;
38820
+ }
38821
+ function linreg(v, len) {
38822
+ const sx = (len - 1) * len / 2;
38823
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
38824
+ const denom = len * sxx - sx * sx;
38825
+ return windowed(v, len, (w, from, to) => {
38826
+ let sy = 0;
38827
+ let sxy = 0;
38828
+ for (let k = from; k <= to; k++) {
38829
+ const x = k - from;
38830
+ sy += w[k];
38831
+ sxy += x * w[k];
38832
+ }
38833
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
38834
+ const intercept = (sy - slope * sx) / len;
38835
+ return intercept + slope * (len - 1);
38836
+ });
38837
+ }
38838
+ function swma(v) {
38839
+ const out = new Array(v.length).fill(Number.NaN);
38840
+ for (let i = 3; i < v.length; i++) {
38841
+ const a = v[i - 3];
38842
+ const b = v[i - 2];
38843
+ const c = v[i - 1];
38844
+ const d = v[i];
38845
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
38846
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
38847
+ }
38848
+ }
38849
+ return out;
38850
+ }
38851
+ function percentRank(v, len) {
38852
+ const out = new Array(v.length).fill(Number.NaN);
38853
+ for (let i = len; i < v.length; i++) {
38854
+ const x = v[i];
38855
+ if (!Number.isFinite(x)) continue;
38856
+ let below = 0;
38857
+ let count = 0;
38858
+ for (let k = i - len; k < i; k++) {
38859
+ const y = v[k];
38860
+ if (!Number.isFinite(y)) {
38861
+ count = -1;
38862
+ break;
38863
+ }
38864
+ count += 1;
38865
+ if (y <= x) below += 1;
38866
+ }
38867
+ if (count === len) out[i] = 100 * below / len;
38868
+ }
38869
+ return out;
38870
+ }
38871
+ function cumSum(v) {
38872
+ const out = new Array(v.length).fill(Number.NaN);
38873
+ let acc = 0;
38874
+ let seen = false;
38875
+ for (let i = 0; i < v.length; i++) {
38876
+ const x = v[i];
38877
+ if (Number.isFinite(x)) {
38878
+ acc += x;
38879
+ seen = true;
38880
+ }
38881
+ if (seen) out[i] = acc;
38882
+ }
38883
+ return out;
38884
+ }
38885
+
38886
+ // src/core/native-indicators/classics/shared.ts
38887
+ function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
38888
+ return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
38889
+ }
38890
+ function sourceInput(defval = "Close") {
38891
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
38892
+ }
38893
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
38894
+ return { key, title, type: "color", defval };
38895
+ }
38896
+ function withAlpha2(hex, alpha = 0.08) {
38897
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
38898
+ if (!m) return hex;
38899
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
38900
+ return `#${m[1]}${a}`;
38901
+ }
38902
+
38903
+ // src/core/native-indicators/classics/averages.ts
38904
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
38905
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
38906
+ function fixedAverage(type, title, shortTitle, smooth) {
38907
+ return {
38908
+ type,
38909
+ title,
38910
+ shortTitle,
38911
+ overlay: true,
38912
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
38913
+ compute: (bars, inputs) => {
38914
+ const len = num3(inputs, "length", 20);
38915
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
38916
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
38917
+ if (offset3 !== 0) values = shift3(values, offset3);
38918
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38919
+ }
38920
+ };
38921
+ }
38922
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
38923
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
38924
+ var movingAverage = {
38925
+ type: "moving-average",
38926
+ title: "Moving Average",
38927
+ shortTitle: "MA",
38928
+ overlay: true,
38929
+ inputs: [
38930
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
38931
+ lengthInput(20),
38932
+ sourceInput(),
38933
+ offsetInput,
38934
+ colorInput()
38935
+ ],
38936
+ compute: (bars, inputs) => {
38937
+ const len = num3(inputs, "length", 20);
38938
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38939
+ const kind = str3(inputs, "maType", "SMA");
38940
+ let values;
38941
+ switch (kind) {
38942
+ case "EMA":
38943
+ values = ema(src, len);
38944
+ break;
38945
+ case "WMA":
38946
+ values = wma(src, len);
38947
+ break;
38948
+ case "RMA":
38949
+ values = rma(src, len);
38950
+ break;
38951
+ case "VWMA":
38952
+ values = vwma(src, volumes(bars), len);
38953
+ break;
38954
+ default:
38955
+ values = sma(src, len);
38956
+ }
38957
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
38958
+ if (offset3 !== 0) values = shift3(values, offset3);
38959
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38960
+ }
38961
+ };
38962
+ var smoothedMa = {
38963
+ type: "rma",
38964
+ title: "Smoothed Moving Average",
38965
+ shortTitle: "RMA",
38966
+ overlay: true,
38967
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38968
+ compute: (bars, inputs) => ({
38969
+ plots: [
38970
+ {
38971
+ key: "rma",
38972
+ title: "RMA",
38973
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38974
+ color: str3(inputs, "color", SERIES_LINE),
38975
+ width: 2
38976
+ }
38977
+ ]
38978
+ })
38979
+ };
38980
+ var zlema = {
38981
+ type: "zlema",
38982
+ title: "Zero-Lag Exponential Moving Average",
38983
+ shortTitle: "ZLEMA",
38984
+ overlay: true,
38985
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38986
+ compute: (bars, inputs) => {
38987
+ const len = num3(inputs, "length", 14);
38988
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38989
+ const lag = Math.floor((len - 1) / 2);
38990
+ const lagged = shift3(src, lag);
38991
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
38992
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38993
+ }
38994
+ };
38995
+ var vidya = {
38996
+ type: "vidya",
38997
+ title: "Variable Index Dynamic Average",
38998
+ shortTitle: "VIDYA",
38999
+ overlay: true,
39000
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
39001
+ compute: (bars, inputs) => {
39002
+ const len = num3(inputs, "length", 14);
39003
+ const cmoLen = num3(inputs, "cmoLength", 9);
39004
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39005
+ const d = change(src);
39006
+ const su = new Array(src.length).fill(Number.NaN);
39007
+ const sd = new Array(src.length).fill(Number.NaN);
39008
+ for (let i = cmoLen; i < src.length; i++) {
39009
+ let u = 0;
39010
+ let w = 0;
39011
+ let ok = true;
39012
+ for (let k = i - cmoLen + 1; k <= i; k++) {
39013
+ const x = d[k];
39014
+ if (!Number.isFinite(x)) {
39015
+ ok = false;
39016
+ break;
39017
+ }
39018
+ if (x > 0) u += x;
39019
+ else w -= x;
39020
+ }
39021
+ if (ok) {
39022
+ su[i] = u;
39023
+ sd[i] = w;
39024
+ }
39025
+ }
39026
+ const alpha = 2 / (len + 1);
39027
+ const out = new Array(src.length).fill(Number.NaN);
39028
+ let prev3 = Number.NaN;
39029
+ for (let i = 0; i < src.length; i++) {
39030
+ const x = src[i];
39031
+ const u = su[i];
39032
+ const w = sd[i];
39033
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
39034
+ const total = u + w;
39035
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
39036
+ prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
39037
+ out[i] = prev3;
39038
+ }
39039
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39040
+ }
39041
+ };
39042
+ var maEnvelope = {
39043
+ type: "ma-envelope",
39044
+ title: "Moving Average Envelope",
39045
+ shortTitle: "MA Env",
39046
+ overlay: true,
39047
+ inputs: [
39048
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
39049
+ lengthInput(20),
39050
+ sourceInput(),
39051
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
39052
+ colorInput()
39053
+ ],
39054
+ compute: (bars, inputs) => {
39055
+ const len = num3(inputs, "length", 20);
39056
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39057
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
39058
+ const k = num3(inputs, "percent", 2.5) / 100;
39059
+ const ink = str3(inputs, "color", SERIES_LINE);
39060
+ return {
39061
+ plots: [
39062
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
39063
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
39064
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
39065
+ ],
39066
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
39067
+ };
39068
+ }
39069
+ };
39070
+ var linearRegression = {
39071
+ type: "linear-regression",
39072
+ title: "Linear Regression Curve",
39073
+ shortTitle: "LinReg",
39074
+ overlay: true,
39075
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39076
+ compute: (bars, inputs) => ({
39077
+ plots: [
39078
+ {
39079
+ key: "linreg",
39080
+ title: "LinReg",
39081
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39082
+ color: str3(inputs, "color", SERIES_LINE),
39083
+ width: 2
39084
+ }
39085
+ ]
39086
+ })
39087
+ };
39088
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
39089
+
39090
+ // src/core/native-indicators/classics/bands.ts
39091
+ function bollinger(bars, inputs) {
39092
+ const len = num3(inputs, "length", 20);
39093
+ const mult = num3(inputs, "mult", 2);
39094
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39095
+ const basis = sma(src, len);
39096
+ const dev = map(stdev(src, len), (x) => x * mult);
39097
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
39098
+ }
39099
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
39100
+ var bollingerBands = {
39101
+ type: "bollinger-bands",
39102
+ title: "Bollinger Bands",
39103
+ shortTitle: "BB",
39104
+ overlay: true,
39105
+ inputs: [...BB_INPUTS, colorInput()],
39106
+ compute: (bars, inputs) => {
39107
+ const { basis, upper, lower } = bollinger(bars, inputs);
39108
+ const ink = str3(inputs, "color", SERIES_LINE);
39109
+ return {
39110
+ plots: [
39111
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39112
+ { key: "upper", title: "Upper", values: upper, color: ink },
39113
+ { key: "lower", title: "Lower", values: lower, color: ink }
39114
+ ],
39115
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
39116
+ };
39117
+ }
39118
+ };
39119
+ var percentB = {
39120
+ type: "percent-b",
39121
+ title: "Bollinger Bands %B",
39122
+ shortTitle: "%B",
39123
+ overlay: false,
39124
+ inputs: [...BB_INPUTS, colorInput()],
39125
+ compute: (bars, inputs) => {
39126
+ const { upper, lower } = bollinger(bars, inputs);
39127
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39128
+ const values = src.map((x, i) => {
39129
+ const u = upper[i];
39130
+ const l = lower[i];
39131
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
39132
+ });
39133
+ return {
39134
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39135
+ levels: [
39136
+ { key: "upper", price: 1, color: NEUTRAL },
39137
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
39138
+ { key: "lower", price: 0, color: NEUTRAL }
39139
+ ]
39140
+ };
39141
+ }
39142
+ };
39143
+ var bandwidth = {
39144
+ type: "bandwidth",
39145
+ title: "Bollinger Bands Width",
39146
+ shortTitle: "BandWidth",
39147
+ overlay: false,
39148
+ inputs: [...BB_INPUTS, colorInput()],
39149
+ compute: (bars, inputs) => {
39150
+ const { basis, upper, lower } = bollinger(bars, inputs);
39151
+ const values = basis.map((b, i) => {
39152
+ const u = upper[i];
39153
+ const l = lower[i];
39154
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
39155
+ });
39156
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39157
+ }
39158
+ };
39159
+ var keltner = {
39160
+ type: "keltner-channels",
39161
+ title: "Keltner Channels",
39162
+ shortTitle: "KC",
39163
+ overlay: true,
39164
+ inputs: [
39165
+ lengthInput(20),
39166
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39167
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39168
+ sourceInput(),
39169
+ colorInput()
39170
+ ],
39171
+ compute: (bars, inputs) => {
39172
+ const len = num3(inputs, "length", 20);
39173
+ const mult = num3(inputs, "mult", 2);
39174
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
39175
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
39176
+ const ink = str3(inputs, "color", SERIES_LINE);
39177
+ return {
39178
+ plots: [
39179
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39180
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
39181
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
39182
+ ],
39183
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39184
+ };
39185
+ }
39186
+ };
39187
+ var donchian = {
39188
+ type: "donchian-channels",
39189
+ title: "Donchian Channels",
39190
+ shortTitle: "DC",
39191
+ overlay: true,
39192
+ inputs: [lengthInput(20), colorInput()],
39193
+ compute: (bars, inputs) => {
39194
+ const len = num3(inputs, "length", 20);
39195
+ const upper = highest(highs(bars), len);
39196
+ const lower = lowest(lows(bars), len);
39197
+ const ink = str3(inputs, "color", SERIES_LINE);
39198
+ return {
39199
+ plots: [
39200
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
39201
+ { key: "upper", title: "Upper", values: upper, color: ink },
39202
+ { key: "lower", title: "Lower", values: lower, color: ink }
39203
+ ],
39204
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39205
+ };
39206
+ }
39207
+ };
39208
+ var chandelier = {
39209
+ type: "chandelier-exit",
39210
+ title: "Chandelier Exit",
39211
+ shortTitle: "CE",
39212
+ overlay: true,
39213
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
39214
+ compute: (bars, inputs) => {
39215
+ const len = num3(inputs, "length", 22);
39216
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
39217
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
39218
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
39219
+ return {
39220
+ plots: [
39221
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
39222
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
39223
+ ]
39224
+ };
39225
+ }
39226
+ };
39227
+ var chandeKroll = {
39228
+ type: "chande-kroll-stop",
39229
+ title: "Chande Kroll Stop",
39230
+ shortTitle: "CKS",
39231
+ overlay: true,
39232
+ inputs: [
39233
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39234
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
39235
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
39236
+ ],
39237
+ compute: (bars, inputs) => {
39238
+ const p = num3(inputs, "p", 10);
39239
+ const x = num3(inputs, "x", 1);
39240
+ const q = num3(inputs, "q", 9);
39241
+ const range = map(atr(bars, p), (v) => v * x);
39242
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
39243
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
39244
+ return {
39245
+ plots: [
39246
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
39247
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
39248
+ ]
39249
+ };
39250
+ }
39251
+ };
39252
+ var supertrend = {
39253
+ type: "supertrend",
39254
+ title: "SuperTrend",
39255
+ shortTitle: "SuperTrend",
39256
+ overlay: true,
39257
+ inputs: [
39258
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39259
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
39260
+ ],
39261
+ compute: (bars, inputs) => {
39262
+ const len = num3(inputs, "atrLength", 10);
39263
+ const mult = num3(inputs, "mult", 3);
39264
+ const range = atr(bars, len);
39265
+ const n = bars.length;
39266
+ const st = new Array(n).fill(Number.NaN);
39267
+ const dir = new Array(n).fill(1);
39268
+ let up = Number.NaN;
39269
+ let dn = Number.NaN;
39270
+ let trend = 1;
39271
+ for (let i = 0; i < n; i++) {
39272
+ const r = range[i];
39273
+ if (!Number.isFinite(r)) continue;
39274
+ const b = bars[i];
39275
+ const mid = (b.high + b.low) / 2;
39276
+ const basicUp = mid - mult * r;
39277
+ const basicDn = mid + mult * r;
39278
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
39279
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
39280
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
39281
+ if (trend === 1 && b.close < up) trend = -1;
39282
+ else if (trend === -1 && b.close > dn) trend = 1;
39283
+ dir[i] = trend;
39284
+ st[i] = trend === 1 ? up : dn;
39285
+ }
39286
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
39287
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
39288
+ }
39289
+ };
39290
+ var alligator = {
39291
+ type: "williams-alligator",
39292
+ title: "Williams Alligator",
39293
+ shortTitle: "Alligator",
39294
+ overlay: true,
39295
+ inputs: [
39296
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
39297
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
39298
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
39299
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
39300
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
39301
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
39302
+ ],
39303
+ compute: (bars, inputs) => {
39304
+ const hl2 = sourceValues(bars, "HL2");
39305
+ const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
39306
+ const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
39307
+ const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
39308
+ return {
39309
+ plots: [
39310
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
39311
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
39312
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
39313
+ ]
39314
+ };
39315
+ }
39316
+ };
39317
+ var gator = {
39318
+ type: "gator-oscillator",
39319
+ title: "Gator Oscillator",
39320
+ shortTitle: "Gator Oscillator",
39321
+ overlay: false,
39322
+ inputs: [],
39323
+ compute: (bars) => {
39324
+ const hl2 = sourceValues(bars, "HL2");
39325
+ const jaw = shift3(rma(hl2, 13), 8);
39326
+ const teeth = shift3(rma(hl2, 8), 5);
39327
+ const lips = shift3(rma(hl2, 5), 3);
39328
+ const upper = map(sub(jaw, teeth), Math.abs);
39329
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
39330
+ const colorByExpansion = (v) => v.map((x, i) => {
39331
+ if (!Number.isFinite(x)) return null;
39332
+ const prev3 = i > 0 ? v[i - 1] : Number.NaN;
39333
+ return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
39334
+ });
39335
+ return {
39336
+ plots: [
39337
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
39338
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
39339
+ ]
39340
+ };
39341
+ }
39342
+ };
39343
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
39344
+
39345
+ // src/core/native-indicators/classics/oscillators.ts
39346
+ function guideLevels(low, high, mid) {
39347
+ const levels = [
39348
+ { key: "upper", price: high, color: NEUTRAL },
39349
+ { key: "lower", price: low, color: NEUTRAL }
39350
+ ];
39351
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
39352
+ return levels;
39353
+ }
39354
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
39355
+ function histColors(values) {
39356
+ return values.map((x, i) => {
39357
+ if (!Number.isFinite(x)) return null;
39358
+ const prev3 = i > 0 ? values[i - 1] : Number.NaN;
39359
+ const rising = Number.isFinite(prev3) ? x >= prev3 : true;
39360
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
39361
+ return rising ? `${BEARISH}80` : BEARISH;
39362
+ });
39363
+ }
39364
+ var rsiSpec = {
39365
+ type: "rsi",
39366
+ title: "Relative Strength Index",
39367
+ shortTitle: "RSI",
39368
+ overlay: false,
39369
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39370
+ compute: (bars, inputs) => ({
39371
+ plots: [
39372
+ {
39373
+ key: "rsi",
39374
+ title: "RSI",
39375
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39376
+ color: str3(inputs, "color", SERIES_LINE),
39377
+ width: 2
39378
+ }
39379
+ ],
39380
+ levels: guideLevels(30, 70, 50)
39381
+ })
39382
+ };
39383
+ var stochastic = {
39384
+ type: "stochastic",
39385
+ title: "Stochastic",
39386
+ shortTitle: "Stoch",
39387
+ overlay: false,
39388
+ inputs: [
39389
+ lengthInput(14, "kLength", "%K length"),
39390
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
39391
+ lengthInput(3, "dLength", "%D smoothing", 500)
39392
+ ],
39393
+ compute: (bars, inputs) => {
39394
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
39395
+ const d = sma(k, num3(inputs, "dLength", 3));
39396
+ return {
39397
+ plots: [
39398
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39399
+ { key: "d", title: "%D", values: d, color: WARNING }
39400
+ ],
39401
+ levels: guideLevels(20, 80)
39402
+ };
39403
+ }
39404
+ };
39405
+ var stochasticRsi = {
39406
+ type: "stochastic-rsi",
39407
+ title: "Stochastic RSI",
39408
+ shortTitle: "Stoch RSI",
39409
+ overlay: false,
39410
+ inputs: [
39411
+ lengthInput(14, "rsiLength", "RSI length"),
39412
+ lengthInput(14, "stochLength", "Stochastic length"),
39413
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
39414
+ lengthInput(3, "dLength", "%D smoothing", 500),
39415
+ sourceInput()
39416
+ ],
39417
+ compute: (bars, inputs) => {
39418
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
39419
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
39420
+ const d = sma(k, num3(inputs, "dLength", 3));
39421
+ return {
39422
+ plots: [
39423
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39424
+ { key: "d", title: "%D", values: d, color: WARNING }
39425
+ ],
39426
+ levels: guideLevels(20, 80)
39427
+ };
39428
+ }
39429
+ };
39430
+ var macd = {
39431
+ type: "macd",
39432
+ title: "MACD",
39433
+ overlay: false,
39434
+ inputs: [
39435
+ lengthInput(12, "fastLength", "Fast length"),
39436
+ lengthInput(26, "slowLength", "Slow length"),
39437
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39438
+ sourceInput()
39439
+ ],
39440
+ compute: (bars, inputs) => {
39441
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39442
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
39443
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39444
+ const hist = sub(line, signal);
39445
+ return {
39446
+ plots: [
39447
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39448
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
39449
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39450
+ ],
39451
+ levels: ZERO_LEVEL
39452
+ };
39453
+ }
39454
+ };
39455
+ var ppo = {
39456
+ type: "ppo",
39457
+ title: "Percentage Price Oscillator",
39458
+ shortTitle: "PPO",
39459
+ overlay: false,
39460
+ inputs: [
39461
+ lengthInput(12, "fastLength", "Fast length"),
39462
+ lengthInput(26, "slowLength", "Slow length"),
39463
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39464
+ sourceInput()
39465
+ ],
39466
+ compute: (bars, inputs) => {
39467
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39468
+ const fast = ema(src, num3(inputs, "fastLength", 12));
39469
+ const slow = ema(src, num3(inputs, "slowLength", 26));
39470
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
39471
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39472
+ const hist = sub(line, signal);
39473
+ return {
39474
+ plots: [
39475
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39476
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
39477
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39478
+ ],
39479
+ levels: ZERO_LEVEL
39480
+ };
39481
+ }
39482
+ };
39483
+ var awesome = {
39484
+ type: "awesome-oscillator",
39485
+ title: "Awesome Oscillator",
39486
+ shortTitle: "AO",
39487
+ overlay: false,
39488
+ inputs: [],
39489
+ compute: (bars) => {
39490
+ const hl2 = sourceValues(bars, "HL2");
39491
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
39492
+ const colors = ao.map((x, i) => {
39493
+ if (!Number.isFinite(x)) return null;
39494
+ const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
39495
+ return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
39496
+ });
39497
+ return {
39498
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
39499
+ levels: ZERO_LEVEL
39500
+ };
39501
+ }
39502
+ };
39503
+ var cci = {
39504
+ type: "commodity-channel-index",
39505
+ title: "Commodity Channel Index",
39506
+ shortTitle: "CCI",
39507
+ overlay: false,
39508
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
39509
+ compute: (bars, inputs) => {
39510
+ const len = num3(inputs, "length", 20);
39511
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
39512
+ const basis = sma(src, len);
39513
+ const dev = new Array(src.length).fill(Number.NaN);
39514
+ for (let i = len - 1; i < src.length; i++) {
39515
+ const mean = basis[i];
39516
+ if (!Number.isFinite(mean)) continue;
39517
+ let d = 0;
39518
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
39519
+ dev[i] = d / len;
39520
+ }
39521
+ const values = src.map((x, i) => {
39522
+ const b = basis[i];
39523
+ const d = dev[i];
39524
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
39525
+ });
39526
+ return {
39527
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39528
+ levels: guideLevels(-100, 100, 0)
39529
+ };
39530
+ }
39531
+ };
39532
+ var williamsR = {
39533
+ type: "williams-percent-r",
39534
+ title: "Williams %R",
39535
+ shortTitle: "%R",
39536
+ overlay: false,
39537
+ inputs: [lengthInput(14), colorInput()],
39538
+ compute: (bars, inputs) => {
39539
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
39540
+ return {
39541
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39542
+ levels: guideLevels(-80, -20)
39543
+ };
39544
+ }
39545
+ };
39546
+ var cmo = {
39547
+ type: "chande-momentum-oscillator",
39548
+ title: "Chande Momentum Oscillator",
39549
+ shortTitle: "CMO",
39550
+ overlay: false,
39551
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39552
+ compute: (bars, inputs) => {
39553
+ const len = num3(inputs, "length", 9);
39554
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
39555
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
39556
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
39557
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
39558
+ return {
39559
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39560
+ levels: guideLevels(-50, 50, 0)
39561
+ };
39562
+ }
39563
+ };
39564
+ var connorsRsi = {
39565
+ type: "connors-rsi",
39566
+ title: "Connors RSI",
39567
+ shortTitle: "CRSI",
39568
+ overlay: false,
39569
+ inputs: [
39570
+ lengthInput(3, "rsiLength", "RSI length"),
39571
+ lengthInput(2, "streakLength", "Streak RSI length"),
39572
+ lengthInput(100, "rankLength", "Percent-rank length"),
39573
+ colorInput()
39574
+ ],
39575
+ compute: (bars, inputs) => {
39576
+ const c = closes(bars);
39577
+ const streak = new Array(c.length).fill(0);
39578
+ for (let i = 1; i < c.length; i++) {
39579
+ const d = c[i] - c[i - 1];
39580
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
39581
+ }
39582
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
39583
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
39584
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
39585
+ const values = c.map((_, i) => {
39586
+ const x = a[i];
39587
+ const y = b[i];
39588
+ const z = r[i];
39589
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
39590
+ });
39591
+ return {
39592
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39593
+ levels: guideLevels(20, 80, 50)
39594
+ };
39595
+ }
39596
+ };
39597
+ var fisher = {
39598
+ type: "fisher-transform",
39599
+ title: "Fisher Transform",
39600
+ shortTitle: "Fisher",
39601
+ overlay: false,
39602
+ inputs: [lengthInput(9)],
39603
+ compute: (bars, inputs) => {
39604
+ const len = num3(inputs, "length", 9);
39605
+ const hl2 = sourceValues(bars, "HL2");
39606
+ const hi = highest(hl2, len);
39607
+ const lo = lowest(hl2, len);
39608
+ const n = bars.length;
39609
+ const out = new Array(n).fill(Number.NaN);
39610
+ let value = 0;
39611
+ let fish = 0;
39612
+ for (let i = 0; i < n; i++) {
39613
+ const h = hi[i];
39614
+ const l = lo[i];
39615
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
39616
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
39617
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
39618
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
39619
+ out[i] = fish;
39620
+ }
39621
+ return {
39622
+ plots: [
39623
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
39624
+ { key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
39625
+ ],
39626
+ levels: ZERO_LEVEL
39627
+ };
39628
+ }
39629
+ };
39630
+ var trix = {
39631
+ type: "trix",
39632
+ title: "TRIX",
39633
+ overlay: false,
39634
+ inputs: [lengthInput(18), colorInput()],
39635
+ compute: (bars, inputs) => {
39636
+ const len = num3(inputs, "length", 18);
39637
+ const logClose = map(closes(bars), Math.log);
39638
+ const smooth = ema(ema(ema(logClose, len), len), len);
39639
+ const values = map(change(smooth), (x) => x * 1e4);
39640
+ return {
39641
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39642
+ levels: ZERO_LEVEL
39643
+ };
39644
+ }
39645
+ };
39646
+ function tsiLine(src, long, short) {
39647
+ const mom = change(src);
39648
+ const numer = ema(ema(mom, long), short);
39649
+ const denom = ema(ema(map(mom, Math.abs), long), short);
39650
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
39651
+ }
39652
+ var tsi = {
39653
+ type: "true-strength-index",
39654
+ title: "True Strength Index",
39655
+ shortTitle: "TSI",
39656
+ overlay: false,
39657
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
39658
+ compute: (bars, inputs) => {
39659
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
39660
+ return {
39661
+ plots: [
39662
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
39663
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39664
+ ],
39665
+ levels: ZERO_LEVEL
39666
+ };
39667
+ }
39668
+ };
39669
+ var smiErgodic = {
39670
+ type: "smi-ergodic",
39671
+ title: "SMI Ergodic Oscillator",
39672
+ shortTitle: "SMIE",
39673
+ overlay: false,
39674
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
39675
+ compute: (bars, inputs) => {
39676
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
39677
+ const signal = ema(line, num3(inputs, "signalLength", 5));
39678
+ const osc = sub(line, signal);
39679
+ return {
39680
+ plots: [
39681
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
39682
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
39683
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39684
+ ],
39685
+ levels: ZERO_LEVEL
39686
+ };
39687
+ }
39688
+ };
39689
+ var stc = {
39690
+ type: "schaff-trend-cycle",
39691
+ title: "Schaff Trend Cycle",
39692
+ shortTitle: "Schaff Trend Cycle",
39693
+ overlay: false,
39694
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
39695
+ compute: (bars, inputs) => {
39696
+ const len = num3(inputs, "cycleLength", 10);
39697
+ const src = closes(bars);
39698
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
39699
+ const stochOf = (v) => {
39700
+ const lo = lowest(v, len);
39701
+ const hi = highest(v, len);
39702
+ const out = new Array(v.length).fill(Number.NaN);
39703
+ let prevRaw = Number.NaN;
39704
+ let smoothed = Number.NaN;
39705
+ for (let i = 0; i < v.length; i++) {
39706
+ const x = v[i];
39707
+ const l = lo[i];
39708
+ const h = hi[i];
39709
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
39710
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
39711
+ prevRaw = raw;
39712
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
39713
+ out[i] = smoothed;
39714
+ }
39715
+ return out;
39716
+ };
39717
+ const values = stochOf(stochOf(macdLine));
39718
+ return {
39719
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
39720
+ levels: guideLevels(25, 75)
39721
+ };
39722
+ }
39723
+ };
39724
+ var kst = {
39725
+ type: "know-sure-thing",
39726
+ title: "Know Sure Thing",
39727
+ shortTitle: "KST",
39728
+ overlay: false,
39729
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
39730
+ compute: (bars, inputs) => {
39731
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39732
+ const line = [
39733
+ sma(roc(src, 10), 10),
39734
+ sma(roc(src, 15), 10),
39735
+ sma(roc(src, 20), 10),
39736
+ sma(roc(src, 30), 15)
39737
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
39738
+ return {
39739
+ plots: [
39740
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
39741
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
39742
+ ],
39743
+ levels: ZERO_LEVEL
39744
+ };
39745
+ }
39746
+ };
39747
+ var coppock = {
39748
+ type: "coppock-curve",
39749
+ title: "Coppock Curve",
39750
+ shortTitle: "Coppock",
39751
+ overlay: false,
39752
+ inputs: [
39753
+ lengthInput(10, "wmaLength", "WMA length"),
39754
+ lengthInput(14, "longRoc", "Long RoC length"),
39755
+ lengthInput(11, "shortRoc", "Short RoC length"),
39756
+ sourceInput(),
39757
+ colorInput()
39758
+ ],
39759
+ compute: (bars, inputs) => {
39760
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39761
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
39762
+ return {
39763
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39764
+ levels: ZERO_LEVEL
39765
+ };
39766
+ }
39767
+ };
39768
+ var dpo = {
39769
+ type: "detrended-price-oscillator",
39770
+ title: "Detrended Price Oscillator",
39771
+ shortTitle: "DPO",
39772
+ overlay: false,
39773
+ inputs: [lengthInput(21), colorInput()],
39774
+ compute: (bars, inputs) => {
39775
+ const len = num3(inputs, "length", 21);
39776
+ const barsback = Math.floor(len / 2) + 1;
39777
+ const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
39778
+ return {
39779
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39780
+ levels: ZERO_LEVEL
39781
+ };
39782
+ }
39783
+ };
39784
+ var rocSpec = {
39785
+ type: "rate-of-change",
39786
+ title: "Rate of Change",
39787
+ shortTitle: "ROC",
39788
+ overlay: false,
39789
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39790
+ compute: (bars, inputs) => ({
39791
+ plots: [
39792
+ {
39793
+ key: "roc",
39794
+ title: "RoC",
39795
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
39796
+ color: str3(inputs, "color", SERIES_LINE),
39797
+ width: 2
39798
+ }
39799
+ ],
39800
+ levels: ZERO_LEVEL
39801
+ })
39802
+ };
39803
+ var ultimate = {
39804
+ type: "ultimate-oscillator",
39805
+ title: "Ultimate Oscillator",
39806
+ shortTitle: "UO",
39807
+ overlay: false,
39808
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
39809
+ compute: (bars, inputs) => {
39810
+ const n = bars.length;
39811
+ const bp = new Array(n).fill(Number.NaN);
39812
+ const tr = new Array(n).fill(Number.NaN);
39813
+ for (let i = 0; i < n; i++) {
39814
+ const b = bars[i];
39815
+ const pc = i > 0 ? bars[i - 1].close : b.close;
39816
+ const lo = Math.min(b.low, pc);
39817
+ const hi = Math.max(b.high, pc);
39818
+ bp[i] = b.close - lo;
39819
+ tr[i] = hi - lo;
39820
+ }
39821
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
39822
+ const a7 = avg(num3(inputs, "fastLength", 7));
39823
+ const a14 = avg(num3(inputs, "middleLength", 14));
39824
+ const a28 = avg(num3(inputs, "slowLength", 28));
39825
+ const values = a7.map((x, i) => {
39826
+ const y = a14[i];
39827
+ const z = a28[i];
39828
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
39829
+ });
39830
+ return {
39831
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39832
+ levels: guideLevels(30, 70, 50)
39833
+ };
39834
+ }
39835
+ };
39836
+ var rvgi = {
39837
+ type: "relative-vigor-index",
39838
+ title: "Relative Vigor Index",
39839
+ shortTitle: "RVGI",
39840
+ overlay: false,
39841
+ inputs: [lengthInput(10)],
39842
+ compute: (bars, inputs) => {
39843
+ const len = num3(inputs, "length", 10);
39844
+ const co = swma(sub(closes(bars), opens(bars)));
39845
+ const hl = swma(sub(highs(bars), lows(bars)));
39846
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
39847
+ return {
39848
+ plots: [
39849
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
39850
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
39851
+ ],
39852
+ levels: ZERO_LEVEL
39853
+ };
39854
+ }
39855
+ };
39856
+ var relVolatility = {
39857
+ type: "relative-volatility-index",
39858
+ title: "Relative Volatility Index",
39859
+ shortTitle: "RVI",
39860
+ overlay: false,
39861
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
39862
+ compute: (bars, inputs) => {
39863
+ const smoothLen = num3(inputs, "smoothLength", 14);
39864
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39865
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
39866
+ const d = change(src);
39867
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
39868
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
39869
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
39870
+ return {
39871
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39872
+ levels: guideLevels(20, 80, 50)
39873
+ };
39874
+ }
39875
+ };
39876
+ var bop = {
39877
+ type: "balance-of-power",
39878
+ title: "Balance of Power",
39879
+ shortTitle: "BOP",
39880
+ overlay: false,
39881
+ inputs: [colorInput()],
39882
+ compute: (bars, inputs) => {
39883
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
39884
+ return {
39885
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39886
+ levels: ZERO_LEVEL
39887
+ };
39888
+ }
39889
+ };
39890
+ var elderRay = {
39891
+ type: "elder-ray",
39892
+ title: "Elder Ray",
39893
+ overlay: false,
39894
+ inputs: [lengthInput(13)],
39895
+ compute: (bars, inputs) => {
39896
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
39897
+ return {
39898
+ plots: [
39899
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
39900
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
39901
+ ],
39902
+ levels: ZERO_LEVEL
39903
+ };
39904
+ }
39905
+ };
39906
+ var ttmSqueeze = {
39907
+ type: "ttm-squeeze",
39908
+ title: "TTM Squeeze",
39909
+ overlay: false,
39910
+ inputs: [
39911
+ lengthInput(20),
39912
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39913
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
39914
+ ],
39915
+ compute: (bars, inputs) => {
39916
+ const len = num3(inputs, "length", 20);
39917
+ const c = closes(bars);
39918
+ const basis = sma(c, len);
39919
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
39920
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
39921
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
39922
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
39923
+ const mom = linreg(sub(c, anchor), len);
39924
+ const squeezed = c.map((_, i) => {
39925
+ const d = dev[i];
39926
+ const r = kcRange[i];
39927
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
39928
+ });
39929
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
39930
+ return {
39931
+ plots: [
39932
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
39933
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
39934
+ ]
39935
+ };
39936
+ }
39937
+ };
39938
+ var oscillatorSpecs = [
39939
+ rsiSpec,
39940
+ stochastic,
39941
+ stochasticRsi,
39942
+ macd,
39943
+ ppo,
39944
+ awesome,
39945
+ cci,
39946
+ williamsR,
39947
+ cmo,
39948
+ connorsRsi,
39949
+ fisher,
39950
+ trix,
39951
+ tsi,
39952
+ smiErgodic,
39953
+ stc,
39954
+ kst,
39955
+ coppock,
39956
+ dpo,
39957
+ rocSpec,
39958
+ ultimate,
39959
+ rvgi,
39960
+ relVolatility,
39961
+ bop,
39962
+ elderRay,
39963
+ ttmSqueeze
39964
+ ];
39965
+
39966
+ // src/core/native-indicators/classics/trend.ts
39967
+ var aroon = {
39968
+ type: "aroon",
39969
+ title: "Aroon",
39970
+ overlay: false,
39971
+ inputs: [lengthInput(14)],
39972
+ compute: (bars, inputs) => {
39973
+ const len = num3(inputs, "length", 14);
39974
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
39975
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
39976
+ return {
39977
+ plots: [
39978
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
39979
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
39980
+ ],
39981
+ levels: [
39982
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
39983
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
39984
+ ]
39985
+ };
39986
+ }
39987
+ };
39988
+ var adx = {
39989
+ type: "average-directional-index",
39990
+ title: "Average Directional Index",
39991
+ shortTitle: "ADX",
39992
+ overlay: false,
39993
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
39994
+ compute: (bars, inputs) => {
39995
+ const diLen = num3(inputs, "diLength", 14);
39996
+ const adxLen = num3(inputs, "adxLength", 14);
39997
+ const n = bars.length;
39998
+ const plusDm = new Array(n).fill(Number.NaN);
39999
+ const minusDm = new Array(n).fill(Number.NaN);
40000
+ for (let i = 1; i < n; i++) {
40001
+ const up = bars[i].high - bars[i - 1].high;
40002
+ const dn = bars[i - 1].low - bars[i].low;
40003
+ plusDm[i] = up > dn && up > 0 ? up : 0;
40004
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
40005
+ }
40006
+ const atrLine = rma(trueRange(bars), diLen);
40007
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40008
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40009
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
40010
+ return {
40011
+ plots: [
40012
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
40013
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
40014
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
40015
+ ],
40016
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
40017
+ };
40018
+ }
40019
+ };
40020
+ var vortex = {
40021
+ type: "vortex-indicator",
40022
+ title: "Vortex Indicator",
40023
+ shortTitle: "VI",
40024
+ overlay: false,
40025
+ inputs: [lengthInput(14)],
40026
+ compute: (bars, inputs) => {
40027
+ const len = num3(inputs, "length", 14);
40028
+ const n = bars.length;
40029
+ const vmPlus = new Array(n).fill(Number.NaN);
40030
+ const vmMinus = new Array(n).fill(Number.NaN);
40031
+ for (let i = 1; i < n; i++) {
40032
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
40033
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
40034
+ }
40035
+ const trSum = sum(trueRange(bars), len);
40036
+ return {
40037
+ plots: [
40038
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
40039
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
40040
+ ],
40041
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
40042
+ };
40043
+ }
40044
+ };
40045
+ var trendSpecs = [aroon, adx, vortex];
40046
+
40047
+ // src/core/native-indicators/classics/volatility.ts
40048
+ var atrSpec = {
40049
+ type: "average-true-range",
40050
+ title: "Average True Range",
40051
+ shortTitle: "ATR",
40052
+ overlay: false,
40053
+ inputs: [lengthInput(14), colorInput()],
40054
+ compute: (bars, inputs) => ({
40055
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40056
+ })
40057
+ };
40058
+ var historicalVolatility = {
40059
+ type: "historical-volatility",
40060
+ title: "Historical Volatility",
40061
+ shortTitle: "HV",
40062
+ overlay: false,
40063
+ inputs: [
40064
+ lengthInput(10),
40065
+ { 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)" },
40066
+ colorInput()
40067
+ ],
40068
+ compute: (bars, inputs) => {
40069
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
40070
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
40071
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40072
+ }
40073
+ };
40074
+ var chaikinVolatility = {
40075
+ type: "chaikin-volatility",
40076
+ title: "Chaikin Volatility",
40077
+ shortTitle: "CHV",
40078
+ overlay: false,
40079
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
40080
+ compute: (bars, inputs) => {
40081
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
40082
+ return {
40083
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40084
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40085
+ };
40086
+ }
40087
+ };
40088
+ var massIndex = {
40089
+ type: "mass-index",
40090
+ title: "Mass Index",
40091
+ shortTitle: "MI",
40092
+ overlay: false,
40093
+ inputs: [lengthInput(25), colorInput()],
40094
+ compute: (bars, inputs) => {
40095
+ const range = sub(highs(bars), lows(bars));
40096
+ const single = ema(range, 9);
40097
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
40098
+ return {
40099
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40100
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
40101
+ };
40102
+ }
40103
+ };
40104
+ var standardDeviation = {
40105
+ type: "standard-deviation",
40106
+ title: "Standard Deviation",
40107
+ shortTitle: "StdDev",
40108
+ overlay: false,
40109
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
40110
+ compute: (bars, inputs) => ({
40111
+ plots: [
40112
+ {
40113
+ key: "stdev",
40114
+ title: "StdDev",
40115
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
40116
+ color: str3(inputs, "color", SERIES_LINE),
40117
+ width: 2
40118
+ }
40119
+ ]
40120
+ })
40121
+ };
40122
+ var ulcerIndex = {
40123
+ type: "ulcer-index",
40124
+ title: "Ulcer Index",
40125
+ overlay: false,
40126
+ inputs: [lengthInput(14), colorInput()],
40127
+ compute: (bars, inputs) => {
40128
+ const len = num3(inputs, "length", 14);
40129
+ const c = closes(bars);
40130
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
40131
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
40132
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40133
+ }
40134
+ };
40135
+ var choppiness = {
40136
+ type: "choppiness-index",
40137
+ title: "Choppiness Index",
40138
+ shortTitle: "CHOP",
40139
+ overlay: false,
40140
+ inputs: [lengthInput(14), colorInput()],
40141
+ compute: (bars, inputs) => {
40142
+ const len = num3(inputs, "length", 14);
40143
+ const trSum = sum(trueRange(bars), len);
40144
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
40145
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
40146
+ return {
40147
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40148
+ levels: [
40149
+ { key: "upper", price: 61.8, color: NEUTRAL },
40150
+ { key: "lower", price: 38.2, color: NEUTRAL }
40151
+ ]
40152
+ };
40153
+ }
40154
+ };
40155
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
40156
+
40157
+ // src/core/native-indicators/classics/volume.ts
40158
+ function moneyFlowVolume(bars) {
40159
+ return bars.map((b) => {
40160
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40161
+ if (b.high === b.low) return 0;
40162
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
40163
+ });
40164
+ }
40165
+ var obv = {
40166
+ type: "on-balance-volume",
40167
+ title: "On Balance Volume",
40168
+ shortTitle: "OBV",
40169
+ overlay: false,
40170
+ inputs: [colorInput()],
40171
+ compute: (bars, inputs) => {
40172
+ const vol = volumes(bars);
40173
+ const d = change(closes(bars));
40174
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
40175
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40176
+ }
40177
+ };
40178
+ var accDist = {
40179
+ type: "accumulation-distribution",
40180
+ title: "Accumulation / Distribution",
40181
+ shortTitle: "A/D",
40182
+ overlay: false,
40183
+ inputs: [colorInput()],
40184
+ compute: (bars, inputs) => ({
40185
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40186
+ })
40187
+ };
40188
+ var cmf = {
40189
+ type: "chaikin-money-flow",
40190
+ title: "Chaikin Money Flow",
40191
+ shortTitle: "CMF",
40192
+ overlay: false,
40193
+ inputs: [lengthInput(20), colorInput()],
40194
+ compute: (bars, inputs) => {
40195
+ const len = num3(inputs, "length", 20);
40196
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
40197
+ return {
40198
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40199
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40200
+ };
40201
+ }
40202
+ };
40203
+ var chaikinOscillator = {
40204
+ type: "chaikin-oscillator",
40205
+ title: "Chaikin Oscillator",
40206
+ shortTitle: "Chaikin Osc",
40207
+ overlay: false,
40208
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40209
+ compute: (bars, inputs) => {
40210
+ const ad = cumSum(moneyFlowVolume(bars));
40211
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
40212
+ return {
40213
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40214
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40215
+ };
40216
+ }
40217
+ };
40218
+ var eom = {
40219
+ type: "ease-of-movement",
40220
+ title: "Ease of Movement",
40221
+ shortTitle: "EOM",
40222
+ overlay: false,
40223
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
40224
+ compute: (bars, inputs) => {
40225
+ const div = num3(inputs, "divisor", 1e4);
40226
+ const hl2 = sourceValues(bars, "HL2");
40227
+ const move = change(hl2);
40228
+ const raw = bars.map((b, i) => {
40229
+ const m = move[i];
40230
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
40231
+ return div * m * (b.high - b.low) / b.volume;
40232
+ });
40233
+ return {
40234
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40235
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40236
+ };
40237
+ }
40238
+ };
40239
+ var forceIndex = {
40240
+ type: "force-index",
40241
+ title: "Force Index",
40242
+ shortTitle: "FI",
40243
+ overlay: false,
40244
+ inputs: [lengthInput(13), colorInput()],
40245
+ compute: (bars, inputs) => {
40246
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
40247
+ return {
40248
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40249
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40250
+ };
40251
+ }
40252
+ };
40253
+ var klinger = {
40254
+ type: "klinger-oscillator",
40255
+ title: "Klinger Oscillator",
40256
+ shortTitle: "KVO",
40257
+ overlay: false,
40258
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
40259
+ compute: (bars, inputs) => {
40260
+ const hlc3 = sourceValues(bars, "HLC3");
40261
+ const trend = change(hlc3);
40262
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
40263
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
40264
+ return {
40265
+ plots: [
40266
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
40267
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
40268
+ ],
40269
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40270
+ };
40271
+ }
40272
+ };
40273
+ var mfi = {
40274
+ type: "money-flow-index",
40275
+ title: "Money Flow Index",
40276
+ shortTitle: "MFI",
40277
+ overlay: false,
40278
+ inputs: [lengthInput(14), colorInput()],
40279
+ compute: (bars, inputs) => {
40280
+ const len = num3(inputs, "length", 14);
40281
+ const tp = sourceValues(bars, "HLC3");
40282
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
40283
+ const d = change(tp);
40284
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
40285
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
40286
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
40287
+ return {
40288
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40289
+ levels: [
40290
+ { key: "upper", price: 80, color: NEUTRAL },
40291
+ { key: "lower", price: 20, color: NEUTRAL }
40292
+ ]
40293
+ };
40294
+ }
40295
+ };
40296
+ function volumeIndex(bars, when) {
40297
+ const out = new Array(bars.length).fill(Number.NaN);
40298
+ let index = 1e3;
40299
+ let started = false;
40300
+ for (let i = 1; i < bars.length; i++) {
40301
+ const v = bars[i].volume;
40302
+ const pv = bars[i - 1].volume;
40303
+ const pc = bars[i - 1].close;
40304
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
40305
+ if (started) out[i] = index;
40306
+ continue;
40307
+ }
40308
+ started = true;
40309
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
40310
+ out[i] = index;
40311
+ }
40312
+ return out;
40313
+ }
40314
+ var nvi = {
40315
+ type: "negative-volume-index",
40316
+ title: "Negative Volume Index",
40317
+ shortTitle: "Negative Volume Index",
40318
+ overlay: false,
40319
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40320
+ compute: (bars, inputs) => {
40321
+ const line = volumeIndex(bars, (v, pv) => v < pv);
40322
+ return {
40323
+ plots: [
40324
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40325
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40326
+ ]
40327
+ };
40328
+ }
40329
+ };
40330
+ var pvi = {
40331
+ type: "positive-volume-index",
40332
+ title: "Positive Volume Index",
40333
+ shortTitle: "Positive Volume Index",
40334
+ overlay: false,
40335
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40336
+ compute: (bars, inputs) => {
40337
+ const line = volumeIndex(bars, (v, pv) => v > pv);
40338
+ return {
40339
+ plots: [
40340
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40341
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40342
+ ]
40343
+ };
40344
+ }
40345
+ };
40346
+ var pvt = {
40347
+ type: "price-volume-trend",
40348
+ title: "Price Volume Trend",
40349
+ shortTitle: "PVT",
40350
+ overlay: false,
40351
+ inputs: [colorInput()],
40352
+ compute: (bars, inputs) => {
40353
+ const c = closes(bars);
40354
+ const raw = bars.map((b, i) => {
40355
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40356
+ const pc = c[i - 1];
40357
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
40358
+ });
40359
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40360
+ }
40361
+ };
40362
+ var volumeOscillator = {
40363
+ type: "volume-oscillator",
40364
+ title: "Volume Oscillator",
40365
+ shortTitle: "Vol Osc",
40366
+ overlay: false,
40367
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40368
+ compute: (bars, inputs) => {
40369
+ const vol = volumes(bars);
40370
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
40371
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
40372
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40373
+ return {
40374
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40375
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40376
+ };
40377
+ }
40378
+ };
40379
+ var pvo = {
40380
+ type: "pvo",
40381
+ title: "Percentage Volume Oscillator",
40382
+ shortTitle: "PVO",
40383
+ overlay: false,
40384
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
40385
+ compute: (bars, inputs) => {
40386
+ const vol = volumes(bars);
40387
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
40388
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
40389
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40390
+ const signal = ema(line, num3(inputs, "signalLength", 9));
40391
+ const hist = sub(line, signal);
40392
+ return {
40393
+ plots: [
40394
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
40395
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
40396
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
40397
+ ],
40398
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40399
+ };
40400
+ }
40401
+ };
40402
+ var vfi = {
40403
+ type: "volume-flow-indicator",
40404
+ title: "Volume Flow Indicator",
40405
+ shortTitle: "Volume Flow Indicator",
40406
+ overlay: false,
40407
+ inputs: [
40408
+ lengthInput(130),
40409
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
40410
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
40411
+ lengthInput(3, "smoothLength", "Smoothing length")
40412
+ ],
40413
+ compute: (bars, inputs) => {
40414
+ const len = num3(inputs, "length", 130);
40415
+ const coef = num3(inputs, "coef", 0.2);
40416
+ const volCoef = num3(inputs, "volCoef", 2.5);
40417
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
40418
+ const inter = change(typical);
40419
+ const vInter = stdev(inter, 30);
40420
+ const vol = volumes(bars);
40421
+ const vAve = sma(vol, len);
40422
+ const mf = change(sourceValues(bars, "HLC3"));
40423
+ const raw = bars.map((b, i) => {
40424
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
40425
+ const v = vol[i];
40426
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40427
+ const m = mf[i];
40428
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
40429
+ const capped = Math.min(v, ave * volCoef);
40430
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
40431
+ });
40432
+ const values = bars.map((_, i) => {
40433
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40434
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
40435
+ });
40436
+ const flowSum = sum(raw, len);
40437
+ const line = zip(flowSum, values, (f, a) => f / a);
40438
+ return {
40439
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
40440
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40441
+ };
40442
+ }
40443
+ };
40444
+ var intradayIntensity = {
40445
+ type: "intraday-intensity",
40446
+ title: "Intraday Intensity",
40447
+ shortTitle: "Intraday Intensity",
40448
+ overlay: false,
40449
+ inputs: [lengthInput(21), colorInput()],
40450
+ compute: (bars, inputs) => {
40451
+ const len = num3(inputs, "length", 21);
40452
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
40453
+ return {
40454
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40455
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40456
+ };
40457
+ }
40458
+ };
40459
+ var volumeSpecs = [
40460
+ obv,
40461
+ accDist,
40462
+ cmf,
40463
+ chaikinOscillator,
40464
+ eom,
40465
+ forceIndex,
40466
+ klinger,
40467
+ mfi,
40468
+ nvi,
40469
+ pvi,
40470
+ pvt,
40471
+ volumeOscillator,
40472
+ pvo,
40473
+ vfi,
40474
+ intradayIntensity
40475
+ ];
40476
+
40477
+ // src/core/native-indicators/classics/overlays.ts
40478
+ var DAY_MS2 = 864e5;
40479
+ function periodKey(anchor, time) {
40480
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
40481
+ if (anchor === "Month") {
40482
+ const d = new Date(time);
40483
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
40484
+ }
40485
+ return Math.floor(time / DAY_MS2);
40486
+ }
40487
+ var parabolicSar = {
40488
+ type: "parabolic-sar",
40489
+ title: "Parabolic SAR",
40490
+ shortTitle: "SAR",
40491
+ overlay: true,
40492
+ inputs: [
40493
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40494
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40495
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
40496
+ ],
40497
+ compute: (bars, inputs) => {
40498
+ const start = num3(inputs, "start", 0.02);
40499
+ const inc = num3(inputs, "increment", 0.02);
40500
+ const max2 = num3(inputs, "maximum", 0.2);
40501
+ const n = bars.length;
40502
+ const values = new Array(n).fill(Number.NaN);
40503
+ const colors = new Array(n).fill(null);
40504
+ if (n >= 2) {
40505
+ let long = bars[1].close >= bars[0].close;
40506
+ let sar = long ? bars[0].low : bars[0].high;
40507
+ let ep = long ? bars[0].high : bars[0].low;
40508
+ let af = start;
40509
+ for (let i = 1; i < n; i++) {
40510
+ const b = bars[i];
40511
+ sar += af * (ep - sar);
40512
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
40513
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
40514
+ const reversed = long ? b.low < sar : b.high > sar;
40515
+ if (reversed) {
40516
+ long = !long;
40517
+ sar = ep;
40518
+ ep = long ? b.high : b.low;
40519
+ af = start;
40520
+ } else if (long && b.high > ep) {
40521
+ ep = b.high;
40522
+ af = Math.min(max2, af + inc);
40523
+ } else if (!long && b.low < ep) {
40524
+ ep = b.low;
40525
+ af = Math.min(max2, af + inc);
40526
+ }
40527
+ values[i] = sar;
40528
+ colors[i] = long ? BULLISH : BEARISH;
40529
+ }
40530
+ }
40531
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
40532
+ }
40533
+ };
40534
+ var vwap = {
40535
+ type: "vwap",
40536
+ title: "Volume Weighted Average Price",
40537
+ shortTitle: "VWAP",
40538
+ overlay: true,
40539
+ inputs: [
40540
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
40541
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
40542
+ colorInput(INFO)
40543
+ ],
40544
+ compute: (bars, inputs) => {
40545
+ const anchor = str3(inputs, "anchor", "Day");
40546
+ const source = str3(inputs, "source", "HLC3");
40547
+ const values = new Array(bars.length).fill(Number.NaN);
40548
+ let period = Number.NaN;
40549
+ let cumPV = 0;
40550
+ let cumV = 0;
40551
+ for (let i = 0; i < bars.length; i++) {
40552
+ const b = bars[i];
40553
+ const key = periodKey(anchor, b.time);
40554
+ if (key !== period) {
40555
+ period = key;
40556
+ cumPV = 0;
40557
+ cumV = 0;
40558
+ }
40559
+ const v = b.volume;
40560
+ if (v != null && Number.isFinite(v) && v > 0) {
40561
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
40562
+ cumPV += tp * v;
40563
+ cumV += v;
40564
+ }
40565
+ if (cumV > 0) values[i] = cumPV / cumV;
40566
+ }
40567
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
40568
+ }
40569
+ };
40570
+ var pivotPoints = {
40571
+ type: "pivot-points",
40572
+ title: "Pivot Points",
40573
+ shortTitle: "Pivots",
40574
+ overlay: true,
40575
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
40576
+ compute: (bars, inputs) => {
40577
+ const anchor = str3(inputs, "anchor", "Day");
40578
+ const aggs = /* @__PURE__ */ new Map();
40579
+ for (const b of bars) {
40580
+ const key = periodKey(anchor, b.time);
40581
+ const a = aggs.get(key);
40582
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
40583
+ else {
40584
+ a.high = Math.max(a.high, b.high);
40585
+ a.low = Math.min(a.low, b.low);
40586
+ a.close = b.close;
40587
+ }
40588
+ }
40589
+ const n = bars.length;
40590
+ const mk = () => new Array(n).fill(Number.NaN);
40591
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
40592
+ for (let i = 0; i < n; i++) {
40593
+ const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
40594
+ if (!prev3) continue;
40595
+ const p = (prev3.high + prev3.low + prev3.close) / 3;
40596
+ levels.p[i] = p;
40597
+ levels.r1[i] = 2 * p - prev3.low;
40598
+ levels.s1[i] = 2 * p - prev3.high;
40599
+ levels.r2[i] = p + (prev3.high - prev3.low);
40600
+ levels.s2[i] = p - (prev3.high - prev3.low);
40601
+ levels.r3[i] = prev3.high + 2 * (p - prev3.low);
40602
+ levels.s3[i] = prev3.low - 2 * (prev3.high - p);
40603
+ }
40604
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
40605
+ return {
40606
+ plots: [
40607
+ plot("p", "P", WARNING),
40608
+ plot("r1", "R1", BEARISH),
40609
+ plot("s1", "S1", BULLISH),
40610
+ plot("r2", "R2", BEARISH),
40611
+ plot("s2", "S2", BULLISH),
40612
+ plot("r3", "R3", BEARISH),
40613
+ plot("s3", "S3", BULLISH)
40614
+ ]
40615
+ };
40616
+ }
40617
+ };
40618
+ var fiftyTwoWeek = {
40619
+ type: "52-week-high-low",
40620
+ title: "52 Week High/Low",
40621
+ shortTitle: "52W H/L",
40622
+ overlay: true,
40623
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
40624
+ compute: (bars, inputs) => {
40625
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
40626
+ const n = bars.length;
40627
+ const hi = new Array(n).fill(Number.NaN);
40628
+ const lo = new Array(n).fill(Number.NaN);
40629
+ const maxIdx = [];
40630
+ const minIdx = [];
40631
+ let from = 0;
40632
+ for (let i = 0; i < n; i++) {
40633
+ const b = bars[i];
40634
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
40635
+ maxIdx.push(i);
40636
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
40637
+ minIdx.push(i);
40638
+ while (bars[from].time < b.time - span) from += 1;
40639
+ while (maxIdx[0] < from) maxIdx.shift();
40640
+ while (minIdx[0] < from) minIdx.shift();
40641
+ hi[i] = bars[maxIdx[0]].high;
40642
+ lo[i] = bars[minIdx[0]].low;
40643
+ }
40644
+ return {
40645
+ plots: [
40646
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
40647
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
40648
+ ]
40649
+ };
40650
+ }
40651
+ };
40652
+ var zigzag = {
40653
+ type: "zigzag",
40654
+ title: "ZigZag",
40655
+ overlay: true,
40656
+ inputs: [
40657
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
40658
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
40659
+ colorInput()
40660
+ ],
40661
+ compute: (bars, inputs) => {
40662
+ const dev = num3(inputs, "deviation", 5) / 100;
40663
+ const depth = num3(inputs, "depth", 10);
40664
+ const pivots = [];
40665
+ if (bars.length > 1) {
40666
+ let up = bars[1].close >= bars[0].close;
40667
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
40668
+ for (let i = 1; i < bars.length; i++) {
40669
+ const b = bars[i];
40670
+ if (up) {
40671
+ if (b.high >= ext.price) ext = { i, price: b.high };
40672
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40673
+ pivots.push(ext);
40674
+ up = false;
40675
+ ext = { i, price: b.low };
40676
+ }
40677
+ } else if (b.low <= ext.price) {
40678
+ ext = { i, price: b.low };
40679
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40680
+ pivots.push(ext);
40681
+ up = true;
40682
+ ext = { i, price: b.high };
40683
+ }
40684
+ }
40685
+ pivots.push(ext);
40686
+ }
40687
+ return {
40688
+ plots: [],
40689
+ polylines: [
40690
+ {
40691
+ key: "zigzag",
40692
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
40693
+ curved: false,
40694
+ closed: false,
40695
+ lineColor: str3(inputs, "color", SERIES_LINE),
40696
+ lineWidth: 2,
40697
+ lineStyle: "solid",
40698
+ arrowLeft: false,
40699
+ arrowRight: false
40700
+ }
40701
+ ]
40702
+ };
40703
+ }
40704
+ };
40705
+ var williamsFractal = {
40706
+ type: "williams-fractal",
40707
+ title: "Williams Fractal",
40708
+ shortTitle: "Fractals",
40709
+ overlay: true,
40710
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
40711
+ compute: (bars, inputs) => {
40712
+ const p = num3(inputs, "periods", 2);
40713
+ const markers = [];
40714
+ const isExtreme = (i, pick, better) => {
40715
+ const v = pick(bars[i]);
40716
+ for (let k = i - p; k <= i + p; k++) {
40717
+ if (k === i) continue;
40718
+ if (!better(v, pick(bars[k]))) return false;
40719
+ }
40720
+ return true;
40721
+ };
40722
+ for (let i = p; i < bars.length - p; i++) {
40723
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
40724
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
40725
+ }
40726
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
40727
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
40728
+ }
40729
+ }
40730
+ return { plots: [], markers };
40731
+ }
40732
+ };
40733
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
40734
+
40735
+ // src/core/native-indicators/classics/index.ts
40736
+ var classicSpecs = [
40737
+ ...averageSpecs,
40738
+ ...bandSpecs,
40739
+ ...oscillatorSpecs,
40740
+ ...trendSpecs,
40741
+ ...volatilitySpecs,
40742
+ ...volumeSpecs,
40743
+ ...overlaySpecs
40744
+ ];
40745
+ function registerClassicIndicators() {
40746
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
40747
+ }
40748
+
38373
40749
  // src/Vela.ts
38374
40750
  var Vela = class {
38375
40751
  constructor(container, options = {}, deps = {}) {
38376
40752
  registerBuiltinChartTypes();
38377
40753
  registerVolume();
38378
40754
  registerVpvr();
40755
+ registerClassicIndicators();
38379
40756
  const element = resolveElement(container);
38380
40757
  const theme = resolveTheme(options.theme);
38381
40758
  let animZoom = true;
@@ -38450,25 +40827,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38450
40827
  }
38451
40828
  /**
38452
40829
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
38453
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
38454
- * events) and is single-instance per type (a second call returns the existing handle). Returns
38455
- * a fail-soft handle for an unregistered type. Native renderer only.
40830
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
40831
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
40832
+ * call returns the existing handle; the classic studies allow several instances, so each call
40833
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
38456
40834
  */
38457
40835
  addNativeIndicator(type, options) {
38458
40836
  return this.orchestrator.addNativeIndicator(type, options);
38459
40837
  }
38460
40838
  /**
38461
40839
  * The catalog of built-in native indicators with their live state on this chart — each entry's
38462
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
38463
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
38464
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
38465
- * because support may probe the provider (a type may need data the symbol lacks).
40840
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
40841
+ * and whether it allows several instances (`multiInstance`; otherwise a second
40842
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
40843
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
40844
+ * provider (a type may need data the symbol lacks).
38466
40845
  */
38467
40846
  availableNativeIndicators() {
38468
40847
  return this.orchestrator.availableNativeIndicators();
38469
40848
  }
38470
40849
  /**
38471
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
40850
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
40851
+ * multi-instance type repeats) — the synchronous slice of
38472
40852
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
38473
40853
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
38474
40854
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -38835,12 +41215,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38835
41215
  return Math.round(Math.min(Math.max(px, lo), hi));
38836
41216
  }
38837
41217
 
38838
- // src/core/model/identity.ts
38839
- function stableSeriesId(parts3) {
38840
- const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
38841
- return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
38842
- }
38843
-
38844
41218
  // src/data/symbol-base.ts
38845
41219
  function baseOf(d) {
38846
41220
  const fromDesc = d.description?.split("/")[0]?.trim();
@@ -38942,9 +41316,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38942
41316
  for (const b of bars) byTime.set(b.time, b);
38943
41317
  return [...byTime.values()].sort((a, b) => a.time - b.time);
38944
41318
  }
38945
- function aggregate(sub, bucketMs) {
41319
+ function aggregate(sub2, bucketMs) {
38946
41320
  const buckets = /* @__PURE__ */ new Map();
38947
- for (const b of sub) {
41321
+ for (const b of sub2) {
38948
41322
  const key = Math.floor(b.time / bucketMs) * bucketMs;
38949
41323
  const cur = buckets.get(key);
38950
41324
  if (!cur) {
@@ -38996,8 +41370,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38996
41370
  }
38997
41371
  const ratio = targetMin / subMin;
38998
41372
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
38999
- const sub = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
39000
- const agg = aggregate(sub, targetMin * 6e4);
41373
+ const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
41374
+ const agg = aggregate(sub2, targetMin * 6e4);
39001
41375
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39002
41376
  } catch (e) {
39003
41377
  console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39328,9 +41702,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39328
41702
  for (const b of bars) byTime.set(b.time, b);
39329
41703
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39330
41704
  }
39331
- function aggregate2(sub, bucketMs) {
41705
+ function aggregate2(sub2, bucketMs) {
39332
41706
  const buckets = /* @__PURE__ */ new Map();
39333
- for (const b of sub) {
41707
+ for (const b of sub2) {
39334
41708
  const key = Math.floor(b.time / bucketMs) * bucketMs;
39335
41709
  const cur = buckets.get(key);
39336
41710
  if (!cur) {
@@ -39381,8 +41755,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39381
41755
  }
39382
41756
  const ratio = targetMin / subMin;
39383
41757
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
39384
- const sub = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
39385
- const agg = aggregate2(sub, targetMin * 6e4);
41758
+ const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
41759
+ const agg = aggregate2(sub2, targetMin * 6e4);
39386
41760
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39387
41761
  } catch (e) {
39388
41762
  console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39734,15 +42108,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39734
42108
  for (const b of bars) byTime.set(b.time, b);
39735
42109
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39736
42110
  }
39737
- function aggregate3(sub, bucketMs) {
39738
- return aggregateBy(sub, (t) => Math.floor(t / bucketMs) * bucketMs);
42111
+ function aggregate3(sub2, bucketMs) {
42112
+ return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
39739
42113
  }
39740
- function aggregateCalendar(sub, unit) {
39741
- return aggregateBy(sub, unit === "W" ? weekStartUTC : monthStartUTC);
42114
+ function aggregateCalendar(sub2, unit) {
42115
+ return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
39742
42116
  }
39743
- function aggregateBy(sub, keyMs) {
42117
+ function aggregateBy(sub2, keyMs) {
39744
42118
  const buckets = /* @__PURE__ */ new Map();
39745
- for (const b of sub) {
42119
+ for (const b of sub2) {
39746
42120
  const key = keyMs(b.time);
39747
42121
  const cur = buckets.get(key);
39748
42122
  if (!cur) {
@@ -39796,8 +42170,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39796
42170
  if (tf === "W" || tf === "M") {
39797
42171
  const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
39798
42172
  const subRange2 = range.from != null ? range : { ...range, limit: range.limit != null ? Math.ceil(range.limit * span / MS_PER_DAY) + 31 : void 0 };
39799
- const sub2 = await this.fetchCandles(product, 86400, subRange2);
39800
- return clampLimit(aggregateCalendar(sub2, tf), range.limit);
42173
+ const sub3 = await this.fetchCandles(product, 86400, subRange2);
42174
+ return clampLimit(aggregateCalendar(sub3, tf), range.limit);
39801
42175
  }
39802
42176
  const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
39803
42177
  const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
@@ -39807,8 +42181,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39807
42181
  }
39808
42182
  const ratio = targetMin / subMin;
39809
42183
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
39810
- const sub = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
39811
- return clampLimit(aggregate3(sub, targetMin * 6e4), range.limit);
42184
+ const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
42185
+ return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
39812
42186
  } catch (e) {
39813
42187
  console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
39814
42188
  return [];
@@ -40078,6 +42452,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
40078
42452
  exports.BarStore = BarStore;
40079
42453
  exports.BinanceProvider = BinanceProvider;
40080
42454
  exports.CATEGORICAL = CATEGORICAL;
42455
+ exports.CHIP_PLATE = CHIP_PLATE;
40081
42456
  exports.CROSSHAIR = CROSSHAIR;
40082
42457
  exports.CachingDataFeed = CachingDataFeed;
40083
42458
  exports.CoinbaseProvider = CoinbaseProvider;