@luxalgo/vela 0.6.15 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +8 -1
  2. package/dist/chunk-FE6V2YAQ.js +1 -0
  3. package/dist/{chunk-J5QIR2Z3.js → chunk-GIJYO4LO.js} +2436 -115
  4. package/dist/{chunk-32A7KHFM.js → chunk-I77F2SVE.js} +7 -1
  5. package/dist/{chunk-MWDNPGU3.js → chunk-MGU3USTP.js} +22 -15
  6. package/dist/{chunk-MWS4CUJL.js → chunk-Q3TFJNJW.js} +1 -1
  7. package/dist/{chunk-7C7NWEXH.js → chunk-SEOO6YG7.js} +82 -55
  8. package/dist/{contributions-x7iamlnd.d.cts → contributions-BEkxwA5F.d.cts} +27 -11
  9. package/dist/{contributions-h5BzEiV2.d.ts → contributions-CoAX7_SF.d.ts} +27 -11
  10. package/dist/index.cjs +2665 -337
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +4 -4
  14. package/dist/{plugin-BLaNPh55.d.ts → plugin-CNUgbRNq.d.ts} +1 -1
  15. package/dist/{plugin-2sJcf5mk.d.cts → plugin-Dxyq35Zf.d.cts} +1 -1
  16. package/dist/plugin.d.cts +2 -2
  17. package/dist/plugin.d.ts +2 -2
  18. package/dist/plugin.js +2 -2
  19. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-CbPza-Wc.d.cts} +1 -1
  20. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-D7cWkw8A.d.ts} +1 -1
  21. package/dist/ui.cjs +22 -15
  22. package/dist/ui.js +2 -2
  23. package/dist/vela.global.js +2501 -173
  24. package/dist/vela.global.min.js +52 -52
  25. package/dist/widget.cjs +2563 -202
  26. package/dist/widget.d.cts +4 -4
  27. package/dist/widget.d.ts +4 -4
  28. package/dist/widget.js +6 -6
  29. package/dist/workspace.cjs +2563 -202
  30. package/dist/workspace.d.cts +23 -12
  31. package/dist/workspace.d.ts +23 -12
  32. package/dist/workspace.js +5 -5
  33. package/package.json +1 -1
  34. package/dist/chunk-H26BEHF4.js +0 -7
@@ -111,7 +111,7 @@ var Vela = (function (exports) {
111
111
 
112
112
  // src/core/engine/IndicatorHandleImpl.ts
113
113
  var IndicatorHandleImpl = class {
114
- constructor(id, title, controller, source) {
114
+ constructor(id, title, controller, source, nativeType) {
115
115
  this.controller = controller;
116
116
  this.schema = [];
117
117
  this.propsSchema = [];
@@ -120,6 +120,7 @@ var Vela = (function (exports) {
120
120
  this.id = id;
121
121
  this.title = title;
122
122
  if (source !== void 0) this.source = source;
123
+ if (nativeType !== void 0) this.nativeType = nativeType;
123
124
  }
124
125
  get inputs() {
125
126
  return this.schema;
@@ -210,24 +211,24 @@ var Vela = (function (exports) {
210
211
  }
211
212
  function inspectModels(models) {
212
213
  const indicators = models.map(summarizeModel);
213
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
214
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
214
215
  return {
215
216
  indicators,
216
217
  totals: {
217
218
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
218
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
219
- fills: sum((s) => s.fills),
220
- backgrounds: sum((s) => s.backgrounds),
221
- priceLines: sum((s) => s.priceLines),
222
- lines: sum((s) => s.lines),
223
- boxes: sum((s) => s.boxes),
224
- labels: sum((s) => s.labels),
225
- polylines: sum((s) => s.polylines),
226
- linefills: sum((s) => s.linefills),
227
- tables: sum((s) => s.tables),
228
- barColors: sum((s) => s.barColors),
229
- trades: sum((s) => s.trades),
230
- forcedOverlay: sum((s) => s.forcedOverlay)
219
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
220
+ fills: sum2((s) => s.fills),
221
+ backgrounds: sum2((s) => s.backgrounds),
222
+ priceLines: sum2((s) => s.priceLines),
223
+ lines: sum2((s) => s.lines),
224
+ boxes: sum2((s) => s.boxes),
225
+ labels: sum2((s) => s.labels),
226
+ polylines: sum2((s) => s.polylines),
227
+ linefills: sum2((s) => s.linefills),
228
+ tables: sum2((s) => s.tables),
229
+ barColors: sum2((s) => s.barColors),
230
+ trades: sum2((s) => s.trades),
231
+ forcedOverlay: sum2((s) => s.forcedOverlay)
231
232
  }
232
233
  };
233
234
  }
@@ -6707,8 +6708,8 @@ var Vela = (function (exports) {
6707
6708
  this.port?.setExternalGhost?.(doc);
6708
6709
  }
6709
6710
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
6710
- setToolShortcuts(map) {
6711
- this.port?.setToolShortcuts?.(map);
6711
+ setToolShortcuts(map2) {
6712
+ this.port?.setToolShortcuts?.(map2);
6712
6713
  }
6713
6714
  // ── programmatic CRUD (facade-facing) ── each is one undo step
6714
6715
  add(type, init = {}) {
@@ -8432,16 +8433,20 @@ var Vela = (function (exports) {
8432
8433
  /**
8433
8434
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
8434
8435
  * 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'`.
8436
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
8437
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
8438
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
8439
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
8437
8440
  */
8438
8441
  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
8442
  const descriptor = getNativeIndicator(type);
8443
+ if (!descriptor?.multiInstance) {
8444
+ const existing = this.registry.all().find((r) => r.native?.type === type);
8445
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8446
+ }
8442
8447
  const id = this.registry.nextId("native");
8443
8448
  const title = descriptor?.title ?? type;
8444
- const handle = new IndicatorHandleImpl(id, title, this);
8449
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8445
8450
  this.handles.set(id, handle);
8446
8451
  if (!descriptor) {
8447
8452
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -8453,16 +8458,18 @@ var Vela = (function (exports) {
8453
8458
  void this.startNativeIndicator(id, handle);
8454
8459
  return handle;
8455
8460
  }
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). */
8461
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
8462
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
8463
8463
  presentNativeIndicators() {
8464
8464
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
8465
8465
  }
8466
+ /**
8467
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
8468
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
8469
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
8470
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
8471
+ * probe the provider for a required capability.
8472
+ */
8466
8473
  async availableNativeIndicators() {
8467
8474
  const symbol = this.qualifiedSymbol();
8468
8475
  const present = new Set(this.presentNativeIndicators());
@@ -8472,7 +8479,8 @@ var Vela = (function (exports) {
8472
8479
  title: d.title,
8473
8480
  supported: await this.isNativeSupported(d, symbol),
8474
8481
  present: present.has(d.type),
8475
- beta: d.beta
8482
+ beta: d.beta,
8483
+ ...d.multiInstance ? { multiInstance: true } : {}
8476
8484
  }))
8477
8485
  );
8478
8486
  }
@@ -9756,7 +9764,7 @@ var Vela = (function (exports) {
9756
9764
 
9757
9765
  // src/data/MultiProviderFeed.ts
9758
9766
  function intervalMs(timeframe) {
9759
- const map = {
9767
+ const map2 = {
9760
9768
  "1m": 6e4,
9761
9769
  "3m": 18e4,
9762
9770
  "5m": 3e5,
@@ -9778,7 +9786,7 @@ var Vela = (function (exports) {
9778
9786
  W: 6048e5,
9779
9787
  M: 2592e6
9780
9788
  };
9781
- return map[timeframe] ?? 36e5;
9789
+ return map2[timeframe] ?? 36e5;
9782
9790
  }
9783
9791
  var MultiProviderFeed = class {
9784
9792
  constructor(store = sharedBarStore) {
@@ -10165,8 +10173,8 @@ var Vela = (function (exports) {
10165
10173
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
10166
10174
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
10167
10175
  * platform formatting, so hints always match the host's actual bindings. */
10168
- setToolShortcuts(map) {
10169
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
10176
+ setToolShortcuts(map2) {
10177
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
10170
10178
  return this;
10171
10179
  }
10172
10180
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -10631,7 +10639,7 @@ var Vela = (function (exports) {
10631
10639
  }
10632
10640
 
10633
10641
  // node_modules/@zag-js/core/dist/merge-props.mjs
10634
- var clsx = (...args) => args.map((str3) => str3?.trim?.()).filter(Boolean).join(" ");
10642
+ var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
10635
10643
  var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
10636
10644
  var serialize = (style) => {
10637
10645
  const res = {};
@@ -10855,12 +10863,12 @@ var Vela = (function (exports) {
10855
10863
  return {
10856
10864
  and: (...guards) => {
10857
10865
  return function andGuard(params) {
10858
- return guards.every((str3) => params.guard(str3));
10866
+ return guards.every((str4) => params.guard(str4));
10859
10867
  };
10860
10868
  },
10861
10869
  or: (...guards) => {
10862
10870
  return function orGuard(params) {
10863
- return guards.some((str3) => params.guard(str3));
10871
+ return guards.some((str4) => params.guard(str4));
10864
10872
  };
10865
10873
  },
10866
10874
  not: (guard) => {
@@ -11421,9 +11429,9 @@ var Vela = (function (exports) {
11421
11429
  const { attributes, callback: fn } = options;
11422
11430
  const win = node.ownerDocument.defaultView || window;
11423
11431
  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);
11432
+ for (const change2 of changes) {
11433
+ if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
11434
+ fn(change2);
11427
11435
  }
11428
11436
  }
11429
11437
  });
@@ -11497,7 +11505,7 @@ var Vela = (function (exports) {
11497
11505
  }
11498
11506
 
11499
11507
  // node_modules/@zag-js/dom-query/dist/searchable.mjs
11500
- var sanitize = (str3) => str3.split("").map((char) => {
11508
+ var sanitize = (str4) => str4.split("").map((char) => {
11501
11509
  const code = char.charCodeAt(0);
11502
11510
  if (code > 0 && code < 128) return char;
11503
11511
  if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
@@ -12197,10 +12205,10 @@ var Vela = (function (exports) {
12197
12205
  fn?.(this.getParams());
12198
12206
  }
12199
12207
  });
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)}"`);
12208
+ __publicField(this, "guard", (str4) => {
12209
+ if (isFunction(str4)) return str4(this.getParams());
12210
+ const fn = this.machine.implementations?.guards?.[str4];
12211
+ if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
12204
12212
  return fn?.(this.getParams());
12205
12213
  });
12206
12214
  __publicField(this, "effect", (keys) => {
@@ -16978,19 +16986,19 @@ var Vela = (function (exports) {
16978
16986
  }
16979
16987
  setItems(items) {
16980
16988
  this.items = items;
16981
- for (const sub of this.subs.values()) sub.destroy();
16989
+ for (const sub2 of this.subs.values()) sub2.destroy();
16982
16990
  this.subs.clear();
16983
16991
  this.build();
16984
16992
  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);
16993
+ for (const sub2 of this.subs.values()) {
16994
+ api.setChild(sub2.handle.service);
16995
+ sub2.api.setParent(this.handle.service);
16988
16996
  }
16989
16997
  this.handle.flush();
16990
16998
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
16991
16999
  }
16992
17000
  destroy() {
16993
- for (const sub of this.subs.values()) sub.destroy();
17001
+ for (const sub2 of this.subs.values()) sub2.destroy();
16994
17002
  this.subs.clear();
16995
17003
  this.handle.stop();
16996
17004
  this.positioner.remove();
@@ -17004,9 +17012,9 @@ var Vela = (function (exports) {
17004
17012
  for (const li of this.list.children) {
17005
17013
  const id = li.dataset.veiId;
17006
17014
  if (!id) continue;
17007
- const sub = this.subs.get(id);
17008
- if (sub) {
17009
- spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
17015
+ const sub2 = this.subs.get(id);
17016
+ if (sub2) {
17017
+ spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
17010
17018
  continue;
17011
17019
  }
17012
17020
  const item = this.byId(id);
@@ -17083,7 +17091,7 @@ var Vela = (function (exports) {
17083
17091
  const arrow3 = iconEl("chevron-right", doc);
17084
17092
  arrow3.classList.add("vela-menu-arrow");
17085
17093
  li.appendChild(arrow3);
17086
- const sub = new _Surface(doc, {
17094
+ const sub2 = new _Surface(doc, {
17087
17095
  host: this.host,
17088
17096
  placement: "right-start",
17089
17097
  onSelect: this.onSelect,
@@ -17091,8 +17099,8 @@ var Vela = (function (exports) {
17091
17099
  checkmarks: this.checkmarks,
17092
17100
  id: `${this.mid}--${item.id}`
17093
17101
  });
17094
- sub.setItems(item.submenu ?? []);
17095
- this.subs.set(item.id, sub);
17102
+ sub2.setItems(item.submenu ?? []);
17103
+ this.subs.set(item.id, sub2);
17096
17104
  }
17097
17105
  this.list.appendChild(li);
17098
17106
  }
@@ -20047,8 +20055,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
20047
20055
  };
20048
20056
  const recentRow = document.createElement("div");
20049
20057
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
20058
+ const recentSwatches = document.createElement("div");
20059
+ recentSwatches.style.cssText = "display:contents;";
20060
+ const add = document.createElement("label");
20061
+ add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
20062
+ add.textContent = "+";
20063
+ const customInput = document.createElement("input");
20064
+ customInput.type = "color";
20065
+ customInput.value = curHex;
20066
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
20067
+ customInput.addEventListener("input", () => previewHex(customInput.value));
20068
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
20069
+ add.appendChild(customInput);
20070
+ recentRow.append(recentSwatches, add);
20050
20071
  const renderRecents = () => {
20051
- recentRow.replaceChildren();
20072
+ recentSwatches.replaceChildren();
20052
20073
  for (const c of recents) {
20053
20074
  const sw = document.createElement("button");
20054
20075
  sw.type = "button";
@@ -20057,18 +20078,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20057
20078
  e.stopPropagation();
20058
20079
  pickHex(c);
20059
20080
  });
20060
- recentRow.appendChild(sw);
20081
+ recentSwatches.appendChild(sw);
20061
20082
  }
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
20083
  };
20073
20084
  const opLabel = document.createElement("div");
20074
20085
  opLabel.textContent = "Opacity";
@@ -20108,14 +20119,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
20108
20119
  function emit() {
20109
20120
  onChange(combineColor(curHex, curAlpha));
20110
20121
  }
20111
- function pickHex(hex, custom = false) {
20122
+ function previewHex(hex) {
20112
20123
  curHex = splitColor(hex).hex6;
20113
- addRecent(curHex);
20114
20124
  paintSelection();
20115
20125
  paintOpacity();
20116
- if (custom) renderRecents();
20117
20126
  emit();
20118
20127
  }
20128
+ function pickHex(hex, custom = false) {
20129
+ previewHex(hex);
20130
+ addRecent(curHex);
20131
+ customInput.value = curHex;
20132
+ if (custom) renderRecents();
20133
+ }
20119
20134
  renderRecents();
20120
20135
  paintSelection();
20121
20136
  paintOpacity();
@@ -22021,8 +22036,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22021
22036
  lg.style.top = `${top}px`;
22022
22037
  }
22023
22038
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22024
- setCollapsedPanes(map) {
22025
- this.paneCollapse = map;
22039
+ setCollapsedPanes(map2) {
22040
+ this.paneCollapse = map2;
22026
22041
  }
22027
22042
  /**
22028
22043
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -22985,9 +23000,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22985
23000
  if (inst.enableKey) defaults2[inst.enableKey] = false;
22986
23001
  addRows(inst.rows);
22987
23002
  }
22988
- for (const sub of section.subsections ?? []) {
22989
- if (sub.enableKey) defaults2[sub.enableKey] = false;
22990
- addRows(sub.rows);
23003
+ for (const sub2 of section.subsections ?? []) {
23004
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
23005
+ addRows(sub2.rows);
22991
23006
  }
22992
23007
  if (!section.instances) addRows(section.rows);
22993
23008
  bag[t.id] = defaults2;
@@ -24316,11 +24331,11 @@ void main() {
24316
24331
  }
24317
24332
  }
24318
24333
  if (!any) return EMPTY_BARCOLORS;
24319
- const map = /* @__PURE__ */ new Map();
24334
+ const map2 = /* @__PURE__ */ new Map();
24320
24335
  for (const m of indicators.values()) {
24321
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24336
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24322
24337
  }
24323
- return map;
24338
+ return map2;
24324
24339
  }
24325
24340
 
24326
24341
  // src/renderers/native/core/ViewportState.ts
@@ -24617,9 +24632,9 @@ void main() {
24617
24632
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24618
24633
  return rightOffset + deltaX / barSpacing;
24619
24634
  }
24620
- function wheelPanDelta(deltaX, deltaY, shift3) {
24635
+ function wheelPanDelta(deltaX, deltaY, shift4) {
24621
24636
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24622
- if (shift3) return deltaY;
24637
+ if (shift4) return deltaY;
24623
24638
  return null;
24624
24639
  }
24625
24640
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -26241,11 +26256,11 @@ void main() {
26241
26256
  }
26242
26257
  }
26243
26258
  if (!any) return EMPTY_BARCOLORS2;
26244
- const map = /* @__PURE__ */ new Map();
26259
+ const map2 = /* @__PURE__ */ new Map();
26245
26260
  for (const m of indicators.values()) {
26246
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26261
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
26247
26262
  }
26248
- return map;
26263
+ return map2;
26249
26264
  }
26250
26265
 
26251
26266
  // src/renderers/shared/drawing-geometry.ts
@@ -27961,9 +27976,9 @@ void main() {
27961
27976
  };
27962
27977
  if (section.rows) addRows(section.rows);
27963
27978
  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);
27979
+ for (const sub2 of section.subsections ?? []) {
27980
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27981
+ addRows(sub2.rows);
27967
27982
  }
27968
27983
  }
27969
27984
  for (const hs of hostSections) {
@@ -28522,14 +28537,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28522
28537
  };
28523
28538
  if (section.rows) seed(section.rows);
28524
28539
  for (const inst of section.instances ?? []) seed(inst.rows);
28525
- for (const sub of section.subsections ?? []) seed(sub.rows);
28540
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28526
28541
  for (const inst of section.instances ?? []) {
28527
28542
  if (!inst.enableKey || inst.enableKey in bag) continue;
28528
28543
  bag[inst.enableKey] = values[inst.enableKey] === true;
28529
28544
  }
28530
- for (const sub of section.subsections ?? []) {
28531
- if (!sub.enableKey || sub.enableKey in bag) continue;
28532
- bag[sub.enableKey] = values[sub.enableKey] === true;
28545
+ for (const sub2 of section.subsections ?? []) {
28546
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28547
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28533
28548
  }
28534
28549
  const refreshers = [];
28535
28550
  const put = (key, v) => {
@@ -28546,16 +28561,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28546
28561
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28547
28562
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28548
28563
  }
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);
28564
+ for (const sub2 of section.subsections ?? []) {
28565
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28566
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28552
28567
  if (rows.length === 0) continue;
28553
- const subMarker = this.section(sub.title);
28568
+ const subMarker = this.section(sub2.title);
28554
28569
  subMarker.dataset.sdStyle = typeId;
28555
28570
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28556
28571
  subMarker.dataset.sdSub = "1";
28557
28572
  body.append(subMarker);
28558
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28573
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28559
28574
  }
28560
28575
  for (const r of refreshers) r();
28561
28576
  }
@@ -30663,11 +30678,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30663
30678
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30664
30679
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30665
30680
  * while placing a line tool it locks the segment angle to 45° steps. */
30666
- down(x, y, mode = "off", shift3 = false) {
30681
+ down(x, y, mode = "off", shift4 = false) {
30667
30682
  const proj = this.deps.projector();
30668
30683
  if (this.state.kind === "placing") {
30669
30684
  if (this.state.draft.placementMode() !== "click") return;
30670
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30685
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30671
30686
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30672
30687
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30673
30688
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30686,7 +30701,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30686
30701
  }
30687
30702
  const hit = this.hitAt(x, y);
30688
30703
  if (hit) {
30689
- if (shift3) {
30704
+ if (shift4) {
30690
30705
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30691
30706
  return;
30692
30707
  }
@@ -30696,7 +30711,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30696
30711
  }
30697
30712
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30698
30713
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30699
- move(x, y, mode = "off", shift3 = false) {
30714
+ move(x, y, mode = "off", shift4 = false) {
30700
30715
  if (this.state.kind === "idle") {
30701
30716
  const tool = this.deps.activeTool();
30702
30717
  const had = this.snapAt != null;
@@ -30710,7 +30725,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30710
30725
  this.captureFreehand(x, y);
30711
30726
  return;
30712
30727
  }
30713
- const pivot = shift3 ? this.placingPivot(this.state.draft) : null;
30728
+ const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
30714
30729
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30715
30730
  this.deps.changed();
30716
30731
  return;
@@ -30723,7 +30738,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30723
30738
  if (this.state.handle >= 0) {
30724
30739
  const a = d.anchors[this.state.handle];
30725
30740
  if (a) {
30726
- const pivot = shift3 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30741
+ const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30727
30742
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30728
30743
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30729
30744
  }
@@ -31421,21 +31436,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
31421
31436
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31422
31437
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31423
31438
  }
31424
- const vwap = drawing.vwap;
31425
- if (paths.has("vwap.midColor") && vwap) {
31439
+ const vwap2 = drawing.vwap;
31440
+ if (paths.has("vwap.midColor") && vwap2) {
31426
31441
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31427
31442
  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 }));
31443
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31444
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31430
31445
  bar.appendChild(
31431
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31446
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31432
31447
  label: (v) => `${v}\u03C3`,
31433
31448
  labelInTrigger: true
31434
31449
  })
31435
31450
  );
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 })));
31451
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31452
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31453
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31439
31454
  }
31440
31455
  if (paths.has("maxCurvature")) {
31441
31456
  const cur = drawing.maxCurvature ?? 24;
@@ -32181,8 +32196,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32181
32196
  }
32182
32197
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
32183
32198
  * right edge of the flyout rows, beside the favorite star. */
32184
- setShortcuts(map) {
32185
- this.shortcuts = new Map(Object.entries(map));
32199
+ setShortcuts(map2) {
32200
+ this.shortcuts = new Map(Object.entries(map2));
32186
32201
  }
32187
32202
  /** Paint one star for the current favorite state (filled + gold, or outline). */
32188
32203
  paintStar(el, type) {
@@ -33084,8 +33099,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33084
33099
  this.toolbar.setFavorites(types);
33085
33100
  }
33086
33101
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
33087
- setToolShortcuts(map) {
33088
- this.toolbar.setShortcuts(map);
33102
+ setToolShortcuts(map2) {
33103
+ this.toolbar.setShortcuts(map2);
33089
33104
  }
33090
33105
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
33091
33106
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -33203,7 +33218,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33203
33218
  this.render();
33204
33219
  }
33205
33220
  }
33206
- pointerDown(x, y, snap = "off", shift3 = false) {
33221
+ pointerDown(x, y, snap = "off", shift4 = false) {
33207
33222
  if (this.eraserMode) {
33208
33223
  this.erasing = true;
33209
33224
  this.deleteAt(x, y);
@@ -33223,9 +33238,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33223
33238
  return;
33224
33239
  }
33225
33240
  }
33226
- this.interaction.down(x, y, snap, shift3);
33241
+ this.interaction.down(x, y, snap, shift4);
33227
33242
  }
33228
- pointerMove(x, y, snap = "off", shift3 = false) {
33243
+ pointerMove(x, y, snap = "off", shift4 = false) {
33229
33244
  if (this.eraserMode) {
33230
33245
  if (this.erasing) this.deleteAt(x, y);
33231
33246
  return;
@@ -33236,7 +33251,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33236
33251
  this.render();
33237
33252
  return;
33238
33253
  }
33239
- this.interaction.move(x, y, snap, shift3);
33254
+ this.interaction.move(x, y, snap, shift4);
33240
33255
  this.updateHover(x, y);
33241
33256
  }
33242
33257
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -33913,14 +33928,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33913
33928
  }
33914
33929
  for (const { box, w: w2, h: h2 } of spanning) {
33915
33930
  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;
33931
+ let sum2 = 0;
33932
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
33933
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
33919
33934
  }
33920
33935
  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;
33936
+ let sum2 = 0;
33937
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
33938
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
33924
33939
  }
33925
33940
  }
33926
33941
  let w = 0;
@@ -34643,24 +34658,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34643
34658
  drawSvgShapes(el, alpha);
34644
34659
  continue;
34645
34660
  }
34646
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34661
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
34647
34662
  let path = null;
34648
34663
  if (tag === "path") {
34649
34664
  const d = el.getAttribute("d");
34650
34665
  if (d) path = new Path2D(d);
34651
34666
  } else if (tag === "circle") {
34652
34667
  path = new Path2D();
34653
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
34668
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
34654
34669
  } else if (tag === "ellipse") {
34655
34670
  path = new Path2D();
34656
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
34671
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
34657
34672
  } else if (tag === "rect") {
34658
34673
  path = new Path2D();
34659
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
34674
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
34660
34675
  } else if (tag === "line") {
34661
34676
  path = new Path2D();
34662
- path.moveTo(num3("x1"), num3("y1"));
34663
- path.lineTo(num3("x2"), num3("y2"));
34677
+ path.moveTo(num4("x1"), num4("y1"));
34678
+ path.lineTo(num4("x2"), num4("y2"));
34664
34679
  } else if (tag === "polyline" || tag === "polygon") {
34665
34680
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
34666
34681
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -36077,8 +36092,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36077
36092
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
36078
36093
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
36079
36094
  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),
36095
+ drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
36096
+ drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
36082
36097
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
36083
36098
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
36084
36099
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -36395,18 +36410,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36395
36410
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
36396
36411
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
36397
36412
  const newHeadTime = this.bars[0].time;
36398
- let shift3 = 0;
36413
+ let shift4 = 0;
36399
36414
  if (newHeadTime !== prevHeadTime) {
36400
36415
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
36401
- if (prepended > 0) shift3 = prepended;
36416
+ if (prepended > 0) shift4 = prepended;
36402
36417
  else {
36403
36418
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
36404
- if (trimmed > 0) shift3 = -trimmed;
36419
+ if (trimmed > 0) shift4 = -trimmed;
36405
36420
  }
36406
36421
  }
36407
- if (shift3 !== 0) {
36408
- this.zoomAnchorLogical += shift3;
36409
- if (this.hoverLogical != null) this.hoverLogical += shift3;
36422
+ if (shift4 !== 0) {
36423
+ this.zoomAnchorLogical += shift4;
36424
+ if (this.hoverLogical != null) this.hoverLogical += shift4;
36410
36425
  }
36411
36426
  }
36412
36427
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -38012,15 +38027,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38012
38027
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
38013
38028
  * strip — merged own-scale indicators are hidden while collapsed. */
38014
38029
  collapsedMasterMap() {
38015
- const map = /* @__PURE__ */ new Map();
38030
+ const map2 = /* @__PURE__ */ new Map();
38016
38031
  for (const pane of this.scene.panes.values()) {
38017
38032
  if (!pane.collapsed) continue;
38018
38033
  const models = this.scene.orderedIndicatorsForPane(pane.id);
38019
38034
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
38020
38035
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
38021
- map.set(pane.id, master?.id ?? null);
38036
+ map2.set(pane.id, master?.id ?? null);
38022
38037
  }
38023
- return map;
38038
+ return map2;
38024
38039
  }
38025
38040
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
38026
38041
  setToolbarGutter(px) {
@@ -38416,12 +38431,2328 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38416
38431
  registerNativeIndicator(vpvrDescriptor);
38417
38432
  }
38418
38433
 
38434
+ // src/core/model/identity.ts
38435
+ function stableSeriesId(parts3) {
38436
+ const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
38437
+ return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
38438
+ }
38439
+
38440
+ // src/core/native-indicators/classics/define.ts
38441
+ function num3(inputs, key, fallback) {
38442
+ const v = inputs[key];
38443
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
38444
+ }
38445
+ function str3(inputs, key, fallback) {
38446
+ const v = inputs[key];
38447
+ return typeof v === "string" && v !== "" ? v : fallback;
38448
+ }
38449
+ function toPoints(bars, values, colors) {
38450
+ const pts = new Array(bars.length);
38451
+ for (let i = 0; i < bars.length; i++) {
38452
+ const v = values[i];
38453
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
38454
+ const c = colors?.[i];
38455
+ if (c != null) p.color = c;
38456
+ pts[i] = p;
38457
+ }
38458
+ return pts;
38459
+ }
38460
+ var ClassicIndicator = class {
38461
+ constructor(spec) {
38462
+ this.spec = spec;
38463
+ this.inputs = {};
38464
+ }
38465
+ start(ctx, inputs) {
38466
+ this.ctx = ctx;
38467
+ this.inputs = inputs;
38468
+ this.recompute();
38469
+ }
38470
+ onBars() {
38471
+ this.recompute();
38472
+ }
38473
+ onViewport() {
38474
+ }
38475
+ setInputs(inputs) {
38476
+ this.inputs = inputs;
38477
+ this.recompute();
38478
+ }
38479
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
38480
+ suspend() {
38481
+ }
38482
+ resume() {
38483
+ this.recompute();
38484
+ }
38485
+ stop() {
38486
+ }
38487
+ recompute() {
38488
+ const bars = this.ctx.bars();
38489
+ const out = this.spec.compute(bars, this.inputs);
38490
+ const instanceId = this.spec.type;
38491
+ const series = [];
38492
+ const idsByKey = /* @__PURE__ */ new Map();
38493
+ out.plots.forEach((plot, ordinal) => {
38494
+ const kind = plot.kind ?? "line";
38495
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
38496
+ idsByKey.set(plot.key, id);
38497
+ series.push({
38498
+ id,
38499
+ title: plot.title,
38500
+ paneId: "",
38501
+ // stamped by the orchestrator
38502
+ kind,
38503
+ points: toPoints(bars, plot.values, plot.colors),
38504
+ style: {
38505
+ color: plot.color,
38506
+ width: plot.width ?? 1,
38507
+ lineStyle: plot.lineStyle ?? "solid",
38508
+ ...plot.base != null ? { base: plot.base } : {}
38509
+ },
38510
+ ...plot.overlay ? { overlay: true } : {}
38511
+ });
38512
+ });
38513
+ if (out.markers && out.markers.length > 0) {
38514
+ series.push({
38515
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
38516
+ title: this.spec.shortTitle ?? this.spec.title,
38517
+ paneId: "",
38518
+ kind: "markers",
38519
+ markers: out.markers
38520
+ });
38521
+ }
38522
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
38523
+ const from = idsByKey.get(band.from);
38524
+ const to = idsByKey.get(band.to);
38525
+ if (!from || !to) return [];
38526
+ return [
38527
+ {
38528
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
38529
+ paneId: "",
38530
+ fromSeriesId: from,
38531
+ toSeriesId: to,
38532
+ color: band.color
38533
+ }
38534
+ ];
38535
+ });
38536
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
38537
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
38538
+ paneId: "",
38539
+ price: level.price,
38540
+ ...level.color != null ? { color: level.color } : {},
38541
+ lineStyle: level.lineStyle ?? "dashed",
38542
+ width: 1,
38543
+ ...level.title != null ? { title: level.title } : {}
38544
+ }));
38545
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
38546
+ const { key, ...rest } = poly;
38547
+ return {
38548
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
38549
+ paneId: "",
38550
+ ...rest
38551
+ };
38552
+ });
38553
+ this.ctx.emit({ series, fills, priceLines, polylines });
38554
+ this.ctx.setStatus("idle");
38555
+ }
38556
+ };
38557
+ function classicDescriptor(spec) {
38558
+ return {
38559
+ type: spec.type,
38560
+ title: spec.title,
38561
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
38562
+ paneHint: spec.overlay ? "price" : "new",
38563
+ overlay: spec.overlay,
38564
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
38565
+ multiInstance: true,
38566
+ inputsSchema: () => spec.inputs,
38567
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
38568
+ create: () => new ClassicIndicator(spec)
38569
+ };
38570
+ }
38571
+
38572
+ // src/core/native-indicators/classics/math.ts
38573
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
38574
+ function sourceValues(bars, source) {
38575
+ const out = new Array(bars.length);
38576
+ for (let i = 0; i < bars.length; i++) {
38577
+ const b = bars[i];
38578
+ switch (source) {
38579
+ case "Open":
38580
+ out[i] = b.open;
38581
+ break;
38582
+ case "High":
38583
+ out[i] = b.high;
38584
+ break;
38585
+ case "Low":
38586
+ out[i] = b.low;
38587
+ break;
38588
+ case "HL2":
38589
+ out[i] = (b.high + b.low) / 2;
38590
+ break;
38591
+ case "HLC3":
38592
+ out[i] = (b.high + b.low + b.close) / 3;
38593
+ break;
38594
+ case "OHLC4":
38595
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
38596
+ break;
38597
+ case "HLCC4":
38598
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
38599
+ break;
38600
+ default:
38601
+ out[i] = b.close;
38602
+ }
38603
+ }
38604
+ return out;
38605
+ }
38606
+ function volumes(bars) {
38607
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
38608
+ }
38609
+ function highs(bars) {
38610
+ return bars.map((b) => b.high);
38611
+ }
38612
+ function lows(bars) {
38613
+ return bars.map((b) => b.low);
38614
+ }
38615
+ function closes(bars) {
38616
+ return bars.map((b) => b.close);
38617
+ }
38618
+ function opens(bars) {
38619
+ return bars.map((b) => b.open);
38620
+ }
38621
+ function zip(a, b, f) {
38622
+ const out = new Array(a.length);
38623
+ for (let i = 0; i < a.length; i++) {
38624
+ const x = a[i];
38625
+ const y = b[i];
38626
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
38627
+ }
38628
+ return out;
38629
+ }
38630
+ function map(a, f) {
38631
+ const out = new Array(a.length);
38632
+ for (let i = 0; i < a.length; i++) {
38633
+ const x = a[i];
38634
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
38635
+ }
38636
+ return out;
38637
+ }
38638
+ function sub(a, b) {
38639
+ return zip(a, b, (x, y) => x - y);
38640
+ }
38641
+ function windowed(v, len, fold) {
38642
+ const out = new Array(v.length).fill(Number.NaN);
38643
+ if (len <= 0) return out;
38644
+ let run = 0;
38645
+ for (let i = 0; i < v.length; i++) {
38646
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
38647
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
38648
+ }
38649
+ return out;
38650
+ }
38651
+ function sum(v, len) {
38652
+ return windowed(v, len, (w, from, to) => {
38653
+ let s = 0;
38654
+ for (let k = from; k <= to; k++) s += w[k];
38655
+ return s;
38656
+ });
38657
+ }
38658
+ function sma(v, len) {
38659
+ return windowed(v, len, (w, from, to) => {
38660
+ let s = 0;
38661
+ for (let k = from; k <= to; k++) s += w[k];
38662
+ return s / len;
38663
+ });
38664
+ }
38665
+ function wma(v, len) {
38666
+ const norm = len * (len + 1) / 2;
38667
+ return windowed(v, len, (w, from, to) => {
38668
+ let s = 0;
38669
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
38670
+ return s / norm;
38671
+ });
38672
+ }
38673
+ function vwma(src, vol, len) {
38674
+ const pv = zip(src, vol, (p, q) => p * q);
38675
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
38676
+ }
38677
+ function stdev(v, len) {
38678
+ return windowed(v, len, (w, from, to) => {
38679
+ let s = 0;
38680
+ for (let k = from; k <= to; k++) s += w[k];
38681
+ const mean = s / len;
38682
+ let sq = 0;
38683
+ for (let k = from; k <= to; k++) {
38684
+ const d = w[k] - mean;
38685
+ sq += d * d;
38686
+ }
38687
+ return Math.sqrt(sq / len);
38688
+ });
38689
+ }
38690
+ function highest(v, len) {
38691
+ return windowed(v, len, (w, from, to) => {
38692
+ let m = -Infinity;
38693
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
38694
+ return m;
38695
+ });
38696
+ }
38697
+ function lowest(v, len) {
38698
+ return windowed(v, len, (w, from, to) => {
38699
+ let m = Infinity;
38700
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
38701
+ return m;
38702
+ });
38703
+ }
38704
+ function barsSinceHighest(v, len) {
38705
+ return windowed(v, len, (w, from, to) => {
38706
+ let m = -Infinity;
38707
+ let at = to;
38708
+ for (let k = from; k <= to; k++) {
38709
+ if (w[k] >= m) {
38710
+ m = w[k];
38711
+ at = k;
38712
+ }
38713
+ }
38714
+ return to - at;
38715
+ });
38716
+ }
38717
+ function barsSinceLowest(v, len) {
38718
+ return windowed(v, len, (w, from, to) => {
38719
+ let m = Infinity;
38720
+ let at = to;
38721
+ for (let k = from; k <= to; k++) {
38722
+ if (w[k] <= m) {
38723
+ m = w[k];
38724
+ at = k;
38725
+ }
38726
+ }
38727
+ return to - at;
38728
+ });
38729
+ }
38730
+ function recursive(v, len, alpha) {
38731
+ const out = new Array(v.length).fill(Number.NaN);
38732
+ let run = 0;
38733
+ let prev3 = Number.NaN;
38734
+ for (let i = 0; i < v.length; i++) {
38735
+ const x = v[i];
38736
+ if (!Number.isFinite(x)) {
38737
+ run = 0;
38738
+ prev3 = Number.NaN;
38739
+ continue;
38740
+ }
38741
+ run += 1;
38742
+ if (Number.isFinite(prev3)) {
38743
+ prev3 = alpha * x + (1 - alpha) * prev3;
38744
+ out[i] = prev3;
38745
+ } else if (run >= len) {
38746
+ let s = 0;
38747
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
38748
+ prev3 = s / len;
38749
+ out[i] = prev3;
38750
+ }
38751
+ }
38752
+ return out;
38753
+ }
38754
+ function ema(v, len) {
38755
+ return recursive(v, len, 2 / (len + 1));
38756
+ }
38757
+ function rma(v, len) {
38758
+ return recursive(v, len, 1 / len);
38759
+ }
38760
+ function change(v, n = 1) {
38761
+ const out = new Array(v.length).fill(Number.NaN);
38762
+ for (let i = n; i < v.length; i++) {
38763
+ const a = v[i];
38764
+ const b = v[i - n];
38765
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
38766
+ }
38767
+ return out;
38768
+ }
38769
+ function roc(v, len) {
38770
+ const out = new Array(v.length).fill(Number.NaN);
38771
+ for (let i = len; i < v.length; i++) {
38772
+ const a = v[i];
38773
+ const b = v[i - len];
38774
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
38775
+ }
38776
+ return out;
38777
+ }
38778
+ function shift3(v, n) {
38779
+ const out = new Array(v.length).fill(Number.NaN);
38780
+ for (let i = 0; i < v.length; i++) {
38781
+ const j = i - n;
38782
+ if (j >= 0 && j < v.length) out[i] = v[j];
38783
+ }
38784
+ return out;
38785
+ }
38786
+ function trueRange(bars) {
38787
+ const out = new Array(bars.length).fill(Number.NaN);
38788
+ for (let i = 0; i < bars.length; i++) {
38789
+ const b = bars[i];
38790
+ if (i === 0) {
38791
+ out[i] = b.high - b.low;
38792
+ continue;
38793
+ }
38794
+ const pc = bars[i - 1].close;
38795
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
38796
+ }
38797
+ return out;
38798
+ }
38799
+ function atr(bars, len) {
38800
+ return rma(trueRange(bars), len);
38801
+ }
38802
+ function rsi(v, len) {
38803
+ const d = change(v);
38804
+ const up = map(d, (x) => Math.max(x, 0));
38805
+ const dn = map(d, (x) => Math.max(-x, 0));
38806
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
38807
+ }
38808
+ function stoch(v, hi, lo, len) {
38809
+ const hh = highest(hi, len);
38810
+ const ll = lowest(lo, len);
38811
+ const out = new Array(v.length).fill(Number.NaN);
38812
+ for (let i = 0; i < v.length; i++) {
38813
+ const x = v[i];
38814
+ const h = hh[i];
38815
+ const l = ll[i];
38816
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
38817
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
38818
+ }
38819
+ return out;
38820
+ }
38821
+ function linreg(v, len) {
38822
+ const sx = (len - 1) * len / 2;
38823
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
38824
+ const denom = len * sxx - sx * sx;
38825
+ return windowed(v, len, (w, from, to) => {
38826
+ let sy = 0;
38827
+ let sxy = 0;
38828
+ for (let k = from; k <= to; k++) {
38829
+ const x = k - from;
38830
+ sy += w[k];
38831
+ sxy += x * w[k];
38832
+ }
38833
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
38834
+ const intercept = (sy - slope * sx) / len;
38835
+ return intercept + slope * (len - 1);
38836
+ });
38837
+ }
38838
+ function swma(v) {
38839
+ const out = new Array(v.length).fill(Number.NaN);
38840
+ for (let i = 3; i < v.length; i++) {
38841
+ const a = v[i - 3];
38842
+ const b = v[i - 2];
38843
+ const c = v[i - 1];
38844
+ const d = v[i];
38845
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
38846
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
38847
+ }
38848
+ }
38849
+ return out;
38850
+ }
38851
+ function percentRank(v, len) {
38852
+ const out = new Array(v.length).fill(Number.NaN);
38853
+ for (let i = len; i < v.length; i++) {
38854
+ const x = v[i];
38855
+ if (!Number.isFinite(x)) continue;
38856
+ let below = 0;
38857
+ let count = 0;
38858
+ for (let k = i - len; k < i; k++) {
38859
+ const y = v[k];
38860
+ if (!Number.isFinite(y)) {
38861
+ count = -1;
38862
+ break;
38863
+ }
38864
+ count += 1;
38865
+ if (y <= x) below += 1;
38866
+ }
38867
+ if (count === len) out[i] = 100 * below / len;
38868
+ }
38869
+ return out;
38870
+ }
38871
+ function cumSum(v) {
38872
+ const out = new Array(v.length).fill(Number.NaN);
38873
+ let acc = 0;
38874
+ let seen = false;
38875
+ for (let i = 0; i < v.length; i++) {
38876
+ const x = v[i];
38877
+ if (Number.isFinite(x)) {
38878
+ acc += x;
38879
+ seen = true;
38880
+ }
38881
+ if (seen) out[i] = acc;
38882
+ }
38883
+ return out;
38884
+ }
38885
+
38886
+ // src/core/native-indicators/classics/shared.ts
38887
+ function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
38888
+ return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
38889
+ }
38890
+ function sourceInput(defval = "Close") {
38891
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
38892
+ }
38893
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
38894
+ return { key, title, type: "color", defval };
38895
+ }
38896
+ function withAlpha2(hex, alpha = 0.08) {
38897
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
38898
+ if (!m) return hex;
38899
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
38900
+ return `#${m[1]}${a}`;
38901
+ }
38902
+
38903
+ // src/core/native-indicators/classics/averages.ts
38904
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
38905
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
38906
+ function fixedAverage(type, title, shortTitle, smooth) {
38907
+ return {
38908
+ type,
38909
+ title,
38910
+ shortTitle,
38911
+ overlay: true,
38912
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
38913
+ compute: (bars, inputs) => {
38914
+ const len = num3(inputs, "length", 20);
38915
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
38916
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
38917
+ if (offset3 !== 0) values = shift3(values, offset3);
38918
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38919
+ }
38920
+ };
38921
+ }
38922
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
38923
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
38924
+ var movingAverage = {
38925
+ type: "moving-average",
38926
+ title: "Moving Average",
38927
+ shortTitle: "MA",
38928
+ overlay: true,
38929
+ inputs: [
38930
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
38931
+ lengthInput(20),
38932
+ sourceInput(),
38933
+ offsetInput,
38934
+ colorInput()
38935
+ ],
38936
+ compute: (bars, inputs) => {
38937
+ const len = num3(inputs, "length", 20);
38938
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38939
+ const kind = str3(inputs, "maType", "SMA");
38940
+ let values;
38941
+ switch (kind) {
38942
+ case "EMA":
38943
+ values = ema(src, len);
38944
+ break;
38945
+ case "WMA":
38946
+ values = wma(src, len);
38947
+ break;
38948
+ case "RMA":
38949
+ values = rma(src, len);
38950
+ break;
38951
+ case "VWMA":
38952
+ values = vwma(src, volumes(bars), len);
38953
+ break;
38954
+ default:
38955
+ values = sma(src, len);
38956
+ }
38957
+ const offset3 = Math.trunc(num3(inputs, "offset", 0));
38958
+ if (offset3 !== 0) values = shift3(values, offset3);
38959
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38960
+ }
38961
+ };
38962
+ var smoothedMa = {
38963
+ type: "rma",
38964
+ title: "Smoothed Moving Average",
38965
+ shortTitle: "RMA",
38966
+ overlay: true,
38967
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38968
+ compute: (bars, inputs) => ({
38969
+ plots: [
38970
+ {
38971
+ key: "rma",
38972
+ title: "RMA",
38973
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38974
+ color: str3(inputs, "color", SERIES_LINE),
38975
+ width: 2
38976
+ }
38977
+ ]
38978
+ })
38979
+ };
38980
+ var zlema = {
38981
+ type: "zlema",
38982
+ title: "Zero-Lag Exponential Moving Average",
38983
+ shortTitle: "ZLEMA",
38984
+ overlay: true,
38985
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38986
+ compute: (bars, inputs) => {
38987
+ const len = num3(inputs, "length", 14);
38988
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38989
+ const lag = Math.floor((len - 1) / 2);
38990
+ const lagged = shift3(src, lag);
38991
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
38992
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38993
+ }
38994
+ };
38995
+ var vidya = {
38996
+ type: "vidya",
38997
+ title: "Variable Index Dynamic Average",
38998
+ shortTitle: "VIDYA",
38999
+ overlay: true,
39000
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
39001
+ compute: (bars, inputs) => {
39002
+ const len = num3(inputs, "length", 14);
39003
+ const cmoLen = num3(inputs, "cmoLength", 9);
39004
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39005
+ const d = change(src);
39006
+ const su = new Array(src.length).fill(Number.NaN);
39007
+ const sd = new Array(src.length).fill(Number.NaN);
39008
+ for (let i = cmoLen; i < src.length; i++) {
39009
+ let u = 0;
39010
+ let w = 0;
39011
+ let ok = true;
39012
+ for (let k = i - cmoLen + 1; k <= i; k++) {
39013
+ const x = d[k];
39014
+ if (!Number.isFinite(x)) {
39015
+ ok = false;
39016
+ break;
39017
+ }
39018
+ if (x > 0) u += x;
39019
+ else w -= x;
39020
+ }
39021
+ if (ok) {
39022
+ su[i] = u;
39023
+ sd[i] = w;
39024
+ }
39025
+ }
39026
+ const alpha = 2 / (len + 1);
39027
+ const out = new Array(src.length).fill(Number.NaN);
39028
+ let prev3 = Number.NaN;
39029
+ for (let i = 0; i < src.length; i++) {
39030
+ const x = src[i];
39031
+ const u = su[i];
39032
+ const w = sd[i];
39033
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
39034
+ const total = u + w;
39035
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
39036
+ prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
39037
+ out[i] = prev3;
39038
+ }
39039
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39040
+ }
39041
+ };
39042
+ var maEnvelope = {
39043
+ type: "ma-envelope",
39044
+ title: "Moving Average Envelope",
39045
+ shortTitle: "MA Env",
39046
+ overlay: true,
39047
+ inputs: [
39048
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
39049
+ lengthInput(20),
39050
+ sourceInput(),
39051
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
39052
+ colorInput()
39053
+ ],
39054
+ compute: (bars, inputs) => {
39055
+ const len = num3(inputs, "length", 20);
39056
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39057
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
39058
+ const k = num3(inputs, "percent", 2.5) / 100;
39059
+ const ink = str3(inputs, "color", SERIES_LINE);
39060
+ return {
39061
+ plots: [
39062
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
39063
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
39064
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
39065
+ ],
39066
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
39067
+ };
39068
+ }
39069
+ };
39070
+ var linearRegression = {
39071
+ type: "linear-regression",
39072
+ title: "Linear Regression Curve",
39073
+ shortTitle: "LinReg",
39074
+ overlay: true,
39075
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39076
+ compute: (bars, inputs) => ({
39077
+ plots: [
39078
+ {
39079
+ key: "linreg",
39080
+ title: "LinReg",
39081
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39082
+ color: str3(inputs, "color", SERIES_LINE),
39083
+ width: 2
39084
+ }
39085
+ ]
39086
+ })
39087
+ };
39088
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
39089
+
39090
+ // src/core/native-indicators/classics/bands.ts
39091
+ function bollinger(bars, inputs) {
39092
+ const len = num3(inputs, "length", 20);
39093
+ const mult = num3(inputs, "mult", 2);
39094
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39095
+ const basis = sma(src, len);
39096
+ const dev = map(stdev(src, len), (x) => x * mult);
39097
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
39098
+ }
39099
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
39100
+ var bollingerBands = {
39101
+ type: "bollinger-bands",
39102
+ title: "Bollinger Bands",
39103
+ shortTitle: "BB",
39104
+ overlay: true,
39105
+ inputs: [...BB_INPUTS, colorInput()],
39106
+ compute: (bars, inputs) => {
39107
+ const { basis, upper, lower } = bollinger(bars, inputs);
39108
+ const ink = str3(inputs, "color", SERIES_LINE);
39109
+ return {
39110
+ plots: [
39111
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39112
+ { key: "upper", title: "Upper", values: upper, color: ink },
39113
+ { key: "lower", title: "Lower", values: lower, color: ink }
39114
+ ],
39115
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
39116
+ };
39117
+ }
39118
+ };
39119
+ var percentB = {
39120
+ type: "percent-b",
39121
+ title: "Bollinger Bands %B",
39122
+ shortTitle: "%B",
39123
+ overlay: false,
39124
+ inputs: [...BB_INPUTS, colorInput()],
39125
+ compute: (bars, inputs) => {
39126
+ const { upper, lower } = bollinger(bars, inputs);
39127
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39128
+ const values = src.map((x, i) => {
39129
+ const u = upper[i];
39130
+ const l = lower[i];
39131
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
39132
+ });
39133
+ return {
39134
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39135
+ levels: [
39136
+ { key: "upper", price: 1, color: NEUTRAL },
39137
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
39138
+ { key: "lower", price: 0, color: NEUTRAL }
39139
+ ]
39140
+ };
39141
+ }
39142
+ };
39143
+ var bandwidth = {
39144
+ type: "bandwidth",
39145
+ title: "Bollinger Bands Width",
39146
+ shortTitle: "BandWidth",
39147
+ overlay: false,
39148
+ inputs: [...BB_INPUTS, colorInput()],
39149
+ compute: (bars, inputs) => {
39150
+ const { basis, upper, lower } = bollinger(bars, inputs);
39151
+ const values = basis.map((b, i) => {
39152
+ const u = upper[i];
39153
+ const l = lower[i];
39154
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
39155
+ });
39156
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39157
+ }
39158
+ };
39159
+ var keltner = {
39160
+ type: "keltner-channels",
39161
+ title: "Keltner Channels",
39162
+ shortTitle: "KC",
39163
+ overlay: true,
39164
+ inputs: [
39165
+ lengthInput(20),
39166
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39167
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39168
+ sourceInput(),
39169
+ colorInput()
39170
+ ],
39171
+ compute: (bars, inputs) => {
39172
+ const len = num3(inputs, "length", 20);
39173
+ const mult = num3(inputs, "mult", 2);
39174
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
39175
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
39176
+ const ink = str3(inputs, "color", SERIES_LINE);
39177
+ return {
39178
+ plots: [
39179
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
39180
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
39181
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
39182
+ ],
39183
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39184
+ };
39185
+ }
39186
+ };
39187
+ var donchian = {
39188
+ type: "donchian-channels",
39189
+ title: "Donchian Channels",
39190
+ shortTitle: "DC",
39191
+ overlay: true,
39192
+ inputs: [lengthInput(20), colorInput()],
39193
+ compute: (bars, inputs) => {
39194
+ const len = num3(inputs, "length", 20);
39195
+ const upper = highest(highs(bars), len);
39196
+ const lower = lowest(lows(bars), len);
39197
+ const ink = str3(inputs, "color", SERIES_LINE);
39198
+ return {
39199
+ plots: [
39200
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
39201
+ { key: "upper", title: "Upper", values: upper, color: ink },
39202
+ { key: "lower", title: "Lower", values: lower, color: ink }
39203
+ ],
39204
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
39205
+ };
39206
+ }
39207
+ };
39208
+ var chandelier = {
39209
+ type: "chandelier-exit",
39210
+ title: "Chandelier Exit",
39211
+ shortTitle: "CE",
39212
+ overlay: true,
39213
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
39214
+ compute: (bars, inputs) => {
39215
+ const len = num3(inputs, "length", 22);
39216
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
39217
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
39218
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
39219
+ return {
39220
+ plots: [
39221
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
39222
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
39223
+ ]
39224
+ };
39225
+ }
39226
+ };
39227
+ var chandeKroll = {
39228
+ type: "chande-kroll-stop",
39229
+ title: "Chande Kroll Stop",
39230
+ shortTitle: "CKS",
39231
+ overlay: true,
39232
+ inputs: [
39233
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39234
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
39235
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
39236
+ ],
39237
+ compute: (bars, inputs) => {
39238
+ const p = num3(inputs, "p", 10);
39239
+ const x = num3(inputs, "x", 1);
39240
+ const q = num3(inputs, "q", 9);
39241
+ const range = map(atr(bars, p), (v) => v * x);
39242
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
39243
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
39244
+ return {
39245
+ plots: [
39246
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
39247
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
39248
+ ]
39249
+ };
39250
+ }
39251
+ };
39252
+ var supertrend = {
39253
+ type: "supertrend",
39254
+ title: "SuperTrend",
39255
+ shortTitle: "SuperTrend",
39256
+ overlay: true,
39257
+ inputs: [
39258
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39259
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
39260
+ ],
39261
+ compute: (bars, inputs) => {
39262
+ const len = num3(inputs, "atrLength", 10);
39263
+ const mult = num3(inputs, "mult", 3);
39264
+ const range = atr(bars, len);
39265
+ const n = bars.length;
39266
+ const st = new Array(n).fill(Number.NaN);
39267
+ const dir = new Array(n).fill(1);
39268
+ let up = Number.NaN;
39269
+ let dn = Number.NaN;
39270
+ let trend = 1;
39271
+ for (let i = 0; i < n; i++) {
39272
+ const r = range[i];
39273
+ if (!Number.isFinite(r)) continue;
39274
+ const b = bars[i];
39275
+ const mid = (b.high + b.low) / 2;
39276
+ const basicUp = mid - mult * r;
39277
+ const basicDn = mid + mult * r;
39278
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
39279
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
39280
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
39281
+ if (trend === 1 && b.close < up) trend = -1;
39282
+ else if (trend === -1 && b.close > dn) trend = 1;
39283
+ dir[i] = trend;
39284
+ st[i] = trend === 1 ? up : dn;
39285
+ }
39286
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
39287
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
39288
+ }
39289
+ };
39290
+ var alligator = {
39291
+ type: "williams-alligator",
39292
+ title: "Williams Alligator",
39293
+ shortTitle: "Alligator",
39294
+ overlay: true,
39295
+ inputs: [
39296
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
39297
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
39298
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
39299
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
39300
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
39301
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
39302
+ ],
39303
+ compute: (bars, inputs) => {
39304
+ const hl2 = sourceValues(bars, "HL2");
39305
+ const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
39306
+ const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
39307
+ const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
39308
+ return {
39309
+ plots: [
39310
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
39311
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
39312
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
39313
+ ]
39314
+ };
39315
+ }
39316
+ };
39317
+ var gator = {
39318
+ type: "gator-oscillator",
39319
+ title: "Gator Oscillator",
39320
+ shortTitle: "Gator Oscillator",
39321
+ overlay: false,
39322
+ inputs: [],
39323
+ compute: (bars) => {
39324
+ const hl2 = sourceValues(bars, "HL2");
39325
+ const jaw = shift3(rma(hl2, 13), 8);
39326
+ const teeth = shift3(rma(hl2, 8), 5);
39327
+ const lips = shift3(rma(hl2, 5), 3);
39328
+ const upper = map(sub(jaw, teeth), Math.abs);
39329
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
39330
+ const colorByExpansion = (v) => v.map((x, i) => {
39331
+ if (!Number.isFinite(x)) return null;
39332
+ const prev3 = i > 0 ? v[i - 1] : Number.NaN;
39333
+ return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
39334
+ });
39335
+ return {
39336
+ plots: [
39337
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
39338
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
39339
+ ]
39340
+ };
39341
+ }
39342
+ };
39343
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
39344
+
39345
+ // src/core/native-indicators/classics/oscillators.ts
39346
+ function guideLevels(low, high, mid) {
39347
+ const levels = [
39348
+ { key: "upper", price: high, color: NEUTRAL },
39349
+ { key: "lower", price: low, color: NEUTRAL }
39350
+ ];
39351
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
39352
+ return levels;
39353
+ }
39354
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
39355
+ function histColors(values) {
39356
+ return values.map((x, i) => {
39357
+ if (!Number.isFinite(x)) return null;
39358
+ const prev3 = i > 0 ? values[i - 1] : Number.NaN;
39359
+ const rising = Number.isFinite(prev3) ? x >= prev3 : true;
39360
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
39361
+ return rising ? `${BEARISH}80` : BEARISH;
39362
+ });
39363
+ }
39364
+ var rsiSpec = {
39365
+ type: "rsi",
39366
+ title: "Relative Strength Index",
39367
+ shortTitle: "RSI",
39368
+ overlay: false,
39369
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
39370
+ compute: (bars, inputs) => ({
39371
+ plots: [
39372
+ {
39373
+ key: "rsi",
39374
+ title: "RSI",
39375
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
39376
+ color: str3(inputs, "color", SERIES_LINE),
39377
+ width: 2
39378
+ }
39379
+ ],
39380
+ levels: guideLevels(30, 70, 50)
39381
+ })
39382
+ };
39383
+ var stochastic = {
39384
+ type: "stochastic",
39385
+ title: "Stochastic",
39386
+ shortTitle: "Stoch",
39387
+ overlay: false,
39388
+ inputs: [
39389
+ lengthInput(14, "kLength", "%K length"),
39390
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
39391
+ lengthInput(3, "dLength", "%D smoothing", 500)
39392
+ ],
39393
+ compute: (bars, inputs) => {
39394
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
39395
+ const d = sma(k, num3(inputs, "dLength", 3));
39396
+ return {
39397
+ plots: [
39398
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39399
+ { key: "d", title: "%D", values: d, color: WARNING }
39400
+ ],
39401
+ levels: guideLevels(20, 80)
39402
+ };
39403
+ }
39404
+ };
39405
+ var stochasticRsi = {
39406
+ type: "stochastic-rsi",
39407
+ title: "Stochastic RSI",
39408
+ shortTitle: "Stoch RSI",
39409
+ overlay: false,
39410
+ inputs: [
39411
+ lengthInput(14, "rsiLength", "RSI length"),
39412
+ lengthInput(14, "stochLength", "Stochastic length"),
39413
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
39414
+ lengthInput(3, "dLength", "%D smoothing", 500),
39415
+ sourceInput()
39416
+ ],
39417
+ compute: (bars, inputs) => {
39418
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
39419
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
39420
+ const d = sma(k, num3(inputs, "dLength", 3));
39421
+ return {
39422
+ plots: [
39423
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
39424
+ { key: "d", title: "%D", values: d, color: WARNING }
39425
+ ],
39426
+ levels: guideLevels(20, 80)
39427
+ };
39428
+ }
39429
+ };
39430
+ var macd = {
39431
+ type: "macd",
39432
+ title: "MACD",
39433
+ overlay: false,
39434
+ inputs: [
39435
+ lengthInput(12, "fastLength", "Fast length"),
39436
+ lengthInput(26, "slowLength", "Slow length"),
39437
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39438
+ sourceInput()
39439
+ ],
39440
+ compute: (bars, inputs) => {
39441
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39442
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
39443
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39444
+ const hist = sub(line, signal);
39445
+ return {
39446
+ plots: [
39447
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39448
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
39449
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39450
+ ],
39451
+ levels: ZERO_LEVEL
39452
+ };
39453
+ }
39454
+ };
39455
+ var ppo = {
39456
+ type: "ppo",
39457
+ title: "Percentage Price Oscillator",
39458
+ shortTitle: "PPO",
39459
+ overlay: false,
39460
+ inputs: [
39461
+ lengthInput(12, "fastLength", "Fast length"),
39462
+ lengthInput(26, "slowLength", "Slow length"),
39463
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
39464
+ sourceInput()
39465
+ ],
39466
+ compute: (bars, inputs) => {
39467
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39468
+ const fast = ema(src, num3(inputs, "fastLength", 12));
39469
+ const slow = ema(src, num3(inputs, "slowLength", 26));
39470
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
39471
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39472
+ const hist = sub(line, signal);
39473
+ return {
39474
+ plots: [
39475
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
39476
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
39477
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39478
+ ],
39479
+ levels: ZERO_LEVEL
39480
+ };
39481
+ }
39482
+ };
39483
+ var awesome = {
39484
+ type: "awesome-oscillator",
39485
+ title: "Awesome Oscillator",
39486
+ shortTitle: "AO",
39487
+ overlay: false,
39488
+ inputs: [],
39489
+ compute: (bars) => {
39490
+ const hl2 = sourceValues(bars, "HL2");
39491
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
39492
+ const colors = ao.map((x, i) => {
39493
+ if (!Number.isFinite(x)) return null;
39494
+ const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
39495
+ return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
39496
+ });
39497
+ return {
39498
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
39499
+ levels: ZERO_LEVEL
39500
+ };
39501
+ }
39502
+ };
39503
+ var cci = {
39504
+ type: "commodity-channel-index",
39505
+ title: "Commodity Channel Index",
39506
+ shortTitle: "CCI",
39507
+ overlay: false,
39508
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
39509
+ compute: (bars, inputs) => {
39510
+ const len = num3(inputs, "length", 20);
39511
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
39512
+ const basis = sma(src, len);
39513
+ const dev = new Array(src.length).fill(Number.NaN);
39514
+ for (let i = len - 1; i < src.length; i++) {
39515
+ const mean = basis[i];
39516
+ if (!Number.isFinite(mean)) continue;
39517
+ let d = 0;
39518
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
39519
+ dev[i] = d / len;
39520
+ }
39521
+ const values = src.map((x, i) => {
39522
+ const b = basis[i];
39523
+ const d = dev[i];
39524
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
39525
+ });
39526
+ return {
39527
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39528
+ levels: guideLevels(-100, 100, 0)
39529
+ };
39530
+ }
39531
+ };
39532
+ var williamsR = {
39533
+ type: "williams-percent-r",
39534
+ title: "Williams %R",
39535
+ shortTitle: "%R",
39536
+ overlay: false,
39537
+ inputs: [lengthInput(14), colorInput()],
39538
+ compute: (bars, inputs) => {
39539
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
39540
+ return {
39541
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39542
+ levels: guideLevels(-80, -20)
39543
+ };
39544
+ }
39545
+ };
39546
+ var cmo = {
39547
+ type: "chande-momentum-oscillator",
39548
+ title: "Chande Momentum Oscillator",
39549
+ shortTitle: "CMO",
39550
+ overlay: false,
39551
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39552
+ compute: (bars, inputs) => {
39553
+ const len = num3(inputs, "length", 9);
39554
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
39555
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
39556
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
39557
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
39558
+ return {
39559
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39560
+ levels: guideLevels(-50, 50, 0)
39561
+ };
39562
+ }
39563
+ };
39564
+ var connorsRsi = {
39565
+ type: "connors-rsi",
39566
+ title: "Connors RSI",
39567
+ shortTitle: "CRSI",
39568
+ overlay: false,
39569
+ inputs: [
39570
+ lengthInput(3, "rsiLength", "RSI length"),
39571
+ lengthInput(2, "streakLength", "Streak RSI length"),
39572
+ lengthInput(100, "rankLength", "Percent-rank length"),
39573
+ colorInput()
39574
+ ],
39575
+ compute: (bars, inputs) => {
39576
+ const c = closes(bars);
39577
+ const streak = new Array(c.length).fill(0);
39578
+ for (let i = 1; i < c.length; i++) {
39579
+ const d = c[i] - c[i - 1];
39580
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
39581
+ }
39582
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
39583
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
39584
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
39585
+ const values = c.map((_, i) => {
39586
+ const x = a[i];
39587
+ const y = b[i];
39588
+ const z = r[i];
39589
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
39590
+ });
39591
+ return {
39592
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39593
+ levels: guideLevels(20, 80, 50)
39594
+ };
39595
+ }
39596
+ };
39597
+ var fisher = {
39598
+ type: "fisher-transform",
39599
+ title: "Fisher Transform",
39600
+ shortTitle: "Fisher",
39601
+ overlay: false,
39602
+ inputs: [lengthInput(9)],
39603
+ compute: (bars, inputs) => {
39604
+ const len = num3(inputs, "length", 9);
39605
+ const hl2 = sourceValues(bars, "HL2");
39606
+ const hi = highest(hl2, len);
39607
+ const lo = lowest(hl2, len);
39608
+ const n = bars.length;
39609
+ const out = new Array(n).fill(Number.NaN);
39610
+ let value = 0;
39611
+ let fish = 0;
39612
+ for (let i = 0; i < n; i++) {
39613
+ const h = hi[i];
39614
+ const l = lo[i];
39615
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
39616
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
39617
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
39618
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
39619
+ out[i] = fish;
39620
+ }
39621
+ return {
39622
+ plots: [
39623
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
39624
+ { key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
39625
+ ],
39626
+ levels: ZERO_LEVEL
39627
+ };
39628
+ }
39629
+ };
39630
+ var trix = {
39631
+ type: "trix",
39632
+ title: "TRIX",
39633
+ overlay: false,
39634
+ inputs: [lengthInput(18), colorInput()],
39635
+ compute: (bars, inputs) => {
39636
+ const len = num3(inputs, "length", 18);
39637
+ const logClose = map(closes(bars), Math.log);
39638
+ const smooth = ema(ema(ema(logClose, len), len), len);
39639
+ const values = map(change(smooth), (x) => x * 1e4);
39640
+ return {
39641
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39642
+ levels: ZERO_LEVEL
39643
+ };
39644
+ }
39645
+ };
39646
+ function tsiLine(src, long, short) {
39647
+ const mom = change(src);
39648
+ const numer = ema(ema(mom, long), short);
39649
+ const denom = ema(ema(map(mom, Math.abs), long), short);
39650
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
39651
+ }
39652
+ var tsi = {
39653
+ type: "true-strength-index",
39654
+ title: "True Strength Index",
39655
+ shortTitle: "TSI",
39656
+ overlay: false,
39657
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
39658
+ compute: (bars, inputs) => {
39659
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
39660
+ return {
39661
+ plots: [
39662
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
39663
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39664
+ ],
39665
+ levels: ZERO_LEVEL
39666
+ };
39667
+ }
39668
+ };
39669
+ var smiErgodic = {
39670
+ type: "smi-ergodic",
39671
+ title: "SMI Ergodic Oscillator",
39672
+ shortTitle: "SMIE",
39673
+ overlay: false,
39674
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
39675
+ compute: (bars, inputs) => {
39676
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
39677
+ const signal = ema(line, num3(inputs, "signalLength", 5));
39678
+ const osc = sub(line, signal);
39679
+ return {
39680
+ plots: [
39681
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
39682
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
39683
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39684
+ ],
39685
+ levels: ZERO_LEVEL
39686
+ };
39687
+ }
39688
+ };
39689
+ var stc = {
39690
+ type: "schaff-trend-cycle",
39691
+ title: "Schaff Trend Cycle",
39692
+ shortTitle: "Schaff Trend Cycle",
39693
+ overlay: false,
39694
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
39695
+ compute: (bars, inputs) => {
39696
+ const len = num3(inputs, "cycleLength", 10);
39697
+ const src = closes(bars);
39698
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
39699
+ const stochOf = (v) => {
39700
+ const lo = lowest(v, len);
39701
+ const hi = highest(v, len);
39702
+ const out = new Array(v.length).fill(Number.NaN);
39703
+ let prevRaw = Number.NaN;
39704
+ let smoothed = Number.NaN;
39705
+ for (let i = 0; i < v.length; i++) {
39706
+ const x = v[i];
39707
+ const l = lo[i];
39708
+ const h = hi[i];
39709
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
39710
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
39711
+ prevRaw = raw;
39712
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
39713
+ out[i] = smoothed;
39714
+ }
39715
+ return out;
39716
+ };
39717
+ const values = stochOf(stochOf(macdLine));
39718
+ return {
39719
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
39720
+ levels: guideLevels(25, 75)
39721
+ };
39722
+ }
39723
+ };
39724
+ var kst = {
39725
+ type: "know-sure-thing",
39726
+ title: "Know Sure Thing",
39727
+ shortTitle: "KST",
39728
+ overlay: false,
39729
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
39730
+ compute: (bars, inputs) => {
39731
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39732
+ const line = [
39733
+ sma(roc(src, 10), 10),
39734
+ sma(roc(src, 15), 10),
39735
+ sma(roc(src, 20), 10),
39736
+ sma(roc(src, 30), 15)
39737
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
39738
+ return {
39739
+ plots: [
39740
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
39741
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
39742
+ ],
39743
+ levels: ZERO_LEVEL
39744
+ };
39745
+ }
39746
+ };
39747
+ var coppock = {
39748
+ type: "coppock-curve",
39749
+ title: "Coppock Curve",
39750
+ shortTitle: "Coppock",
39751
+ overlay: false,
39752
+ inputs: [
39753
+ lengthInput(10, "wmaLength", "WMA length"),
39754
+ lengthInput(14, "longRoc", "Long RoC length"),
39755
+ lengthInput(11, "shortRoc", "Short RoC length"),
39756
+ sourceInput(),
39757
+ colorInput()
39758
+ ],
39759
+ compute: (bars, inputs) => {
39760
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39761
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
39762
+ return {
39763
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39764
+ levels: ZERO_LEVEL
39765
+ };
39766
+ }
39767
+ };
39768
+ var dpo = {
39769
+ type: "detrended-price-oscillator",
39770
+ title: "Detrended Price Oscillator",
39771
+ shortTitle: "DPO",
39772
+ overlay: false,
39773
+ inputs: [lengthInput(21), colorInput()],
39774
+ compute: (bars, inputs) => {
39775
+ const len = num3(inputs, "length", 21);
39776
+ const barsback = Math.floor(len / 2) + 1;
39777
+ const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
39778
+ return {
39779
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39780
+ levels: ZERO_LEVEL
39781
+ };
39782
+ }
39783
+ };
39784
+ var rocSpec = {
39785
+ type: "rate-of-change",
39786
+ title: "Rate of Change",
39787
+ shortTitle: "ROC",
39788
+ overlay: false,
39789
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
39790
+ compute: (bars, inputs) => ({
39791
+ plots: [
39792
+ {
39793
+ key: "roc",
39794
+ title: "RoC",
39795
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
39796
+ color: str3(inputs, "color", SERIES_LINE),
39797
+ width: 2
39798
+ }
39799
+ ],
39800
+ levels: ZERO_LEVEL
39801
+ })
39802
+ };
39803
+ var ultimate = {
39804
+ type: "ultimate-oscillator",
39805
+ title: "Ultimate Oscillator",
39806
+ shortTitle: "UO",
39807
+ overlay: false,
39808
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
39809
+ compute: (bars, inputs) => {
39810
+ const n = bars.length;
39811
+ const bp = new Array(n).fill(Number.NaN);
39812
+ const tr = new Array(n).fill(Number.NaN);
39813
+ for (let i = 0; i < n; i++) {
39814
+ const b = bars[i];
39815
+ const pc = i > 0 ? bars[i - 1].close : b.close;
39816
+ const lo = Math.min(b.low, pc);
39817
+ const hi = Math.max(b.high, pc);
39818
+ bp[i] = b.close - lo;
39819
+ tr[i] = hi - lo;
39820
+ }
39821
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
39822
+ const a7 = avg(num3(inputs, "fastLength", 7));
39823
+ const a14 = avg(num3(inputs, "middleLength", 14));
39824
+ const a28 = avg(num3(inputs, "slowLength", 28));
39825
+ const values = a7.map((x, i) => {
39826
+ const y = a14[i];
39827
+ const z = a28[i];
39828
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
39829
+ });
39830
+ return {
39831
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39832
+ levels: guideLevels(30, 70, 50)
39833
+ };
39834
+ }
39835
+ };
39836
+ var rvgi = {
39837
+ type: "relative-vigor-index",
39838
+ title: "Relative Vigor Index",
39839
+ shortTitle: "RVGI",
39840
+ overlay: false,
39841
+ inputs: [lengthInput(10)],
39842
+ compute: (bars, inputs) => {
39843
+ const len = num3(inputs, "length", 10);
39844
+ const co = swma(sub(closes(bars), opens(bars)));
39845
+ const hl = swma(sub(highs(bars), lows(bars)));
39846
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
39847
+ return {
39848
+ plots: [
39849
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
39850
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
39851
+ ],
39852
+ levels: ZERO_LEVEL
39853
+ };
39854
+ }
39855
+ };
39856
+ var relVolatility = {
39857
+ type: "relative-volatility-index",
39858
+ title: "Relative Volatility Index",
39859
+ shortTitle: "RVI",
39860
+ overlay: false,
39861
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
39862
+ compute: (bars, inputs) => {
39863
+ const smoothLen = num3(inputs, "smoothLength", 14);
39864
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
39865
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
39866
+ const d = change(src);
39867
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
39868
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
39869
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
39870
+ return {
39871
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39872
+ levels: guideLevels(20, 80, 50)
39873
+ };
39874
+ }
39875
+ };
39876
+ var bop = {
39877
+ type: "balance-of-power",
39878
+ title: "Balance of Power",
39879
+ shortTitle: "BOP",
39880
+ overlay: false,
39881
+ inputs: [colorInput()],
39882
+ compute: (bars, inputs) => {
39883
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
39884
+ return {
39885
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39886
+ levels: ZERO_LEVEL
39887
+ };
39888
+ }
39889
+ };
39890
+ var elderRay = {
39891
+ type: "elder-ray",
39892
+ title: "Elder Ray",
39893
+ overlay: false,
39894
+ inputs: [lengthInput(13)],
39895
+ compute: (bars, inputs) => {
39896
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
39897
+ return {
39898
+ plots: [
39899
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
39900
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
39901
+ ],
39902
+ levels: ZERO_LEVEL
39903
+ };
39904
+ }
39905
+ };
39906
+ var ttmSqueeze = {
39907
+ type: "ttm-squeeze",
39908
+ title: "TTM Squeeze",
39909
+ overlay: false,
39910
+ inputs: [
39911
+ lengthInput(20),
39912
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
39913
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
39914
+ ],
39915
+ compute: (bars, inputs) => {
39916
+ const len = num3(inputs, "length", 20);
39917
+ const c = closes(bars);
39918
+ const basis = sma(c, len);
39919
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
39920
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
39921
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
39922
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
39923
+ const mom = linreg(sub(c, anchor), len);
39924
+ const squeezed = c.map((_, i) => {
39925
+ const d = dev[i];
39926
+ const r = kcRange[i];
39927
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
39928
+ });
39929
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
39930
+ return {
39931
+ plots: [
39932
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
39933
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
39934
+ ]
39935
+ };
39936
+ }
39937
+ };
39938
+ var oscillatorSpecs = [
39939
+ rsiSpec,
39940
+ stochastic,
39941
+ stochasticRsi,
39942
+ macd,
39943
+ ppo,
39944
+ awesome,
39945
+ cci,
39946
+ williamsR,
39947
+ cmo,
39948
+ connorsRsi,
39949
+ fisher,
39950
+ trix,
39951
+ tsi,
39952
+ smiErgodic,
39953
+ stc,
39954
+ kst,
39955
+ coppock,
39956
+ dpo,
39957
+ rocSpec,
39958
+ ultimate,
39959
+ rvgi,
39960
+ relVolatility,
39961
+ bop,
39962
+ elderRay,
39963
+ ttmSqueeze
39964
+ ];
39965
+
39966
+ // src/core/native-indicators/classics/trend.ts
39967
+ var aroon = {
39968
+ type: "aroon",
39969
+ title: "Aroon",
39970
+ overlay: false,
39971
+ inputs: [lengthInput(14)],
39972
+ compute: (bars, inputs) => {
39973
+ const len = num3(inputs, "length", 14);
39974
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
39975
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
39976
+ return {
39977
+ plots: [
39978
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
39979
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
39980
+ ],
39981
+ levels: [
39982
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
39983
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
39984
+ ]
39985
+ };
39986
+ }
39987
+ };
39988
+ var adx = {
39989
+ type: "average-directional-index",
39990
+ title: "Average Directional Index",
39991
+ shortTitle: "ADX",
39992
+ overlay: false,
39993
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
39994
+ compute: (bars, inputs) => {
39995
+ const diLen = num3(inputs, "diLength", 14);
39996
+ const adxLen = num3(inputs, "adxLength", 14);
39997
+ const n = bars.length;
39998
+ const plusDm = new Array(n).fill(Number.NaN);
39999
+ const minusDm = new Array(n).fill(Number.NaN);
40000
+ for (let i = 1; i < n; i++) {
40001
+ const up = bars[i].high - bars[i - 1].high;
40002
+ const dn = bars[i - 1].low - bars[i].low;
40003
+ plusDm[i] = up > dn && up > 0 ? up : 0;
40004
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
40005
+ }
40006
+ const atrLine = rma(trueRange(bars), diLen);
40007
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40008
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
40009
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
40010
+ return {
40011
+ plots: [
40012
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
40013
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
40014
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
40015
+ ],
40016
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
40017
+ };
40018
+ }
40019
+ };
40020
+ var vortex = {
40021
+ type: "vortex-indicator",
40022
+ title: "Vortex Indicator",
40023
+ shortTitle: "VI",
40024
+ overlay: false,
40025
+ inputs: [lengthInput(14)],
40026
+ compute: (bars, inputs) => {
40027
+ const len = num3(inputs, "length", 14);
40028
+ const n = bars.length;
40029
+ const vmPlus = new Array(n).fill(Number.NaN);
40030
+ const vmMinus = new Array(n).fill(Number.NaN);
40031
+ for (let i = 1; i < n; i++) {
40032
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
40033
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
40034
+ }
40035
+ const trSum = sum(trueRange(bars), len);
40036
+ return {
40037
+ plots: [
40038
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
40039
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
40040
+ ],
40041
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
40042
+ };
40043
+ }
40044
+ };
40045
+ var trendSpecs = [aroon, adx, vortex];
40046
+
40047
+ // src/core/native-indicators/classics/volatility.ts
40048
+ var atrSpec = {
40049
+ type: "average-true-range",
40050
+ title: "Average True Range",
40051
+ shortTitle: "ATR",
40052
+ overlay: false,
40053
+ inputs: [lengthInput(14), colorInput()],
40054
+ compute: (bars, inputs) => ({
40055
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40056
+ })
40057
+ };
40058
+ var historicalVolatility = {
40059
+ type: "historical-volatility",
40060
+ title: "Historical Volatility",
40061
+ shortTitle: "HV",
40062
+ overlay: false,
40063
+ inputs: [
40064
+ lengthInput(10),
40065
+ { key: "annual", title: "Periods per year", type: "int", defval: 365, min: 1, max: 1e5, step: 1, tooltip: "Annualization factor (365 for daily crypto bars, 252 for stock sessions)" },
40066
+ colorInput()
40067
+ ],
40068
+ compute: (bars, inputs) => {
40069
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
40070
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
40071
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40072
+ }
40073
+ };
40074
+ var chaikinVolatility = {
40075
+ type: "chaikin-volatility",
40076
+ title: "Chaikin Volatility",
40077
+ shortTitle: "CHV",
40078
+ overlay: false,
40079
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
40080
+ compute: (bars, inputs) => {
40081
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
40082
+ return {
40083
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40084
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40085
+ };
40086
+ }
40087
+ };
40088
+ var massIndex = {
40089
+ type: "mass-index",
40090
+ title: "Mass Index",
40091
+ shortTitle: "MI",
40092
+ overlay: false,
40093
+ inputs: [lengthInput(25), colorInput()],
40094
+ compute: (bars, inputs) => {
40095
+ const range = sub(highs(bars), lows(bars));
40096
+ const single = ema(range, 9);
40097
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
40098
+ return {
40099
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40100
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
40101
+ };
40102
+ }
40103
+ };
40104
+ var standardDeviation = {
40105
+ type: "standard-deviation",
40106
+ title: "Standard Deviation",
40107
+ shortTitle: "StdDev",
40108
+ overlay: false,
40109
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
40110
+ compute: (bars, inputs) => ({
40111
+ plots: [
40112
+ {
40113
+ key: "stdev",
40114
+ title: "StdDev",
40115
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
40116
+ color: str3(inputs, "color", SERIES_LINE),
40117
+ width: 2
40118
+ }
40119
+ ]
40120
+ })
40121
+ };
40122
+ var ulcerIndex = {
40123
+ type: "ulcer-index",
40124
+ title: "Ulcer Index",
40125
+ overlay: false,
40126
+ inputs: [lengthInput(14), colorInput()],
40127
+ compute: (bars, inputs) => {
40128
+ const len = num3(inputs, "length", 14);
40129
+ const c = closes(bars);
40130
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
40131
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
40132
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40133
+ }
40134
+ };
40135
+ var choppiness = {
40136
+ type: "choppiness-index",
40137
+ title: "Choppiness Index",
40138
+ shortTitle: "CHOP",
40139
+ overlay: false,
40140
+ inputs: [lengthInput(14), colorInput()],
40141
+ compute: (bars, inputs) => {
40142
+ const len = num3(inputs, "length", 14);
40143
+ const trSum = sum(trueRange(bars), len);
40144
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
40145
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
40146
+ return {
40147
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40148
+ levels: [
40149
+ { key: "upper", price: 61.8, color: NEUTRAL },
40150
+ { key: "lower", price: 38.2, color: NEUTRAL }
40151
+ ]
40152
+ };
40153
+ }
40154
+ };
40155
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
40156
+
40157
+ // src/core/native-indicators/classics/volume.ts
40158
+ function moneyFlowVolume(bars) {
40159
+ return bars.map((b) => {
40160
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40161
+ if (b.high === b.low) return 0;
40162
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
40163
+ });
40164
+ }
40165
+ var obv = {
40166
+ type: "on-balance-volume",
40167
+ title: "On Balance Volume",
40168
+ shortTitle: "OBV",
40169
+ overlay: false,
40170
+ inputs: [colorInput()],
40171
+ compute: (bars, inputs) => {
40172
+ const vol = volumes(bars);
40173
+ const d = change(closes(bars));
40174
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
40175
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40176
+ }
40177
+ };
40178
+ var accDist = {
40179
+ type: "accumulation-distribution",
40180
+ title: "Accumulation / Distribution",
40181
+ shortTitle: "A/D",
40182
+ overlay: false,
40183
+ inputs: [colorInput()],
40184
+ compute: (bars, inputs) => ({
40185
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
40186
+ })
40187
+ };
40188
+ var cmf = {
40189
+ type: "chaikin-money-flow",
40190
+ title: "Chaikin Money Flow",
40191
+ shortTitle: "CMF",
40192
+ overlay: false,
40193
+ inputs: [lengthInput(20), colorInput()],
40194
+ compute: (bars, inputs) => {
40195
+ const len = num3(inputs, "length", 20);
40196
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
40197
+ return {
40198
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40199
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40200
+ };
40201
+ }
40202
+ };
40203
+ var chaikinOscillator = {
40204
+ type: "chaikin-oscillator",
40205
+ title: "Chaikin Oscillator",
40206
+ shortTitle: "Chaikin Osc",
40207
+ overlay: false,
40208
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40209
+ compute: (bars, inputs) => {
40210
+ const ad = cumSum(moneyFlowVolume(bars));
40211
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
40212
+ return {
40213
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40214
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40215
+ };
40216
+ }
40217
+ };
40218
+ var eom = {
40219
+ type: "ease-of-movement",
40220
+ title: "Ease of Movement",
40221
+ shortTitle: "EOM",
40222
+ overlay: false,
40223
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
40224
+ compute: (bars, inputs) => {
40225
+ const div = num3(inputs, "divisor", 1e4);
40226
+ const hl2 = sourceValues(bars, "HL2");
40227
+ const move = change(hl2);
40228
+ const raw = bars.map((b, i) => {
40229
+ const m = move[i];
40230
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
40231
+ return div * m * (b.high - b.low) / b.volume;
40232
+ });
40233
+ return {
40234
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40235
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40236
+ };
40237
+ }
40238
+ };
40239
+ var forceIndex = {
40240
+ type: "force-index",
40241
+ title: "Force Index",
40242
+ shortTitle: "FI",
40243
+ overlay: false,
40244
+ inputs: [lengthInput(13), colorInput()],
40245
+ compute: (bars, inputs) => {
40246
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
40247
+ return {
40248
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40249
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40250
+ };
40251
+ }
40252
+ };
40253
+ var klinger = {
40254
+ type: "klinger-oscillator",
40255
+ title: "Klinger Oscillator",
40256
+ shortTitle: "KVO",
40257
+ overlay: false,
40258
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
40259
+ compute: (bars, inputs) => {
40260
+ const hlc3 = sourceValues(bars, "HLC3");
40261
+ const trend = change(hlc3);
40262
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
40263
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
40264
+ return {
40265
+ plots: [
40266
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
40267
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
40268
+ ],
40269
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40270
+ };
40271
+ }
40272
+ };
40273
+ var mfi = {
40274
+ type: "money-flow-index",
40275
+ title: "Money Flow Index",
40276
+ shortTitle: "MFI",
40277
+ overlay: false,
40278
+ inputs: [lengthInput(14), colorInput()],
40279
+ compute: (bars, inputs) => {
40280
+ const len = num3(inputs, "length", 14);
40281
+ const tp = sourceValues(bars, "HLC3");
40282
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
40283
+ const d = change(tp);
40284
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
40285
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
40286
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
40287
+ return {
40288
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40289
+ levels: [
40290
+ { key: "upper", price: 80, color: NEUTRAL },
40291
+ { key: "lower", price: 20, color: NEUTRAL }
40292
+ ]
40293
+ };
40294
+ }
40295
+ };
40296
+ function volumeIndex(bars, when) {
40297
+ const out = new Array(bars.length).fill(Number.NaN);
40298
+ let index = 1e3;
40299
+ let started = false;
40300
+ for (let i = 1; i < bars.length; i++) {
40301
+ const v = bars[i].volume;
40302
+ const pv = bars[i - 1].volume;
40303
+ const pc = bars[i - 1].close;
40304
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
40305
+ if (started) out[i] = index;
40306
+ continue;
40307
+ }
40308
+ started = true;
40309
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
40310
+ out[i] = index;
40311
+ }
40312
+ return out;
40313
+ }
40314
+ var nvi = {
40315
+ type: "negative-volume-index",
40316
+ title: "Negative Volume Index",
40317
+ shortTitle: "Negative Volume Index",
40318
+ overlay: false,
40319
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40320
+ compute: (bars, inputs) => {
40321
+ const line = volumeIndex(bars, (v, pv) => v < pv);
40322
+ return {
40323
+ plots: [
40324
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40325
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40326
+ ]
40327
+ };
40328
+ }
40329
+ };
40330
+ var pvi = {
40331
+ type: "positive-volume-index",
40332
+ title: "Positive Volume Index",
40333
+ shortTitle: "Positive Volume Index",
40334
+ overlay: false,
40335
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
40336
+ compute: (bars, inputs) => {
40337
+ const line = volumeIndex(bars, (v, pv) => v > pv);
40338
+ return {
40339
+ plots: [
40340
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
40341
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
40342
+ ]
40343
+ };
40344
+ }
40345
+ };
40346
+ var pvt = {
40347
+ type: "price-volume-trend",
40348
+ title: "Price Volume Trend",
40349
+ shortTitle: "PVT",
40350
+ overlay: false,
40351
+ inputs: [colorInput()],
40352
+ compute: (bars, inputs) => {
40353
+ const c = closes(bars);
40354
+ const raw = bars.map((b, i) => {
40355
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
40356
+ const pc = c[i - 1];
40357
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
40358
+ });
40359
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
40360
+ }
40361
+ };
40362
+ var volumeOscillator = {
40363
+ type: "volume-oscillator",
40364
+ title: "Volume Oscillator",
40365
+ shortTitle: "Vol Osc",
40366
+ overlay: false,
40367
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
40368
+ compute: (bars, inputs) => {
40369
+ const vol = volumes(bars);
40370
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
40371
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
40372
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40373
+ return {
40374
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40375
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40376
+ };
40377
+ }
40378
+ };
40379
+ var pvo = {
40380
+ type: "pvo",
40381
+ title: "Percentage Volume Oscillator",
40382
+ shortTitle: "PVO",
40383
+ overlay: false,
40384
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
40385
+ compute: (bars, inputs) => {
40386
+ const vol = volumes(bars);
40387
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
40388
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
40389
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
40390
+ const signal = ema(line, num3(inputs, "signalLength", 9));
40391
+ const hist = sub(line, signal);
40392
+ return {
40393
+ plots: [
40394
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
40395
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
40396
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
40397
+ ],
40398
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40399
+ };
40400
+ }
40401
+ };
40402
+ var vfi = {
40403
+ type: "volume-flow-indicator",
40404
+ title: "Volume Flow Indicator",
40405
+ shortTitle: "Volume Flow Indicator",
40406
+ overlay: false,
40407
+ inputs: [
40408
+ lengthInput(130),
40409
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
40410
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
40411
+ lengthInput(3, "smoothLength", "Smoothing length")
40412
+ ],
40413
+ compute: (bars, inputs) => {
40414
+ const len = num3(inputs, "length", 130);
40415
+ const coef = num3(inputs, "coef", 0.2);
40416
+ const volCoef = num3(inputs, "volCoef", 2.5);
40417
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
40418
+ const inter = change(typical);
40419
+ const vInter = stdev(inter, 30);
40420
+ const vol = volumes(bars);
40421
+ const vAve = sma(vol, len);
40422
+ const mf = change(sourceValues(bars, "HLC3"));
40423
+ const raw = bars.map((b, i) => {
40424
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
40425
+ const v = vol[i];
40426
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40427
+ const m = mf[i];
40428
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
40429
+ const capped = Math.min(v, ave * volCoef);
40430
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
40431
+ });
40432
+ const values = bars.map((_, i) => {
40433
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
40434
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
40435
+ });
40436
+ const flowSum = sum(raw, len);
40437
+ const line = zip(flowSum, values, (f, a) => f / a);
40438
+ return {
40439
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
40440
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40441
+ };
40442
+ }
40443
+ };
40444
+ var intradayIntensity = {
40445
+ type: "intraday-intensity",
40446
+ title: "Intraday Intensity",
40447
+ shortTitle: "Intraday Intensity",
40448
+ overlay: false,
40449
+ inputs: [lengthInput(21), colorInput()],
40450
+ compute: (bars, inputs) => {
40451
+ const len = num3(inputs, "length", 21);
40452
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
40453
+ return {
40454
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
40455
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
40456
+ };
40457
+ }
40458
+ };
40459
+ var volumeSpecs = [
40460
+ obv,
40461
+ accDist,
40462
+ cmf,
40463
+ chaikinOscillator,
40464
+ eom,
40465
+ forceIndex,
40466
+ klinger,
40467
+ mfi,
40468
+ nvi,
40469
+ pvi,
40470
+ pvt,
40471
+ volumeOscillator,
40472
+ pvo,
40473
+ vfi,
40474
+ intradayIntensity
40475
+ ];
40476
+
40477
+ // src/core/native-indicators/classics/overlays.ts
40478
+ var DAY_MS2 = 864e5;
40479
+ function periodKey(anchor, time) {
40480
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
40481
+ if (anchor === "Month") {
40482
+ const d = new Date(time);
40483
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
40484
+ }
40485
+ return Math.floor(time / DAY_MS2);
40486
+ }
40487
+ var parabolicSar = {
40488
+ type: "parabolic-sar",
40489
+ title: "Parabolic SAR",
40490
+ shortTitle: "SAR",
40491
+ overlay: true,
40492
+ inputs: [
40493
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40494
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
40495
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
40496
+ ],
40497
+ compute: (bars, inputs) => {
40498
+ const start = num3(inputs, "start", 0.02);
40499
+ const inc = num3(inputs, "increment", 0.02);
40500
+ const max2 = num3(inputs, "maximum", 0.2);
40501
+ const n = bars.length;
40502
+ const values = new Array(n).fill(Number.NaN);
40503
+ const colors = new Array(n).fill(null);
40504
+ if (n >= 2) {
40505
+ let long = bars[1].close >= bars[0].close;
40506
+ let sar = long ? bars[0].low : bars[0].high;
40507
+ let ep = long ? bars[0].high : bars[0].low;
40508
+ let af = start;
40509
+ for (let i = 1; i < n; i++) {
40510
+ const b = bars[i];
40511
+ sar += af * (ep - sar);
40512
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
40513
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
40514
+ const reversed = long ? b.low < sar : b.high > sar;
40515
+ if (reversed) {
40516
+ long = !long;
40517
+ sar = ep;
40518
+ ep = long ? b.high : b.low;
40519
+ af = start;
40520
+ } else if (long && b.high > ep) {
40521
+ ep = b.high;
40522
+ af = Math.min(max2, af + inc);
40523
+ } else if (!long && b.low < ep) {
40524
+ ep = b.low;
40525
+ af = Math.min(max2, af + inc);
40526
+ }
40527
+ values[i] = sar;
40528
+ colors[i] = long ? BULLISH : BEARISH;
40529
+ }
40530
+ }
40531
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
40532
+ }
40533
+ };
40534
+ var vwap = {
40535
+ type: "vwap",
40536
+ title: "Volume Weighted Average Price",
40537
+ shortTitle: "VWAP",
40538
+ overlay: true,
40539
+ inputs: [
40540
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
40541
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
40542
+ colorInput(INFO)
40543
+ ],
40544
+ compute: (bars, inputs) => {
40545
+ const anchor = str3(inputs, "anchor", "Day");
40546
+ const source = str3(inputs, "source", "HLC3");
40547
+ const values = new Array(bars.length).fill(Number.NaN);
40548
+ let period = Number.NaN;
40549
+ let cumPV = 0;
40550
+ let cumV = 0;
40551
+ for (let i = 0; i < bars.length; i++) {
40552
+ const b = bars[i];
40553
+ const key = periodKey(anchor, b.time);
40554
+ if (key !== period) {
40555
+ period = key;
40556
+ cumPV = 0;
40557
+ cumV = 0;
40558
+ }
40559
+ const v = b.volume;
40560
+ if (v != null && Number.isFinite(v) && v > 0) {
40561
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
40562
+ cumPV += tp * v;
40563
+ cumV += v;
40564
+ }
40565
+ if (cumV > 0) values[i] = cumPV / cumV;
40566
+ }
40567
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
40568
+ }
40569
+ };
40570
+ var pivotPoints = {
40571
+ type: "pivot-points",
40572
+ title: "Pivot Points",
40573
+ shortTitle: "Pivots",
40574
+ overlay: true,
40575
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
40576
+ compute: (bars, inputs) => {
40577
+ const anchor = str3(inputs, "anchor", "Day");
40578
+ const aggs = /* @__PURE__ */ new Map();
40579
+ for (const b of bars) {
40580
+ const key = periodKey(anchor, b.time);
40581
+ const a = aggs.get(key);
40582
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
40583
+ else {
40584
+ a.high = Math.max(a.high, b.high);
40585
+ a.low = Math.min(a.low, b.low);
40586
+ a.close = b.close;
40587
+ }
40588
+ }
40589
+ const n = bars.length;
40590
+ const mk = () => new Array(n).fill(Number.NaN);
40591
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
40592
+ for (let i = 0; i < n; i++) {
40593
+ const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
40594
+ if (!prev3) continue;
40595
+ const p = (prev3.high + prev3.low + prev3.close) / 3;
40596
+ levels.p[i] = p;
40597
+ levels.r1[i] = 2 * p - prev3.low;
40598
+ levels.s1[i] = 2 * p - prev3.high;
40599
+ levels.r2[i] = p + (prev3.high - prev3.low);
40600
+ levels.s2[i] = p - (prev3.high - prev3.low);
40601
+ levels.r3[i] = prev3.high + 2 * (p - prev3.low);
40602
+ levels.s3[i] = prev3.low - 2 * (prev3.high - p);
40603
+ }
40604
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
40605
+ return {
40606
+ plots: [
40607
+ plot("p", "P", WARNING),
40608
+ plot("r1", "R1", BEARISH),
40609
+ plot("s1", "S1", BULLISH),
40610
+ plot("r2", "R2", BEARISH),
40611
+ plot("s2", "S2", BULLISH),
40612
+ plot("r3", "R3", BEARISH),
40613
+ plot("s3", "S3", BULLISH)
40614
+ ]
40615
+ };
40616
+ }
40617
+ };
40618
+ var fiftyTwoWeek = {
40619
+ type: "52-week-high-low",
40620
+ title: "52 Week High/Low",
40621
+ shortTitle: "52W H/L",
40622
+ overlay: true,
40623
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
40624
+ compute: (bars, inputs) => {
40625
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
40626
+ const n = bars.length;
40627
+ const hi = new Array(n).fill(Number.NaN);
40628
+ const lo = new Array(n).fill(Number.NaN);
40629
+ const maxIdx = [];
40630
+ const minIdx = [];
40631
+ let from = 0;
40632
+ for (let i = 0; i < n; i++) {
40633
+ const b = bars[i];
40634
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
40635
+ maxIdx.push(i);
40636
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
40637
+ minIdx.push(i);
40638
+ while (bars[from].time < b.time - span) from += 1;
40639
+ while (maxIdx[0] < from) maxIdx.shift();
40640
+ while (minIdx[0] < from) minIdx.shift();
40641
+ hi[i] = bars[maxIdx[0]].high;
40642
+ lo[i] = bars[minIdx[0]].low;
40643
+ }
40644
+ return {
40645
+ plots: [
40646
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
40647
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
40648
+ ]
40649
+ };
40650
+ }
40651
+ };
40652
+ var zigzag = {
40653
+ type: "zigzag",
40654
+ title: "ZigZag",
40655
+ overlay: true,
40656
+ inputs: [
40657
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
40658
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
40659
+ colorInput()
40660
+ ],
40661
+ compute: (bars, inputs) => {
40662
+ const dev = num3(inputs, "deviation", 5) / 100;
40663
+ const depth = num3(inputs, "depth", 10);
40664
+ const pivots = [];
40665
+ if (bars.length > 1) {
40666
+ let up = bars[1].close >= bars[0].close;
40667
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
40668
+ for (let i = 1; i < bars.length; i++) {
40669
+ const b = bars[i];
40670
+ if (up) {
40671
+ if (b.high >= ext.price) ext = { i, price: b.high };
40672
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40673
+ pivots.push(ext);
40674
+ up = false;
40675
+ ext = { i, price: b.low };
40676
+ }
40677
+ } else if (b.low <= ext.price) {
40678
+ ext = { i, price: b.low };
40679
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
40680
+ pivots.push(ext);
40681
+ up = true;
40682
+ ext = { i, price: b.high };
40683
+ }
40684
+ }
40685
+ pivots.push(ext);
40686
+ }
40687
+ return {
40688
+ plots: [],
40689
+ polylines: [
40690
+ {
40691
+ key: "zigzag",
40692
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
40693
+ curved: false,
40694
+ closed: false,
40695
+ lineColor: str3(inputs, "color", SERIES_LINE),
40696
+ lineWidth: 2,
40697
+ lineStyle: "solid",
40698
+ arrowLeft: false,
40699
+ arrowRight: false
40700
+ }
40701
+ ]
40702
+ };
40703
+ }
40704
+ };
40705
+ var williamsFractal = {
40706
+ type: "williams-fractal",
40707
+ title: "Williams Fractal",
40708
+ shortTitle: "Fractals",
40709
+ overlay: true,
40710
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
40711
+ compute: (bars, inputs) => {
40712
+ const p = num3(inputs, "periods", 2);
40713
+ const markers = [];
40714
+ const isExtreme = (i, pick, better) => {
40715
+ const v = pick(bars[i]);
40716
+ for (let k = i - p; k <= i + p; k++) {
40717
+ if (k === i) continue;
40718
+ if (!better(v, pick(bars[k]))) return false;
40719
+ }
40720
+ return true;
40721
+ };
40722
+ for (let i = p; i < bars.length - p; i++) {
40723
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
40724
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
40725
+ }
40726
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
40727
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
40728
+ }
40729
+ }
40730
+ return { plots: [], markers };
40731
+ }
40732
+ };
40733
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
40734
+
40735
+ // src/core/native-indicators/classics/index.ts
40736
+ var classicSpecs = [
40737
+ ...averageSpecs,
40738
+ ...bandSpecs,
40739
+ ...oscillatorSpecs,
40740
+ ...trendSpecs,
40741
+ ...volatilitySpecs,
40742
+ ...volumeSpecs,
40743
+ ...overlaySpecs
40744
+ ];
40745
+ function registerClassicIndicators() {
40746
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
40747
+ }
40748
+
38419
40749
  // src/Vela.ts
38420
40750
  var Vela = class {
38421
40751
  constructor(container, options = {}, deps = {}) {
38422
40752
  registerBuiltinChartTypes();
38423
40753
  registerVolume();
38424
40754
  registerVpvr();
40755
+ registerClassicIndicators();
38425
40756
  const element = resolveElement(container);
38426
40757
  const theme = resolveTheme(options.theme);
38427
40758
  let animZoom = true;
@@ -38496,25 +40827,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38496
40827
  }
38497
40828
  /**
38498
40829
  * 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.
40830
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
40831
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
40832
+ * call returns the existing handle; the classic studies allow several instances, so each call
40833
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
38502
40834
  */
38503
40835
  addNativeIndicator(type, options) {
38504
40836
  return this.orchestrator.addNativeIndicator(type, options);
38505
40837
  }
38506
40838
  /**
38507
40839
  * 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).
40840
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
40841
+ * and whether it allows several instances (`multiInstance`; otherwise a second
40842
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
40843
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
40844
+ * provider (a type may need data the symbol lacks).
38512
40845
  */
38513
40846
  availableNativeIndicators() {
38514
40847
  return this.orchestrator.availableNativeIndicators();
38515
40848
  }
38516
40849
  /**
38517
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
40850
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
40851
+ * multi-instance type repeats) — the synchronous slice of
38518
40852
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
38519
40853
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
38520
40854
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -38881,12 +41215,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38881
41215
  return Math.round(Math.min(Math.max(px, lo), hi));
38882
41216
  }
38883
41217
 
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
41218
  // src/data/symbol-base.ts
38891
41219
  function baseOf(d) {
38892
41220
  const fromDesc = d.description?.split("/")[0]?.trim();
@@ -38988,9 +41316,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38988
41316
  for (const b of bars) byTime.set(b.time, b);
38989
41317
  return [...byTime.values()].sort((a, b) => a.time - b.time);
38990
41318
  }
38991
- function aggregate(sub, bucketMs) {
41319
+ function aggregate(sub2, bucketMs) {
38992
41320
  const buckets = /* @__PURE__ */ new Map();
38993
- for (const b of sub) {
41321
+ for (const b of sub2) {
38994
41322
  const key = Math.floor(b.time / bucketMs) * bucketMs;
38995
41323
  const cur = buckets.get(key);
38996
41324
  if (!cur) {
@@ -39042,8 +41370,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39042
41370
  }
39043
41371
  const ratio = targetMin / subMin;
39044
41372
  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);
41373
+ const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
41374
+ const agg = aggregate(sub2, targetMin * 6e4);
39047
41375
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39048
41376
  } catch (e) {
39049
41377
  console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39374,9 +41702,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39374
41702
  for (const b of bars) byTime.set(b.time, b);
39375
41703
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39376
41704
  }
39377
- function aggregate2(sub, bucketMs) {
41705
+ function aggregate2(sub2, bucketMs) {
39378
41706
  const buckets = /* @__PURE__ */ new Map();
39379
- for (const b of sub) {
41707
+ for (const b of sub2) {
39380
41708
  const key = Math.floor(b.time / bucketMs) * bucketMs;
39381
41709
  const cur = buckets.get(key);
39382
41710
  if (!cur) {
@@ -39427,8 +41755,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39427
41755
  }
39428
41756
  const ratio = targetMin / subMin;
39429
41757
  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);
41758
+ const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
41759
+ const agg = aggregate2(sub2, targetMin * 6e4);
39432
41760
  return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
39433
41761
  } catch (e) {
39434
41762
  console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
@@ -39780,15 +42108,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39780
42108
  for (const b of bars) byTime.set(b.time, b);
39781
42109
  return [...byTime.values()].sort((a, b) => a.time - b.time);
39782
42110
  }
39783
- function aggregate3(sub, bucketMs) {
39784
- return aggregateBy(sub, (t) => Math.floor(t / bucketMs) * bucketMs);
42111
+ function aggregate3(sub2, bucketMs) {
42112
+ return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
39785
42113
  }
39786
- function aggregateCalendar(sub, unit) {
39787
- return aggregateBy(sub, unit === "W" ? weekStartUTC : monthStartUTC);
42114
+ function aggregateCalendar(sub2, unit) {
42115
+ return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
39788
42116
  }
39789
- function aggregateBy(sub, keyMs) {
42117
+ function aggregateBy(sub2, keyMs) {
39790
42118
  const buckets = /* @__PURE__ */ new Map();
39791
- for (const b of sub) {
42119
+ for (const b of sub2) {
39792
42120
  const key = keyMs(b.time);
39793
42121
  const cur = buckets.get(key);
39794
42122
  if (!cur) {
@@ -39842,8 +42170,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39842
42170
  if (tf === "W" || tf === "M") {
39843
42171
  const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
39844
42172
  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);
42173
+ const sub3 = await this.fetchCandles(product, 86400, subRange2);
42174
+ return clampLimit(aggregateCalendar(sub3, tf), range.limit);
39847
42175
  }
39848
42176
  const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
39849
42177
  const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
@@ -39853,8 +42181,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39853
42181
  }
39854
42182
  const ratio = targetMin / subMin;
39855
42183
  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);
42184
+ const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
42185
+ return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
39858
42186
  } catch (e) {
39859
42187
  console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
39860
42188
  return [];