@luxalgo/vela 0.6.15 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +12 -1
  2. package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
  3. package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
  4. package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
  5. package/dist/chunk-FE6V2YAQ.js +1 -0
  6. package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
  7. package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
  8. package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
  9. package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
  10. package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
  11. package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
  12. package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
  13. package/dist/index.cjs +2746 -355
  14. package/dist/index.d.cts +15 -6
  15. package/dist/index.d.ts +15 -6
  16. package/dist/index.js +5 -5
  17. package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
  18. package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
  19. package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
  20. package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
  21. package/dist/plugin.cjs +1 -0
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +3 -3
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
  32. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
  33. package/dist/ui.cjs +23 -15
  34. package/dist/ui.d.cts +1 -1
  35. package/dist/ui.d.ts +1 -1
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +2600 -209
  38. package/dist/vela.global.min.js +52 -55
  39. package/dist/widget.cjs +2663 -241
  40. package/dist/widget.d.cts +6 -6
  41. package/dist/widget.d.ts +6 -6
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +2663 -241
  44. package/dist/workspace.d.cts +25 -14
  45. package/dist/workspace.d.ts +25 -14
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. package/dist/chunk-H26BEHF4.js +0 -7
@@ -1,6 +1,23 @@
1
1
  var Vela = (function (exports) {
2
2
  'use strict';
3
3
 
4
+ // src/core/options.ts
5
+ var LIVE_BAR_EASE_DEFAULT_MS = 90;
6
+ var LIVE_BAR_EASE_MAX_MS = 1e3;
7
+ function resolveLiveBarEaseMs(value) {
8
+ if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
9
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
10
+ return Math.min(value, LIVE_BAR_EASE_MAX_MS);
11
+ }
12
+ function resolveAnimations(animations) {
13
+ if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
14
+ return {
15
+ animZoom: animations?.zoom ?? true,
16
+ animPan: animations?.pan ?? true,
17
+ animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
18
+ };
19
+ }
20
+
4
21
  // src/core/model/series.ts
5
22
  function isLineLikeSeries(spec) {
6
23
  return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
@@ -111,7 +128,7 @@ var Vela = (function (exports) {
111
128
 
112
129
  // src/core/engine/IndicatorHandleImpl.ts
113
130
  var IndicatorHandleImpl = class {
114
- constructor(id, title, controller, source) {
131
+ constructor(id, title, controller, source, nativeType) {
115
132
  this.controller = controller;
116
133
  this.schema = [];
117
134
  this.propsSchema = [];
@@ -120,6 +137,7 @@ var Vela = (function (exports) {
120
137
  this.id = id;
121
138
  this.title = title;
122
139
  if (source !== void 0) this.source = source;
140
+ if (nativeType !== void 0) this.nativeType = nativeType;
123
141
  }
124
142
  get inputs() {
125
143
  return this.schema;
@@ -210,24 +228,24 @@ var Vela = (function (exports) {
210
228
  }
211
229
  function inspectModels(models) {
212
230
  const indicators = models.map(summarizeModel);
213
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
231
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
214
232
  return {
215
233
  indicators,
216
234
  totals: {
217
235
  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)
236
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
237
+ fills: sum2((s) => s.fills),
238
+ backgrounds: sum2((s) => s.backgrounds),
239
+ priceLines: sum2((s) => s.priceLines),
240
+ lines: sum2((s) => s.lines),
241
+ boxes: sum2((s) => s.boxes),
242
+ labels: sum2((s) => s.labels),
243
+ polylines: sum2((s) => s.polylines),
244
+ linefills: sum2((s) => s.linefills),
245
+ tables: sum2((s) => s.tables),
246
+ barColors: sum2((s) => s.barColors),
247
+ trades: sum2((s) => s.trades),
248
+ forcedOverlay: sum2((s) => s.forcedOverlay)
231
249
  }
232
250
  };
233
251
  }
@@ -550,6 +568,7 @@ var Vela = (function (exports) {
550
568
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
551
569
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
552
570
  registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
571
+ registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
553
572
  registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
554
573
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
555
574
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
@@ -6707,8 +6726,8 @@ var Vela = (function (exports) {
6707
6726
  this.port?.setExternalGhost?.(doc);
6708
6727
  }
6709
6728
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
6710
- setToolShortcuts(map) {
6711
- this.port?.setToolShortcuts?.(map);
6729
+ setToolShortcuts(map2) {
6730
+ this.port?.setToolShortcuts?.(map2);
6712
6731
  }
6713
6732
  // ── programmatic CRUD (facade-facing) ── each is one undo step
6714
6733
  add(type, init = {}) {
@@ -8432,16 +8451,20 @@ var Vela = (function (exports) {
8432
8451
  /**
8433
8452
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
8434
8453
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
8435
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
8436
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
8454
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
8455
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
8456
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
8457
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
8437
8458
  */
8438
8459
  addNativeIndicator(type, options = {}) {
8439
- const existing = this.registry.all().find((r) => r.native?.type === type);
8440
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
8441
8460
  const descriptor = getNativeIndicator(type);
8461
+ if (!descriptor?.multiInstance) {
8462
+ const existing = this.registry.all().find((r) => r.native?.type === type);
8463
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8464
+ }
8442
8465
  const id = this.registry.nextId("native");
8443
8466
  const title = descriptor?.title ?? type;
8444
- const handle = new IndicatorHandleImpl(id, title, this);
8467
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8445
8468
  this.handles.set(id, handle);
8446
8469
  if (!descriptor) {
8447
8470
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -8453,16 +8476,18 @@ var Vela = (function (exports) {
8453
8476
  void this.startNativeIndicator(id, handle);
8454
8477
  return handle;
8455
8478
  }
8456
- /**
8457
- * The catalog of registered native-indicator types with their live state on THIS chart: each
8458
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
8459
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
8460
- * because a descriptor's `isSupported` may probe the provider for a required capability.
8461
- */
8462
- /** The native types present on the chart — SYNC (registry state; no support probe). */
8479
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
8480
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
8463
8481
  presentNativeIndicators() {
8464
8482
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
8465
8483
  }
8484
+ /**
8485
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
8486
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
8487
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
8488
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
8489
+ * probe the provider for a required capability.
8490
+ */
8466
8491
  async availableNativeIndicators() {
8467
8492
  const symbol = this.qualifiedSymbol();
8468
8493
  const present = new Set(this.presentNativeIndicators());
@@ -8472,7 +8497,8 @@ var Vela = (function (exports) {
8472
8497
  title: d.title,
8473
8498
  supported: await this.isNativeSupported(d, symbol),
8474
8499
  present: present.has(d.type),
8475
- beta: d.beta
8500
+ beta: d.beta,
8501
+ ...d.multiInstance ? { multiInstance: true } : {}
8476
8502
  }))
8477
8503
  );
8478
8504
  }
@@ -9756,7 +9782,7 @@ var Vela = (function (exports) {
9756
9782
 
9757
9783
  // src/data/MultiProviderFeed.ts
9758
9784
  function intervalMs(timeframe) {
9759
- const map = {
9785
+ const map2 = {
9760
9786
  "1m": 6e4,
9761
9787
  "3m": 18e4,
9762
9788
  "5m": 3e5,
@@ -9778,7 +9804,7 @@ var Vela = (function (exports) {
9778
9804
  W: 6048e5,
9779
9805
  M: 2592e6
9780
9806
  };
9781
- return map[timeframe] ?? 36e5;
9807
+ return map2[timeframe] ?? 36e5;
9782
9808
  }
9783
9809
  var MultiProviderFeed = class {
9784
9810
  constructor(store = sharedBarStore) {
@@ -10165,8 +10191,8 @@ var Vela = (function (exports) {
10165
10191
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
10166
10192
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
10167
10193
  * platform formatting, so hints always match the host's actual bindings. */
10168
- setToolShortcuts(map) {
10169
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
10194
+ setToolShortcuts(map2) {
10195
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
10170
10196
  return this;
10171
10197
  }
10172
10198
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -10493,6 +10519,23 @@ var Vela = (function (exports) {
10493
10519
  clear();
10494
10520
  };
10495
10521
  }
10522
+ function chromeHint(text, opts) {
10523
+ const px = opts.size ?? 16;
10524
+ const el = document.createElement("span");
10525
+ el.className = "vela-hint";
10526
+ el.setAttribute("role", "img");
10527
+ el.setAttribute("aria-label", text);
10528
+ el.innerHTML = iconAt("info", px);
10529
+ el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
10530
+ const hover = (on) => {
10531
+ el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
10532
+ el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
10533
+ };
10534
+ el.addEventListener("pointerenter", () => hover(true));
10535
+ el.addEventListener("pointerleave", () => hover(false));
10536
+ const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
10537
+ return { el, dispose };
10538
+ }
10496
10539
 
10497
10540
  // node_modules/@zag-js/vanilla/dist/chunk-QZ7TP4HQ.mjs
10498
10541
  var __defProp = Object.defineProperty;
@@ -10631,7 +10674,7 @@ var Vela = (function (exports) {
10631
10674
  }
10632
10675
 
10633
10676
  // node_modules/@zag-js/core/dist/merge-props.mjs
10634
- var clsx = (...args) => args.map((str3) => str3?.trim?.()).filter(Boolean).join(" ");
10677
+ var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
10635
10678
  var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
10636
10679
  var serialize = (style) => {
10637
10680
  const res = {};
@@ -10855,12 +10898,12 @@ var Vela = (function (exports) {
10855
10898
  return {
10856
10899
  and: (...guards) => {
10857
10900
  return function andGuard(params) {
10858
- return guards.every((str3) => params.guard(str3));
10901
+ return guards.every((str4) => params.guard(str4));
10859
10902
  };
10860
10903
  },
10861
10904
  or: (...guards) => {
10862
10905
  return function orGuard(params) {
10863
- return guards.some((str3) => params.guard(str3));
10906
+ return guards.some((str4) => params.guard(str4));
10864
10907
  };
10865
10908
  },
10866
10909
  not: (guard) => {
@@ -11421,9 +11464,9 @@ var Vela = (function (exports) {
11421
11464
  const { attributes, callback: fn } = options;
11422
11465
  const win = node.ownerDocument.defaultView || window;
11423
11466
  const obs = new win.MutationObserver((changes) => {
11424
- for (const change of changes) {
11425
- if (change.type === "attributes" && change.attributeName && attributes.includes(change.attributeName)) {
11426
- fn(change);
11467
+ for (const change2 of changes) {
11468
+ if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
11469
+ fn(change2);
11427
11470
  }
11428
11471
  }
11429
11472
  });
@@ -11497,7 +11540,7 @@ var Vela = (function (exports) {
11497
11540
  }
11498
11541
 
11499
11542
  // node_modules/@zag-js/dom-query/dist/searchable.mjs
11500
- var sanitize = (str3) => str3.split("").map((char) => {
11543
+ var sanitize = (str4) => str4.split("").map((char) => {
11501
11544
  const code = char.charCodeAt(0);
11502
11545
  if (code > 0 && code < 128) return char;
11503
11546
  if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
@@ -12197,10 +12240,10 @@ var Vela = (function (exports) {
12197
12240
  fn?.(this.getParams());
12198
12241
  }
12199
12242
  });
12200
- __publicField(this, "guard", (str3) => {
12201
- if (isFunction(str3)) return str3(this.getParams());
12202
- const fn = this.machine.implementations?.guards?.[str3];
12203
- if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str3)}"`);
12243
+ __publicField(this, "guard", (str4) => {
12244
+ if (isFunction(str4)) return str4(this.getParams());
12245
+ const fn = this.machine.implementations?.guards?.[str4];
12246
+ if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
12204
12247
  return fn?.(this.getParams());
12205
12248
  });
12206
12249
  __publicField(this, "effect", (keys) => {
@@ -16978,19 +17021,19 @@ var Vela = (function (exports) {
16978
17021
  }
16979
17022
  setItems(items) {
16980
17023
  this.items = items;
16981
- for (const sub of this.subs.values()) sub.destroy();
17024
+ for (const sub2 of this.subs.values()) sub2.destroy();
16982
17025
  this.subs.clear();
16983
17026
  this.build();
16984
17027
  const api = this.api;
16985
- for (const sub of this.subs.values()) {
16986
- api.setChild(sub.handle.service);
16987
- sub.api.setParent(this.handle.service);
17028
+ for (const sub2 of this.subs.values()) {
17029
+ api.setChild(sub2.handle.service);
17030
+ sub2.api.setParent(this.handle.service);
16988
17031
  }
16989
17032
  this.handle.flush();
16990
17033
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
16991
17034
  }
16992
17035
  destroy() {
16993
- for (const sub of this.subs.values()) sub.destroy();
17036
+ for (const sub2 of this.subs.values()) sub2.destroy();
16994
17037
  this.subs.clear();
16995
17038
  this.handle.stop();
16996
17039
  this.positioner.remove();
@@ -17004,9 +17047,9 @@ var Vela = (function (exports) {
17004
17047
  for (const li of this.list.children) {
17005
17048
  const id = li.dataset.veiId;
17006
17049
  if (!id) continue;
17007
- const sub = this.subs.get(id);
17008
- if (sub) {
17009
- spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
17050
+ const sub2 = this.subs.get(id);
17051
+ if (sub2) {
17052
+ spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
17010
17053
  continue;
17011
17054
  }
17012
17055
  const item = this.byId(id);
@@ -17083,7 +17126,7 @@ var Vela = (function (exports) {
17083
17126
  const arrow3 = iconEl("chevron-right", doc);
17084
17127
  arrow3.classList.add("vela-menu-arrow");
17085
17128
  li.appendChild(arrow3);
17086
- const sub = new _Surface(doc, {
17129
+ const sub2 = new _Surface(doc, {
17087
17130
  host: this.host,
17088
17131
  placement: "right-start",
17089
17132
  onSelect: this.onSelect,
@@ -17091,8 +17134,8 @@ var Vela = (function (exports) {
17091
17134
  checkmarks: this.checkmarks,
17092
17135
  id: `${this.mid}--${item.id}`
17093
17136
  });
17094
- sub.setItems(item.submenu ?? []);
17095
- this.subs.set(item.id, sub);
17137
+ sub2.setItems(item.submenu ?? []);
17138
+ this.subs.set(item.id, sub2);
17096
17139
  }
17097
17140
  this.list.appendChild(li);
17098
17141
  }
@@ -20047,8 +20090,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
20047
20090
  };
20048
20091
  const recentRow = document.createElement("div");
20049
20092
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
20093
+ const recentSwatches = document.createElement("div");
20094
+ recentSwatches.style.cssText = "display:contents;";
20095
+ const add = document.createElement("label");
20096
+ 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;`;
20097
+ add.textContent = "+";
20098
+ const customInput = document.createElement("input");
20099
+ customInput.type = "color";
20100
+ customInput.value = curHex;
20101
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
20102
+ customInput.addEventListener("input", () => previewHex(customInput.value));
20103
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
20104
+ add.appendChild(customInput);
20105
+ recentRow.append(recentSwatches, add);
20050
20106
  const renderRecents = () => {
20051
- recentRow.replaceChildren();
20107
+ recentSwatches.replaceChildren();
20052
20108
  for (const c of recents) {
20053
20109
  const sw = document.createElement("button");
20054
20110
  sw.type = "button";
@@ -20057,18 +20113,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20057
20113
  e.stopPropagation();
20058
20114
  pickHex(c);
20059
20115
  });
20060
- recentRow.appendChild(sw);
20116
+ recentSwatches.appendChild(sw);
20061
20117
  }
20062
- const add = document.createElement("label");
20063
- 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;`;
20064
- add.textContent = "+";
20065
- const input = document.createElement("input");
20066
- input.type = "color";
20067
- input.value = curHex;
20068
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
20069
- input.addEventListener("input", () => pickHex(input.value, true));
20070
- add.appendChild(input);
20071
- recentRow.appendChild(add);
20072
20118
  };
20073
20119
  const opLabel = document.createElement("div");
20074
20120
  opLabel.textContent = "Opacity";
@@ -20108,14 +20154,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
20108
20154
  function emit() {
20109
20155
  onChange(combineColor(curHex, curAlpha));
20110
20156
  }
20111
- function pickHex(hex, custom = false) {
20157
+ function previewHex(hex) {
20112
20158
  curHex = splitColor(hex).hex6;
20113
- addRecent(curHex);
20114
20159
  paintSelection();
20115
20160
  paintOpacity();
20116
- if (custom) renderRecents();
20117
20161
  emit();
20118
20162
  }
20163
+ function pickHex(hex, custom = false) {
20164
+ previewHex(hex);
20165
+ addRecent(curHex);
20166
+ customInput.value = curHex;
20167
+ if (custom) renderRecents();
20168
+ }
20119
20169
  renderRecents();
20120
20170
  paintSelection();
20121
20171
  paintOpacity();
@@ -21067,14 +21117,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
21067
21117
  buildTextField(id, current, emit) {
21068
21118
  return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
21069
21119
  }
21070
- /** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
21120
+ /** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
21071
21121
  infoButton(tooltip) {
21072
- const b = document.createElement("span");
21073
- b.textContent = "i";
21074
- this.dialogTips.push(attachChromeTooltip(b, { host: this.host.container, theme: () => this.host.theme(), text: () => tooltip, wrap: true, delayMs: 250 }));
21075
- b.className = "vela-ind-hint";
21076
- b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
21077
- return b;
21122
+ const { el, dispose } = chromeHint(tooltip, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
21123
+ this.dialogTips.push(dispose);
21124
+ return el;
21078
21125
  }
21079
21126
  /** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
21080
21127
  select(id, options, current, emit) {
@@ -21511,8 +21558,6 @@ ${overlayScrollbarCss(".vela-dialog *")}
21511
21558
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21512
21559
  .vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
21513
21560
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
21514
- .vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
21515
- .vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
21516
21561
  .vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
21517
21562
  .vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
21518
21563
  .vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
@@ -22021,8 +22066,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22021
22066
  lg.style.top = `${top}px`;
22022
22067
  }
22023
22068
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22024
- setCollapsedPanes(map) {
22025
- this.paneCollapse = map;
22069
+ setCollapsedPanes(map2) {
22070
+ this.paneCollapse = map2;
22026
22071
  }
22027
22072
  /**
22028
22073
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -22985,9 +23030,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22985
23030
  if (inst.enableKey) defaults2[inst.enableKey] = false;
22986
23031
  addRows(inst.rows);
22987
23032
  }
22988
- for (const sub of section.subsections ?? []) {
22989
- if (sub.enableKey) defaults2[sub.enableKey] = false;
22990
- addRows(sub.rows);
23033
+ for (const sub2 of section.subsections ?? []) {
23034
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
23035
+ addRows(sub2.rows);
22991
23036
  }
22992
23037
  if (!section.instances) addRows(section.rows);
22993
23038
  bag[t.id] = defaults2;
@@ -23056,7 +23101,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23056
23101
  labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
23057
23102
  currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
23058
23103
  priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
23059
- countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
23104
+ countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
23105
+ animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
23060
23106
  },
23061
23107
  panes: {
23062
23108
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
@@ -24316,11 +24362,11 @@ void main() {
24316
24362
  }
24317
24363
  }
24318
24364
  if (!any) return EMPTY_BARCOLORS;
24319
- const map = /* @__PURE__ */ new Map();
24365
+ const map2 = /* @__PURE__ */ new Map();
24320
24366
  for (const m of indicators.values()) {
24321
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24367
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24322
24368
  }
24323
- return map;
24369
+ return map2;
24324
24370
  }
24325
24371
 
24326
24372
  // src/renderers/native/core/ViewportState.ts
@@ -24617,9 +24663,9 @@ void main() {
24617
24663
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24618
24664
  return rightOffset + deltaX / barSpacing;
24619
24665
  }
24620
- function wheelPanDelta(deltaX, deltaY, shift3) {
24666
+ function wheelPanDelta(deltaX, deltaY, shift4) {
24621
24667
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24622
- if (shift3) return deltaY;
24668
+ if (shift4) return deltaY;
24623
24669
  return null;
24624
24670
  }
24625
24671
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -26241,11 +26287,11 @@ void main() {
26241
26287
  }
26242
26288
  }
26243
26289
  if (!any) return EMPTY_BARCOLORS2;
26244
- const map = /* @__PURE__ */ new Map();
26290
+ const map2 = /* @__PURE__ */ new Map();
26245
26291
  for (const m of indicators.values()) {
26246
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26292
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
26247
26293
  }
26248
- return map;
26294
+ return map2;
26249
26295
  }
26250
26296
 
26251
26297
  // src/renderers/shared/drawing-geometry.ts
@@ -27920,6 +27966,8 @@ void main() {
27920
27966
  "symbol.style.baseline.fill-bottom",
27921
27967
  "symbol.style.baseline.base-level",
27922
27968
  "symbol.style.baseline.width",
27969
+ "symbol.animation",
27970
+ "symbol.animation.price-changes",
27923
27971
  "symbol.timezone",
27924
27972
  "scales",
27925
27973
  "scales.price-scale",
@@ -27961,9 +28009,9 @@ void main() {
27961
28009
  };
27962
28010
  if (section.rows) addRows(section.rows);
27963
28011
  for (const inst of section.instances ?? []) addRows(inst.rows);
27964
- for (const sub of section.subsections ?? []) {
27965
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
27966
- addRows(sub.rows);
28012
+ for (const sub2 of section.subsections ?? []) {
28013
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
28014
+ addRows(sub2.rows);
27967
28015
  }
27968
28016
  }
27969
28017
  for (const hs of hostSections) {
@@ -28062,8 +28110,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28062
28110
  .vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
28063
28111
  .vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
28064
28112
  .vela-sd-mobile .vela-sd-btn{height:38px;}
28065
- .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
28066
- `;
28113
+ .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
28067
28114
  if (!existing) document.head.appendChild(st);
28068
28115
  }
28069
28116
  var SettingsDialog = class {
@@ -28084,6 +28131,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28084
28131
  this.themeControl = null;
28085
28132
  /** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
28086
28133
  this.tabs = [];
28134
+ this.hintTips = [];
28135
+ // ⓘ tooltip disposers of the open build (see hint)
28087
28136
  /** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
28088
28137
  this.typeActiveInstance = /* @__PURE__ */ new Map();
28089
28138
  /** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
@@ -28272,6 +28321,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28272
28321
  body.append(g);
28273
28322
  }
28274
28323
  showActive(config.series.style);
28324
+ body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
28325
+ body.append(sid(this.boolRow(
28326
+ "Animate price changes",
28327
+ config.priceScale.animateLastPrice,
28328
+ (v) => this.emit({ priceScale: { animateLastPrice: v } }),
28329
+ this.hint("Glide the live bar to each new price instead of snapping.")
28330
+ ), "symbol.animation.price-changes"));
28275
28331
  body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
28276
28332
  body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
28277
28333
  const renderHostSections = (placement) => {
@@ -28479,6 +28535,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28479
28535
  this.ui = null;
28480
28536
  this.root = null;
28481
28537
  this.tabs = [];
28538
+ for (const dispose of this.hintTips) dispose();
28539
+ this.hintTips = [];
28482
28540
  ui?.destroy();
28483
28541
  }
28484
28542
  destroy() {
@@ -28522,14 +28580,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28522
28580
  };
28523
28581
  if (section.rows) seed(section.rows);
28524
28582
  for (const inst of section.instances ?? []) seed(inst.rows);
28525
- for (const sub of section.subsections ?? []) seed(sub.rows);
28583
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28526
28584
  for (const inst of section.instances ?? []) {
28527
28585
  if (!inst.enableKey || inst.enableKey in bag) continue;
28528
28586
  bag[inst.enableKey] = values[inst.enableKey] === true;
28529
28587
  }
28530
- for (const sub of section.subsections ?? []) {
28531
- if (!sub.enableKey || sub.enableKey in bag) continue;
28532
- bag[sub.enableKey] = values[sub.enableKey] === true;
28588
+ for (const sub2 of section.subsections ?? []) {
28589
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28590
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28533
28591
  }
28534
28592
  const refreshers = [];
28535
28593
  const put = (key, v) => {
@@ -28546,16 +28604,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28546
28604
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28547
28605
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28548
28606
  }
28549
- for (const sub of section.subsections ?? []) {
28550
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
28551
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
28607
+ for (const sub2 of section.subsections ?? []) {
28608
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28609
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28552
28610
  if (rows.length === 0) continue;
28553
- const subMarker = this.section(sub.title);
28611
+ const subMarker = this.section(sub2.title);
28554
28612
  subMarker.dataset.sdStyle = typeId;
28555
28613
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28556
28614
  subMarker.dataset.sdSub = "1";
28557
28615
  body.append(subMarker);
28558
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28616
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28559
28617
  }
28560
28618
  for (const r of refreshers) r();
28561
28619
  }
@@ -28892,23 +28950,31 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28892
28950
  }).el
28893
28951
  });
28894
28952
  }
28895
- /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area). */
28896
- boolRow(label, value, onChange) {
28897
- return this.toggleRow(label, value, onChange, []);
28953
+ /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
28954
+ * `info` (see {@link hint}) rides after the label. */
28955
+ boolRow(label, value, onChange, info) {
28956
+ return this.toggleRow(label, value, onChange, [], void 0, info);
28898
28957
  }
28899
28958
  /** An enable row: checkbox + label in the label column, dependent controls in the
28900
28959
  * shared control column; the control group dims and ignores input while the toggle
28901
28960
  * is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
28902
- toggleRow(label, value, onToggle, controls, get) {
28961
+ toggleRow(label, value, onToggle, controls, get, info) {
28903
28962
  return fieldRow({
28904
28963
  label,
28905
28964
  labelSize: "sm",
28906
28965
  bool: controls.length === 0,
28907
28966
  toggle: { checked: value, onChange: onToggle, get },
28908
28967
  control: controls,
28968
+ info,
28909
28969
  className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
28910
28970
  });
28911
28971
  }
28972
+ /** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
28973
+ hint(text) {
28974
+ const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
28975
+ this.hintTips.push(dispose);
28976
+ return el;
28977
+ }
28912
28978
  numberRow(label, value, min2, max2, step, onChange) {
28913
28979
  return fieldRow({
28914
28980
  label,
@@ -30663,11 +30729,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30663
30729
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30664
30730
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30665
30731
  * while placing a line tool it locks the segment angle to 45° steps. */
30666
- down(x, y, mode = "off", shift3 = false) {
30732
+ down(x, y, mode = "off", shift4 = false) {
30667
30733
  const proj = this.deps.projector();
30668
30734
  if (this.state.kind === "placing") {
30669
30735
  if (this.state.draft.placementMode() !== "click") return;
30670
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30736
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30671
30737
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30672
30738
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30673
30739
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30686,7 +30752,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30686
30752
  }
30687
30753
  const hit = this.hitAt(x, y);
30688
30754
  if (hit) {
30689
- if (shift3) {
30755
+ if (shift4) {
30690
30756
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30691
30757
  return;
30692
30758
  }
@@ -30696,7 +30762,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30696
30762
  }
30697
30763
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30698
30764
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30699
- move(x, y, mode = "off", shift3 = false) {
30765
+ move(x, y, mode = "off", shift4 = false) {
30700
30766
  if (this.state.kind === "idle") {
30701
30767
  const tool = this.deps.activeTool();
30702
30768
  const had = this.snapAt != null;
@@ -30710,7 +30776,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30710
30776
  this.captureFreehand(x, y);
30711
30777
  return;
30712
30778
  }
30713
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30779
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30714
30780
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30715
30781
  this.deps.changed();
30716
30782
  return;
@@ -30723,7 +30789,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30723
30789
  if (this.state.handle >= 0) {
30724
30790
  const a = d.anchors[this.state.handle];
30725
30791
  if (a) {
30726
- const pivot = shift3 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30792
+ const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30727
30793
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30728
30794
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30729
30795
  }
@@ -31421,21 +31487,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
31421
31487
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31422
31488
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31423
31489
  }
31424
- const vwap = drawing.vwap;
31425
- if (paths.has("vwap.midColor") && vwap) {
31490
+ const vwap2 = drawing.vwap;
31491
+ if (paths.has("vwap.midColor") && vwap2) {
31426
31492
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31427
31493
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
31428
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31429
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31494
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31495
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31430
31496
  bar.appendChild(
31431
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31497
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31432
31498
  label: (v) => `${v}\u03C3`,
31433
31499
  labelInTrigger: true
31434
31500
  })
31435
31501
  );
31436
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31437
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31438
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31502
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31503
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31504
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31439
31505
  }
31440
31506
  if (paths.has("maxCurvature")) {
31441
31507
  const cur = drawing.maxCurvature ?? 24;
@@ -32181,8 +32247,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32181
32247
  }
32182
32248
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
32183
32249
  * right edge of the flyout rows, beside the favorite star. */
32184
- setShortcuts(map) {
32185
- this.shortcuts = new Map(Object.entries(map));
32250
+ setShortcuts(map2) {
32251
+ this.shortcuts = new Map(Object.entries(map2));
32186
32252
  }
32187
32253
  /** Paint one star for the current favorite state (filled + gold, or outline). */
32188
32254
  paintStar(el, type) {
@@ -33084,8 +33150,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33084
33150
  this.toolbar.setFavorites(types);
33085
33151
  }
33086
33152
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
33087
- setToolShortcuts(map) {
33088
- this.toolbar.setShortcuts(map);
33153
+ setToolShortcuts(map2) {
33154
+ this.toolbar.setShortcuts(map2);
33089
33155
  }
33090
33156
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
33091
33157
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -33203,7 +33269,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33203
33269
  this.render();
33204
33270
  }
33205
33271
  }
33206
- pointerDown(x, y, snap = "off", shift3 = false) {
33272
+ pointerDown(x, y, snap = "off", shift4 = false) {
33207
33273
  if (this.eraserMode) {
33208
33274
  this.erasing = true;
33209
33275
  this.deleteAt(x, y);
@@ -33223,9 +33289,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33223
33289
  return;
33224
33290
  }
33225
33291
  }
33226
- this.interaction.down(x, y, snap, shift3);
33292
+ this.interaction.down(x, y, snap, shift4);
33227
33293
  }
33228
- pointerMove(x, y, snap = "off", shift3 = false) {
33294
+ pointerMove(x, y, snap = "off", shift4 = false) {
33229
33295
  if (this.eraserMode) {
33230
33296
  if (this.erasing) this.deleteAt(x, y);
33231
33297
  return;
@@ -33236,7 +33302,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33236
33302
  this.render();
33237
33303
  return;
33238
33304
  }
33239
- this.interaction.move(x, y, snap, shift3);
33305
+ this.interaction.move(x, y, snap, shift4);
33240
33306
  this.updateHover(x, y);
33241
33307
  }
33242
33308
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -33913,14 +33979,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33913
33979
  }
33914
33980
  for (const { box, w: w2, h: h2 } of spanning) {
33915
33981
  if (box.cs > 1) {
33916
- let sum = 0;
33917
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
33918
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
33982
+ let sum2 = 0;
33983
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
33984
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
33919
33985
  }
33920
33986
  if (box.rs > 1) {
33921
- let sum = 0;
33922
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
33923
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
33987
+ let sum2 = 0;
33988
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
33989
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
33924
33990
  }
33925
33991
  }
33926
33992
  let w = 0;
@@ -34643,24 +34709,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34643
34709
  drawSvgShapes(el, alpha);
34644
34710
  continue;
34645
34711
  }
34646
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34712
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34647
34713
  let path = null;
34648
34714
  if (tag === "path") {
34649
34715
  const d = el.getAttribute("d");
34650
34716
  if (d) path = new Path2D(d);
34651
34717
  } else if (tag === "circle") {
34652
34718
  path = new Path2D();
34653
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
34719
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
34654
34720
  } else if (tag === "ellipse") {
34655
34721
  path = new Path2D();
34656
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
34722
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
34657
34723
  } else if (tag === "rect") {
34658
34724
  path = new Path2D();
34659
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
34725
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
34660
34726
  } else if (tag === "line") {
34661
34727
  path = new Path2D();
34662
- path.moveTo(num3("x1"), num3("y1"));
34663
- path.lineTo(num3("x2"), num3("y2"));
34728
+ path.moveTo(num4("x1"), num4("y1"));
34729
+ path.lineTo(num4("x2"), num4("y2"));
34664
34730
  } else if (tag === "polyline" || tag === "polygon") {
34665
34731
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
34666
34732
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -34921,7 +34987,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34921
34987
  var ZOOM_TAU_MS = 70;
34922
34988
  var SCALE_TAU_MS = 80;
34923
34989
  var FLING_TAU_MS = 110;
34924
- var LIVE_BAR_TAU_MS = 90;
34925
34990
  var SCROLL_TO_TAU_MS = 130;
34926
34991
  var FLING_STOP_PX = 0.02;
34927
34992
  var PRICE_SCALE_K = 4e-3;
@@ -34992,9 +35057,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34992
35057
  /** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
34993
35058
  this.historyChordsEnabled = true;
34994
35059
  this.liveRegion = null;
34995
- // ── animation state (eased zoom + inertial pan) ──
35060
+ // ── animation state (eased zoom + inertial pan + live-bar glide) ──
34996
35061
  this.animZoom = true;
34997
35062
  this.animPan = true;
35063
+ this.animLiveBarMs = 0;
35064
+ // forming-bar OHLC glide time-constant; 0 = each tick snaps
35065
+ this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
35066
+ // the duration the settings-dialog on/off toggle restores (last non-zero value configured)
34998
35067
  // Brand default candles.
34999
35068
  this.candleUp = BULLISH;
35000
35069
  this.candleDown = BEARISH;
@@ -35119,7 +35188,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35119
35188
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
35120
35189
  this.priceStyleCbs = /* @__PURE__ */ new Set();
35121
35190
  this.name = "native";
35122
- this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
35191
+ this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
35123
35192
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
35124
35193
  * so moving onto the button doesn't count as leaving). */
35125
35194
  this.onScrollProximityMove = (e) => {
@@ -35153,6 +35222,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35153
35222
  this.backendMode = opts.nativeBackend;
35154
35223
  this.animZoom = opts.animZoom;
35155
35224
  this.animPan = opts.animPan;
35225
+ this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
35156
35226
  this.glowAmount = opts.glow;
35157
35227
  this.candleUp = opts.upColor;
35158
35228
  this.candleDown = opts.downColor;
@@ -35202,6 +35272,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35202
35272
  case "animPan":
35203
35273
  this.animPan = Boolean(value);
35204
35274
  return;
35275
+ case "animLiveBar":
35276
+ this.setLiveBarEase(resolveLiveBarEaseMs(value));
35277
+ return;
35278
+ // affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
35205
35279
  case "intro": {
35206
35280
  const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
35207
35281
  this.introStyle = s;
@@ -35338,6 +35412,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35338
35412
  return this.animZoom;
35339
35413
  case "animPan":
35340
35414
  return this.animPan;
35415
+ case "animLiveBar":
35416
+ return this.animLiveBarMs;
35341
35417
  case "intro":
35342
35418
  return this.introStyle;
35343
35419
  case "zoomAnchor":
@@ -35506,7 +35582,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35506
35582
  labelsVisible: this.scene.showAxisLabels,
35507
35583
  currentPriceLine: this.scene.showPriceLine,
35508
35584
  priceLabel: this.scene.showPriceLabel,
35509
- countdown: this.scene.showCountdown
35585
+ countdown: this.scene.showCountdown,
35586
+ animateLastPrice: this.animLiveBarMs > 0
35510
35587
  },
35511
35588
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
35512
35589
  trades: {
@@ -35613,6 +35690,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35613
35690
  this.scene.showPriceLabel = next2.priceScale.priceLabel;
35614
35691
  this.scene.showCountdown = next2.priceScale.countdown;
35615
35692
  this.syncCountdownTimer();
35693
+ this.animLiveBarMs = next2.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
35616
35694
  s.separatorColor = keepInherit(s.separatorColor, next2.panes.separatorColor, prevTheme.borderColor);
35617
35695
  this.scene.tradeMarkers = {
35618
35696
  visible: next2.trades.visible,
@@ -36077,8 +36155,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36077
36155
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
36078
36156
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
36079
36157
  drawingsSnapMode: () => this.snapMode,
36080
- drawingsPointerDown: (x, y, snap, shift3) => this.userDrawings?.pointerDown(x, y, snap, shift3),
36081
- drawingsPointerMove: (x, y, snap, shift3) => this.userDrawings?.pointerMove(x, y, snap, shift3),
36158
+ drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
36159
+ drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
36082
36160
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
36083
36161
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
36084
36162
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -36395,18 +36473,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36395
36473
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
36396
36474
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
36397
36475
  const newHeadTime = this.bars[0].time;
36398
- let shift3 = 0;
36476
+ let shift4 = 0;
36399
36477
  if (newHeadTime !== prevHeadTime) {
36400
36478
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
36401
- if (prepended > 0) shift3 = prepended;
36479
+ if (prepended > 0) shift4 = prepended;
36402
36480
  else {
36403
36481
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
36404
- if (trimmed > 0) shift3 = -trimmed;
36482
+ if (trimmed > 0) shift4 = -trimmed;
36405
36483
  }
36406
36484
  }
36407
- if (shift3 !== 0) {
36408
- this.zoomAnchorLogical += shift3;
36409
- if (this.hoverLogical != null) this.hoverLogical += shift3;
36485
+ if (shift4 !== 0) {
36486
+ this.zoomAnchorLogical += shift4;
36487
+ if (this.hoverLogical != null) this.hoverLogical += shift4;
36410
36488
  }
36411
36489
  }
36412
36490
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -36429,8 +36507,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36429
36507
  const last2 = this.bars[n - 1];
36430
36508
  if (last2 && bar.time === last2.time) {
36431
36509
  this.bars[n - 1] = bar;
36432
- if (this.liveEaseTime !== bar.time) this.syncLiveEase(bar);
36433
- this.animator.start();
36510
+ if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
36511
+ this.syncLiveEase(bar);
36512
+ } else {
36513
+ this.animator.start();
36514
+ }
36434
36515
  } else if (!last2 || bar.time > last2.time) {
36435
36516
  this.bars.push(bar);
36436
36517
  this.syncLiveEase(bar);
@@ -36442,6 +36523,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36442
36523
  this.scene.bars = this.bars;
36443
36524
  this.scheduler.invalidate(4 /* Full */);
36444
36525
  }
36526
+ /** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
36527
+ * what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
36528
+ setLiveBarEase(ms) {
36529
+ this.animLiveBarMs = ms;
36530
+ if (ms > 0) this.animLiveBarOnMs = ms;
36531
+ }
36445
36532
  /** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
36446
36533
  syncLiveEase(bar) {
36447
36534
  this.liveEaseTime = bar.time;
@@ -36454,9 +36541,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36454
36541
  const target = this.bars[this.bars.length - 1];
36455
36542
  if (!target || this.liveEaseTime !== target.time) return false;
36456
36543
  const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
36457
- const nh = easeToward(this.liveEaseHigh, target.high, dtMs, LIVE_BAR_TAU_MS);
36458
- const nl = easeToward(this.liveEaseLow, target.low, dtMs, LIVE_BAR_TAU_MS);
36459
- const nc = easeToward(this.liveEaseClose, target.close, dtMs, LIVE_BAR_TAU_MS);
36544
+ const tau = this.animLiveBarMs;
36545
+ const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
36546
+ const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
36547
+ const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
36460
36548
  const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
36461
36549
  this.liveEaseHigh = active ? nh : target.high;
36462
36550
  this.liveEaseLow = active ? nl : target.low;
@@ -38012,15 +38100,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38012
38100
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
38013
38101
  * strip — merged own-scale indicators are hidden while collapsed. */
38014
38102
  collapsedMasterMap() {
38015
- const map = /* @__PURE__ */ new Map();
38103
+ const map2 = /* @__PURE__ */ new Map();
38016
38104
  for (const pane of this.scene.panes.values()) {
38017
38105
  if (!pane.collapsed) continue;
38018
38106
  const models = this.scene.orderedIndicatorsForPane(pane.id);
38019
38107
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
38020
38108
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
38021
- map.set(pane.id, master?.id ?? null);
38109
+ map2.set(pane.id, master?.id ?? null);
38022
38110
  }
38023
- return map;
38111
+ return map2;
38024
38112
  }
38025
38113
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
38026
38114
  setToolbarGutter(px) {
@@ -38416,29 +38504,2335 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38416
38504
  registerNativeIndicator(vpvrDescriptor);
38417
38505
  }
38418
38506
 
38507
+ // src/core/model/identity.ts
38508
+ function stableSeriesId(parts3) {
38509
+ const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
38510
+ return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
38511
+ }
38512
+
38513
+ // src/core/native-indicators/classics/define.ts
38514
+ function num3(inputs, key, fallback) {
38515
+ const v = inputs[key];
38516
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
38517
+ }
38518
+ function str3(inputs, key, fallback) {
38519
+ const v = inputs[key];
38520
+ return typeof v === "string" && v !== "" ? v : fallback;
38521
+ }
38522
+ function toPoints(bars, values, colors) {
38523
+ const pts = new Array(bars.length);
38524
+ for (let i = 0; i < bars.length; i++) {
38525
+ const v = values[i];
38526
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
38527
+ const c = colors?.[i];
38528
+ if (c != null) p.color = c;
38529
+ pts[i] = p;
38530
+ }
38531
+ return pts;
38532
+ }
38533
+ var ClassicIndicator = class {
38534
+ constructor(spec) {
38535
+ this.spec = spec;
38536
+ this.inputs = {};
38537
+ }
38538
+ start(ctx, inputs) {
38539
+ this.ctx = ctx;
38540
+ this.inputs = inputs;
38541
+ this.recompute();
38542
+ }
38543
+ onBars() {
38544
+ this.recompute();
38545
+ }
38546
+ onViewport() {
38547
+ }
38548
+ setInputs(inputs) {
38549
+ this.inputs = inputs;
38550
+ this.recompute();
38551
+ }
38552
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
38553
+ suspend() {
38554
+ }
38555
+ resume() {
38556
+ this.recompute();
38557
+ }
38558
+ stop() {
38559
+ }
38560
+ recompute() {
38561
+ const bars = this.ctx.bars();
38562
+ const out = this.spec.compute(bars, this.inputs);
38563
+ const instanceId = this.spec.type;
38564
+ const series = [];
38565
+ const idsByKey = /* @__PURE__ */ new Map();
38566
+ out.plots.forEach((plot, ordinal) => {
38567
+ const kind = plot.kind ?? "line";
38568
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
38569
+ idsByKey.set(plot.key, id);
38570
+ series.push({
38571
+ id,
38572
+ title: plot.title,
38573
+ paneId: "",
38574
+ // stamped by the orchestrator
38575
+ kind,
38576
+ points: toPoints(bars, plot.values, plot.colors),
38577
+ style: {
38578
+ color: plot.color,
38579
+ width: plot.width ?? 1,
38580
+ lineStyle: plot.lineStyle ?? "solid",
38581
+ ...plot.base != null ? { base: plot.base } : {}
38582
+ },
38583
+ ...plot.overlay ? { overlay: true } : {}
38584
+ });
38585
+ });
38586
+ if (out.markers && out.markers.length > 0) {
38587
+ series.push({
38588
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
38589
+ title: this.spec.shortTitle ?? this.spec.title,
38590
+ paneId: "",
38591
+ kind: "markers",
38592
+ markers: out.markers
38593
+ });
38594
+ }
38595
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
38596
+ const from = idsByKey.get(band.from);
38597
+ const to = idsByKey.get(band.to);
38598
+ if (!from || !to) return [];
38599
+ return [
38600
+ {
38601
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
38602
+ paneId: "",
38603
+ fromSeriesId: from,
38604
+ toSeriesId: to,
38605
+ color: band.color
38606
+ }
38607
+ ];
38608
+ });
38609
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
38610
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
38611
+ paneId: "",
38612
+ price: level.price,
38613
+ ...level.color != null ? { color: level.color } : {},
38614
+ lineStyle: level.lineStyle ?? "dashed",
38615
+ width: 1,
38616
+ ...level.title != null ? { title: level.title } : {}
38617
+ }));
38618
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
38619
+ const { key, ...rest } = poly;
38620
+ return {
38621
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
38622
+ paneId: "",
38623
+ ...rest
38624
+ };
38625
+ });
38626
+ this.ctx.emit({ series, fills, priceLines, polylines });
38627
+ this.ctx.setStatus("idle");
38628
+ }
38629
+ };
38630
+ function classicDescriptor(spec) {
38631
+ return {
38632
+ type: spec.type,
38633
+ title: spec.title,
38634
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
38635
+ paneHint: spec.overlay ? "price" : "new",
38636
+ overlay: spec.overlay,
38637
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
38638
+ multiInstance: true,
38639
+ inputsSchema: () => spec.inputs,
38640
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
38641
+ create: () => new ClassicIndicator(spec)
38642
+ };
38643
+ }
38644
+
38645
+ // src/core/native-indicators/classics/math.ts
38646
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
38647
+ function sourceValues(bars, source) {
38648
+ const out = new Array(bars.length);
38649
+ for (let i = 0; i < bars.length; i++) {
38650
+ const b = bars[i];
38651
+ switch (source) {
38652
+ case "Open":
38653
+ out[i] = b.open;
38654
+ break;
38655
+ case "High":
38656
+ out[i] = b.high;
38657
+ break;
38658
+ case "Low":
38659
+ out[i] = b.low;
38660
+ break;
38661
+ case "HL2":
38662
+ out[i] = (b.high + b.low) / 2;
38663
+ break;
38664
+ case "HLC3":
38665
+ out[i] = (b.high + b.low + b.close) / 3;
38666
+ break;
38667
+ case "OHLC4":
38668
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
38669
+ break;
38670
+ case "HLCC4":
38671
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
38672
+ break;
38673
+ default:
38674
+ out[i] = b.close;
38675
+ }
38676
+ }
38677
+ return out;
38678
+ }
38679
+ function volumes(bars) {
38680
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
38681
+ }
38682
+ function highs(bars) {
38683
+ return bars.map((b) => b.high);
38684
+ }
38685
+ function lows(bars) {
38686
+ return bars.map((b) => b.low);
38687
+ }
38688
+ function closes(bars) {
38689
+ return bars.map((b) => b.close);
38690
+ }
38691
+ function opens(bars) {
38692
+ return bars.map((b) => b.open);
38693
+ }
38694
+ function zip(a, b, f) {
38695
+ const out = new Array(a.length);
38696
+ for (let i = 0; i < a.length; i++) {
38697
+ const x = a[i];
38698
+ const y = b[i];
38699
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
38700
+ }
38701
+ return out;
38702
+ }
38703
+ function map(a, f) {
38704
+ const out = new Array(a.length);
38705
+ for (let i = 0; i < a.length; i++) {
38706
+ const x = a[i];
38707
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
38708
+ }
38709
+ return out;
38710
+ }
38711
+ function sub(a, b) {
38712
+ return zip(a, b, (x, y) => x - y);
38713
+ }
38714
+ function windowed(v, len, fold) {
38715
+ const out = new Array(v.length).fill(Number.NaN);
38716
+ if (len <= 0) return out;
38717
+ let run = 0;
38718
+ for (let i = 0; i < v.length; i++) {
38719
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
38720
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
38721
+ }
38722
+ return out;
38723
+ }
38724
+ function sum(v, len) {
38725
+ return windowed(v, len, (w, from, to) => {
38726
+ let s = 0;
38727
+ for (let k = from; k <= to; k++) s += w[k];
38728
+ return s;
38729
+ });
38730
+ }
38731
+ function sma(v, len) {
38732
+ return windowed(v, len, (w, from, to) => {
38733
+ let s = 0;
38734
+ for (let k = from; k <= to; k++) s += w[k];
38735
+ return s / len;
38736
+ });
38737
+ }
38738
+ function wma(v, len) {
38739
+ const norm = len * (len + 1) / 2;
38740
+ return windowed(v, len, (w, from, to) => {
38741
+ let s = 0;
38742
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
38743
+ return s / norm;
38744
+ });
38745
+ }
38746
+ function vwma(src, vol, len) {
38747
+ const pv = zip(src, vol, (p, q) => p * q);
38748
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
38749
+ }
38750
+ function stdev(v, len) {
38751
+ return windowed(v, len, (w, from, to) => {
38752
+ let s = 0;
38753
+ for (let k = from; k <= to; k++) s += w[k];
38754
+ const mean = s / len;
38755
+ let sq = 0;
38756
+ for (let k = from; k <= to; k++) {
38757
+ const d = w[k] - mean;
38758
+ sq += d * d;
38759
+ }
38760
+ return Math.sqrt(sq / len);
38761
+ });
38762
+ }
38763
+ function highest(v, len) {
38764
+ return windowed(v, len, (w, from, to) => {
38765
+ let m = -Infinity;
38766
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
38767
+ return m;
38768
+ });
38769
+ }
38770
+ function lowest(v, len) {
38771
+ return windowed(v, len, (w, from, to) => {
38772
+ let m = Infinity;
38773
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
38774
+ return m;
38775
+ });
38776
+ }
38777
+ function barsSinceHighest(v, len) {
38778
+ return windowed(v, len, (w, from, to) => {
38779
+ let m = -Infinity;
38780
+ let at = to;
38781
+ for (let k = from; k <= to; k++) {
38782
+ if (w[k] >= m) {
38783
+ m = w[k];
38784
+ at = k;
38785
+ }
38786
+ }
38787
+ return to - at;
38788
+ });
38789
+ }
38790
+ function barsSinceLowest(v, len) {
38791
+ return windowed(v, len, (w, from, to) => {
38792
+ let m = Infinity;
38793
+ let at = to;
38794
+ for (let k = from; k <= to; k++) {
38795
+ if (w[k] <= m) {
38796
+ m = w[k];
38797
+ at = k;
38798
+ }
38799
+ }
38800
+ return to - at;
38801
+ });
38802
+ }
38803
+ function recursive(v, len, alpha) {
38804
+ const out = new Array(v.length).fill(Number.NaN);
38805
+ let run = 0;
38806
+ let prev3 = Number.NaN;
38807
+ for (let i = 0; i < v.length; i++) {
38808
+ const x = v[i];
38809
+ if (!Number.isFinite(x)) {
38810
+ run = 0;
38811
+ prev3 = Number.NaN;
38812
+ continue;
38813
+ }
38814
+ run += 1;
38815
+ if (Number.isFinite(prev3)) {
38816
+ prev3 = alpha * x + (1 - alpha) * prev3;
38817
+ out[i] = prev3;
38818
+ } else if (run >= len) {
38819
+ let s = 0;
38820
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
38821
+ prev3 = s / len;
38822
+ out[i] = prev3;
38823
+ }
38824
+ }
38825
+ return out;
38826
+ }
38827
+ function ema(v, len) {
38828
+ return recursive(v, len, 2 / (len + 1));
38829
+ }
38830
+ function rma(v, len) {
38831
+ return recursive(v, len, 1 / len);
38832
+ }
38833
+ function change(v, n = 1) {
38834
+ const out = new Array(v.length).fill(Number.NaN);
38835
+ for (let i = n; i < v.length; i++) {
38836
+ const a = v[i];
38837
+ const b = v[i - n];
38838
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
38839
+ }
38840
+ return out;
38841
+ }
38842
+ function roc(v, len) {
38843
+ const out = new Array(v.length).fill(Number.NaN);
38844
+ for (let i = len; i < v.length; i++) {
38845
+ const a = v[i];
38846
+ const b = v[i - len];
38847
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
38848
+ }
38849
+ return out;
38850
+ }
38851
+ function shift3(v, n) {
38852
+ const out = new Array(v.length).fill(Number.NaN);
38853
+ for (let i = 0; i < v.length; i++) {
38854
+ const j = i - n;
38855
+ if (j >= 0 && j < v.length) out[i] = v[j];
38856
+ }
38857
+ return out;
38858
+ }
38859
+ function trueRange(bars) {
38860
+ const out = new Array(bars.length).fill(Number.NaN);
38861
+ for (let i = 0; i < bars.length; i++) {
38862
+ const b = bars[i];
38863
+ if (i === 0) {
38864
+ out[i] = b.high - b.low;
38865
+ continue;
38866
+ }
38867
+ const pc = bars[i - 1].close;
38868
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
38869
+ }
38870
+ return out;
38871
+ }
38872
+ function atr(bars, len) {
38873
+ return rma(trueRange(bars), len);
38874
+ }
38875
+ function rsi(v, len) {
38876
+ const d = change(v);
38877
+ const up = map(d, (x) => Math.max(x, 0));
38878
+ const dn = map(d, (x) => Math.max(-x, 0));
38879
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
38880
+ }
38881
+ function stoch(v, hi, lo, len) {
38882
+ const hh = highest(hi, len);
38883
+ const ll = lowest(lo, len);
38884
+ const out = new Array(v.length).fill(Number.NaN);
38885
+ for (let i = 0; i < v.length; i++) {
38886
+ const x = v[i];
38887
+ const h = hh[i];
38888
+ const l = ll[i];
38889
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
38890
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
38891
+ }
38892
+ return out;
38893
+ }
38894
+ function linreg(v, len) {
38895
+ const sx = (len - 1) * len / 2;
38896
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
38897
+ const denom = len * sxx - sx * sx;
38898
+ return windowed(v, len, (w, from, to) => {
38899
+ let sy = 0;
38900
+ let sxy = 0;
38901
+ for (let k = from; k <= to; k++) {
38902
+ const x = k - from;
38903
+ sy += w[k];
38904
+ sxy += x * w[k];
38905
+ }
38906
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
38907
+ const intercept = (sy - slope * sx) / len;
38908
+ return intercept + slope * (len - 1);
38909
+ });
38910
+ }
38911
+ function swma(v) {
38912
+ const out = new Array(v.length).fill(Number.NaN);
38913
+ for (let i = 3; i < v.length; i++) {
38914
+ const a = v[i - 3];
38915
+ const b = v[i - 2];
38916
+ const c = v[i - 1];
38917
+ const d = v[i];
38918
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
38919
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
38920
+ }
38921
+ }
38922
+ return out;
38923
+ }
38924
+ function percentRank(v, len) {
38925
+ const out = new Array(v.length).fill(Number.NaN);
38926
+ for (let i = len; i < v.length; i++) {
38927
+ const x = v[i];
38928
+ if (!Number.isFinite(x)) continue;
38929
+ let below = 0;
38930
+ let count = 0;
38931
+ for (let k = i - len; k < i; k++) {
38932
+ const y = v[k];
38933
+ if (!Number.isFinite(y)) {
38934
+ count = -1;
38935
+ break;
38936
+ }
38937
+ count += 1;
38938
+ if (y <= x) below += 1;
38939
+ }
38940
+ if (count === len) out[i] = 100 * below / len;
38941
+ }
38942
+ return out;
38943
+ }
38944
+ function cumSum(v) {
38945
+ const out = new Array(v.length).fill(Number.NaN);
38946
+ let acc = 0;
38947
+ let seen = false;
38948
+ for (let i = 0; i < v.length; i++) {
38949
+ const x = v[i];
38950
+ if (Number.isFinite(x)) {
38951
+ acc += x;
38952
+ seen = true;
38953
+ }
38954
+ if (seen) out[i] = acc;
38955
+ }
38956
+ return out;
38957
+ }
38958
+
38959
+ // src/core/native-indicators/classics/shared.ts
38960
+ function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
38961
+ return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
38962
+ }
38963
+ function sourceInput(defval = "Close") {
38964
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
38965
+ }
38966
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
38967
+ return { key, title, type: "color", defval };
38968
+ }
38969
+ function withAlpha2(hex, alpha = 0.08) {
38970
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
38971
+ if (!m) return hex;
38972
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
38973
+ return `#${m[1]}${a}`;
38974
+ }
38975
+
38976
+ // src/core/native-indicators/classics/averages.ts
38977
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
38978
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
38979
+ function fixedAverage(type, title, shortTitle, smooth) {
38980
+ return {
38981
+ type,
38982
+ title,
38983
+ shortTitle,
38984
+ overlay: true,
38985
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
38986
+ compute: (bars, inputs) => {
38987
+ const len = num3(inputs, "length", 20);
38988
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
38989
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
38990
+ if (offset3 !== 0) values = shift3(values, offset3);
38991
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38992
+ }
38993
+ };
38994
+ }
38995
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
38996
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
38997
+ var movingAverage = {
38998
+ type: "moving-average",
38999
+ title: "Moving Average",
39000
+ shortTitle: "MA",
39001
+ overlay: true,
39002
+ inputs: [
39003
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
39004
+ lengthInput(20),
39005
+ sourceInput(),
39006
+ offsetInput,
39007
+ colorInput()
39008
+ ],
39009
+ compute: (bars, inputs) => {
39010
+ const len = num3(inputs, "length", 20);
39011
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39012
+ const kind = str3(inputs, "maType", "SMA");
39013
+ let values;
39014
+ switch (kind) {
39015
+ case "EMA":
39016
+ values = ema(src, len);
39017
+ break;
39018
+ case "WMA":
39019
+ values = wma(src, len);
39020
+ break;
39021
+ case "RMA":
39022
+ values = rma(src, len);
39023
+ break;
39024
+ case "VWMA":
39025
+ values = vwma(src, volumes(bars), len);
39026
+ break;
39027
+ default:
39028
+ values = sma(src, len);
39029
+ }
39030
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
39031
+ if (offset3 !== 0) values = shift3(values, offset3);
39032
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39033
+ }
39034
+ };
39035
+ var smoothedMa = {
39036
+ type: "rma",
39037
+ title: "Smoothed Moving Average",
39038
+ shortTitle: "RMA",
39039
+ overlay: true,
39040
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39041
+ compute: (bars, inputs) => ({
39042
+ plots: [
39043
+ {
39044
+ key: "rma",
39045
+ title: "RMA",
39046
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39047
+ color: str3(inputs, "color", SERIES_LINE),
39048
+ width: 2
39049
+ }
39050
+ ]
39051
+ })
39052
+ };
39053
+ var zlema = {
39054
+ type: "zlema",
39055
+ title: "Zero-Lag Exponential Moving Average",
39056
+ shortTitle: "ZLEMA",
39057
+ overlay: true,
39058
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39059
+ compute: (bars, inputs) => {
39060
+ const len = num3(inputs, "length", 14);
39061
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39062
+ const lag = Math.floor((len - 1) / 2);
39063
+ const lagged = shift3(src, lag);
39064
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
39065
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39066
+ }
39067
+ };
39068
+ var vidya = {
39069
+ type: "vidya",
39070
+ title: "Variable Index Dynamic Average",
39071
+ shortTitle: "VIDYA",
39072
+ overlay: true,
39073
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
39074
+ compute: (bars, inputs) => {
39075
+ const len = num3(inputs, "length", 14);
39076
+ const cmoLen = num3(inputs, "cmoLength", 9);
39077
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39078
+ const d = change(src);
39079
+ const su = new Array(src.length).fill(Number.NaN);
39080
+ const sd = new Array(src.length).fill(Number.NaN);
39081
+ for (let i = cmoLen; i < src.length; i++) {
39082
+ let u = 0;
39083
+ let w = 0;
39084
+ let ok = true;
39085
+ for (let k = i - cmoLen + 1; k <= i; k++) {
39086
+ const x = d[k];
39087
+ if (!Number.isFinite(x)) {
39088
+ ok = false;
39089
+ break;
39090
+ }
39091
+ if (x > 0) u += x;
39092
+ else w -= x;
39093
+ }
39094
+ if (ok) {
39095
+ su[i] = u;
39096
+ sd[i] = w;
39097
+ }
39098
+ }
39099
+ const alpha = 2 / (len + 1);
39100
+ const out = new Array(src.length).fill(Number.NaN);
39101
+ let prev3 = Number.NaN;
39102
+ for (let i = 0; i < src.length; i++) {
39103
+ const x = src[i];
39104
+ const u = su[i];
39105
+ const w = sd[i];
39106
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
39107
+ const total = u + w;
39108
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
39109
+ prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
39110
+ out[i] = prev3;
39111
+ }
39112
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39113
+ }
39114
+ };
39115
+ var maEnvelope = {
39116
+ type: "ma-envelope",
39117
+ title: "Moving Average Envelope",
39118
+ shortTitle: "MA Env",
39119
+ overlay: true,
39120
+ inputs: [
39121
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
39122
+ lengthInput(20),
39123
+ sourceInput(),
39124
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
39125
+ colorInput()
39126
+ ],
39127
+ compute: (bars, inputs) => {
39128
+ const len = num3(inputs, "length", 20);
39129
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39130
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
39131
+ const k = num3(inputs, "percent", 2.5) / 100;
39132
+ const ink = str3(inputs, "color", SERIES_LINE);
39133
+ return {
39134
+ plots: [
39135
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
39136
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
39137
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
39138
+ ],
39139
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
39140
+ };
39141
+ }
39142
+ };
39143
+ var linearRegression = {
39144
+ type: "linear-regression",
39145
+ title: "Linear Regression Curve",
39146
+ shortTitle: "LinReg",
39147
+ overlay: true,
39148
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39149
+ compute: (bars, inputs) => ({
39150
+ plots: [
39151
+ {
39152
+ key: "linreg",
39153
+ title: "LinReg",
39154
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39155
+ color: str3(inputs, "color", SERIES_LINE),
39156
+ width: 2
39157
+ }
39158
+ ]
39159
+ })
39160
+ };
39161
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
39162
+
39163
+ // src/core/native-indicators/classics/bands.ts
39164
+ function bollinger(bars, inputs) {
39165
+ const len = num3(inputs, "length", 20);
39166
+ const mult = num3(inputs, "mult", 2);
39167
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39168
+ const basis = sma(src, len);
39169
+ const dev = map(stdev(src, len), (x) => x * mult);
39170
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
39171
+ }
39172
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
39173
+ var bollingerBands = {
39174
+ type: "bollinger-bands",
39175
+ title: "Bollinger Bands",
39176
+ shortTitle: "BB",
39177
+ overlay: true,
39178
+ inputs: [...BB_INPUTS, colorInput()],
39179
+ compute: (bars, inputs) => {
39180
+ const { basis, upper, lower } = bollinger(bars, inputs);
39181
+ const ink = str3(inputs, "color", SERIES_LINE);
39182
+ return {
39183
+ plots: [
39184
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39185
+ { key: "upper", title: "Upper", values: upper, color: ink },
39186
+ { key: "lower", title: "Lower", values: lower, color: ink }
39187
+ ],
39188
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
39189
+ };
39190
+ }
39191
+ };
39192
+ var percentB = {
39193
+ type: "percent-b",
39194
+ title: "Bollinger Bands %B",
39195
+ shortTitle: "%B",
39196
+ overlay: false,
39197
+ inputs: [...BB_INPUTS, colorInput()],
39198
+ compute: (bars, inputs) => {
39199
+ const { upper, lower } = bollinger(bars, inputs);
39200
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39201
+ const values = src.map((x, i) => {
39202
+ const u = upper[i];
39203
+ const l = lower[i];
39204
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
39205
+ });
39206
+ return {
39207
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39208
+ levels: [
39209
+ { key: "upper", price: 1, color: NEUTRAL },
39210
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
39211
+ { key: "lower", price: 0, color: NEUTRAL }
39212
+ ]
39213
+ };
39214
+ }
39215
+ };
39216
+ var bandwidth = {
39217
+ type: "bandwidth",
39218
+ title: "Bollinger Bands Width",
39219
+ shortTitle: "BandWidth",
39220
+ overlay: false,
39221
+ inputs: [...BB_INPUTS, colorInput()],
39222
+ compute: (bars, inputs) => {
39223
+ const { basis, upper, lower } = bollinger(bars, inputs);
39224
+ const values = basis.map((b, i) => {
39225
+ const u = upper[i];
39226
+ const l = lower[i];
39227
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
39228
+ });
39229
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39230
+ }
39231
+ };
39232
+ var keltner = {
39233
+ type: "keltner-channels",
39234
+ title: "Keltner Channels",
39235
+ shortTitle: "KC",
39236
+ overlay: true,
39237
+ inputs: [
39238
+ lengthInput(20),
39239
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39240
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39241
+ sourceInput(),
39242
+ colorInput()
39243
+ ],
39244
+ compute: (bars, inputs) => {
39245
+ const len = num3(inputs, "length", 20);
39246
+ const mult = num3(inputs, "mult", 2);
39247
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
39248
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
39249
+ const ink = str3(inputs, "color", SERIES_LINE);
39250
+ return {
39251
+ plots: [
39252
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39253
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
39254
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
39255
+ ],
39256
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39257
+ };
39258
+ }
39259
+ };
39260
+ var donchian = {
39261
+ type: "donchian-channels",
39262
+ title: "Donchian Channels",
39263
+ shortTitle: "DC",
39264
+ overlay: true,
39265
+ inputs: [lengthInput(20), colorInput()],
39266
+ compute: (bars, inputs) => {
39267
+ const len = num3(inputs, "length", 20);
39268
+ const upper = highest(highs(bars), len);
39269
+ const lower = lowest(lows(bars), len);
39270
+ const ink = str3(inputs, "color", SERIES_LINE);
39271
+ return {
39272
+ plots: [
39273
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
39274
+ { key: "upper", title: "Upper", values: upper, color: ink },
39275
+ { key: "lower", title: "Lower", values: lower, color: ink }
39276
+ ],
39277
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39278
+ };
39279
+ }
39280
+ };
39281
+ var chandelier = {
39282
+ type: "chandelier-exit",
39283
+ title: "Chandelier Exit",
39284
+ shortTitle: "CE",
39285
+ overlay: true,
39286
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
39287
+ compute: (bars, inputs) => {
39288
+ const len = num3(inputs, "length", 22);
39289
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
39290
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
39291
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
39292
+ return {
39293
+ plots: [
39294
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
39295
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
39296
+ ]
39297
+ };
39298
+ }
39299
+ };
39300
+ var chandeKroll = {
39301
+ type: "chande-kroll-stop",
39302
+ title: "Chande Kroll Stop",
39303
+ shortTitle: "CKS",
39304
+ overlay: true,
39305
+ inputs: [
39306
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39307
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
39308
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
39309
+ ],
39310
+ compute: (bars, inputs) => {
39311
+ const p = num3(inputs, "p", 10);
39312
+ const x = num3(inputs, "x", 1);
39313
+ const q = num3(inputs, "q", 9);
39314
+ const range = map(atr(bars, p), (v) => v * x);
39315
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
39316
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
39317
+ return {
39318
+ plots: [
39319
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
39320
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
39321
+ ]
39322
+ };
39323
+ }
39324
+ };
39325
+ var supertrend = {
39326
+ type: "supertrend",
39327
+ title: "SuperTrend",
39328
+ shortTitle: "SuperTrend",
39329
+ overlay: true,
39330
+ inputs: [
39331
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39332
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
39333
+ ],
39334
+ compute: (bars, inputs) => {
39335
+ const len = num3(inputs, "atrLength", 10);
39336
+ const mult = num3(inputs, "mult", 3);
39337
+ const range = atr(bars, len);
39338
+ const n = bars.length;
39339
+ const st = new Array(n).fill(Number.NaN);
39340
+ const dir = new Array(n).fill(1);
39341
+ let up = Number.NaN;
39342
+ let dn = Number.NaN;
39343
+ let trend = 1;
39344
+ for (let i = 0; i < n; i++) {
39345
+ const r = range[i];
39346
+ if (!Number.isFinite(r)) continue;
39347
+ const b = bars[i];
39348
+ const mid = (b.high + b.low) / 2;
39349
+ const basicUp = mid - mult * r;
39350
+ const basicDn = mid + mult * r;
39351
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
39352
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
39353
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
39354
+ if (trend === 1 && b.close < up) trend = -1;
39355
+ else if (trend === -1 && b.close > dn) trend = 1;
39356
+ dir[i] = trend;
39357
+ st[i] = trend === 1 ? up : dn;
39358
+ }
39359
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
39360
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
39361
+ }
39362
+ };
39363
+ var alligator = {
39364
+ type: "williams-alligator",
39365
+ title: "Williams Alligator",
39366
+ shortTitle: "Alligator",
39367
+ overlay: true,
39368
+ inputs: [
39369
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
39370
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
39371
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
39372
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
39373
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
39374
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
39375
+ ],
39376
+ compute: (bars, inputs) => {
39377
+ const hl2 = sourceValues(bars, "HL2");
39378
+ const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
39379
+ const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
39380
+ const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
39381
+ return {
39382
+ plots: [
39383
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
39384
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
39385
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
39386
+ ]
39387
+ };
39388
+ }
39389
+ };
39390
+ var gator = {
39391
+ type: "gator-oscillator",
39392
+ title: "Gator Oscillator",
39393
+ shortTitle: "Gator Oscillator",
39394
+ overlay: false,
39395
+ inputs: [],
39396
+ compute: (bars) => {
39397
+ const hl2 = sourceValues(bars, "HL2");
39398
+ const jaw = shift3(rma(hl2, 13), 8);
39399
+ const teeth = shift3(rma(hl2, 8), 5);
39400
+ const lips = shift3(rma(hl2, 5), 3);
39401
+ const upper = map(sub(jaw, teeth), Math.abs);
39402
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
39403
+ const colorByExpansion = (v) => v.map((x, i) => {
39404
+ if (!Number.isFinite(x)) return null;
39405
+ const prev3 = i > 0 ? v[i - 1] : Number.NaN;
39406
+ return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
39407
+ });
39408
+ return {
39409
+ plots: [
39410
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
39411
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
39412
+ ]
39413
+ };
39414
+ }
39415
+ };
39416
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
39417
+
39418
+ // src/core/native-indicators/classics/oscillators.ts
39419
+ function guideLevels(low, high, mid) {
39420
+ const levels = [
39421
+ { key: "upper", price: high, color: NEUTRAL },
39422
+ { key: "lower", price: low, color: NEUTRAL }
39423
+ ];
39424
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
39425
+ return levels;
39426
+ }
39427
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
39428
+ function histColors(values) {
39429
+ return values.map((x, i) => {
39430
+ if (!Number.isFinite(x)) return null;
39431
+ const prev3 = i > 0 ? values[i - 1] : Number.NaN;
39432
+ const rising = Number.isFinite(prev3) ? x >= prev3 : true;
39433
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
39434
+ return rising ? `${BEARISH}80` : BEARISH;
39435
+ });
39436
+ }
39437
+ var rsiSpec = {
39438
+ type: "rsi",
39439
+ title: "Relative Strength Index",
39440
+ shortTitle: "RSI",
39441
+ overlay: false,
39442
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39443
+ compute: (bars, inputs) => ({
39444
+ plots: [
39445
+ {
39446
+ key: "rsi",
39447
+ title: "RSI",
39448
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39449
+ color: str3(inputs, "color", SERIES_LINE),
39450
+ width: 2
39451
+ }
39452
+ ],
39453
+ levels: guideLevels(30, 70, 50)
39454
+ })
39455
+ };
39456
+ var stochastic = {
39457
+ type: "stochastic",
39458
+ title: "Stochastic",
39459
+ shortTitle: "Stoch",
39460
+ overlay: false,
39461
+ inputs: [
39462
+ lengthInput(14, "kLength", "%K length"),
39463
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
39464
+ lengthInput(3, "dLength", "%D smoothing", 500)
39465
+ ],
39466
+ compute: (bars, inputs) => {
39467
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
39468
+ const d = sma(k, num3(inputs, "dLength", 3));
39469
+ return {
39470
+ plots: [
39471
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39472
+ { key: "d", title: "%D", values: d, color: WARNING }
39473
+ ],
39474
+ levels: guideLevels(20, 80)
39475
+ };
39476
+ }
39477
+ };
39478
+ var stochasticRsi = {
39479
+ type: "stochastic-rsi",
39480
+ title: "Stochastic RSI",
39481
+ shortTitle: "Stoch RSI",
39482
+ overlay: false,
39483
+ inputs: [
39484
+ lengthInput(14, "rsiLength", "RSI length"),
39485
+ lengthInput(14, "stochLength", "Stochastic length"),
39486
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
39487
+ lengthInput(3, "dLength", "%D smoothing", 500),
39488
+ sourceInput()
39489
+ ],
39490
+ compute: (bars, inputs) => {
39491
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
39492
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
39493
+ const d = sma(k, num3(inputs, "dLength", 3));
39494
+ return {
39495
+ plots: [
39496
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39497
+ { key: "d", title: "%D", values: d, color: WARNING }
39498
+ ],
39499
+ levels: guideLevels(20, 80)
39500
+ };
39501
+ }
39502
+ };
39503
+ var macd = {
39504
+ type: "macd",
39505
+ title: "MACD",
39506
+ overlay: false,
39507
+ inputs: [
39508
+ lengthInput(12, "fastLength", "Fast length"),
39509
+ lengthInput(26, "slowLength", "Slow length"),
39510
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39511
+ sourceInput()
39512
+ ],
39513
+ compute: (bars, inputs) => {
39514
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39515
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
39516
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39517
+ const hist = sub(line, signal);
39518
+ return {
39519
+ plots: [
39520
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39521
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
39522
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39523
+ ],
39524
+ levels: ZERO_LEVEL
39525
+ };
39526
+ }
39527
+ };
39528
+ var ppo = {
39529
+ type: "ppo",
39530
+ title: "Percentage Price Oscillator",
39531
+ shortTitle: "PPO",
39532
+ overlay: false,
39533
+ inputs: [
39534
+ lengthInput(12, "fastLength", "Fast length"),
39535
+ lengthInput(26, "slowLength", "Slow length"),
39536
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39537
+ sourceInput()
39538
+ ],
39539
+ compute: (bars, inputs) => {
39540
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39541
+ const fast = ema(src, num3(inputs, "fastLength", 12));
39542
+ const slow = ema(src, num3(inputs, "slowLength", 26));
39543
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
39544
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39545
+ const hist = sub(line, signal);
39546
+ return {
39547
+ plots: [
39548
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39549
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
39550
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39551
+ ],
39552
+ levels: ZERO_LEVEL
39553
+ };
39554
+ }
39555
+ };
39556
+ var awesome = {
39557
+ type: "awesome-oscillator",
39558
+ title: "Awesome Oscillator",
39559
+ shortTitle: "AO",
39560
+ overlay: false,
39561
+ inputs: [],
39562
+ compute: (bars) => {
39563
+ const hl2 = sourceValues(bars, "HL2");
39564
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
39565
+ const colors = ao.map((x, i) => {
39566
+ if (!Number.isFinite(x)) return null;
39567
+ const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
39568
+ return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
39569
+ });
39570
+ return {
39571
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
39572
+ levels: ZERO_LEVEL
39573
+ };
39574
+ }
39575
+ };
39576
+ var cci = {
39577
+ type: "commodity-channel-index",
39578
+ title: "Commodity Channel Index",
39579
+ shortTitle: "CCI",
39580
+ overlay: false,
39581
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
39582
+ compute: (bars, inputs) => {
39583
+ const len = num3(inputs, "length", 20);
39584
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
39585
+ const basis = sma(src, len);
39586
+ const dev = new Array(src.length).fill(Number.NaN);
39587
+ for (let i = len - 1; i < src.length; i++) {
39588
+ const mean = basis[i];
39589
+ if (!Number.isFinite(mean)) continue;
39590
+ let d = 0;
39591
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
39592
+ dev[i] = d / len;
39593
+ }
39594
+ const values = src.map((x, i) => {
39595
+ const b = basis[i];
39596
+ const d = dev[i];
39597
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
39598
+ });
39599
+ return {
39600
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39601
+ levels: guideLevels(-100, 100, 0)
39602
+ };
39603
+ }
39604
+ };
39605
+ var williamsR = {
39606
+ type: "williams-percent-r",
39607
+ title: "Williams %R",
39608
+ shortTitle: "%R",
39609
+ overlay: false,
39610
+ inputs: [lengthInput(14), colorInput()],
39611
+ compute: (bars, inputs) => {
39612
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
39613
+ return {
39614
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39615
+ levels: guideLevels(-80, -20)
39616
+ };
39617
+ }
39618
+ };
39619
+ var cmo = {
39620
+ type: "chande-momentum-oscillator",
39621
+ title: "Chande Momentum Oscillator",
39622
+ shortTitle: "CMO",
39623
+ overlay: false,
39624
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39625
+ compute: (bars, inputs) => {
39626
+ const len = num3(inputs, "length", 9);
39627
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
39628
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
39629
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
39630
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
39631
+ return {
39632
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39633
+ levels: guideLevels(-50, 50, 0)
39634
+ };
39635
+ }
39636
+ };
39637
+ var connorsRsi = {
39638
+ type: "connors-rsi",
39639
+ title: "Connors RSI",
39640
+ shortTitle: "CRSI",
39641
+ overlay: false,
39642
+ inputs: [
39643
+ lengthInput(3, "rsiLength", "RSI length"),
39644
+ lengthInput(2, "streakLength", "Streak RSI length"),
39645
+ lengthInput(100, "rankLength", "Percent-rank length"),
39646
+ colorInput()
39647
+ ],
39648
+ compute: (bars, inputs) => {
39649
+ const c = closes(bars);
39650
+ const streak = new Array(c.length).fill(0);
39651
+ for (let i = 1; i < c.length; i++) {
39652
+ const d = c[i] - c[i - 1];
39653
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
39654
+ }
39655
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
39656
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
39657
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
39658
+ const values = c.map((_, i) => {
39659
+ const x = a[i];
39660
+ const y = b[i];
39661
+ const z = r[i];
39662
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
39663
+ });
39664
+ return {
39665
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39666
+ levels: guideLevels(20, 80, 50)
39667
+ };
39668
+ }
39669
+ };
39670
+ var fisher = {
39671
+ type: "fisher-transform",
39672
+ title: "Fisher Transform",
39673
+ shortTitle: "Fisher",
39674
+ overlay: false,
39675
+ inputs: [lengthInput(9)],
39676
+ compute: (bars, inputs) => {
39677
+ const len = num3(inputs, "length", 9);
39678
+ const hl2 = sourceValues(bars, "HL2");
39679
+ const hi = highest(hl2, len);
39680
+ const lo = lowest(hl2, len);
39681
+ const n = bars.length;
39682
+ const out = new Array(n).fill(Number.NaN);
39683
+ let value = 0;
39684
+ let fish = 0;
39685
+ for (let i = 0; i < n; i++) {
39686
+ const h = hi[i];
39687
+ const l = lo[i];
39688
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
39689
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
39690
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
39691
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
39692
+ out[i] = fish;
39693
+ }
39694
+ return {
39695
+ plots: [
39696
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
39697
+ { key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
39698
+ ],
39699
+ levels: ZERO_LEVEL
39700
+ };
39701
+ }
39702
+ };
39703
+ var trix = {
39704
+ type: "trix",
39705
+ title: "TRIX",
39706
+ overlay: false,
39707
+ inputs: [lengthInput(18), colorInput()],
39708
+ compute: (bars, inputs) => {
39709
+ const len = num3(inputs, "length", 18);
39710
+ const logClose = map(closes(bars), Math.log);
39711
+ const smooth = ema(ema(ema(logClose, len), len), len);
39712
+ const values = map(change(smooth), (x) => x * 1e4);
39713
+ return {
39714
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39715
+ levels: ZERO_LEVEL
39716
+ };
39717
+ }
39718
+ };
39719
+ function tsiLine(src, long, short) {
39720
+ const mom = change(src);
39721
+ const numer = ema(ema(mom, long), short);
39722
+ const denom = ema(ema(map(mom, Math.abs), long), short);
39723
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
39724
+ }
39725
+ var tsi = {
39726
+ type: "true-strength-index",
39727
+ title: "True Strength Index",
39728
+ shortTitle: "TSI",
39729
+ overlay: false,
39730
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
39731
+ compute: (bars, inputs) => {
39732
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
39733
+ return {
39734
+ plots: [
39735
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
39736
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39737
+ ],
39738
+ levels: ZERO_LEVEL
39739
+ };
39740
+ }
39741
+ };
39742
+ var smiErgodic = {
39743
+ type: "smi-ergodic",
39744
+ title: "SMI Ergodic Oscillator",
39745
+ shortTitle: "SMIE",
39746
+ overlay: false,
39747
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
39748
+ compute: (bars, inputs) => {
39749
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
39750
+ const signal = ema(line, num3(inputs, "signalLength", 5));
39751
+ const osc = sub(line, signal);
39752
+ return {
39753
+ plots: [
39754
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
39755
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
39756
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39757
+ ],
39758
+ levels: ZERO_LEVEL
39759
+ };
39760
+ }
39761
+ };
39762
+ var stc = {
39763
+ type: "schaff-trend-cycle",
39764
+ title: "Schaff Trend Cycle",
39765
+ shortTitle: "Schaff Trend Cycle",
39766
+ overlay: false,
39767
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
39768
+ compute: (bars, inputs) => {
39769
+ const len = num3(inputs, "cycleLength", 10);
39770
+ const src = closes(bars);
39771
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
39772
+ const stochOf = (v) => {
39773
+ const lo = lowest(v, len);
39774
+ const hi = highest(v, len);
39775
+ const out = new Array(v.length).fill(Number.NaN);
39776
+ let prevRaw = Number.NaN;
39777
+ let smoothed = Number.NaN;
39778
+ for (let i = 0; i < v.length; i++) {
39779
+ const x = v[i];
39780
+ const l = lo[i];
39781
+ const h = hi[i];
39782
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
39783
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
39784
+ prevRaw = raw;
39785
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
39786
+ out[i] = smoothed;
39787
+ }
39788
+ return out;
39789
+ };
39790
+ const values = stochOf(stochOf(macdLine));
39791
+ return {
39792
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
39793
+ levels: guideLevels(25, 75)
39794
+ };
39795
+ }
39796
+ };
39797
+ var kst = {
39798
+ type: "know-sure-thing",
39799
+ title: "Know Sure Thing",
39800
+ shortTitle: "KST",
39801
+ overlay: false,
39802
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
39803
+ compute: (bars, inputs) => {
39804
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39805
+ const line = [
39806
+ sma(roc(src, 10), 10),
39807
+ sma(roc(src, 15), 10),
39808
+ sma(roc(src, 20), 10),
39809
+ sma(roc(src, 30), 15)
39810
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
39811
+ return {
39812
+ plots: [
39813
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
39814
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
39815
+ ],
39816
+ levels: ZERO_LEVEL
39817
+ };
39818
+ }
39819
+ };
39820
+ var coppock = {
39821
+ type: "coppock-curve",
39822
+ title: "Coppock Curve",
39823
+ shortTitle: "Coppock",
39824
+ overlay: false,
39825
+ inputs: [
39826
+ lengthInput(10, "wmaLength", "WMA length"),
39827
+ lengthInput(14, "longRoc", "Long RoC length"),
39828
+ lengthInput(11, "shortRoc", "Short RoC length"),
39829
+ sourceInput(),
39830
+ colorInput()
39831
+ ],
39832
+ compute: (bars, inputs) => {
39833
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39834
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
39835
+ return {
39836
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39837
+ levels: ZERO_LEVEL
39838
+ };
39839
+ }
39840
+ };
39841
+ var dpo = {
39842
+ type: "detrended-price-oscillator",
39843
+ title: "Detrended Price Oscillator",
39844
+ shortTitle: "DPO",
39845
+ overlay: false,
39846
+ inputs: [lengthInput(21), colorInput()],
39847
+ compute: (bars, inputs) => {
39848
+ const len = num3(inputs, "length", 21);
39849
+ const barsback = Math.floor(len / 2) + 1;
39850
+ const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
39851
+ return {
39852
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39853
+ levels: ZERO_LEVEL
39854
+ };
39855
+ }
39856
+ };
39857
+ var rocSpec = {
39858
+ type: "rate-of-change",
39859
+ title: "Rate of Change",
39860
+ shortTitle: "ROC",
39861
+ overlay: false,
39862
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39863
+ compute: (bars, inputs) => ({
39864
+ plots: [
39865
+ {
39866
+ key: "roc",
39867
+ title: "RoC",
39868
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
39869
+ color: str3(inputs, "color", SERIES_LINE),
39870
+ width: 2
39871
+ }
39872
+ ],
39873
+ levels: ZERO_LEVEL
39874
+ })
39875
+ };
39876
+ var ultimate = {
39877
+ type: "ultimate-oscillator",
39878
+ title: "Ultimate Oscillator",
39879
+ shortTitle: "UO",
39880
+ overlay: false,
39881
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
39882
+ compute: (bars, inputs) => {
39883
+ const n = bars.length;
39884
+ const bp = new Array(n).fill(Number.NaN);
39885
+ const tr = new Array(n).fill(Number.NaN);
39886
+ for (let i = 0; i < n; i++) {
39887
+ const b = bars[i];
39888
+ const pc = i > 0 ? bars[i - 1].close : b.close;
39889
+ const lo = Math.min(b.low, pc);
39890
+ const hi = Math.max(b.high, pc);
39891
+ bp[i] = b.close - lo;
39892
+ tr[i] = hi - lo;
39893
+ }
39894
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
39895
+ const a7 = avg(num3(inputs, "fastLength", 7));
39896
+ const a14 = avg(num3(inputs, "middleLength", 14));
39897
+ const a28 = avg(num3(inputs, "slowLength", 28));
39898
+ const values = a7.map((x, i) => {
39899
+ const y = a14[i];
39900
+ const z = a28[i];
39901
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
39902
+ });
39903
+ return {
39904
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39905
+ levels: guideLevels(30, 70, 50)
39906
+ };
39907
+ }
39908
+ };
39909
+ var rvgi = {
39910
+ type: "relative-vigor-index",
39911
+ title: "Relative Vigor Index",
39912
+ shortTitle: "RVGI",
39913
+ overlay: false,
39914
+ inputs: [lengthInput(10)],
39915
+ compute: (bars, inputs) => {
39916
+ const len = num3(inputs, "length", 10);
39917
+ const co = swma(sub(closes(bars), opens(bars)));
39918
+ const hl = swma(sub(highs(bars), lows(bars)));
39919
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
39920
+ return {
39921
+ plots: [
39922
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
39923
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
39924
+ ],
39925
+ levels: ZERO_LEVEL
39926
+ };
39927
+ }
39928
+ };
39929
+ var relVolatility = {
39930
+ type: "relative-volatility-index",
39931
+ title: "Relative Volatility Index",
39932
+ shortTitle: "RVI",
39933
+ overlay: false,
39934
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
39935
+ compute: (bars, inputs) => {
39936
+ const smoothLen = num3(inputs, "smoothLength", 14);
39937
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39938
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
39939
+ const d = change(src);
39940
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
39941
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
39942
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
39943
+ return {
39944
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39945
+ levels: guideLevels(20, 80, 50)
39946
+ };
39947
+ }
39948
+ };
39949
+ var bop = {
39950
+ type: "balance-of-power",
39951
+ title: "Balance of Power",
39952
+ shortTitle: "BOP",
39953
+ overlay: false,
39954
+ inputs: [colorInput()],
39955
+ compute: (bars, inputs) => {
39956
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
39957
+ return {
39958
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39959
+ levels: ZERO_LEVEL
39960
+ };
39961
+ }
39962
+ };
39963
+ var elderRay = {
39964
+ type: "elder-ray",
39965
+ title: "Elder Ray",
39966
+ overlay: false,
39967
+ inputs: [lengthInput(13)],
39968
+ compute: (bars, inputs) => {
39969
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
39970
+ return {
39971
+ plots: [
39972
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
39973
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
39974
+ ],
39975
+ levels: ZERO_LEVEL
39976
+ };
39977
+ }
39978
+ };
39979
+ var ttmSqueeze = {
39980
+ type: "ttm-squeeze",
39981
+ title: "TTM Squeeze",
39982
+ overlay: false,
39983
+ inputs: [
39984
+ lengthInput(20),
39985
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39986
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
39987
+ ],
39988
+ compute: (bars, inputs) => {
39989
+ const len = num3(inputs, "length", 20);
39990
+ const c = closes(bars);
39991
+ const basis = sma(c, len);
39992
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
39993
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
39994
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
39995
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
39996
+ const mom = linreg(sub(c, anchor), len);
39997
+ const squeezed = c.map((_, i) => {
39998
+ const d = dev[i];
39999
+ const r = kcRange[i];
40000
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
40001
+ });
40002
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
40003
+ return {
40004
+ plots: [
40005
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
40006
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
40007
+ ]
40008
+ };
40009
+ }
40010
+ };
40011
+ var oscillatorSpecs = [
40012
+ rsiSpec,
40013
+ stochastic,
40014
+ stochasticRsi,
40015
+ macd,
40016
+ ppo,
40017
+ awesome,
40018
+ cci,
40019
+ williamsR,
40020
+ cmo,
40021
+ connorsRsi,
40022
+ fisher,
40023
+ trix,
40024
+ tsi,
40025
+ smiErgodic,
40026
+ stc,
40027
+ kst,
40028
+ coppock,
40029
+ dpo,
40030
+ rocSpec,
40031
+ ultimate,
40032
+ rvgi,
40033
+ relVolatility,
40034
+ bop,
40035
+ elderRay,
40036
+ ttmSqueeze
40037
+ ];
40038
+
40039
+ // src/core/native-indicators/classics/trend.ts
40040
+ var aroon = {
40041
+ type: "aroon",
40042
+ title: "Aroon",
40043
+ overlay: false,
40044
+ inputs: [lengthInput(14)],
40045
+ compute: (bars, inputs) => {
40046
+ const len = num3(inputs, "length", 14);
40047
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
40048
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
40049
+ return {
40050
+ plots: [
40051
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
40052
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
40053
+ ],
40054
+ levels: [
40055
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
40056
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
40057
+ ]
40058
+ };
40059
+ }
40060
+ };
40061
+ var adx = {
40062
+ type: "average-directional-index",
40063
+ title: "Average Directional Index",
40064
+ shortTitle: "ADX",
40065
+ overlay: false,
40066
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
40067
+ compute: (bars, inputs) => {
40068
+ const diLen = num3(inputs, "diLength", 14);
40069
+ const adxLen = num3(inputs, "adxLength", 14);
40070
+ const n = bars.length;
40071
+ const plusDm = new Array(n).fill(Number.NaN);
40072
+ const minusDm = new Array(n).fill(Number.NaN);
40073
+ for (let i = 1; i < n; i++) {
40074
+ const up = bars[i].high - bars[i - 1].high;
40075
+ const dn = bars[i - 1].low - bars[i].low;
40076
+ plusDm[i] = up > dn && up > 0 ? up : 0;
40077
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
40078
+ }
40079
+ const atrLine = rma(trueRange(bars), diLen);
40080
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40081
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40082
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
40083
+ return {
40084
+ plots: [
40085
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
40086
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
40087
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
40088
+ ],
40089
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
40090
+ };
40091
+ }
40092
+ };
40093
+ var vortex = {
40094
+ type: "vortex-indicator",
40095
+ title: "Vortex Indicator",
40096
+ shortTitle: "VI",
40097
+ overlay: false,
40098
+ inputs: [lengthInput(14)],
40099
+ compute: (bars, inputs) => {
40100
+ const len = num3(inputs, "length", 14);
40101
+ const n = bars.length;
40102
+ const vmPlus = new Array(n).fill(Number.NaN);
40103
+ const vmMinus = new Array(n).fill(Number.NaN);
40104
+ for (let i = 1; i < n; i++) {
40105
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
40106
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
40107
+ }
40108
+ const trSum = sum(trueRange(bars), len);
40109
+ return {
40110
+ plots: [
40111
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
40112
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
40113
+ ],
40114
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
40115
+ };
40116
+ }
40117
+ };
40118
+ var trendSpecs = [aroon, adx, vortex];
40119
+
40120
+ // src/core/native-indicators/classics/volatility.ts
40121
+ var atrSpec = {
40122
+ type: "average-true-range",
40123
+ title: "Average True Range",
40124
+ shortTitle: "ATR",
40125
+ overlay: false,
40126
+ inputs: [lengthInput(14), colorInput()],
40127
+ compute: (bars, inputs) => ({
40128
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40129
+ })
40130
+ };
40131
+ var historicalVolatility = {
40132
+ type: "historical-volatility",
40133
+ title: "Historical Volatility",
40134
+ shortTitle: "HV",
40135
+ overlay: false,
40136
+ inputs: [
40137
+ lengthInput(10),
40138
+ { 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)" },
40139
+ colorInput()
40140
+ ],
40141
+ compute: (bars, inputs) => {
40142
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
40143
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
40144
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40145
+ }
40146
+ };
40147
+ var chaikinVolatility = {
40148
+ type: "chaikin-volatility",
40149
+ title: "Chaikin Volatility",
40150
+ shortTitle: "CHV",
40151
+ overlay: false,
40152
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
40153
+ compute: (bars, inputs) => {
40154
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
40155
+ return {
40156
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40157
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40158
+ };
40159
+ }
40160
+ };
40161
+ var massIndex = {
40162
+ type: "mass-index",
40163
+ title: "Mass Index",
40164
+ shortTitle: "MI",
40165
+ overlay: false,
40166
+ inputs: [lengthInput(25), colorInput()],
40167
+ compute: (bars, inputs) => {
40168
+ const range = sub(highs(bars), lows(bars));
40169
+ const single = ema(range, 9);
40170
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
40171
+ return {
40172
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40173
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
40174
+ };
40175
+ }
40176
+ };
40177
+ var standardDeviation = {
40178
+ type: "standard-deviation",
40179
+ title: "Standard Deviation",
40180
+ shortTitle: "StdDev",
40181
+ overlay: false,
40182
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
40183
+ compute: (bars, inputs) => ({
40184
+ plots: [
40185
+ {
40186
+ key: "stdev",
40187
+ title: "StdDev",
40188
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
40189
+ color: str3(inputs, "color", SERIES_LINE),
40190
+ width: 2
40191
+ }
40192
+ ]
40193
+ })
40194
+ };
40195
+ var ulcerIndex = {
40196
+ type: "ulcer-index",
40197
+ title: "Ulcer Index",
40198
+ overlay: false,
40199
+ inputs: [lengthInput(14), colorInput()],
40200
+ compute: (bars, inputs) => {
40201
+ const len = num3(inputs, "length", 14);
40202
+ const c = closes(bars);
40203
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
40204
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
40205
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40206
+ }
40207
+ };
40208
+ var choppiness = {
40209
+ type: "choppiness-index",
40210
+ title: "Choppiness Index",
40211
+ shortTitle: "CHOP",
40212
+ overlay: false,
40213
+ inputs: [lengthInput(14), colorInput()],
40214
+ compute: (bars, inputs) => {
40215
+ const len = num3(inputs, "length", 14);
40216
+ const trSum = sum(trueRange(bars), len);
40217
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
40218
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
40219
+ return {
40220
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40221
+ levels: [
40222
+ { key: "upper", price: 61.8, color: NEUTRAL },
40223
+ { key: "lower", price: 38.2, color: NEUTRAL }
40224
+ ]
40225
+ };
40226
+ }
40227
+ };
40228
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
40229
+
40230
+ // src/core/native-indicators/classics/volume.ts
40231
+ function moneyFlowVolume(bars) {
40232
+ return bars.map((b) => {
40233
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40234
+ if (b.high === b.low) return 0;
40235
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
40236
+ });
40237
+ }
40238
+ var obv = {
40239
+ type: "on-balance-volume",
40240
+ title: "On Balance Volume",
40241
+ shortTitle: "OBV",
40242
+ overlay: false,
40243
+ inputs: [colorInput()],
40244
+ compute: (bars, inputs) => {
40245
+ const vol = volumes(bars);
40246
+ const d = change(closes(bars));
40247
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
40248
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40249
+ }
40250
+ };
40251
+ var accDist = {
40252
+ type: "accumulation-distribution",
40253
+ title: "Accumulation / Distribution",
40254
+ shortTitle: "A/D",
40255
+ overlay: false,
40256
+ inputs: [colorInput()],
40257
+ compute: (bars, inputs) => ({
40258
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40259
+ })
40260
+ };
40261
+ var cmf = {
40262
+ type: "chaikin-money-flow",
40263
+ title: "Chaikin Money Flow",
40264
+ shortTitle: "CMF",
40265
+ overlay: false,
40266
+ inputs: [lengthInput(20), colorInput()],
40267
+ compute: (bars, inputs) => {
40268
+ const len = num3(inputs, "length", 20);
40269
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
40270
+ return {
40271
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40272
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40273
+ };
40274
+ }
40275
+ };
40276
+ var chaikinOscillator = {
40277
+ type: "chaikin-oscillator",
40278
+ title: "Chaikin Oscillator",
40279
+ shortTitle: "Chaikin Osc",
40280
+ overlay: false,
40281
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40282
+ compute: (bars, inputs) => {
40283
+ const ad = cumSum(moneyFlowVolume(bars));
40284
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
40285
+ return {
40286
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40287
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40288
+ };
40289
+ }
40290
+ };
40291
+ var eom = {
40292
+ type: "ease-of-movement",
40293
+ title: "Ease of Movement",
40294
+ shortTitle: "EOM",
40295
+ overlay: false,
40296
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
40297
+ compute: (bars, inputs) => {
40298
+ const div = num3(inputs, "divisor", 1e4);
40299
+ const hl2 = sourceValues(bars, "HL2");
40300
+ const move = change(hl2);
40301
+ const raw = bars.map((b, i) => {
40302
+ const m = move[i];
40303
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
40304
+ return div * m * (b.high - b.low) / b.volume;
40305
+ });
40306
+ return {
40307
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40308
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40309
+ };
40310
+ }
40311
+ };
40312
+ var forceIndex = {
40313
+ type: "force-index",
40314
+ title: "Force Index",
40315
+ shortTitle: "FI",
40316
+ overlay: false,
40317
+ inputs: [lengthInput(13), colorInput()],
40318
+ compute: (bars, inputs) => {
40319
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
40320
+ return {
40321
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40322
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40323
+ };
40324
+ }
40325
+ };
40326
+ var klinger = {
40327
+ type: "klinger-oscillator",
40328
+ title: "Klinger Oscillator",
40329
+ shortTitle: "KVO",
40330
+ overlay: false,
40331
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
40332
+ compute: (bars, inputs) => {
40333
+ const hlc3 = sourceValues(bars, "HLC3");
40334
+ const trend = change(hlc3);
40335
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
40336
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
40337
+ return {
40338
+ plots: [
40339
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
40340
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
40341
+ ],
40342
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40343
+ };
40344
+ }
40345
+ };
40346
+ var mfi = {
40347
+ type: "money-flow-index",
40348
+ title: "Money Flow Index",
40349
+ shortTitle: "MFI",
40350
+ overlay: false,
40351
+ inputs: [lengthInput(14), colorInput()],
40352
+ compute: (bars, inputs) => {
40353
+ const len = num3(inputs, "length", 14);
40354
+ const tp = sourceValues(bars, "HLC3");
40355
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
40356
+ const d = change(tp);
40357
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
40358
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
40359
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
40360
+ return {
40361
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40362
+ levels: [
40363
+ { key: "upper", price: 80, color: NEUTRAL },
40364
+ { key: "lower", price: 20, color: NEUTRAL }
40365
+ ]
40366
+ };
40367
+ }
40368
+ };
40369
+ function volumeIndex(bars, when) {
40370
+ const out = new Array(bars.length).fill(Number.NaN);
40371
+ let index = 1e3;
40372
+ let started = false;
40373
+ for (let i = 1; i < bars.length; i++) {
40374
+ const v = bars[i].volume;
40375
+ const pv = bars[i - 1].volume;
40376
+ const pc = bars[i - 1].close;
40377
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
40378
+ if (started) out[i] = index;
40379
+ continue;
40380
+ }
40381
+ started = true;
40382
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
40383
+ out[i] = index;
40384
+ }
40385
+ return out;
40386
+ }
40387
+ var nvi = {
40388
+ type: "negative-volume-index",
40389
+ title: "Negative Volume Index",
40390
+ shortTitle: "Negative Volume Index",
40391
+ overlay: false,
40392
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40393
+ compute: (bars, inputs) => {
40394
+ const line = volumeIndex(bars, (v, pv) => v < pv);
40395
+ return {
40396
+ plots: [
40397
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40398
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40399
+ ]
40400
+ };
40401
+ }
40402
+ };
40403
+ var pvi = {
40404
+ type: "positive-volume-index",
40405
+ title: "Positive Volume Index",
40406
+ shortTitle: "Positive Volume Index",
40407
+ overlay: false,
40408
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40409
+ compute: (bars, inputs) => {
40410
+ const line = volumeIndex(bars, (v, pv) => v > pv);
40411
+ return {
40412
+ plots: [
40413
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40414
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40415
+ ]
40416
+ };
40417
+ }
40418
+ };
40419
+ var pvt = {
40420
+ type: "price-volume-trend",
40421
+ title: "Price Volume Trend",
40422
+ shortTitle: "PVT",
40423
+ overlay: false,
40424
+ inputs: [colorInput()],
40425
+ compute: (bars, inputs) => {
40426
+ const c = closes(bars);
40427
+ const raw = bars.map((b, i) => {
40428
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40429
+ const pc = c[i - 1];
40430
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
40431
+ });
40432
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40433
+ }
40434
+ };
40435
+ var volumeOscillator = {
40436
+ type: "volume-oscillator",
40437
+ title: "Volume Oscillator",
40438
+ shortTitle: "Vol Osc",
40439
+ overlay: false,
40440
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40441
+ compute: (bars, inputs) => {
40442
+ const vol = volumes(bars);
40443
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
40444
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
40445
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40446
+ return {
40447
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40448
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40449
+ };
40450
+ }
40451
+ };
40452
+ var pvo = {
40453
+ type: "pvo",
40454
+ title: "Percentage Volume Oscillator",
40455
+ shortTitle: "PVO",
40456
+ overlay: false,
40457
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
40458
+ compute: (bars, inputs) => {
40459
+ const vol = volumes(bars);
40460
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
40461
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
40462
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40463
+ const signal = ema(line, num3(inputs, "signalLength", 9));
40464
+ const hist = sub(line, signal);
40465
+ return {
40466
+ plots: [
40467
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
40468
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
40469
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
40470
+ ],
40471
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40472
+ };
40473
+ }
40474
+ };
40475
+ var vfi = {
40476
+ type: "volume-flow-indicator",
40477
+ title: "Volume Flow Indicator",
40478
+ shortTitle: "Volume Flow Indicator",
40479
+ overlay: false,
40480
+ inputs: [
40481
+ lengthInput(130),
40482
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
40483
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
40484
+ lengthInput(3, "smoothLength", "Smoothing length")
40485
+ ],
40486
+ compute: (bars, inputs) => {
40487
+ const len = num3(inputs, "length", 130);
40488
+ const coef = num3(inputs, "coef", 0.2);
40489
+ const volCoef = num3(inputs, "volCoef", 2.5);
40490
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
40491
+ const inter = change(typical);
40492
+ const vInter = stdev(inter, 30);
40493
+ const vol = volumes(bars);
40494
+ const vAve = sma(vol, len);
40495
+ const mf = change(sourceValues(bars, "HLC3"));
40496
+ const raw = bars.map((b, i) => {
40497
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
40498
+ const v = vol[i];
40499
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40500
+ const m = mf[i];
40501
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
40502
+ const capped = Math.min(v, ave * volCoef);
40503
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
40504
+ });
40505
+ const values = bars.map((_, i) => {
40506
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40507
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
40508
+ });
40509
+ const flowSum = sum(raw, len);
40510
+ const line = zip(flowSum, values, (f, a) => f / a);
40511
+ return {
40512
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
40513
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40514
+ };
40515
+ }
40516
+ };
40517
+ var intradayIntensity = {
40518
+ type: "intraday-intensity",
40519
+ title: "Intraday Intensity",
40520
+ shortTitle: "Intraday Intensity",
40521
+ overlay: false,
40522
+ inputs: [lengthInput(21), colorInput()],
40523
+ compute: (bars, inputs) => {
40524
+ const len = num3(inputs, "length", 21);
40525
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
40526
+ return {
40527
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40528
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40529
+ };
40530
+ }
40531
+ };
40532
+ var volumeSpecs = [
40533
+ obv,
40534
+ accDist,
40535
+ cmf,
40536
+ chaikinOscillator,
40537
+ eom,
40538
+ forceIndex,
40539
+ klinger,
40540
+ mfi,
40541
+ nvi,
40542
+ pvi,
40543
+ pvt,
40544
+ volumeOscillator,
40545
+ pvo,
40546
+ vfi,
40547
+ intradayIntensity
40548
+ ];
40549
+
40550
+ // src/core/native-indicators/classics/overlays.ts
40551
+ var DAY_MS2 = 864e5;
40552
+ function periodKey(anchor, time) {
40553
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
40554
+ if (anchor === "Month") {
40555
+ const d = new Date(time);
40556
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
40557
+ }
40558
+ return Math.floor(time / DAY_MS2);
40559
+ }
40560
+ var parabolicSar = {
40561
+ type: "parabolic-sar",
40562
+ title: "Parabolic SAR",
40563
+ shortTitle: "SAR",
40564
+ overlay: true,
40565
+ inputs: [
40566
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40567
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40568
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
40569
+ ],
40570
+ compute: (bars, inputs) => {
40571
+ const start = num3(inputs, "start", 0.02);
40572
+ const inc = num3(inputs, "increment", 0.02);
40573
+ const max2 = num3(inputs, "maximum", 0.2);
40574
+ const n = bars.length;
40575
+ const values = new Array(n).fill(Number.NaN);
40576
+ const colors = new Array(n).fill(null);
40577
+ if (n >= 2) {
40578
+ let long = bars[1].close >= bars[0].close;
40579
+ let sar = long ? bars[0].low : bars[0].high;
40580
+ let ep = long ? bars[0].high : bars[0].low;
40581
+ let af = start;
40582
+ for (let i = 1; i < n; i++) {
40583
+ const b = bars[i];
40584
+ sar += af * (ep - sar);
40585
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
40586
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
40587
+ const reversed = long ? b.low < sar : b.high > sar;
40588
+ if (reversed) {
40589
+ long = !long;
40590
+ sar = ep;
40591
+ ep = long ? b.high : b.low;
40592
+ af = start;
40593
+ } else if (long && b.high > ep) {
40594
+ ep = b.high;
40595
+ af = Math.min(max2, af + inc);
40596
+ } else if (!long && b.low < ep) {
40597
+ ep = b.low;
40598
+ af = Math.min(max2, af + inc);
40599
+ }
40600
+ values[i] = sar;
40601
+ colors[i] = long ? BULLISH : BEARISH;
40602
+ }
40603
+ }
40604
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
40605
+ }
40606
+ };
40607
+ var vwap = {
40608
+ type: "vwap",
40609
+ title: "Volume Weighted Average Price",
40610
+ shortTitle: "VWAP",
40611
+ overlay: true,
40612
+ inputs: [
40613
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
40614
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
40615
+ colorInput(INFO)
40616
+ ],
40617
+ compute: (bars, inputs) => {
40618
+ const anchor = str3(inputs, "anchor", "Day");
40619
+ const source = str3(inputs, "source", "HLC3");
40620
+ const values = new Array(bars.length).fill(Number.NaN);
40621
+ let period = Number.NaN;
40622
+ let cumPV = 0;
40623
+ let cumV = 0;
40624
+ for (let i = 0; i < bars.length; i++) {
40625
+ const b = bars[i];
40626
+ const key = periodKey(anchor, b.time);
40627
+ if (key !== period) {
40628
+ period = key;
40629
+ cumPV = 0;
40630
+ cumV = 0;
40631
+ }
40632
+ const v = b.volume;
40633
+ if (v != null && Number.isFinite(v) && v > 0) {
40634
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
40635
+ cumPV += tp * v;
40636
+ cumV += v;
40637
+ }
40638
+ if (cumV > 0) values[i] = cumPV / cumV;
40639
+ }
40640
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
40641
+ }
40642
+ };
40643
+ var pivotPoints = {
40644
+ type: "pivot-points",
40645
+ title: "Pivot Points",
40646
+ shortTitle: "Pivots",
40647
+ overlay: true,
40648
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
40649
+ compute: (bars, inputs) => {
40650
+ const anchor = str3(inputs, "anchor", "Day");
40651
+ const aggs = /* @__PURE__ */ new Map();
40652
+ for (const b of bars) {
40653
+ const key = periodKey(anchor, b.time);
40654
+ const a = aggs.get(key);
40655
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
40656
+ else {
40657
+ a.high = Math.max(a.high, b.high);
40658
+ a.low = Math.min(a.low, b.low);
40659
+ a.close = b.close;
40660
+ }
40661
+ }
40662
+ const n = bars.length;
40663
+ const mk = () => new Array(n).fill(Number.NaN);
40664
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
40665
+ for (let i = 0; i < n; i++) {
40666
+ const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
40667
+ if (!prev3) continue;
40668
+ const p = (prev3.high + prev3.low + prev3.close) / 3;
40669
+ levels.p[i] = p;
40670
+ levels.r1[i] = 2 * p - prev3.low;
40671
+ levels.s1[i] = 2 * p - prev3.high;
40672
+ levels.r2[i] = p + (prev3.high - prev3.low);
40673
+ levels.s2[i] = p - (prev3.high - prev3.low);
40674
+ levels.r3[i] = prev3.high + 2 * (p - prev3.low);
40675
+ levels.s3[i] = prev3.low - 2 * (prev3.high - p);
40676
+ }
40677
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
40678
+ return {
40679
+ plots: [
40680
+ plot("p", "P", WARNING),
40681
+ plot("r1", "R1", BEARISH),
40682
+ plot("s1", "S1", BULLISH),
40683
+ plot("r2", "R2", BEARISH),
40684
+ plot("s2", "S2", BULLISH),
40685
+ plot("r3", "R3", BEARISH),
40686
+ plot("s3", "S3", BULLISH)
40687
+ ]
40688
+ };
40689
+ }
40690
+ };
40691
+ var fiftyTwoWeek = {
40692
+ type: "52-week-high-low",
40693
+ title: "52 Week High/Low",
40694
+ shortTitle: "52W H/L",
40695
+ overlay: true,
40696
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
40697
+ compute: (bars, inputs) => {
40698
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
40699
+ const n = bars.length;
40700
+ const hi = new Array(n).fill(Number.NaN);
40701
+ const lo = new Array(n).fill(Number.NaN);
40702
+ const maxIdx = [];
40703
+ const minIdx = [];
40704
+ let from = 0;
40705
+ for (let i = 0; i < n; i++) {
40706
+ const b = bars[i];
40707
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
40708
+ maxIdx.push(i);
40709
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
40710
+ minIdx.push(i);
40711
+ while (bars[from].time < b.time - span) from += 1;
40712
+ while (maxIdx[0] < from) maxIdx.shift();
40713
+ while (minIdx[0] < from) minIdx.shift();
40714
+ hi[i] = bars[maxIdx[0]].high;
40715
+ lo[i] = bars[minIdx[0]].low;
40716
+ }
40717
+ return {
40718
+ plots: [
40719
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
40720
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
40721
+ ]
40722
+ };
40723
+ }
40724
+ };
40725
+ var zigzag = {
40726
+ type: "zigzag",
40727
+ title: "ZigZag",
40728
+ overlay: true,
40729
+ inputs: [
40730
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
40731
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
40732
+ colorInput()
40733
+ ],
40734
+ compute: (bars, inputs) => {
40735
+ const dev = num3(inputs, "deviation", 5) / 100;
40736
+ const depth = num3(inputs, "depth", 10);
40737
+ const pivots = [];
40738
+ if (bars.length > 1) {
40739
+ let up = bars[1].close >= bars[0].close;
40740
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
40741
+ for (let i = 1; i < bars.length; i++) {
40742
+ const b = bars[i];
40743
+ if (up) {
40744
+ if (b.high >= ext.price) ext = { i, price: b.high };
40745
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40746
+ pivots.push(ext);
40747
+ up = false;
40748
+ ext = { i, price: b.low };
40749
+ }
40750
+ } else if (b.low <= ext.price) {
40751
+ ext = { i, price: b.low };
40752
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40753
+ pivots.push(ext);
40754
+ up = true;
40755
+ ext = { i, price: b.high };
40756
+ }
40757
+ }
40758
+ pivots.push(ext);
40759
+ }
40760
+ return {
40761
+ plots: [],
40762
+ polylines: [
40763
+ {
40764
+ key: "zigzag",
40765
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
40766
+ curved: false,
40767
+ closed: false,
40768
+ lineColor: str3(inputs, "color", SERIES_LINE),
40769
+ lineWidth: 2,
40770
+ lineStyle: "solid",
40771
+ arrowLeft: false,
40772
+ arrowRight: false
40773
+ }
40774
+ ]
40775
+ };
40776
+ }
40777
+ };
40778
+ var williamsFractal = {
40779
+ type: "williams-fractal",
40780
+ title: "Williams Fractal",
40781
+ shortTitle: "Fractals",
40782
+ overlay: true,
40783
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
40784
+ compute: (bars, inputs) => {
40785
+ const p = num3(inputs, "periods", 2);
40786
+ const markers = [];
40787
+ const isExtreme = (i, pick, better) => {
40788
+ const v = pick(bars[i]);
40789
+ for (let k = i - p; k <= i + p; k++) {
40790
+ if (k === i) continue;
40791
+ if (!better(v, pick(bars[k]))) return false;
40792
+ }
40793
+ return true;
40794
+ };
40795
+ for (let i = p; i < bars.length - p; i++) {
40796
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
40797
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
40798
+ }
40799
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
40800
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
40801
+ }
40802
+ }
40803
+ return { plots: [], markers };
40804
+ }
40805
+ };
40806
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
40807
+
40808
+ // src/core/native-indicators/classics/index.ts
40809
+ var classicSpecs = [
40810
+ ...averageSpecs,
40811
+ ...bandSpecs,
40812
+ ...oscillatorSpecs,
40813
+ ...trendSpecs,
40814
+ ...volatilitySpecs,
40815
+ ...volumeSpecs,
40816
+ ...overlaySpecs
40817
+ ];
40818
+ function registerClassicIndicators() {
40819
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
40820
+ }
40821
+
38419
40822
  // src/Vela.ts
38420
40823
  var Vela = class {
38421
40824
  constructor(container, options = {}, deps = {}) {
38422
40825
  registerBuiltinChartTypes();
38423
40826
  registerVolume();
38424
40827
  registerVpvr();
40828
+ registerClassicIndicators();
38425
40829
  const element = resolveElement(container);
38426
40830
  const theme = resolveTheme(options.theme);
38427
- let animZoom = true;
38428
- let animPan = true;
38429
- if (typeof options.animations === "boolean") {
38430
- animZoom = options.animations;
38431
- animPan = options.animations;
38432
- } else if (options.animations) {
38433
- animZoom = options.animations.zoom ?? true;
38434
- animPan = options.animations.pan ?? true;
38435
- }
38436
40831
  const display = {
38437
40832
  currentPriceLine: options.currentPriceLine ?? true,
38438
40833
  logScale: options.logScale ?? false,
38439
40834
  nativeBackend: options.nativeBackend ?? "auto",
38440
- animZoom,
38441
- animPan,
40835
+ ...resolveAnimations(options.animations),
38442
40836
  glow: options.glow ?? 0,
38443
40837
  upColor: options.upColor ?? BULLISH,
38444
40838
  downColor: options.downColor ?? BEARISH,
@@ -38496,25 +40890,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38496
40890
  }
38497
40891
  /**
38498
40892
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
38499
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
38500
- * events) and is single-instance per type (a second call returns the existing handle). Returns
38501
- * a fail-soft handle for an unregistered type. Native renderer only.
40893
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
40894
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
40895
+ * call returns the existing handle; the classic studies allow several instances, so each call
40896
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
38502
40897
  */
38503
40898
  addNativeIndicator(type, options) {
38504
40899
  return this.orchestrator.addNativeIndicator(type, options);
38505
40900
  }
38506
40901
  /**
38507
40902
  * The catalog of built-in native indicators with their live state on this chart — each entry's
38508
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
38509
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
38510
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
38511
- * because support may probe the provider (a type may need data the symbol lacks).
40903
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
40904
+ * and whether it allows several instances (`multiInstance`; otherwise a second
40905
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
40906
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
40907
+ * provider (a type may need data the symbol lacks).
38512
40908
  */
38513
40909
  availableNativeIndicators() {
38514
40910
  return this.orchestrator.availableNativeIndicators();
38515
40911
  }
38516
40912
  /**
38517
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
40913
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
40914
+ * multi-instance type repeats) — the synchronous slice of
38518
40915
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
38519
40916
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
38520
40917
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -38881,12 +41278,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38881
41278
  return Math.round(Math.min(Math.max(px, lo), hi));
38882
41279
  }
38883
41280
 
38884
- // src/core/model/identity.ts
38885
- function stableSeriesId(parts3) {
38886
- const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
38887
- return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
38888
- }
38889
-
38890
41281
  // src/data/symbol-base.ts
38891
41282
  function baseOf(d) {
38892
41283
  const fromDesc = d.description?.split("/")[0]?.trim();
@@ -38988,9 +41379,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38988
41379
  for (const b of bars) byTime.set(b.time, b);
38989
41380
  return [...byTime.values()].sort((a, b) => a.time - b.time);
38990
41381
  }
38991
- function aggregate(sub, bucketMs) {
41382
+ function aggregate(sub2, bucketMs) {
38992
41383
  const buckets = /* @__PURE__ */ new Map();
38993
- for (const b of sub) {
41384
+ for (const b of sub2) {
38994
41385
  const key = Math.floor(b.time / bucketMs) * bucketMs;
38995
41386
  const cur = buckets.get(key);
38996
41387
  if (!cur) {
@@ -39042,8 +41433,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39042
41433
  }
39043
41434
  const ratio = targetMin / subMin;
39044
41435
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
39045
- const sub = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
39046
- const agg = aggregate(sub, targetMin * 6e4);
41436
+ const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
41437
+ const agg = aggregate(sub2, targetMin * 6e4);
39047
41438
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39048
41439
  } catch (e) {
39049
41440
  console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39374,9 +41765,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39374
41765
  for (const b of bars) byTime.set(b.time, b);
39375
41766
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39376
41767
  }
39377
- function aggregate2(sub, bucketMs) {
41768
+ function aggregate2(sub2, bucketMs) {
39378
41769
  const buckets = /* @__PURE__ */ new Map();
39379
- for (const b of sub) {
41770
+ for (const b of sub2) {
39380
41771
  const key = Math.floor(b.time / bucketMs) * bucketMs;
39381
41772
  const cur = buckets.get(key);
39382
41773
  if (!cur) {
@@ -39427,8 +41818,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39427
41818
  }
39428
41819
  const ratio = targetMin / subMin;
39429
41820
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
39430
- const sub = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
39431
- const agg = aggregate2(sub, targetMin * 6e4);
41821
+ const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
41822
+ const agg = aggregate2(sub2, targetMin * 6e4);
39432
41823
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39433
41824
  } catch (e) {
39434
41825
  console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39780,15 +42171,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39780
42171
  for (const b of bars) byTime.set(b.time, b);
39781
42172
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39782
42173
  }
39783
- function aggregate3(sub, bucketMs) {
39784
- return aggregateBy(sub, (t) => Math.floor(t / bucketMs) * bucketMs);
42174
+ function aggregate3(sub2, bucketMs) {
42175
+ return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
39785
42176
  }
39786
- function aggregateCalendar(sub, unit) {
39787
- return aggregateBy(sub, unit === "W" ? weekStartUTC : monthStartUTC);
42177
+ function aggregateCalendar(sub2, unit) {
42178
+ return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
39788
42179
  }
39789
- function aggregateBy(sub, keyMs) {
42180
+ function aggregateBy(sub2, keyMs) {
39790
42181
  const buckets = /* @__PURE__ */ new Map();
39791
- for (const b of sub) {
42182
+ for (const b of sub2) {
39792
42183
  const key = keyMs(b.time);
39793
42184
  const cur = buckets.get(key);
39794
42185
  if (!cur) {
@@ -39842,8 +42233,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39842
42233
  if (tf === "W" || tf === "M") {
39843
42234
  const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
39844
42235
  const subRange2 = range.from != null ? range : { ...range, limit: range.limit != null ? Math.ceil(range.limit * span / MS_PER_DAY) + 31 : void 0 };
39845
- const sub2 = await this.fetchCandles(product, 86400, subRange2);
39846
- return clampLimit(aggregateCalendar(sub2, tf), range.limit);
42236
+ const sub3 = await this.fetchCandles(product, 86400, subRange2);
42237
+ return clampLimit(aggregateCalendar(sub3, tf), range.limit);
39847
42238
  }
39848
42239
  const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
39849
42240
  const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
@@ -39853,8 +42244,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39853
42244
  }
39854
42245
  const ratio = targetMin / subMin;
39855
42246
  const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
39856
- const sub = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
39857
- return clampLimit(aggregate3(sub, targetMin * 6e4), range.limit);
42247
+ const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
42248
+ return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
39858
42249
  } catch (e) {
39859
42250
  console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
39860
42251
  return [];