@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
@@ -667,7 +667,7 @@ function attachForming(closed, forming) {
667
667
 
668
668
  // src/data/MultiProviderFeed.ts
669
669
  function intervalMs(timeframe) {
670
- const map = {
670
+ const map2 = {
671
671
  "1m": 6e4,
672
672
  "3m": 18e4,
673
673
  "5m": 3e5,
@@ -689,7 +689,7 @@ function intervalMs(timeframe) {
689
689
  W: 6048e5,
690
690
  M: 2592e6
691
691
  };
692
- return map[timeframe] ?? 36e5;
692
+ return map2[timeframe] ?? 36e5;
693
693
  }
694
694
  var MultiProviderFeed = class {
695
695
  constructor(store = sharedBarStore) {
@@ -1714,19 +1714,19 @@ var Surface = class _Surface {
1714
1714
  }
1715
1715
  setItems(items) {
1716
1716
  this.items = items;
1717
- for (const sub of this.subs.values()) sub.destroy();
1717
+ for (const sub2 of this.subs.values()) sub2.destroy();
1718
1718
  this.subs.clear();
1719
1719
  this.build();
1720
1720
  const api = this.api;
1721
- for (const sub of this.subs.values()) {
1722
- api.setChild(sub.handle.service);
1723
- sub.api.setParent(this.handle.service);
1721
+ for (const sub2 of this.subs.values()) {
1722
+ api.setChild(sub2.handle.service);
1723
+ sub2.api.setParent(this.handle.service);
1724
1724
  }
1725
1725
  this.handle.flush();
1726
1726
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
1727
1727
  }
1728
1728
  destroy() {
1729
- for (const sub of this.subs.values()) sub.destroy();
1729
+ for (const sub2 of this.subs.values()) sub2.destroy();
1730
1730
  this.subs.clear();
1731
1731
  this.handle.stop();
1732
1732
  this.positioner.remove();
@@ -1740,9 +1740,9 @@ var Surface = class _Surface {
1740
1740
  for (const li of this.list.children) {
1741
1741
  const id = li.dataset.veiId;
1742
1742
  if (!id) continue;
1743
- const sub = this.subs.get(id);
1744
- if (sub) {
1745
- vanilla.spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
1743
+ const sub2 = this.subs.get(id);
1744
+ if (sub2) {
1745
+ vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
1746
1746
  continue;
1747
1747
  }
1748
1748
  const item = this.byId(id);
@@ -1819,7 +1819,7 @@ var Surface = class _Surface {
1819
1819
  const arrow = iconEl("chevron-right", doc);
1820
1820
  arrow.classList.add("vela-menu-arrow");
1821
1821
  li.appendChild(arrow);
1822
- const sub = new _Surface(doc, {
1822
+ const sub2 = new _Surface(doc, {
1823
1823
  host: this.host,
1824
1824
  placement: "right-start",
1825
1825
  onSelect: this.onSelect,
@@ -1827,8 +1827,8 @@ var Surface = class _Surface {
1827
1827
  checkmarks: this.checkmarks,
1828
1828
  id: `${this.mid}--${item.id}`
1829
1829
  });
1830
- sub.setItems(item.submenu ?? []);
1831
- this.subs.set(item.id, sub);
1830
+ sub2.setItems(item.submenu ?? []);
1831
+ this.subs.set(item.id, sub2);
1832
1832
  }
1833
1833
  this.list.appendChild(li);
1834
1834
  }
@@ -3528,8 +3528,21 @@ function buildColorPicker(color, theme, onChange) {
3528
3528
  };
3529
3529
  const recentRow = document.createElement("div");
3530
3530
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
3531
+ const recentSwatches = document.createElement("div");
3532
+ recentSwatches.style.cssText = "display:contents;";
3533
+ const add = document.createElement("label");
3534
+ 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;`;
3535
+ add.textContent = "+";
3536
+ const customInput = document.createElement("input");
3537
+ customInput.type = "color";
3538
+ customInput.value = curHex;
3539
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
3540
+ customInput.addEventListener("input", () => previewHex(customInput.value));
3541
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
3542
+ add.appendChild(customInput);
3543
+ recentRow.append(recentSwatches, add);
3531
3544
  const renderRecents = () => {
3532
- recentRow.replaceChildren();
3545
+ recentSwatches.replaceChildren();
3533
3546
  for (const c of recents) {
3534
3547
  const sw = document.createElement("button");
3535
3548
  sw.type = "button";
@@ -3538,18 +3551,8 @@ function buildColorPicker(color, theme, onChange) {
3538
3551
  e.stopPropagation();
3539
3552
  pickHex(c);
3540
3553
  });
3541
- recentRow.appendChild(sw);
3554
+ recentSwatches.appendChild(sw);
3542
3555
  }
3543
- const add = document.createElement("label");
3544
- 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;`;
3545
- add.textContent = "+";
3546
- const input = document.createElement("input");
3547
- input.type = "color";
3548
- input.value = curHex;
3549
- input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
3550
- input.addEventListener("input", () => pickHex(input.value, true));
3551
- add.appendChild(input);
3552
- recentRow.appendChild(add);
3553
3556
  };
3554
3557
  const opLabel = document.createElement("div");
3555
3558
  opLabel.textContent = "Opacity";
@@ -3589,14 +3592,18 @@ function buildColorPicker(color, theme, onChange) {
3589
3592
  function emit() {
3590
3593
  onChange(combineColor(curHex, curAlpha));
3591
3594
  }
3592
- function pickHex(hex, custom = false) {
3595
+ function previewHex(hex) {
3593
3596
  curHex = splitColor(hex).hex6;
3594
- addRecent(curHex);
3595
3597
  paintSelection();
3596
3598
  paintOpacity();
3597
- if (custom) renderRecents();
3598
3599
  emit();
3599
3600
  }
3601
+ function pickHex(hex, custom = false) {
3602
+ previewHex(hex);
3603
+ addRecent(curHex);
3604
+ customInput.value = curHex;
3605
+ if (custom) renderRecents();
3606
+ }
3600
3607
  renderRecents();
3601
3608
  paintSelection();
3602
3609
  paintOpacity();
@@ -5113,9 +5120,9 @@ function factoryResetConfig(factory) {
5113
5120
  if (inst.enableKey) defaults2[inst.enableKey] = false;
5114
5121
  addRows(inst.rows);
5115
5122
  }
5116
- for (const sub of section.subsections ?? []) {
5117
- if (sub.enableKey) defaults2[sub.enableKey] = false;
5118
- addRows(sub.rows);
5123
+ for (const sub2 of section.subsections ?? []) {
5124
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
5125
+ addRows(sub2.rows);
5119
5126
  }
5120
5127
  if (!section.instances) addRows(section.rows);
5121
5128
  bag[t.id] = defaults2;
@@ -14031,17 +14038,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
14031
14038
  }
14032
14039
  var STYLE_ID7 = "vela-widget-symbolpicker";
14033
14040
  var CSS8 = `
14034
- /* Search + market tabs stay pinned while the result list scrolls underneath
14035
- (mobile fullscreen: the dialog body is the scroller). Negative margin eats
14036
- the body's padding so scrolled rows cannot peek around the island. */
14037
- .vela-sp-sticky {
14038
- position: sticky;
14039
- top: 0;
14040
- z-index: 1;
14041
- background: var(--vela-surface);
14042
- margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
14043
- padding: var(--vela-space-4) var(--vela-space-4) 0;
14044
- }
14041
+ /* The dialog body is flush (a flex column): search + market tabs are a fixed head
14042
+ and the result list is the ONLY scroller, in both layouts. A sticky head over a
14043
+ scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
14044
+ scroller's content box, so the head lands one padding below the top and covers
14045
+ the first row. */
14046
+ .vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
14045
14047
  .vela-sp-searchrow {
14046
14048
  display: flex;
14047
14049
  align-items: center;
@@ -14066,12 +14068,13 @@ var CSS8 = `
14066
14068
  .vela-sp-input::placeholder { text-transform: none; }
14067
14069
  .vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
14068
14070
  /* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
14069
- overflowing the body, and the result list stops capping itself \u2014 the body owns
14070
- the scrolling in the fullscreen presentation. */
14071
+ overflowing the body, and the result list stops capping itself \u2014 it fills the
14072
+ rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
14073
+ dialog body carries it, flush or not, so the list adds none of its own. */
14071
14074
  [data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
14072
14075
  [data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
14073
14076
  [data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
14074
- [data-layout='mobile'] .vela-sp-list { max-height: none; }
14077
+ [data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
14075
14078
  .vela-sp-tab {
14076
14079
  all: unset;
14077
14080
  padding: 3px 10px;
@@ -14083,7 +14086,12 @@ var CSS8 = `
14083
14086
  }
14084
14087
  .vela-sp-tab:hover { color: var(--vela-fg); }
14085
14088
  .vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
14086
- .vela-sp-list { margin-top: var(--vela-space-2); max-height: 46vh; overflow: auto; }
14089
+ .vela-sp-list {
14090
+ min-height: 0;
14091
+ margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
14092
+ max-height: 46vh;
14093
+ overflow: auto;
14094
+ }
14087
14095
  .vela-sp-list::-webkit-scrollbar { width: 8px; }
14088
14096
  .vela-sp-list::-webkit-scrollbar-thumb {
14089
14097
  background: var(--vela-scroll);
@@ -14176,8 +14184,8 @@ var SymbolPicker = class {
14176
14184
  searchRow.append(iconEl("search", doc), this.input);
14177
14185
  this.tabs = doc.createElement("div");
14178
14186
  this.tabs.className = "vela-sp-tabs";
14179
- const sticky = doc.createElement("div");
14180
- sticky.className = "vela-sp-sticky";
14187
+ const head = doc.createElement("div");
14188
+ head.className = "vela-sp-head";
14181
14189
  for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
14182
14190
  const b = doc.createElement("button");
14183
14191
  b.className = "vela-sp-tab";
@@ -14191,7 +14199,7 @@ var SymbolPicker = class {
14191
14199
  });
14192
14200
  this.tabs.appendChild(b);
14193
14201
  }
14194
- sticky.append(searchRow, this.tabs);
14202
+ head.append(searchRow, this.tabs);
14195
14203
  this.list = doc.createElement("div");
14196
14204
  this.list.className = "vela-sp-list";
14197
14205
  this.list.addEventListener("scroll", () => {
@@ -14204,7 +14212,8 @@ var SymbolPicker = class {
14204
14212
  title: "Symbol Search",
14205
14213
  host: opts.host,
14206
14214
  closeOnInteractOutside: true,
14207
- content: (body) => body.append(sticky, this.list),
14215
+ flush: true,
14216
+ content: (body) => body.append(head, this.list),
14208
14217
  onOpenChange: (open2) => {
14209
14218
  if (open2) {
14210
14219
  this.input.value = this.seed.toUpperCase();
@@ -15960,11 +15969,11 @@ var PriceScaleDrawer = class {
15960
15969
  for (const item of items) {
15961
15970
  if (item.id === "labels" && item.submenu) {
15962
15971
  section("Labels");
15963
- for (const sub of item.submenu) {
15964
- row(sub.label, {
15965
- checked: sub.checked,
15972
+ for (const sub2 of item.submenu) {
15973
+ row(sub2.label, {
15974
+ checked: sub2.checked,
15966
15975
  onClick: () => {
15967
- const feature = sub.id.slice("toggle:".length);
15976
+ const feature = sub2.id.slice("toggle:".length);
15968
15977
  chart.renderer.set(feature, !this.flag(feature));
15969
15978
  this.render();
15970
15979
  }
@@ -15974,11 +15983,11 @@ var PriceScaleDrawer = class {
15974
15983
  }
15975
15984
  if (item.id === "levels" && item.submenu) {
15976
15985
  section("Levels");
15977
- for (const sub of item.submenu) {
15978
- row(sub.label, {
15979
- checked: sub.checked,
15986
+ for (const sub2 of item.submenu) {
15987
+ row(sub2.label, {
15988
+ checked: sub2.checked,
15980
15989
  onClick: () => {
15981
- const feature = sub.id.slice("toggle:".length);
15990
+ const feature = sub2.id.slice("toggle:".length);
15982
15991
  chart.renderer.set(feature, !this.flag(feature));
15983
15992
  this.render();
15984
15993
  }
@@ -16552,8 +16561,8 @@ var DrawingController = class {
16552
16561
  this.port?.setExternalGhost?.(doc);
16553
16562
  }
16554
16563
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
16555
- setToolShortcuts(map) {
16556
- this.port?.setToolShortcuts?.(map);
16564
+ setToolShortcuts(map2) {
16565
+ this.port?.setToolShortcuts?.(map2);
16557
16566
  }
16558
16567
  // ── programmatic CRUD (facade-facing) ── each is one undo step
16559
16568
  add(type, init = {}) {
@@ -17224,8 +17233,8 @@ var DrawingToolbar = class {
17224
17233
  }
17225
17234
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
17226
17235
  * right edge of the flyout rows, beside the favorite star. */
17227
- setShortcuts(map) {
17228
- this.shortcuts = new Map(Object.entries(map));
17236
+ setShortcuts(map2) {
17237
+ this.shortcuts = new Map(Object.entries(map2));
17229
17238
  }
17230
17239
  /** Paint one star for the current favorite state (filled + gold, or outline). */
17231
17240
  paintStar(el, type) {
@@ -18186,7 +18195,7 @@ var LiveSession = class {
18186
18195
 
18187
18196
  // src/core/engine/IndicatorHandleImpl.ts
18188
18197
  var IndicatorHandleImpl = class {
18189
- constructor(id, title, controller, source) {
18198
+ constructor(id, title, controller, source, nativeType) {
18190
18199
  this.controller = controller;
18191
18200
  this.schema = [];
18192
18201
  this.propsSchema = [];
@@ -18195,6 +18204,7 @@ var IndicatorHandleImpl = class {
18195
18204
  this.id = id;
18196
18205
  this.title = title;
18197
18206
  if (source !== void 0) this.source = source;
18207
+ if (nativeType !== void 0) this.nativeType = nativeType;
18198
18208
  }
18199
18209
  get inputs() {
18200
18210
  return this.schema;
@@ -18285,24 +18295,24 @@ function summarizeModel(model) {
18285
18295
  }
18286
18296
  function inspectModels(models) {
18287
18297
  const indicators = models.map(summarizeModel);
18288
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18298
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18289
18299
  return {
18290
18300
  indicators,
18291
18301
  totals: {
18292
18302
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
18293
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18294
- fills: sum((s) => s.fills),
18295
- backgrounds: sum((s) => s.backgrounds),
18296
- priceLines: sum((s) => s.priceLines),
18297
- lines: sum((s) => s.lines),
18298
- boxes: sum((s) => s.boxes),
18299
- labels: sum((s) => s.labels),
18300
- polylines: sum((s) => s.polylines),
18301
- linefills: sum((s) => s.linefills),
18302
- tables: sum((s) => s.tables),
18303
- barColors: sum((s) => s.barColors),
18304
- trades: sum((s) => s.trades),
18305
- forcedOverlay: sum((s) => s.forcedOverlay)
18303
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18304
+ fills: sum2((s) => s.fills),
18305
+ backgrounds: sum2((s) => s.backgrounds),
18306
+ priceLines: sum2((s) => s.priceLines),
18307
+ lines: sum2((s) => s.lines),
18308
+ boxes: sum2((s) => s.boxes),
18309
+ labels: sum2((s) => s.labels),
18310
+ polylines: sum2((s) => s.polylines),
18311
+ linefills: sum2((s) => s.linefills),
18312
+ tables: sum2((s) => s.tables),
18313
+ barColors: sum2((s) => s.barColors),
18314
+ trades: sum2((s) => s.trades),
18315
+ forcedOverlay: sum2((s) => s.forcedOverlay)
18306
18316
  }
18307
18317
  };
18308
18318
  }
@@ -19371,16 +19381,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
19371
19381
  /**
19372
19382
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
19373
19383
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
19374
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
19375
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
19384
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
19385
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
19386
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
19387
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
19376
19388
  */
19377
19389
  addNativeIndicator(type, options = {}) {
19378
- const existing = this.registry.all().find((r) => r.native?.type === type);
19379
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
19380
19390
  const descriptor = getNativeIndicator(type);
19391
+ if (!descriptor?.multiInstance) {
19392
+ const existing = this.registry.all().find((r) => r.native?.type === type);
19393
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19394
+ }
19381
19395
  const id = this.registry.nextId("native");
19382
19396
  const title = descriptor?.title ?? type;
19383
- const handle = new IndicatorHandleImpl(id, title, this);
19397
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19384
19398
  this.handles.set(id, handle);
19385
19399
  if (!descriptor) {
19386
19400
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -19392,16 +19406,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
19392
19406
  void this.startNativeIndicator(id, handle);
19393
19407
  return handle;
19394
19408
  }
19395
- /**
19396
- * The catalog of registered native-indicator types with their live state on THIS chart: each
19397
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
19398
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
19399
- * because a descriptor's `isSupported` may probe the provider for a required capability.
19400
- */
19401
- /** The native types present on the chart — SYNC (registry state; no support probe). */
19409
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
19410
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
19402
19411
  presentNativeIndicators() {
19403
19412
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
19404
19413
  }
19414
+ /**
19415
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
19416
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
19417
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
19418
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
19419
+ * probe the provider for a required capability.
19420
+ */
19405
19421
  async availableNativeIndicators() {
19406
19422
  const symbol = this.qualifiedSymbol();
19407
19423
  const present = new Set(this.presentNativeIndicators());
@@ -19411,7 +19427,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
19411
19427
  title: d.title,
19412
19428
  supported: await this.isNativeSupported(d, symbol),
19413
19429
  present: present.has(d.type),
19414
- beta: d.beta
19430
+ beta: d.beta,
19431
+ ...d.multiInstance ? { multiInstance: true } : {}
19415
19432
  }))
19416
19433
  );
19417
19434
  }
@@ -20551,8 +20568,8 @@ var DrawingsControl = class {
20551
20568
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
20552
20569
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
20553
20570
  * platform formatting, so hints always match the host's actual bindings. */
20554
- setToolShortcuts(map) {
20555
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
20571
+ setToolShortcuts(map2) {
20572
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
20556
20573
  return this;
20557
20574
  }
20558
20575
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -22033,8 +22050,8 @@ var InputsUI = class {
22033
22050
  lg.style.top = `${top}px`;
22034
22051
  }
22035
22052
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22036
- setCollapsedPanes(map) {
22037
- this.paneCollapse = map;
22053
+ setCollapsedPanes(map2) {
22054
+ this.paneCollapse = map2;
22038
22055
  }
22039
22056
  /**
22040
22057
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -24043,11 +24060,11 @@ function mergeBarColors(indicators) {
24043
24060
  }
24044
24061
  }
24045
24062
  if (!any) return EMPTY_BARCOLORS;
24046
- const map = /* @__PURE__ */ new Map();
24063
+ const map2 = /* @__PURE__ */ new Map();
24047
24064
  for (const m of indicators.values()) {
24048
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24065
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24049
24066
  }
24050
- return map;
24067
+ return map2;
24051
24068
  }
24052
24069
 
24053
24070
  // src/renderers/native/core/ViewportState.ts
@@ -24344,9 +24361,9 @@ function isHorizontalWheel(deltaX, deltaY) {
24344
24361
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24345
24362
  return rightOffset + deltaX / barSpacing;
24346
24363
  }
24347
- function wheelPanDelta(deltaX, deltaY, shift) {
24364
+ function wheelPanDelta(deltaX, deltaY, shift2) {
24348
24365
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24349
- if (shift) return deltaY;
24366
+ if (shift2) return deltaY;
24350
24367
  return null;
24351
24368
  }
24352
24369
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -25968,11 +25985,11 @@ function mergeBarColors2(indicators) {
25968
25985
  }
25969
25986
  }
25970
25987
  if (!any) return EMPTY_BARCOLORS2;
25971
- const map = /* @__PURE__ */ new Map();
25988
+ const map2 = /* @__PURE__ */ new Map();
25972
25989
  for (const m of indicators.values()) {
25973
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
25990
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
25974
25991
  }
25975
- return map;
25992
+ return map2;
25976
25993
  }
25977
25994
 
25978
25995
  // src/renderers/shared/drawing-geometry.ts
@@ -27611,9 +27628,9 @@ function settingsIdCatalog(hostSections) {
27611
27628
  };
27612
27629
  if (section.rows) addRows(section.rows);
27613
27630
  for (const inst of section.instances ?? []) addRows(inst.rows);
27614
- for (const sub of section.subsections ?? []) {
27615
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
27616
- addRows(sub.rows);
27631
+ for (const sub2 of section.subsections ?? []) {
27632
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27633
+ addRows(sub2.rows);
27617
27634
  }
27618
27635
  }
27619
27636
  for (const hs of hostSections) {
@@ -28172,14 +28189,14 @@ var SettingsDialog = class {
28172
28189
  };
28173
28190
  if (section.rows) seed(section.rows);
28174
28191
  for (const inst of section.instances ?? []) seed(inst.rows);
28175
- for (const sub of section.subsections ?? []) seed(sub.rows);
28192
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28176
28193
  for (const inst of section.instances ?? []) {
28177
28194
  if (!inst.enableKey || inst.enableKey in bag) continue;
28178
28195
  bag[inst.enableKey] = values[inst.enableKey] === true;
28179
28196
  }
28180
- for (const sub of section.subsections ?? []) {
28181
- if (!sub.enableKey || sub.enableKey in bag) continue;
28182
- bag[sub.enableKey] = values[sub.enableKey] === true;
28197
+ for (const sub2 of section.subsections ?? []) {
28198
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28199
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28183
28200
  }
28184
28201
  const refreshers = [];
28185
28202
  const put = (key, v) => {
@@ -28196,16 +28213,16 @@ var SettingsDialog = class {
28196
28213
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28197
28214
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28198
28215
  }
28199
- for (const sub of section.subsections ?? []) {
28200
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
28201
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
28216
+ for (const sub2 of section.subsections ?? []) {
28217
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28218
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28202
28219
  if (rows.length === 0) continue;
28203
- const subMarker = this.section(sub.title);
28220
+ const subMarker = this.section(sub2.title);
28204
28221
  subMarker.dataset.sdStyle = typeId;
28205
28222
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28206
28223
  subMarker.dataset.sdSub = "1";
28207
28224
  body.append(subMarker);
28208
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28225
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28209
28226
  }
28210
28227
  for (const r of refreshers) r();
28211
28228
  }
@@ -30300,11 +30317,11 @@ var DrawingInteraction = class {
30300
30317
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30301
30318
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30302
30319
  * while placing a line tool it locks the segment angle to 45° steps. */
30303
- down(x, y, mode = "off", shift = false) {
30320
+ down(x, y, mode = "off", shift2 = false) {
30304
30321
  const proj = this.deps.projector();
30305
30322
  if (this.state.kind === "placing") {
30306
30323
  if (this.state.draft.placementMode() !== "click") return;
30307
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30324
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30308
30325
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30309
30326
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30310
30327
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30323,7 +30340,7 @@ var DrawingInteraction = class {
30323
30340
  }
30324
30341
  const hit = this.hitAt(x, y);
30325
30342
  if (hit) {
30326
- if (shift) {
30343
+ if (shift2) {
30327
30344
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30328
30345
  return;
30329
30346
  }
@@ -30333,7 +30350,7 @@ var DrawingInteraction = class {
30333
30350
  }
30334
30351
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30335
30352
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30336
- move(x, y, mode = "off", shift = false) {
30353
+ move(x, y, mode = "off", shift2 = false) {
30337
30354
  if (this.state.kind === "idle") {
30338
30355
  const tool = this.deps.activeTool();
30339
30356
  const had = this.snapAt != null;
@@ -30347,7 +30364,7 @@ var DrawingInteraction = class {
30347
30364
  this.captureFreehand(x, y);
30348
30365
  return;
30349
30366
  }
30350
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30367
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30351
30368
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30352
30369
  this.deps.changed();
30353
30370
  return;
@@ -30360,7 +30377,7 @@ var DrawingInteraction = class {
30360
30377
  if (this.state.handle >= 0) {
30361
30378
  const a = d.anchors[this.state.handle];
30362
30379
  if (a) {
30363
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30380
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30364
30381
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30365
30382
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30366
30383
  }
@@ -31058,21 +31075,21 @@ var DrawingSettingsPopup = class {
31058
31075
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31059
31076
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31060
31077
  }
31061
- const vwap = drawing.vwap;
31062
- if (paths.has("vwap.midColor") && vwap) {
31078
+ const vwap2 = drawing.vwap;
31079
+ if (paths.has("vwap.midColor") && vwap2) {
31063
31080
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31064
31081
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
31065
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31066
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31082
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31083
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31067
31084
  bar.appendChild(
31068
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31085
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31069
31086
  label: (v) => `${v}\u03C3`,
31070
31087
  labelInTrigger: true
31071
31088
  })
31072
31089
  );
31073
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31074
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31075
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31090
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31091
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31092
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31076
31093
  }
31077
31094
  if (paths.has("maxCurvature")) {
31078
31095
  const cur = drawing.maxCurvature ?? 24;
@@ -32089,8 +32106,8 @@ var UserDrawingController = class {
32089
32106
  this.toolbar.setFavorites(types);
32090
32107
  }
32091
32108
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
32092
- setToolShortcuts(map) {
32093
- this.toolbar.setShortcuts(map);
32109
+ setToolShortcuts(map2) {
32110
+ this.toolbar.setShortcuts(map2);
32094
32111
  }
32095
32112
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
32096
32113
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -32208,7 +32225,7 @@ var UserDrawingController = class {
32208
32225
  this.render();
32209
32226
  }
32210
32227
  }
32211
- pointerDown(x, y, snap = "off", shift = false) {
32228
+ pointerDown(x, y, snap = "off", shift2 = false) {
32212
32229
  if (this.eraserMode) {
32213
32230
  this.erasing = true;
32214
32231
  this.deleteAt(x, y);
@@ -32228,9 +32245,9 @@ var UserDrawingController = class {
32228
32245
  return;
32229
32246
  }
32230
32247
  }
32231
- this.interaction.down(x, y, snap, shift);
32248
+ this.interaction.down(x, y, snap, shift2);
32232
32249
  }
32233
- pointerMove(x, y, snap = "off", shift = false) {
32250
+ pointerMove(x, y, snap = "off", shift2 = false) {
32234
32251
  if (this.eraserMode) {
32235
32252
  if (this.erasing) this.deleteAt(x, y);
32236
32253
  return;
@@ -32241,7 +32258,7 @@ var UserDrawingController = class {
32241
32258
  this.render();
32242
32259
  return;
32243
32260
  }
32244
- this.interaction.move(x, y, snap, shift);
32261
+ this.interaction.move(x, y, snap, shift2);
32245
32262
  this.updateHover(x, y);
32246
32263
  }
32247
32264
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -32918,14 +32935,14 @@ function layoutTable(ctx, t, args) {
32918
32935
  }
32919
32936
  for (const { box, w: w2, h: h2 } of spanning) {
32920
32937
  if (box.cs > 1) {
32921
- let sum = 0;
32922
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
32923
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
32938
+ let sum2 = 0;
32939
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
32940
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
32924
32941
  }
32925
32942
  if (box.rs > 1) {
32926
- let sum = 0;
32927
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
32928
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
32943
+ let sum2 = 0;
32944
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
32945
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
32929
32946
  }
32930
32947
  }
32931
32948
  let w = 0;
@@ -33512,24 +33529,24 @@ function rasterizeOverlay(ctx, root, frame) {
33512
33529
  drawSvgShapes(el, alpha);
33513
33530
  continue;
33514
33531
  }
33515
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33532
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33516
33533
  let path = null;
33517
33534
  if (tag === "path") {
33518
33535
  const d = el.getAttribute("d");
33519
33536
  if (d) path = new Path2D(d);
33520
33537
  } else if (tag === "circle") {
33521
33538
  path = new Path2D();
33522
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
33539
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
33523
33540
  } else if (tag === "ellipse") {
33524
33541
  path = new Path2D();
33525
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
33542
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
33526
33543
  } else if (tag === "rect") {
33527
33544
  path = new Path2D();
33528
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
33545
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
33529
33546
  } else if (tag === "line") {
33530
33547
  path = new Path2D();
33531
- path.moveTo(num3("x1"), num3("y1"));
33532
- path.lineTo(num3("x2"), num3("y2"));
33548
+ path.moveTo(num4("x1"), num4("y1"));
33549
+ path.lineTo(num4("x2"), num4("y2"));
33533
33550
  } else if (tag === "polyline" || tag === "polygon") {
33534
33551
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
33535
33552
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -34946,8 +34963,8 @@ var NativeRenderer = class {
34946
34963
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
34947
34964
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
34948
34965
  drawingsSnapMode: () => this.snapMode,
34949
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
34950
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
34966
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
34967
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
34951
34968
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
34952
34969
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
34953
34970
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -35264,18 +35281,18 @@ var NativeRenderer = class {
35264
35281
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
35265
35282
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
35266
35283
  const newHeadTime = this.bars[0].time;
35267
- let shift = 0;
35284
+ let shift2 = 0;
35268
35285
  if (newHeadTime !== prevHeadTime) {
35269
35286
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
35270
- if (prepended > 0) shift = prepended;
35287
+ if (prepended > 0) shift2 = prepended;
35271
35288
  else {
35272
35289
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
35273
- if (trimmed > 0) shift = -trimmed;
35290
+ if (trimmed > 0) shift2 = -trimmed;
35274
35291
  }
35275
35292
  }
35276
- if (shift !== 0) {
35277
- this.zoomAnchorLogical += shift;
35278
- if (this.hoverLogical != null) this.hoverLogical += shift;
35293
+ if (shift2 !== 0) {
35294
+ this.zoomAnchorLogical += shift2;
35295
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
35279
35296
  }
35280
35297
  }
35281
35298
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -36881,15 +36898,15 @@ var NativeRenderer = class {
36881
36898
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
36882
36899
  * strip — merged own-scale indicators are hidden while collapsed. */
36883
36900
  collapsedMasterMap() {
36884
- const map = /* @__PURE__ */ new Map();
36901
+ const map2 = /* @__PURE__ */ new Map();
36885
36902
  for (const pane of this.scene.panes.values()) {
36886
36903
  if (!pane.collapsed) continue;
36887
36904
  const models = this.scene.orderedIndicatorsForPane(pane.id);
36888
36905
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
36889
36906
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
36890
- map.set(pane.id, master?.id ?? null);
36907
+ map2.set(pane.id, master?.id ?? null);
36891
36908
  }
36892
- return map;
36909
+ return map2;
36893
36910
  }
36894
36911
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
36895
36912
  setToolbarGutter(px) {
@@ -37256,12 +37273,2328 @@ function registerVpvr() {
37256
37273
  registerNativeIndicator(vpvrDescriptor);
37257
37274
  }
37258
37275
 
37276
+ // src/core/model/identity.ts
37277
+ function stableSeriesId(parts) {
37278
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
37279
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
37280
+ }
37281
+
37282
+ // src/core/native-indicators/classics/define.ts
37283
+ function num3(inputs, key, fallback) {
37284
+ const v = inputs[key];
37285
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
37286
+ }
37287
+ function str3(inputs, key, fallback) {
37288
+ const v = inputs[key];
37289
+ return typeof v === "string" && v !== "" ? v : fallback;
37290
+ }
37291
+ function toPoints(bars, values, colors) {
37292
+ const pts = new Array(bars.length);
37293
+ for (let i = 0; i < bars.length; i++) {
37294
+ const v = values[i];
37295
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
37296
+ const c = colors?.[i];
37297
+ if (c != null) p.color = c;
37298
+ pts[i] = p;
37299
+ }
37300
+ return pts;
37301
+ }
37302
+ var ClassicIndicator = class {
37303
+ constructor(spec) {
37304
+ this.spec = spec;
37305
+ this.inputs = {};
37306
+ }
37307
+ start(ctx, inputs) {
37308
+ this.ctx = ctx;
37309
+ this.inputs = inputs;
37310
+ this.recompute();
37311
+ }
37312
+ onBars() {
37313
+ this.recompute();
37314
+ }
37315
+ onViewport() {
37316
+ }
37317
+ setInputs(inputs) {
37318
+ this.inputs = inputs;
37319
+ this.recompute();
37320
+ }
37321
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
37322
+ suspend() {
37323
+ }
37324
+ resume() {
37325
+ this.recompute();
37326
+ }
37327
+ stop() {
37328
+ }
37329
+ recompute() {
37330
+ const bars = this.ctx.bars();
37331
+ const out = this.spec.compute(bars, this.inputs);
37332
+ const instanceId = this.spec.type;
37333
+ const series = [];
37334
+ const idsByKey = /* @__PURE__ */ new Map();
37335
+ out.plots.forEach((plot, ordinal) => {
37336
+ const kind = plot.kind ?? "line";
37337
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
37338
+ idsByKey.set(plot.key, id);
37339
+ series.push({
37340
+ id,
37341
+ title: plot.title,
37342
+ paneId: "",
37343
+ // stamped by the orchestrator
37344
+ kind,
37345
+ points: toPoints(bars, plot.values, plot.colors),
37346
+ style: {
37347
+ color: plot.color,
37348
+ width: plot.width ?? 1,
37349
+ lineStyle: plot.lineStyle ?? "solid",
37350
+ ...plot.base != null ? { base: plot.base } : {}
37351
+ },
37352
+ ...plot.overlay ? { overlay: true } : {}
37353
+ });
37354
+ });
37355
+ if (out.markers && out.markers.length > 0) {
37356
+ series.push({
37357
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
37358
+ title: this.spec.shortTitle ?? this.spec.title,
37359
+ paneId: "",
37360
+ kind: "markers",
37361
+ markers: out.markers
37362
+ });
37363
+ }
37364
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
37365
+ const from = idsByKey.get(band.from);
37366
+ const to = idsByKey.get(band.to);
37367
+ if (!from || !to) return [];
37368
+ return [
37369
+ {
37370
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
37371
+ paneId: "",
37372
+ fromSeriesId: from,
37373
+ toSeriesId: to,
37374
+ color: band.color
37375
+ }
37376
+ ];
37377
+ });
37378
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
37379
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
37380
+ paneId: "",
37381
+ price: level.price,
37382
+ ...level.color != null ? { color: level.color } : {},
37383
+ lineStyle: level.lineStyle ?? "dashed",
37384
+ width: 1,
37385
+ ...level.title != null ? { title: level.title } : {}
37386
+ }));
37387
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
37388
+ const { key, ...rest } = poly;
37389
+ return {
37390
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
37391
+ paneId: "",
37392
+ ...rest
37393
+ };
37394
+ });
37395
+ this.ctx.emit({ series, fills, priceLines, polylines });
37396
+ this.ctx.setStatus("idle");
37397
+ }
37398
+ };
37399
+ function classicDescriptor(spec) {
37400
+ return {
37401
+ type: spec.type,
37402
+ title: spec.title,
37403
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
37404
+ paneHint: spec.overlay ? "price" : "new",
37405
+ overlay: spec.overlay,
37406
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
37407
+ multiInstance: true,
37408
+ inputsSchema: () => spec.inputs,
37409
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
37410
+ create: () => new ClassicIndicator(spec)
37411
+ };
37412
+ }
37413
+
37414
+ // src/core/native-indicators/classics/math.ts
37415
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
37416
+ function sourceValues(bars, source) {
37417
+ const out = new Array(bars.length);
37418
+ for (let i = 0; i < bars.length; i++) {
37419
+ const b = bars[i];
37420
+ switch (source) {
37421
+ case "Open":
37422
+ out[i] = b.open;
37423
+ break;
37424
+ case "High":
37425
+ out[i] = b.high;
37426
+ break;
37427
+ case "Low":
37428
+ out[i] = b.low;
37429
+ break;
37430
+ case "HL2":
37431
+ out[i] = (b.high + b.low) / 2;
37432
+ break;
37433
+ case "HLC3":
37434
+ out[i] = (b.high + b.low + b.close) / 3;
37435
+ break;
37436
+ case "OHLC4":
37437
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
37438
+ break;
37439
+ case "HLCC4":
37440
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
37441
+ break;
37442
+ default:
37443
+ out[i] = b.close;
37444
+ }
37445
+ }
37446
+ return out;
37447
+ }
37448
+ function volumes(bars) {
37449
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
37450
+ }
37451
+ function highs(bars) {
37452
+ return bars.map((b) => b.high);
37453
+ }
37454
+ function lows(bars) {
37455
+ return bars.map((b) => b.low);
37456
+ }
37457
+ function closes(bars) {
37458
+ return bars.map((b) => b.close);
37459
+ }
37460
+ function opens(bars) {
37461
+ return bars.map((b) => b.open);
37462
+ }
37463
+ function zip(a, b, f) {
37464
+ const out = new Array(a.length);
37465
+ for (let i = 0; i < a.length; i++) {
37466
+ const x = a[i];
37467
+ const y = b[i];
37468
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
37469
+ }
37470
+ return out;
37471
+ }
37472
+ function map(a, f) {
37473
+ const out = new Array(a.length);
37474
+ for (let i = 0; i < a.length; i++) {
37475
+ const x = a[i];
37476
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
37477
+ }
37478
+ return out;
37479
+ }
37480
+ function sub(a, b) {
37481
+ return zip(a, b, (x, y) => x - y);
37482
+ }
37483
+ function windowed(v, len, fold) {
37484
+ const out = new Array(v.length).fill(Number.NaN);
37485
+ if (len <= 0) return out;
37486
+ let run = 0;
37487
+ for (let i = 0; i < v.length; i++) {
37488
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
37489
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
37490
+ }
37491
+ return out;
37492
+ }
37493
+ function sum(v, len) {
37494
+ return windowed(v, len, (w, from, to) => {
37495
+ let s = 0;
37496
+ for (let k = from; k <= to; k++) s += w[k];
37497
+ return s;
37498
+ });
37499
+ }
37500
+ function sma(v, len) {
37501
+ return windowed(v, len, (w, from, to) => {
37502
+ let s = 0;
37503
+ for (let k = from; k <= to; k++) s += w[k];
37504
+ return s / len;
37505
+ });
37506
+ }
37507
+ function wma(v, len) {
37508
+ const norm = len * (len + 1) / 2;
37509
+ return windowed(v, len, (w, from, to) => {
37510
+ let s = 0;
37511
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
37512
+ return s / norm;
37513
+ });
37514
+ }
37515
+ function vwma(src, vol, len) {
37516
+ const pv = zip(src, vol, (p, q) => p * q);
37517
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
37518
+ }
37519
+ function stdev(v, len) {
37520
+ return windowed(v, len, (w, from, to) => {
37521
+ let s = 0;
37522
+ for (let k = from; k <= to; k++) s += w[k];
37523
+ const mean = s / len;
37524
+ let sq = 0;
37525
+ for (let k = from; k <= to; k++) {
37526
+ const d = w[k] - mean;
37527
+ sq += d * d;
37528
+ }
37529
+ return Math.sqrt(sq / len);
37530
+ });
37531
+ }
37532
+ function highest(v, len) {
37533
+ return windowed(v, len, (w, from, to) => {
37534
+ let m = -Infinity;
37535
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
37536
+ return m;
37537
+ });
37538
+ }
37539
+ function lowest(v, len) {
37540
+ return windowed(v, len, (w, from, to) => {
37541
+ let m = Infinity;
37542
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
37543
+ return m;
37544
+ });
37545
+ }
37546
+ function barsSinceHighest(v, len) {
37547
+ return windowed(v, len, (w, from, to) => {
37548
+ let m = -Infinity;
37549
+ let at = to;
37550
+ for (let k = from; k <= to; k++) {
37551
+ if (w[k] >= m) {
37552
+ m = w[k];
37553
+ at = k;
37554
+ }
37555
+ }
37556
+ return to - at;
37557
+ });
37558
+ }
37559
+ function barsSinceLowest(v, len) {
37560
+ return windowed(v, len, (w, from, to) => {
37561
+ let m = Infinity;
37562
+ let at = to;
37563
+ for (let k = from; k <= to; k++) {
37564
+ if (w[k] <= m) {
37565
+ m = w[k];
37566
+ at = k;
37567
+ }
37568
+ }
37569
+ return to - at;
37570
+ });
37571
+ }
37572
+ function recursive(v, len, alpha) {
37573
+ const out = new Array(v.length).fill(Number.NaN);
37574
+ let run = 0;
37575
+ let prev2 = Number.NaN;
37576
+ for (let i = 0; i < v.length; i++) {
37577
+ const x = v[i];
37578
+ if (!Number.isFinite(x)) {
37579
+ run = 0;
37580
+ prev2 = Number.NaN;
37581
+ continue;
37582
+ }
37583
+ run += 1;
37584
+ if (Number.isFinite(prev2)) {
37585
+ prev2 = alpha * x + (1 - alpha) * prev2;
37586
+ out[i] = prev2;
37587
+ } else if (run >= len) {
37588
+ let s = 0;
37589
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
37590
+ prev2 = s / len;
37591
+ out[i] = prev2;
37592
+ }
37593
+ }
37594
+ return out;
37595
+ }
37596
+ function ema(v, len) {
37597
+ return recursive(v, len, 2 / (len + 1));
37598
+ }
37599
+ function rma(v, len) {
37600
+ return recursive(v, len, 1 / len);
37601
+ }
37602
+ function change(v, n = 1) {
37603
+ const out = new Array(v.length).fill(Number.NaN);
37604
+ for (let i = n; i < v.length; i++) {
37605
+ const a = v[i];
37606
+ const b = v[i - n];
37607
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
37608
+ }
37609
+ return out;
37610
+ }
37611
+ function roc(v, len) {
37612
+ const out = new Array(v.length).fill(Number.NaN);
37613
+ for (let i = len; i < v.length; i++) {
37614
+ const a = v[i];
37615
+ const b = v[i - len];
37616
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
37617
+ }
37618
+ return out;
37619
+ }
37620
+ function shift(v, n) {
37621
+ const out = new Array(v.length).fill(Number.NaN);
37622
+ for (let i = 0; i < v.length; i++) {
37623
+ const j = i - n;
37624
+ if (j >= 0 && j < v.length) out[i] = v[j];
37625
+ }
37626
+ return out;
37627
+ }
37628
+ function trueRange(bars) {
37629
+ const out = new Array(bars.length).fill(Number.NaN);
37630
+ for (let i = 0; i < bars.length; i++) {
37631
+ const b = bars[i];
37632
+ if (i === 0) {
37633
+ out[i] = b.high - b.low;
37634
+ continue;
37635
+ }
37636
+ const pc = bars[i - 1].close;
37637
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
37638
+ }
37639
+ return out;
37640
+ }
37641
+ function atr(bars, len) {
37642
+ return rma(trueRange(bars), len);
37643
+ }
37644
+ function rsi(v, len) {
37645
+ const d = change(v);
37646
+ const up = map(d, (x) => Math.max(x, 0));
37647
+ const dn = map(d, (x) => Math.max(-x, 0));
37648
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
37649
+ }
37650
+ function stoch(v, hi, lo, len) {
37651
+ const hh = highest(hi, len);
37652
+ const ll = lowest(lo, len);
37653
+ const out = new Array(v.length).fill(Number.NaN);
37654
+ for (let i = 0; i < v.length; i++) {
37655
+ const x = v[i];
37656
+ const h = hh[i];
37657
+ const l = ll[i];
37658
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
37659
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
37660
+ }
37661
+ return out;
37662
+ }
37663
+ function linreg(v, len) {
37664
+ const sx = (len - 1) * len / 2;
37665
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
37666
+ const denom = len * sxx - sx * sx;
37667
+ return windowed(v, len, (w, from, to) => {
37668
+ let sy = 0;
37669
+ let sxy = 0;
37670
+ for (let k = from; k <= to; k++) {
37671
+ const x = k - from;
37672
+ sy += w[k];
37673
+ sxy += x * w[k];
37674
+ }
37675
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
37676
+ const intercept = (sy - slope * sx) / len;
37677
+ return intercept + slope * (len - 1);
37678
+ });
37679
+ }
37680
+ function swma(v) {
37681
+ const out = new Array(v.length).fill(Number.NaN);
37682
+ for (let i = 3; i < v.length; i++) {
37683
+ const a = v[i - 3];
37684
+ const b = v[i - 2];
37685
+ const c = v[i - 1];
37686
+ const d = v[i];
37687
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
37688
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
37689
+ }
37690
+ }
37691
+ return out;
37692
+ }
37693
+ function percentRank(v, len) {
37694
+ const out = new Array(v.length).fill(Number.NaN);
37695
+ for (let i = len; i < v.length; i++) {
37696
+ const x = v[i];
37697
+ if (!Number.isFinite(x)) continue;
37698
+ let below = 0;
37699
+ let count = 0;
37700
+ for (let k = i - len; k < i; k++) {
37701
+ const y = v[k];
37702
+ if (!Number.isFinite(y)) {
37703
+ count = -1;
37704
+ break;
37705
+ }
37706
+ count += 1;
37707
+ if (y <= x) below += 1;
37708
+ }
37709
+ if (count === len) out[i] = 100 * below / len;
37710
+ }
37711
+ return out;
37712
+ }
37713
+ function cumSum(v) {
37714
+ const out = new Array(v.length).fill(Number.NaN);
37715
+ let acc = 0;
37716
+ let seen = false;
37717
+ for (let i = 0; i < v.length; i++) {
37718
+ const x = v[i];
37719
+ if (Number.isFinite(x)) {
37720
+ acc += x;
37721
+ seen = true;
37722
+ }
37723
+ if (seen) out[i] = acc;
37724
+ }
37725
+ return out;
37726
+ }
37727
+
37728
+ // src/core/native-indicators/classics/shared.ts
37729
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
37730
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
37731
+ }
37732
+ function sourceInput(defval = "Close") {
37733
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
37734
+ }
37735
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
37736
+ return { key, title, type: "color", defval };
37737
+ }
37738
+ function withAlpha2(hex, alpha = 0.08) {
37739
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
37740
+ if (!m) return hex;
37741
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
37742
+ return `#${m[1]}${a}`;
37743
+ }
37744
+
37745
+ // src/core/native-indicators/classics/averages.ts
37746
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
37747
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
37748
+ function fixedAverage(type, title, shortTitle, smooth) {
37749
+ return {
37750
+ type,
37751
+ title,
37752
+ shortTitle,
37753
+ overlay: true,
37754
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
37755
+ compute: (bars, inputs) => {
37756
+ const len = num3(inputs, "length", 20);
37757
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
37758
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37759
+ if (offset !== 0) values = shift(values, offset);
37760
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37761
+ }
37762
+ };
37763
+ }
37764
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
37765
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
37766
+ var movingAverage = {
37767
+ type: "moving-average",
37768
+ title: "Moving Average",
37769
+ shortTitle: "MA",
37770
+ overlay: true,
37771
+ inputs: [
37772
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
37773
+ lengthInput(20),
37774
+ sourceInput(),
37775
+ offsetInput,
37776
+ colorInput()
37777
+ ],
37778
+ compute: (bars, inputs) => {
37779
+ const len = num3(inputs, "length", 20);
37780
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37781
+ const kind = str3(inputs, "maType", "SMA");
37782
+ let values;
37783
+ switch (kind) {
37784
+ case "EMA":
37785
+ values = ema(src, len);
37786
+ break;
37787
+ case "WMA":
37788
+ values = wma(src, len);
37789
+ break;
37790
+ case "RMA":
37791
+ values = rma(src, len);
37792
+ break;
37793
+ case "VWMA":
37794
+ values = vwma(src, volumes(bars), len);
37795
+ break;
37796
+ default:
37797
+ values = sma(src, len);
37798
+ }
37799
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37800
+ if (offset !== 0) values = shift(values, offset);
37801
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37802
+ }
37803
+ };
37804
+ var smoothedMa = {
37805
+ type: "rma",
37806
+ title: "Smoothed Moving Average",
37807
+ shortTitle: "RMA",
37808
+ overlay: true,
37809
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37810
+ compute: (bars, inputs) => ({
37811
+ plots: [
37812
+ {
37813
+ key: "rma",
37814
+ title: "RMA",
37815
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37816
+ color: str3(inputs, "color", SERIES_LINE),
37817
+ width: 2
37818
+ }
37819
+ ]
37820
+ })
37821
+ };
37822
+ var zlema = {
37823
+ type: "zlema",
37824
+ title: "Zero-Lag Exponential Moving Average",
37825
+ shortTitle: "ZLEMA",
37826
+ overlay: true,
37827
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37828
+ compute: (bars, inputs) => {
37829
+ const len = num3(inputs, "length", 14);
37830
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37831
+ const lag = Math.floor((len - 1) / 2);
37832
+ const lagged = shift(src, lag);
37833
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
37834
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37835
+ }
37836
+ };
37837
+ var vidya = {
37838
+ type: "vidya",
37839
+ title: "Variable Index Dynamic Average",
37840
+ shortTitle: "VIDYA",
37841
+ overlay: true,
37842
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
37843
+ compute: (bars, inputs) => {
37844
+ const len = num3(inputs, "length", 14);
37845
+ const cmoLen = num3(inputs, "cmoLength", 9);
37846
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37847
+ const d = change(src);
37848
+ const su = new Array(src.length).fill(Number.NaN);
37849
+ const sd = new Array(src.length).fill(Number.NaN);
37850
+ for (let i = cmoLen; i < src.length; i++) {
37851
+ let u = 0;
37852
+ let w = 0;
37853
+ let ok = true;
37854
+ for (let k = i - cmoLen + 1; k <= i; k++) {
37855
+ const x = d[k];
37856
+ if (!Number.isFinite(x)) {
37857
+ ok = false;
37858
+ break;
37859
+ }
37860
+ if (x > 0) u += x;
37861
+ else w -= x;
37862
+ }
37863
+ if (ok) {
37864
+ su[i] = u;
37865
+ sd[i] = w;
37866
+ }
37867
+ }
37868
+ const alpha = 2 / (len + 1);
37869
+ const out = new Array(src.length).fill(Number.NaN);
37870
+ let prev2 = Number.NaN;
37871
+ for (let i = 0; i < src.length; i++) {
37872
+ const x = src[i];
37873
+ const u = su[i];
37874
+ const w = sd[i];
37875
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
37876
+ const total = u + w;
37877
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
37878
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
37879
+ out[i] = prev2;
37880
+ }
37881
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37882
+ }
37883
+ };
37884
+ var maEnvelope = {
37885
+ type: "ma-envelope",
37886
+ title: "Moving Average Envelope",
37887
+ shortTitle: "MA Env",
37888
+ overlay: true,
37889
+ inputs: [
37890
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
37891
+ lengthInput(20),
37892
+ sourceInput(),
37893
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
37894
+ colorInput()
37895
+ ],
37896
+ compute: (bars, inputs) => {
37897
+ const len = num3(inputs, "length", 20);
37898
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37899
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
37900
+ const k = num3(inputs, "percent", 2.5) / 100;
37901
+ const ink = str3(inputs, "color", SERIES_LINE);
37902
+ return {
37903
+ plots: [
37904
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
37905
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
37906
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
37907
+ ],
37908
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
37909
+ };
37910
+ }
37911
+ };
37912
+ var linearRegression = {
37913
+ type: "linear-regression",
37914
+ title: "Linear Regression Curve",
37915
+ shortTitle: "LinReg",
37916
+ overlay: true,
37917
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37918
+ compute: (bars, inputs) => ({
37919
+ plots: [
37920
+ {
37921
+ key: "linreg",
37922
+ title: "LinReg",
37923
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37924
+ color: str3(inputs, "color", SERIES_LINE),
37925
+ width: 2
37926
+ }
37927
+ ]
37928
+ })
37929
+ };
37930
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
37931
+
37932
+ // src/core/native-indicators/classics/bands.ts
37933
+ function bollinger(bars, inputs) {
37934
+ const len = num3(inputs, "length", 20);
37935
+ const mult = num3(inputs, "mult", 2);
37936
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37937
+ const basis = sma(src, len);
37938
+ const dev = map(stdev(src, len), (x) => x * mult);
37939
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
37940
+ }
37941
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
37942
+ var bollingerBands = {
37943
+ type: "bollinger-bands",
37944
+ title: "Bollinger Bands",
37945
+ shortTitle: "BB",
37946
+ overlay: true,
37947
+ inputs: [...BB_INPUTS, colorInput()],
37948
+ compute: (bars, inputs) => {
37949
+ const { basis, upper, lower } = bollinger(bars, inputs);
37950
+ const ink = str3(inputs, "color", SERIES_LINE);
37951
+ return {
37952
+ plots: [
37953
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
37954
+ { key: "upper", title: "Upper", values: upper, color: ink },
37955
+ { key: "lower", title: "Lower", values: lower, color: ink }
37956
+ ],
37957
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
37958
+ };
37959
+ }
37960
+ };
37961
+ var percentB = {
37962
+ type: "percent-b",
37963
+ title: "Bollinger Bands %B",
37964
+ shortTitle: "%B",
37965
+ overlay: false,
37966
+ inputs: [...BB_INPUTS, colorInput()],
37967
+ compute: (bars, inputs) => {
37968
+ const { upper, lower } = bollinger(bars, inputs);
37969
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37970
+ const values = src.map((x, i) => {
37971
+ const u = upper[i];
37972
+ const l = lower[i];
37973
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
37974
+ });
37975
+ return {
37976
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
37977
+ levels: [
37978
+ { key: "upper", price: 1, color: NEUTRAL },
37979
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
37980
+ { key: "lower", price: 0, color: NEUTRAL }
37981
+ ]
37982
+ };
37983
+ }
37984
+ };
37985
+ var bandwidth = {
37986
+ type: "bandwidth",
37987
+ title: "Bollinger Bands Width",
37988
+ shortTitle: "BandWidth",
37989
+ overlay: false,
37990
+ inputs: [...BB_INPUTS, colorInput()],
37991
+ compute: (bars, inputs) => {
37992
+ const { basis, upper, lower } = bollinger(bars, inputs);
37993
+ const values = basis.map((b, i) => {
37994
+ const u = upper[i];
37995
+ const l = lower[i];
37996
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
37997
+ });
37998
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37999
+ }
38000
+ };
38001
+ var keltner = {
38002
+ type: "keltner-channels",
38003
+ title: "Keltner Channels",
38004
+ shortTitle: "KC",
38005
+ overlay: true,
38006
+ inputs: [
38007
+ lengthInput(20),
38008
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38009
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38010
+ sourceInput(),
38011
+ colorInput()
38012
+ ],
38013
+ compute: (bars, inputs) => {
38014
+ const len = num3(inputs, "length", 20);
38015
+ const mult = num3(inputs, "mult", 2);
38016
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
38017
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
38018
+ const ink = str3(inputs, "color", SERIES_LINE);
38019
+ return {
38020
+ plots: [
38021
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
38022
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
38023
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
38024
+ ],
38025
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38026
+ };
38027
+ }
38028
+ };
38029
+ var donchian = {
38030
+ type: "donchian-channels",
38031
+ title: "Donchian Channels",
38032
+ shortTitle: "DC",
38033
+ overlay: true,
38034
+ inputs: [lengthInput(20), colorInput()],
38035
+ compute: (bars, inputs) => {
38036
+ const len = num3(inputs, "length", 20);
38037
+ const upper = highest(highs(bars), len);
38038
+ const lower = lowest(lows(bars), len);
38039
+ const ink = str3(inputs, "color", SERIES_LINE);
38040
+ return {
38041
+ plots: [
38042
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
38043
+ { key: "upper", title: "Upper", values: upper, color: ink },
38044
+ { key: "lower", title: "Lower", values: lower, color: ink }
38045
+ ],
38046
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38047
+ };
38048
+ }
38049
+ };
38050
+ var chandelier = {
38051
+ type: "chandelier-exit",
38052
+ title: "Chandelier Exit",
38053
+ shortTitle: "CE",
38054
+ overlay: true,
38055
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
38056
+ compute: (bars, inputs) => {
38057
+ const len = num3(inputs, "length", 22);
38058
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
38059
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
38060
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
38061
+ return {
38062
+ plots: [
38063
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
38064
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
38065
+ ]
38066
+ };
38067
+ }
38068
+ };
38069
+ var chandeKroll = {
38070
+ type: "chande-kroll-stop",
38071
+ title: "Chande Kroll Stop",
38072
+ shortTitle: "CKS",
38073
+ overlay: true,
38074
+ inputs: [
38075
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38076
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
38077
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
38078
+ ],
38079
+ compute: (bars, inputs) => {
38080
+ const p = num3(inputs, "p", 10);
38081
+ const x = num3(inputs, "x", 1);
38082
+ const q = num3(inputs, "q", 9);
38083
+ const range = map(atr(bars, p), (v) => v * x);
38084
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
38085
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
38086
+ return {
38087
+ plots: [
38088
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
38089
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
38090
+ ]
38091
+ };
38092
+ }
38093
+ };
38094
+ var supertrend = {
38095
+ type: "supertrend",
38096
+ title: "SuperTrend",
38097
+ shortTitle: "SuperTrend",
38098
+ overlay: true,
38099
+ inputs: [
38100
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38101
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
38102
+ ],
38103
+ compute: (bars, inputs) => {
38104
+ const len = num3(inputs, "atrLength", 10);
38105
+ const mult = num3(inputs, "mult", 3);
38106
+ const range = atr(bars, len);
38107
+ const n = bars.length;
38108
+ const st = new Array(n).fill(Number.NaN);
38109
+ const dir = new Array(n).fill(1);
38110
+ let up = Number.NaN;
38111
+ let dn = Number.NaN;
38112
+ let trend = 1;
38113
+ for (let i = 0; i < n; i++) {
38114
+ const r = range[i];
38115
+ if (!Number.isFinite(r)) continue;
38116
+ const b = bars[i];
38117
+ const mid = (b.high + b.low) / 2;
38118
+ const basicUp = mid - mult * r;
38119
+ const basicDn = mid + mult * r;
38120
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
38121
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
38122
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
38123
+ if (trend === 1 && b.close < up) trend = -1;
38124
+ else if (trend === -1 && b.close > dn) trend = 1;
38125
+ dir[i] = trend;
38126
+ st[i] = trend === 1 ? up : dn;
38127
+ }
38128
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
38129
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
38130
+ }
38131
+ };
38132
+ var alligator = {
38133
+ type: "williams-alligator",
38134
+ title: "Williams Alligator",
38135
+ shortTitle: "Alligator",
38136
+ overlay: true,
38137
+ inputs: [
38138
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
38139
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
38140
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
38141
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
38142
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
38143
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
38144
+ ],
38145
+ compute: (bars, inputs) => {
38146
+ const hl2 = sourceValues(bars, "HL2");
38147
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
38148
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
38149
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
38150
+ return {
38151
+ plots: [
38152
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
38153
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
38154
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
38155
+ ]
38156
+ };
38157
+ }
38158
+ };
38159
+ var gator = {
38160
+ type: "gator-oscillator",
38161
+ title: "Gator Oscillator",
38162
+ shortTitle: "Gator Oscillator",
38163
+ overlay: false,
38164
+ inputs: [],
38165
+ compute: (bars) => {
38166
+ const hl2 = sourceValues(bars, "HL2");
38167
+ const jaw = shift(rma(hl2, 13), 8);
38168
+ const teeth = shift(rma(hl2, 8), 5);
38169
+ const lips = shift(rma(hl2, 5), 3);
38170
+ const upper = map(sub(jaw, teeth), Math.abs);
38171
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
38172
+ const colorByExpansion = (v) => v.map((x, i) => {
38173
+ if (!Number.isFinite(x)) return null;
38174
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
38175
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
38176
+ });
38177
+ return {
38178
+ plots: [
38179
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
38180
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
38181
+ ]
38182
+ };
38183
+ }
38184
+ };
38185
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
38186
+
38187
+ // src/core/native-indicators/classics/oscillators.ts
38188
+ function guideLevels(low, high, mid) {
38189
+ const levels = [
38190
+ { key: "upper", price: high, color: NEUTRAL },
38191
+ { key: "lower", price: low, color: NEUTRAL }
38192
+ ];
38193
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
38194
+ return levels;
38195
+ }
38196
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
38197
+ function histColors(values) {
38198
+ return values.map((x, i) => {
38199
+ if (!Number.isFinite(x)) return null;
38200
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
38201
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
38202
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
38203
+ return rising ? `${BEARISH}80` : BEARISH;
38204
+ });
38205
+ }
38206
+ var rsiSpec = {
38207
+ type: "rsi",
38208
+ title: "Relative Strength Index",
38209
+ shortTitle: "RSI",
38210
+ overlay: false,
38211
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38212
+ compute: (bars, inputs) => ({
38213
+ plots: [
38214
+ {
38215
+ key: "rsi",
38216
+ title: "RSI",
38217
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38218
+ color: str3(inputs, "color", SERIES_LINE),
38219
+ width: 2
38220
+ }
38221
+ ],
38222
+ levels: guideLevels(30, 70, 50)
38223
+ })
38224
+ };
38225
+ var stochastic = {
38226
+ type: "stochastic",
38227
+ title: "Stochastic",
38228
+ shortTitle: "Stoch",
38229
+ overlay: false,
38230
+ inputs: [
38231
+ lengthInput(14, "kLength", "%K length"),
38232
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
38233
+ lengthInput(3, "dLength", "%D smoothing", 500)
38234
+ ],
38235
+ compute: (bars, inputs) => {
38236
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
38237
+ const d = sma(k, num3(inputs, "dLength", 3));
38238
+ return {
38239
+ plots: [
38240
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38241
+ { key: "d", title: "%D", values: d, color: WARNING }
38242
+ ],
38243
+ levels: guideLevels(20, 80)
38244
+ };
38245
+ }
38246
+ };
38247
+ var stochasticRsi = {
38248
+ type: "stochastic-rsi",
38249
+ title: "Stochastic RSI",
38250
+ shortTitle: "Stoch RSI",
38251
+ overlay: false,
38252
+ inputs: [
38253
+ lengthInput(14, "rsiLength", "RSI length"),
38254
+ lengthInput(14, "stochLength", "Stochastic length"),
38255
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
38256
+ lengthInput(3, "dLength", "%D smoothing", 500),
38257
+ sourceInput()
38258
+ ],
38259
+ compute: (bars, inputs) => {
38260
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
38261
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
38262
+ const d = sma(k, num3(inputs, "dLength", 3));
38263
+ return {
38264
+ plots: [
38265
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38266
+ { key: "d", title: "%D", values: d, color: WARNING }
38267
+ ],
38268
+ levels: guideLevels(20, 80)
38269
+ };
38270
+ }
38271
+ };
38272
+ var macd = {
38273
+ type: "macd",
38274
+ title: "MACD",
38275
+ overlay: false,
38276
+ inputs: [
38277
+ lengthInput(12, "fastLength", "Fast length"),
38278
+ lengthInput(26, "slowLength", "Slow length"),
38279
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38280
+ sourceInput()
38281
+ ],
38282
+ compute: (bars, inputs) => {
38283
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38284
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
38285
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38286
+ const hist = sub(line, signal);
38287
+ return {
38288
+ plots: [
38289
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38290
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
38291
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38292
+ ],
38293
+ levels: ZERO_LEVEL
38294
+ };
38295
+ }
38296
+ };
38297
+ var ppo = {
38298
+ type: "ppo",
38299
+ title: "Percentage Price Oscillator",
38300
+ shortTitle: "PPO",
38301
+ overlay: false,
38302
+ inputs: [
38303
+ lengthInput(12, "fastLength", "Fast length"),
38304
+ lengthInput(26, "slowLength", "Slow length"),
38305
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38306
+ sourceInput()
38307
+ ],
38308
+ compute: (bars, inputs) => {
38309
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38310
+ const fast = ema(src, num3(inputs, "fastLength", 12));
38311
+ const slow = ema(src, num3(inputs, "slowLength", 26));
38312
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
38313
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38314
+ const hist = sub(line, signal);
38315
+ return {
38316
+ plots: [
38317
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38318
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
38319
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38320
+ ],
38321
+ levels: ZERO_LEVEL
38322
+ };
38323
+ }
38324
+ };
38325
+ var awesome = {
38326
+ type: "awesome-oscillator",
38327
+ title: "Awesome Oscillator",
38328
+ shortTitle: "AO",
38329
+ overlay: false,
38330
+ inputs: [],
38331
+ compute: (bars) => {
38332
+ const hl2 = sourceValues(bars, "HL2");
38333
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
38334
+ const colors = ao.map((x, i) => {
38335
+ if (!Number.isFinite(x)) return null;
38336
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
38337
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
38338
+ });
38339
+ return {
38340
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
38341
+ levels: ZERO_LEVEL
38342
+ };
38343
+ }
38344
+ };
38345
+ var cci = {
38346
+ type: "commodity-channel-index",
38347
+ title: "Commodity Channel Index",
38348
+ shortTitle: "CCI",
38349
+ overlay: false,
38350
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
38351
+ compute: (bars, inputs) => {
38352
+ const len = num3(inputs, "length", 20);
38353
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
38354
+ const basis = sma(src, len);
38355
+ const dev = new Array(src.length).fill(Number.NaN);
38356
+ for (let i = len - 1; i < src.length; i++) {
38357
+ const mean = basis[i];
38358
+ if (!Number.isFinite(mean)) continue;
38359
+ let d = 0;
38360
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
38361
+ dev[i] = d / len;
38362
+ }
38363
+ const values = src.map((x, i) => {
38364
+ const b = basis[i];
38365
+ const d = dev[i];
38366
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
38367
+ });
38368
+ return {
38369
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38370
+ levels: guideLevels(-100, 100, 0)
38371
+ };
38372
+ }
38373
+ };
38374
+ var williamsR = {
38375
+ type: "williams-percent-r",
38376
+ title: "Williams %R",
38377
+ shortTitle: "%R",
38378
+ overlay: false,
38379
+ inputs: [lengthInput(14), colorInput()],
38380
+ compute: (bars, inputs) => {
38381
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
38382
+ return {
38383
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38384
+ levels: guideLevels(-80, -20)
38385
+ };
38386
+ }
38387
+ };
38388
+ var cmo = {
38389
+ type: "chande-momentum-oscillator",
38390
+ title: "Chande Momentum Oscillator",
38391
+ shortTitle: "CMO",
38392
+ overlay: false,
38393
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38394
+ compute: (bars, inputs) => {
38395
+ const len = num3(inputs, "length", 9);
38396
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
38397
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
38398
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
38399
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
38400
+ return {
38401
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38402
+ levels: guideLevels(-50, 50, 0)
38403
+ };
38404
+ }
38405
+ };
38406
+ var connorsRsi = {
38407
+ type: "connors-rsi",
38408
+ title: "Connors RSI",
38409
+ shortTitle: "CRSI",
38410
+ overlay: false,
38411
+ inputs: [
38412
+ lengthInput(3, "rsiLength", "RSI length"),
38413
+ lengthInput(2, "streakLength", "Streak RSI length"),
38414
+ lengthInput(100, "rankLength", "Percent-rank length"),
38415
+ colorInput()
38416
+ ],
38417
+ compute: (bars, inputs) => {
38418
+ const c = closes(bars);
38419
+ const streak = new Array(c.length).fill(0);
38420
+ for (let i = 1; i < c.length; i++) {
38421
+ const d = c[i] - c[i - 1];
38422
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
38423
+ }
38424
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
38425
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
38426
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
38427
+ const values = c.map((_, i) => {
38428
+ const x = a[i];
38429
+ const y = b[i];
38430
+ const z = r[i];
38431
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
38432
+ });
38433
+ return {
38434
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38435
+ levels: guideLevels(20, 80, 50)
38436
+ };
38437
+ }
38438
+ };
38439
+ var fisher = {
38440
+ type: "fisher-transform",
38441
+ title: "Fisher Transform",
38442
+ shortTitle: "Fisher",
38443
+ overlay: false,
38444
+ inputs: [lengthInput(9)],
38445
+ compute: (bars, inputs) => {
38446
+ const len = num3(inputs, "length", 9);
38447
+ const hl2 = sourceValues(bars, "HL2");
38448
+ const hi = highest(hl2, len);
38449
+ const lo = lowest(hl2, len);
38450
+ const n = bars.length;
38451
+ const out = new Array(n).fill(Number.NaN);
38452
+ let value = 0;
38453
+ let fish = 0;
38454
+ for (let i = 0; i < n; i++) {
38455
+ const h = hi[i];
38456
+ const l = lo[i];
38457
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
38458
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
38459
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
38460
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
38461
+ out[i] = fish;
38462
+ }
38463
+ return {
38464
+ plots: [
38465
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
38466
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
38467
+ ],
38468
+ levels: ZERO_LEVEL
38469
+ };
38470
+ }
38471
+ };
38472
+ var trix = {
38473
+ type: "trix",
38474
+ title: "TRIX",
38475
+ overlay: false,
38476
+ inputs: [lengthInput(18), colorInput()],
38477
+ compute: (bars, inputs) => {
38478
+ const len = num3(inputs, "length", 18);
38479
+ const logClose = map(closes(bars), Math.log);
38480
+ const smooth = ema(ema(ema(logClose, len), len), len);
38481
+ const values = map(change(smooth), (x) => x * 1e4);
38482
+ return {
38483
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38484
+ levels: ZERO_LEVEL
38485
+ };
38486
+ }
38487
+ };
38488
+ function tsiLine(src, long, short) {
38489
+ const mom = change(src);
38490
+ const numer = ema(ema(mom, long), short);
38491
+ const denom = ema(ema(map(mom, Math.abs), long), short);
38492
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
38493
+ }
38494
+ var tsi = {
38495
+ type: "true-strength-index",
38496
+ title: "True Strength Index",
38497
+ shortTitle: "TSI",
38498
+ overlay: false,
38499
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
38500
+ compute: (bars, inputs) => {
38501
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
38502
+ return {
38503
+ plots: [
38504
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
38505
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
38506
+ ],
38507
+ levels: ZERO_LEVEL
38508
+ };
38509
+ }
38510
+ };
38511
+ var smiErgodic = {
38512
+ type: "smi-ergodic",
38513
+ title: "SMI Ergodic Oscillator",
38514
+ shortTitle: "SMIE",
38515
+ overlay: false,
38516
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
38517
+ compute: (bars, inputs) => {
38518
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
38519
+ const signal = ema(line, num3(inputs, "signalLength", 5));
38520
+ const osc = sub(line, signal);
38521
+ return {
38522
+ plots: [
38523
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
38524
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
38525
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38526
+ ],
38527
+ levels: ZERO_LEVEL
38528
+ };
38529
+ }
38530
+ };
38531
+ var stc = {
38532
+ type: "schaff-trend-cycle",
38533
+ title: "Schaff Trend Cycle",
38534
+ shortTitle: "Schaff Trend Cycle",
38535
+ overlay: false,
38536
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
38537
+ compute: (bars, inputs) => {
38538
+ const len = num3(inputs, "cycleLength", 10);
38539
+ const src = closes(bars);
38540
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
38541
+ const stochOf = (v) => {
38542
+ const lo = lowest(v, len);
38543
+ const hi = highest(v, len);
38544
+ const out = new Array(v.length).fill(Number.NaN);
38545
+ let prevRaw = Number.NaN;
38546
+ let smoothed = Number.NaN;
38547
+ for (let i = 0; i < v.length; i++) {
38548
+ const x = v[i];
38549
+ const l = lo[i];
38550
+ const h = hi[i];
38551
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
38552
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
38553
+ prevRaw = raw;
38554
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
38555
+ out[i] = smoothed;
38556
+ }
38557
+ return out;
38558
+ };
38559
+ const values = stochOf(stochOf(macdLine));
38560
+ return {
38561
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
38562
+ levels: guideLevels(25, 75)
38563
+ };
38564
+ }
38565
+ };
38566
+ var kst = {
38567
+ type: "know-sure-thing",
38568
+ title: "Know Sure Thing",
38569
+ shortTitle: "KST",
38570
+ overlay: false,
38571
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
38572
+ compute: (bars, inputs) => {
38573
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38574
+ const line = [
38575
+ sma(roc(src, 10), 10),
38576
+ sma(roc(src, 15), 10),
38577
+ sma(roc(src, 20), 10),
38578
+ sma(roc(src, 30), 15)
38579
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
38580
+ return {
38581
+ plots: [
38582
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
38583
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
38584
+ ],
38585
+ levels: ZERO_LEVEL
38586
+ };
38587
+ }
38588
+ };
38589
+ var coppock = {
38590
+ type: "coppock-curve",
38591
+ title: "Coppock Curve",
38592
+ shortTitle: "Coppock",
38593
+ overlay: false,
38594
+ inputs: [
38595
+ lengthInput(10, "wmaLength", "WMA length"),
38596
+ lengthInput(14, "longRoc", "Long RoC length"),
38597
+ lengthInput(11, "shortRoc", "Short RoC length"),
38598
+ sourceInput(),
38599
+ colorInput()
38600
+ ],
38601
+ compute: (bars, inputs) => {
38602
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38603
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
38604
+ return {
38605
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38606
+ levels: ZERO_LEVEL
38607
+ };
38608
+ }
38609
+ };
38610
+ var dpo = {
38611
+ type: "detrended-price-oscillator",
38612
+ title: "Detrended Price Oscillator",
38613
+ shortTitle: "DPO",
38614
+ overlay: false,
38615
+ inputs: [lengthInput(21), colorInput()],
38616
+ compute: (bars, inputs) => {
38617
+ const len = num3(inputs, "length", 21);
38618
+ const barsback = Math.floor(len / 2) + 1;
38619
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
38620
+ return {
38621
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38622
+ levels: ZERO_LEVEL
38623
+ };
38624
+ }
38625
+ };
38626
+ var rocSpec = {
38627
+ type: "rate-of-change",
38628
+ title: "Rate of Change",
38629
+ shortTitle: "ROC",
38630
+ overlay: false,
38631
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38632
+ compute: (bars, inputs) => ({
38633
+ plots: [
38634
+ {
38635
+ key: "roc",
38636
+ title: "RoC",
38637
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
38638
+ color: str3(inputs, "color", SERIES_LINE),
38639
+ width: 2
38640
+ }
38641
+ ],
38642
+ levels: ZERO_LEVEL
38643
+ })
38644
+ };
38645
+ var ultimate = {
38646
+ type: "ultimate-oscillator",
38647
+ title: "Ultimate Oscillator",
38648
+ shortTitle: "UO",
38649
+ overlay: false,
38650
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
38651
+ compute: (bars, inputs) => {
38652
+ const n = bars.length;
38653
+ const bp = new Array(n).fill(Number.NaN);
38654
+ const tr = new Array(n).fill(Number.NaN);
38655
+ for (let i = 0; i < n; i++) {
38656
+ const b = bars[i];
38657
+ const pc = i > 0 ? bars[i - 1].close : b.close;
38658
+ const lo = Math.min(b.low, pc);
38659
+ const hi = Math.max(b.high, pc);
38660
+ bp[i] = b.close - lo;
38661
+ tr[i] = hi - lo;
38662
+ }
38663
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
38664
+ const a7 = avg(num3(inputs, "fastLength", 7));
38665
+ const a14 = avg(num3(inputs, "middleLength", 14));
38666
+ const a28 = avg(num3(inputs, "slowLength", 28));
38667
+ const values = a7.map((x, i) => {
38668
+ const y = a14[i];
38669
+ const z = a28[i];
38670
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
38671
+ });
38672
+ return {
38673
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38674
+ levels: guideLevels(30, 70, 50)
38675
+ };
38676
+ }
38677
+ };
38678
+ var rvgi = {
38679
+ type: "relative-vigor-index",
38680
+ title: "Relative Vigor Index",
38681
+ shortTitle: "RVGI",
38682
+ overlay: false,
38683
+ inputs: [lengthInput(10)],
38684
+ compute: (bars, inputs) => {
38685
+ const len = num3(inputs, "length", 10);
38686
+ const co = swma(sub(closes(bars), opens(bars)));
38687
+ const hl = swma(sub(highs(bars), lows(bars)));
38688
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
38689
+ return {
38690
+ plots: [
38691
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
38692
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
38693
+ ],
38694
+ levels: ZERO_LEVEL
38695
+ };
38696
+ }
38697
+ };
38698
+ var relVolatility = {
38699
+ type: "relative-volatility-index",
38700
+ title: "Relative Volatility Index",
38701
+ shortTitle: "RVI",
38702
+ overlay: false,
38703
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
38704
+ compute: (bars, inputs) => {
38705
+ const smoothLen = num3(inputs, "smoothLength", 14);
38706
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38707
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
38708
+ const d = change(src);
38709
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
38710
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
38711
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
38712
+ return {
38713
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38714
+ levels: guideLevels(20, 80, 50)
38715
+ };
38716
+ }
38717
+ };
38718
+ var bop = {
38719
+ type: "balance-of-power",
38720
+ title: "Balance of Power",
38721
+ shortTitle: "BOP",
38722
+ overlay: false,
38723
+ inputs: [colorInput()],
38724
+ compute: (bars, inputs) => {
38725
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
38726
+ return {
38727
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38728
+ levels: ZERO_LEVEL
38729
+ };
38730
+ }
38731
+ };
38732
+ var elderRay = {
38733
+ type: "elder-ray",
38734
+ title: "Elder Ray",
38735
+ overlay: false,
38736
+ inputs: [lengthInput(13)],
38737
+ compute: (bars, inputs) => {
38738
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
38739
+ return {
38740
+ plots: [
38741
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
38742
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
38743
+ ],
38744
+ levels: ZERO_LEVEL
38745
+ };
38746
+ }
38747
+ };
38748
+ var ttmSqueeze = {
38749
+ type: "ttm-squeeze",
38750
+ title: "TTM Squeeze",
38751
+ overlay: false,
38752
+ inputs: [
38753
+ lengthInput(20),
38754
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38755
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
38756
+ ],
38757
+ compute: (bars, inputs) => {
38758
+ const len = num3(inputs, "length", 20);
38759
+ const c = closes(bars);
38760
+ const basis = sma(c, len);
38761
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
38762
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
38763
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
38764
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
38765
+ const mom = linreg(sub(c, anchor), len);
38766
+ const squeezed = c.map((_, i) => {
38767
+ const d = dev[i];
38768
+ const r = kcRange[i];
38769
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
38770
+ });
38771
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
38772
+ return {
38773
+ plots: [
38774
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
38775
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
38776
+ ]
38777
+ };
38778
+ }
38779
+ };
38780
+ var oscillatorSpecs = [
38781
+ rsiSpec,
38782
+ stochastic,
38783
+ stochasticRsi,
38784
+ macd,
38785
+ ppo,
38786
+ awesome,
38787
+ cci,
38788
+ williamsR,
38789
+ cmo,
38790
+ connorsRsi,
38791
+ fisher,
38792
+ trix,
38793
+ tsi,
38794
+ smiErgodic,
38795
+ stc,
38796
+ kst,
38797
+ coppock,
38798
+ dpo,
38799
+ rocSpec,
38800
+ ultimate,
38801
+ rvgi,
38802
+ relVolatility,
38803
+ bop,
38804
+ elderRay,
38805
+ ttmSqueeze
38806
+ ];
38807
+
38808
+ // src/core/native-indicators/classics/trend.ts
38809
+ var aroon = {
38810
+ type: "aroon",
38811
+ title: "Aroon",
38812
+ overlay: false,
38813
+ inputs: [lengthInput(14)],
38814
+ compute: (bars, inputs) => {
38815
+ const len = num3(inputs, "length", 14);
38816
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
38817
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
38818
+ return {
38819
+ plots: [
38820
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
38821
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
38822
+ ],
38823
+ levels: [
38824
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
38825
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
38826
+ ]
38827
+ };
38828
+ }
38829
+ };
38830
+ var adx = {
38831
+ type: "average-directional-index",
38832
+ title: "Average Directional Index",
38833
+ shortTitle: "ADX",
38834
+ overlay: false,
38835
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
38836
+ compute: (bars, inputs) => {
38837
+ const diLen = num3(inputs, "diLength", 14);
38838
+ const adxLen = num3(inputs, "adxLength", 14);
38839
+ const n = bars.length;
38840
+ const plusDm = new Array(n).fill(Number.NaN);
38841
+ const minusDm = new Array(n).fill(Number.NaN);
38842
+ for (let i = 1; i < n; i++) {
38843
+ const up = bars[i].high - bars[i - 1].high;
38844
+ const dn = bars[i - 1].low - bars[i].low;
38845
+ plusDm[i] = up > dn && up > 0 ? up : 0;
38846
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
38847
+ }
38848
+ const atrLine = rma(trueRange(bars), diLen);
38849
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38850
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38851
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
38852
+ return {
38853
+ plots: [
38854
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
38855
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
38856
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
38857
+ ],
38858
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
38859
+ };
38860
+ }
38861
+ };
38862
+ var vortex = {
38863
+ type: "vortex-indicator",
38864
+ title: "Vortex Indicator",
38865
+ shortTitle: "VI",
38866
+ overlay: false,
38867
+ inputs: [lengthInput(14)],
38868
+ compute: (bars, inputs) => {
38869
+ const len = num3(inputs, "length", 14);
38870
+ const n = bars.length;
38871
+ const vmPlus = new Array(n).fill(Number.NaN);
38872
+ const vmMinus = new Array(n).fill(Number.NaN);
38873
+ for (let i = 1; i < n; i++) {
38874
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
38875
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
38876
+ }
38877
+ const trSum = sum(trueRange(bars), len);
38878
+ return {
38879
+ plots: [
38880
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
38881
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
38882
+ ],
38883
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
38884
+ };
38885
+ }
38886
+ };
38887
+ var trendSpecs = [aroon, adx, vortex];
38888
+
38889
+ // src/core/native-indicators/classics/volatility.ts
38890
+ var atrSpec = {
38891
+ type: "average-true-range",
38892
+ title: "Average True Range",
38893
+ shortTitle: "ATR",
38894
+ overlay: false,
38895
+ inputs: [lengthInput(14), colorInput()],
38896
+ compute: (bars, inputs) => ({
38897
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
38898
+ })
38899
+ };
38900
+ var historicalVolatility = {
38901
+ type: "historical-volatility",
38902
+ title: "Historical Volatility",
38903
+ shortTitle: "HV",
38904
+ overlay: false,
38905
+ inputs: [
38906
+ lengthInput(10),
38907
+ { 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)" },
38908
+ colorInput()
38909
+ ],
38910
+ compute: (bars, inputs) => {
38911
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
38912
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
38913
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38914
+ }
38915
+ };
38916
+ var chaikinVolatility = {
38917
+ type: "chaikin-volatility",
38918
+ title: "Chaikin Volatility",
38919
+ shortTitle: "CHV",
38920
+ overlay: false,
38921
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
38922
+ compute: (bars, inputs) => {
38923
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
38924
+ return {
38925
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38926
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
38927
+ };
38928
+ }
38929
+ };
38930
+ var massIndex = {
38931
+ type: "mass-index",
38932
+ title: "Mass Index",
38933
+ shortTitle: "MI",
38934
+ overlay: false,
38935
+ inputs: [lengthInput(25), colorInput()],
38936
+ compute: (bars, inputs) => {
38937
+ const range = sub(highs(bars), lows(bars));
38938
+ const single = ema(range, 9);
38939
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
38940
+ return {
38941
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38942
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
38943
+ };
38944
+ }
38945
+ };
38946
+ var standardDeviation = {
38947
+ type: "standard-deviation",
38948
+ title: "Standard Deviation",
38949
+ shortTitle: "StdDev",
38950
+ overlay: false,
38951
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
38952
+ compute: (bars, inputs) => ({
38953
+ plots: [
38954
+ {
38955
+ key: "stdev",
38956
+ title: "StdDev",
38957
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
38958
+ color: str3(inputs, "color", SERIES_LINE),
38959
+ width: 2
38960
+ }
38961
+ ]
38962
+ })
38963
+ };
38964
+ var ulcerIndex = {
38965
+ type: "ulcer-index",
38966
+ title: "Ulcer Index",
38967
+ overlay: false,
38968
+ inputs: [lengthInput(14), colorInput()],
38969
+ compute: (bars, inputs) => {
38970
+ const len = num3(inputs, "length", 14);
38971
+ const c = closes(bars);
38972
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
38973
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
38974
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38975
+ }
38976
+ };
38977
+ var choppiness = {
38978
+ type: "choppiness-index",
38979
+ title: "Choppiness Index",
38980
+ shortTitle: "CHOP",
38981
+ overlay: false,
38982
+ inputs: [lengthInput(14), colorInput()],
38983
+ compute: (bars, inputs) => {
38984
+ const len = num3(inputs, "length", 14);
38985
+ const trSum = sum(trueRange(bars), len);
38986
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
38987
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
38988
+ return {
38989
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38990
+ levels: [
38991
+ { key: "upper", price: 61.8, color: NEUTRAL },
38992
+ { key: "lower", price: 38.2, color: NEUTRAL }
38993
+ ]
38994
+ };
38995
+ }
38996
+ };
38997
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
38998
+
38999
+ // src/core/native-indicators/classics/volume.ts
39000
+ function moneyFlowVolume(bars) {
39001
+ return bars.map((b) => {
39002
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39003
+ if (b.high === b.low) return 0;
39004
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
39005
+ });
39006
+ }
39007
+ var obv = {
39008
+ type: "on-balance-volume",
39009
+ title: "On Balance Volume",
39010
+ shortTitle: "OBV",
39011
+ overlay: false,
39012
+ inputs: [colorInput()],
39013
+ compute: (bars, inputs) => {
39014
+ const vol = volumes(bars);
39015
+ const d = change(closes(bars));
39016
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
39017
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39018
+ }
39019
+ };
39020
+ var accDist = {
39021
+ type: "accumulation-distribution",
39022
+ title: "Accumulation / Distribution",
39023
+ shortTitle: "A/D",
39024
+ overlay: false,
39025
+ inputs: [colorInput()],
39026
+ compute: (bars, inputs) => ({
39027
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
39028
+ })
39029
+ };
39030
+ var cmf = {
39031
+ type: "chaikin-money-flow",
39032
+ title: "Chaikin Money Flow",
39033
+ shortTitle: "CMF",
39034
+ overlay: false,
39035
+ inputs: [lengthInput(20), colorInput()],
39036
+ compute: (bars, inputs) => {
39037
+ const len = num3(inputs, "length", 20);
39038
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
39039
+ return {
39040
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39041
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39042
+ };
39043
+ }
39044
+ };
39045
+ var chaikinOscillator = {
39046
+ type: "chaikin-oscillator",
39047
+ title: "Chaikin Oscillator",
39048
+ shortTitle: "Chaikin Osc",
39049
+ overlay: false,
39050
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39051
+ compute: (bars, inputs) => {
39052
+ const ad = cumSum(moneyFlowVolume(bars));
39053
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
39054
+ return {
39055
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39056
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39057
+ };
39058
+ }
39059
+ };
39060
+ var eom = {
39061
+ type: "ease-of-movement",
39062
+ title: "Ease of Movement",
39063
+ shortTitle: "EOM",
39064
+ overlay: false,
39065
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
39066
+ compute: (bars, inputs) => {
39067
+ const div = num3(inputs, "divisor", 1e4);
39068
+ const hl2 = sourceValues(bars, "HL2");
39069
+ const move = change(hl2);
39070
+ const raw = bars.map((b, i) => {
39071
+ const m = move[i];
39072
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
39073
+ return div * m * (b.high - b.low) / b.volume;
39074
+ });
39075
+ return {
39076
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39077
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39078
+ };
39079
+ }
39080
+ };
39081
+ var forceIndex = {
39082
+ type: "force-index",
39083
+ title: "Force Index",
39084
+ shortTitle: "FI",
39085
+ overlay: false,
39086
+ inputs: [lengthInput(13), colorInput()],
39087
+ compute: (bars, inputs) => {
39088
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
39089
+ return {
39090
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39091
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39092
+ };
39093
+ }
39094
+ };
39095
+ var klinger = {
39096
+ type: "klinger-oscillator",
39097
+ title: "Klinger Oscillator",
39098
+ shortTitle: "KVO",
39099
+ overlay: false,
39100
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
39101
+ compute: (bars, inputs) => {
39102
+ const hlc3 = sourceValues(bars, "HLC3");
39103
+ const trend = change(hlc3);
39104
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
39105
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
39106
+ return {
39107
+ plots: [
39108
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
39109
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39110
+ ],
39111
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39112
+ };
39113
+ }
39114
+ };
39115
+ var mfi = {
39116
+ type: "money-flow-index",
39117
+ title: "Money Flow Index",
39118
+ shortTitle: "MFI",
39119
+ overlay: false,
39120
+ inputs: [lengthInput(14), colorInput()],
39121
+ compute: (bars, inputs) => {
39122
+ const len = num3(inputs, "length", 14);
39123
+ const tp = sourceValues(bars, "HLC3");
39124
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
39125
+ const d = change(tp);
39126
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
39127
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
39128
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
39129
+ return {
39130
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39131
+ levels: [
39132
+ { key: "upper", price: 80, color: NEUTRAL },
39133
+ { key: "lower", price: 20, color: NEUTRAL }
39134
+ ]
39135
+ };
39136
+ }
39137
+ };
39138
+ function volumeIndex(bars, when) {
39139
+ const out = new Array(bars.length).fill(Number.NaN);
39140
+ let index = 1e3;
39141
+ let started = false;
39142
+ for (let i = 1; i < bars.length; i++) {
39143
+ const v = bars[i].volume;
39144
+ const pv = bars[i - 1].volume;
39145
+ const pc = bars[i - 1].close;
39146
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
39147
+ if (started) out[i] = index;
39148
+ continue;
39149
+ }
39150
+ started = true;
39151
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
39152
+ out[i] = index;
39153
+ }
39154
+ return out;
39155
+ }
39156
+ var nvi = {
39157
+ type: "negative-volume-index",
39158
+ title: "Negative Volume Index",
39159
+ shortTitle: "Negative Volume Index",
39160
+ overlay: false,
39161
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39162
+ compute: (bars, inputs) => {
39163
+ const line = volumeIndex(bars, (v, pv) => v < pv);
39164
+ return {
39165
+ plots: [
39166
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39167
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39168
+ ]
39169
+ };
39170
+ }
39171
+ };
39172
+ var pvi = {
39173
+ type: "positive-volume-index",
39174
+ title: "Positive Volume Index",
39175
+ shortTitle: "Positive Volume Index",
39176
+ overlay: false,
39177
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39178
+ compute: (bars, inputs) => {
39179
+ const line = volumeIndex(bars, (v, pv) => v > pv);
39180
+ return {
39181
+ plots: [
39182
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39183
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39184
+ ]
39185
+ };
39186
+ }
39187
+ };
39188
+ var pvt = {
39189
+ type: "price-volume-trend",
39190
+ title: "Price Volume Trend",
39191
+ shortTitle: "PVT",
39192
+ overlay: false,
39193
+ inputs: [colorInput()],
39194
+ compute: (bars, inputs) => {
39195
+ const c = closes(bars);
39196
+ const raw = bars.map((b, i) => {
39197
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39198
+ const pc = c[i - 1];
39199
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
39200
+ });
39201
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39202
+ }
39203
+ };
39204
+ var volumeOscillator = {
39205
+ type: "volume-oscillator",
39206
+ title: "Volume Oscillator",
39207
+ shortTitle: "Vol Osc",
39208
+ overlay: false,
39209
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39210
+ compute: (bars, inputs) => {
39211
+ const vol = volumes(bars);
39212
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
39213
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
39214
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39215
+ return {
39216
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39217
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39218
+ };
39219
+ }
39220
+ };
39221
+ var pvo = {
39222
+ type: "pvo",
39223
+ title: "Percentage Volume Oscillator",
39224
+ shortTitle: "PVO",
39225
+ overlay: false,
39226
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
39227
+ compute: (bars, inputs) => {
39228
+ const vol = volumes(bars);
39229
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
39230
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
39231
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39232
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39233
+ const hist = sub(line, signal);
39234
+ return {
39235
+ plots: [
39236
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
39237
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
39238
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39239
+ ],
39240
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39241
+ };
39242
+ }
39243
+ };
39244
+ var vfi = {
39245
+ type: "volume-flow-indicator",
39246
+ title: "Volume Flow Indicator",
39247
+ shortTitle: "Volume Flow Indicator",
39248
+ overlay: false,
39249
+ inputs: [
39250
+ lengthInput(130),
39251
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
39252
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
39253
+ lengthInput(3, "smoothLength", "Smoothing length")
39254
+ ],
39255
+ compute: (bars, inputs) => {
39256
+ const len = num3(inputs, "length", 130);
39257
+ const coef = num3(inputs, "coef", 0.2);
39258
+ const volCoef = num3(inputs, "volCoef", 2.5);
39259
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
39260
+ const inter = change(typical);
39261
+ const vInter = stdev(inter, 30);
39262
+ const vol = volumes(bars);
39263
+ const vAve = sma(vol, len);
39264
+ const mf = change(sourceValues(bars, "HLC3"));
39265
+ const raw = bars.map((b, i) => {
39266
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
39267
+ const v = vol[i];
39268
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39269
+ const m = mf[i];
39270
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
39271
+ const capped = Math.min(v, ave * volCoef);
39272
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
39273
+ });
39274
+ const values = bars.map((_, i) => {
39275
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39276
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
39277
+ });
39278
+ const flowSum = sum(raw, len);
39279
+ const line = zip(flowSum, values, (f, a) => f / a);
39280
+ return {
39281
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
39282
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39283
+ };
39284
+ }
39285
+ };
39286
+ var intradayIntensity = {
39287
+ type: "intraday-intensity",
39288
+ title: "Intraday Intensity",
39289
+ shortTitle: "Intraday Intensity",
39290
+ overlay: false,
39291
+ inputs: [lengthInput(21), colorInput()],
39292
+ compute: (bars, inputs) => {
39293
+ const len = num3(inputs, "length", 21);
39294
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
39295
+ return {
39296
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39297
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39298
+ };
39299
+ }
39300
+ };
39301
+ var volumeSpecs = [
39302
+ obv,
39303
+ accDist,
39304
+ cmf,
39305
+ chaikinOscillator,
39306
+ eom,
39307
+ forceIndex,
39308
+ klinger,
39309
+ mfi,
39310
+ nvi,
39311
+ pvi,
39312
+ pvt,
39313
+ volumeOscillator,
39314
+ pvo,
39315
+ vfi,
39316
+ intradayIntensity
39317
+ ];
39318
+
39319
+ // src/core/native-indicators/classics/overlays.ts
39320
+ var DAY_MS2 = 864e5;
39321
+ function periodKey(anchor, time) {
39322
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
39323
+ if (anchor === "Month") {
39324
+ const d = new Date(time);
39325
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
39326
+ }
39327
+ return Math.floor(time / DAY_MS2);
39328
+ }
39329
+ var parabolicSar = {
39330
+ type: "parabolic-sar",
39331
+ title: "Parabolic SAR",
39332
+ shortTitle: "SAR",
39333
+ overlay: true,
39334
+ inputs: [
39335
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39336
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39337
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
39338
+ ],
39339
+ compute: (bars, inputs) => {
39340
+ const start = num3(inputs, "start", 0.02);
39341
+ const inc = num3(inputs, "increment", 0.02);
39342
+ const max = num3(inputs, "maximum", 0.2);
39343
+ const n = bars.length;
39344
+ const values = new Array(n).fill(Number.NaN);
39345
+ const colors = new Array(n).fill(null);
39346
+ if (n >= 2) {
39347
+ let long = bars[1].close >= bars[0].close;
39348
+ let sar = long ? bars[0].low : bars[0].high;
39349
+ let ep = long ? bars[0].high : bars[0].low;
39350
+ let af = start;
39351
+ for (let i = 1; i < n; i++) {
39352
+ const b = bars[i];
39353
+ sar += af * (ep - sar);
39354
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
39355
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
39356
+ const reversed = long ? b.low < sar : b.high > sar;
39357
+ if (reversed) {
39358
+ long = !long;
39359
+ sar = ep;
39360
+ ep = long ? b.high : b.low;
39361
+ af = start;
39362
+ } else if (long && b.high > ep) {
39363
+ ep = b.high;
39364
+ af = Math.min(max, af + inc);
39365
+ } else if (!long && b.low < ep) {
39366
+ ep = b.low;
39367
+ af = Math.min(max, af + inc);
39368
+ }
39369
+ values[i] = sar;
39370
+ colors[i] = long ? BULLISH : BEARISH;
39371
+ }
39372
+ }
39373
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
39374
+ }
39375
+ };
39376
+ var vwap = {
39377
+ type: "vwap",
39378
+ title: "Volume Weighted Average Price",
39379
+ shortTitle: "VWAP",
39380
+ overlay: true,
39381
+ inputs: [
39382
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
39383
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
39384
+ colorInput(INFO)
39385
+ ],
39386
+ compute: (bars, inputs) => {
39387
+ const anchor = str3(inputs, "anchor", "Day");
39388
+ const source = str3(inputs, "source", "HLC3");
39389
+ const values = new Array(bars.length).fill(Number.NaN);
39390
+ let period = Number.NaN;
39391
+ let cumPV = 0;
39392
+ let cumV = 0;
39393
+ for (let i = 0; i < bars.length; i++) {
39394
+ const b = bars[i];
39395
+ const key = periodKey(anchor, b.time);
39396
+ if (key !== period) {
39397
+ period = key;
39398
+ cumPV = 0;
39399
+ cumV = 0;
39400
+ }
39401
+ const v = b.volume;
39402
+ if (v != null && Number.isFinite(v) && v > 0) {
39403
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
39404
+ cumPV += tp * v;
39405
+ cumV += v;
39406
+ }
39407
+ if (cumV > 0) values[i] = cumPV / cumV;
39408
+ }
39409
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
39410
+ }
39411
+ };
39412
+ var pivotPoints = {
39413
+ type: "pivot-points",
39414
+ title: "Pivot Points",
39415
+ shortTitle: "Pivots",
39416
+ overlay: true,
39417
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
39418
+ compute: (bars, inputs) => {
39419
+ const anchor = str3(inputs, "anchor", "Day");
39420
+ const aggs = /* @__PURE__ */ new Map();
39421
+ for (const b of bars) {
39422
+ const key = periodKey(anchor, b.time);
39423
+ const a = aggs.get(key);
39424
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
39425
+ else {
39426
+ a.high = Math.max(a.high, b.high);
39427
+ a.low = Math.min(a.low, b.low);
39428
+ a.close = b.close;
39429
+ }
39430
+ }
39431
+ const n = bars.length;
39432
+ const mk = () => new Array(n).fill(Number.NaN);
39433
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
39434
+ for (let i = 0; i < n; i++) {
39435
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
39436
+ if (!prev2) continue;
39437
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
39438
+ levels.p[i] = p;
39439
+ levels.r1[i] = 2 * p - prev2.low;
39440
+ levels.s1[i] = 2 * p - prev2.high;
39441
+ levels.r2[i] = p + (prev2.high - prev2.low);
39442
+ levels.s2[i] = p - (prev2.high - prev2.low);
39443
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
39444
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
39445
+ }
39446
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
39447
+ return {
39448
+ plots: [
39449
+ plot("p", "P", WARNING),
39450
+ plot("r1", "R1", BEARISH),
39451
+ plot("s1", "S1", BULLISH),
39452
+ plot("r2", "R2", BEARISH),
39453
+ plot("s2", "S2", BULLISH),
39454
+ plot("r3", "R3", BEARISH),
39455
+ plot("s3", "S3", BULLISH)
39456
+ ]
39457
+ };
39458
+ }
39459
+ };
39460
+ var fiftyTwoWeek = {
39461
+ type: "52-week-high-low",
39462
+ title: "52 Week High/Low",
39463
+ shortTitle: "52W H/L",
39464
+ overlay: true,
39465
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
39466
+ compute: (bars, inputs) => {
39467
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
39468
+ const n = bars.length;
39469
+ const hi = new Array(n).fill(Number.NaN);
39470
+ const lo = new Array(n).fill(Number.NaN);
39471
+ const maxIdx = [];
39472
+ const minIdx = [];
39473
+ let from = 0;
39474
+ for (let i = 0; i < n; i++) {
39475
+ const b = bars[i];
39476
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
39477
+ maxIdx.push(i);
39478
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
39479
+ minIdx.push(i);
39480
+ while (bars[from].time < b.time - span) from += 1;
39481
+ while (maxIdx[0] < from) maxIdx.shift();
39482
+ while (minIdx[0] < from) minIdx.shift();
39483
+ hi[i] = bars[maxIdx[0]].high;
39484
+ lo[i] = bars[minIdx[0]].low;
39485
+ }
39486
+ return {
39487
+ plots: [
39488
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
39489
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
39490
+ ]
39491
+ };
39492
+ }
39493
+ };
39494
+ var zigzag = {
39495
+ type: "zigzag",
39496
+ title: "ZigZag",
39497
+ overlay: true,
39498
+ inputs: [
39499
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
39500
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39501
+ colorInput()
39502
+ ],
39503
+ compute: (bars, inputs) => {
39504
+ const dev = num3(inputs, "deviation", 5) / 100;
39505
+ const depth = num3(inputs, "depth", 10);
39506
+ const pivots = [];
39507
+ if (bars.length > 1) {
39508
+ let up = bars[1].close >= bars[0].close;
39509
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
39510
+ for (let i = 1; i < bars.length; i++) {
39511
+ const b = bars[i];
39512
+ if (up) {
39513
+ if (b.high >= ext.price) ext = { i, price: b.high };
39514
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39515
+ pivots.push(ext);
39516
+ up = false;
39517
+ ext = { i, price: b.low };
39518
+ }
39519
+ } else if (b.low <= ext.price) {
39520
+ ext = { i, price: b.low };
39521
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39522
+ pivots.push(ext);
39523
+ up = true;
39524
+ ext = { i, price: b.high };
39525
+ }
39526
+ }
39527
+ pivots.push(ext);
39528
+ }
39529
+ return {
39530
+ plots: [],
39531
+ polylines: [
39532
+ {
39533
+ key: "zigzag",
39534
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
39535
+ curved: false,
39536
+ closed: false,
39537
+ lineColor: str3(inputs, "color", SERIES_LINE),
39538
+ lineWidth: 2,
39539
+ lineStyle: "solid",
39540
+ arrowLeft: false,
39541
+ arrowRight: false
39542
+ }
39543
+ ]
39544
+ };
39545
+ }
39546
+ };
39547
+ var williamsFractal = {
39548
+ type: "williams-fractal",
39549
+ title: "Williams Fractal",
39550
+ shortTitle: "Fractals",
39551
+ overlay: true,
39552
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
39553
+ compute: (bars, inputs) => {
39554
+ const p = num3(inputs, "periods", 2);
39555
+ const markers = [];
39556
+ const isExtreme = (i, pick, better) => {
39557
+ const v = pick(bars[i]);
39558
+ for (let k = i - p; k <= i + p; k++) {
39559
+ if (k === i) continue;
39560
+ if (!better(v, pick(bars[k]))) return false;
39561
+ }
39562
+ return true;
39563
+ };
39564
+ for (let i = p; i < bars.length - p; i++) {
39565
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
39566
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
39567
+ }
39568
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
39569
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
39570
+ }
39571
+ }
39572
+ return { plots: [], markers };
39573
+ }
39574
+ };
39575
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
39576
+
39577
+ // src/core/native-indicators/classics/index.ts
39578
+ var classicSpecs = [
39579
+ ...averageSpecs,
39580
+ ...bandSpecs,
39581
+ ...oscillatorSpecs,
39582
+ ...trendSpecs,
39583
+ ...volatilitySpecs,
39584
+ ...volumeSpecs,
39585
+ ...overlaySpecs
39586
+ ];
39587
+ function registerClassicIndicators() {
39588
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
39589
+ }
39590
+
37259
39591
  // src/Vela.ts
37260
39592
  var Vela = class {
37261
39593
  constructor(container, options = {}, deps = {}) {
37262
39594
  registerBuiltinChartTypes();
37263
39595
  registerVolume();
37264
39596
  registerVpvr();
39597
+ registerClassicIndicators();
37265
39598
  const element = resolveElement(container);
37266
39599
  const theme = resolveTheme(options.theme);
37267
39600
  let animZoom = true;
@@ -37336,25 +39669,28 @@ var Vela = class {
37336
39669
  }
37337
39670
  /**
37338
39671
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
37339
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
37340
- * events) and is single-instance per type (a second call returns the existing handle). Returns
37341
- * a fail-soft handle for an unregistered type. Native renderer only.
39672
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
39673
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
39674
+ * call returns the existing handle; the classic studies allow several instances, so each call
39675
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
37342
39676
  */
37343
39677
  addNativeIndicator(type, options) {
37344
39678
  return this.orchestrator.addNativeIndicator(type, options);
37345
39679
  }
37346
39680
  /**
37347
39681
  * The catalog of built-in native indicators with their live state on this chart — each entry's
37348
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
37349
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
37350
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
37351
- * because support may probe the provider (a type may need data the symbol lacks).
39682
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
39683
+ * and whether it allows several instances (`multiInstance`; otherwise a second
39684
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
39685
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
39686
+ * provider (a type may need data the symbol lacks).
37352
39687
  */
37353
39688
  availableNativeIndicators() {
37354
39689
  return this.orchestrator.availableNativeIndicators();
37355
39690
  }
37356
39691
  /**
37357
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
39692
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
39693
+ * multi-instance type repeats) — the synchronous slice of
37358
39694
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
37359
39695
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
37360
39696
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -38097,7 +40433,7 @@ var Statusline = class {
38097
40433
  };
38098
40434
 
38099
40435
  // src/widget/session-shading.ts
38100
- var DAY_MS2 = 864e5;
40436
+ var DAY_MS3 = 864e5;
38101
40437
  var MINUTE_MS = 6e4;
38102
40438
  function parseWindow(text, allowOvernight = false) {
38103
40439
  if (typeof text !== "string") return null;
@@ -38176,7 +40512,7 @@ function expandSessionZones(spec, from, to) {
38176
40512
  const dtf = civilFormatter(spec.timezone);
38177
40513
  if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
38178
40514
  const seen = /* @__PURE__ */ new Set();
38179
- for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
40515
+ for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
38180
40516
  const civil = civilParts(dtf, cursor);
38181
40517
  const key = civil.year * 1e4 + civil.month * 100 + civil.day;
38182
40518
  if (!Number.isFinite(key) || seen.has(key)) continue;
@@ -38201,7 +40537,7 @@ function expandSessionZones(spec, from, to) {
38201
40537
  }
38202
40538
  return { pre, post, extended: mergeAbutting(extended) };
38203
40539
  }
38204
- var COVER_PAD_MIN_MS = 2 * DAY_MS2;
40540
+ var COVER_PAD_MIN_MS = 2 * DAY_MS3;
38205
40541
  var SessionShadingTracker = class {
38206
40542
  constructor(onZones) {
38207
40543
  this.onZones = onZones;
@@ -38225,7 +40561,7 @@ var SessionShadingTracker = class {
38225
40561
  this.covered = null;
38226
40562
  this.session = opts.session;
38227
40563
  const tfMs = timeframeMs(opts.timeframe);
38228
- this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
40564
+ this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
38229
40565
  this.lastRange = opts.range;
38230
40566
  void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
38231
40567
  if (my !== this.epoch) return;
@@ -38899,8 +41235,8 @@ var ChartCell = class {
38899
41235
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
38900
41236
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
38901
41237
  this.volumeMayBePending = true;
38902
- /** Sync mirror of the chart's present native types — the removal handler diffs
38903
- * against it to identify (and record) whichever type was just removed. */
41238
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
41239
+ * looks the removed id up here to learn which type an undo must re-add. */
38904
41240
  this.presentNatives = [];
38905
41241
  this.rangeBars = 0;
38906
41242
  this.pendingRange = null;
@@ -39072,15 +41408,18 @@ var ChartCell = class {
39072
41408
  redo: () => this.dropInstance(snapshot)
39073
41409
  });
39074
41410
  } else {
39075
- const now = this.inner?.presentNativeIndicators() ?? [];
39076
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
41411
+ const gone = this.presentNatives.find((n) => n.id === id2);
41412
+ if (gone) {
41413
+ const { type } = gone;
41414
+ let revived = null;
39077
41415
  this.history.push({
39078
41416
  undo: () => {
39079
- this.inner?.addNativeIndicator(type);
41417
+ revived = this.inner?.addNativeIndicator(type) ?? null;
39080
41418
  this.refreshNativeCatalog();
39081
41419
  },
39082
41420
  redo: () => {
39083
- this.inner?.addNativeIndicator(type).remove();
41421
+ revived?.remove();
41422
+ revived = null;
39084
41423
  this.refreshNativeCatalog();
39085
41424
  }
39086
41425
  });
@@ -39385,9 +41724,9 @@ var ChartCell = class {
39385
41724
  const live = this.inner?.renderer.get("priceStyle");
39386
41725
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
39387
41726
  }
39388
- /** Manifest instances + present natives — the topbar indicator count. */
41727
+ /** Manifest instances + native instances — the topbar indicator count. */
39389
41728
  get indicatorCount() {
39390
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
41729
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
39391
41730
  }
39392
41731
  /** Switch this cell's market in place (the chart instance survives). The projection
39393
41732
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -39519,13 +41858,15 @@ var ChartCell = class {
39519
41858
  if (!chart) return;
39520
41859
  this.volumeIntent = led.natives.includes("volume");
39521
41860
  this.history.silently(() => {
39522
- const present = chart.presentNativeIndicators();
39523
- for (const type of led.natives) {
39524
- if (!present.includes(type)) chart.addNativeIndicator(type);
39525
- }
39526
- for (const type of present) {
39527
- if (!led.natives.includes(type)) chart.addNativeIndicator(type).remove();
39528
- }
41861
+ const owed = /* @__PURE__ */ new Map();
41862
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
41863
+ for (const h of this.nativeHandles()) {
41864
+ const type = h.nativeType;
41865
+ const n = owed.get(type) ?? 0;
41866
+ if (n > 0) owed.set(type, n - 1);
41867
+ else h.remove();
41868
+ }
41869
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
39529
41870
  for (const it of [...this.instances]) this.dropInstance(it);
39530
41871
  if (this.manifest.length > 0) {
39531
41872
  for (const item of led.manifest) {
@@ -39543,34 +41884,42 @@ var ChartCell = class {
39543
41884
  this.syncPresentNatives();
39544
41885
  this.refreshNativeCatalog();
39545
41886
  }
39546
- /** The picker's library rows: supported natives first, then the manifest. */
41887
+ /** The supported natives in picker order: A→Z by title — registration order follows
41888
+ * the catalog's families, which is meaningless to the reader. This is the library
41889
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
41890
+ * read it — indexing the unsorted catalog on add would land on a different study. */
41891
+ supportedNatives() {
41892
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
41893
+ }
41894
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
41895
+ * then the manifest in the host's own order. */
39547
41896
  libraryRows() {
39548
41897
  return [
39549
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
41898
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
39550
41899
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
39551
41900
  ];
39552
41901
  }
39553
- /** The picker's on-chart rows: present natives first, then live instances. */
41902
+ /** The picker's on-chart rows: native instances first, then live script instances. */
39554
41903
  onChartRows() {
39555
41904
  return [
39556
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
41905
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
39557
41906
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
39558
41907
  ];
39559
41908
  }
39560
41909
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
39561
41910
  addFromLibrary(index) {
39562
- const natives = this.nativeCatalog.filter((n) => n.supported);
41911
+ const natives = this.supportedNatives();
39563
41912
  if (index < natives.length) this.addNative(natives[index].type);
39564
41913
  else {
39565
41914
  const entry = this.manifest[index - natives.length];
39566
41915
  if (entry) this.addManifestInstance(entry);
39567
41916
  }
39568
41917
  }
39569
- /** Remove by picker ON-CHART index (present natives precede instances). */
41918
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
39570
41919
  removeFromChart(index) {
39571
- const present = this.nativeCatalog.filter((n) => n.present);
39572
- if (index < present.length) this.removeNative(present[index].type);
39573
- else this.removeInstance(index - present.length);
41920
+ const natives = this.nativeHandles();
41921
+ if (index < natives.length) this.removeNative(natives[index]);
41922
+ else this.removeInstance(index - natives.length);
39574
41923
  }
39575
41924
  /**
39576
41925
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -39630,30 +41979,42 @@ var ChartCell = class {
39630
41979
  it.handle = null;
39631
41980
  this.deps.onIndicatorsChanged(this.id);
39632
41981
  }
39633
- /** Add a native indicator (single-instance per type — the core dedupes). */
41982
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
41983
+ * single-instance type already on the chart hands back its existing one — nothing
41984
+ * changed, so nothing enters the undo timeline. */
39634
41985
  addNative(type) {
39635
- this.inner?.addNativeIndicator(type);
41986
+ const chart = this.inner;
41987
+ if (!chart) return;
41988
+ const before = new Set(chart.indicators().map((h) => h.id));
41989
+ let added = chart.addNativeIndicator(type);
39636
41990
  this.syncPresentNatives();
39637
41991
  this.refreshNativeCatalog();
41992
+ if (before.has(added.id)) return;
39638
41993
  this.history.push({
39639
41994
  undo: () => {
39640
- this.inner?.addNativeIndicator(type).remove();
41995
+ added?.remove();
41996
+ added = null;
39641
41997
  this.refreshNativeCatalog();
39642
41998
  },
39643
41999
  redo: () => {
39644
- this.inner?.addNativeIndicator(type);
42000
+ added = this.inner?.addNativeIndicator(type) ?? null;
39645
42001
  this.refreshNativeCatalog();
39646
42002
  }
39647
42003
  });
39648
42004
  }
39649
- removeNative(type) {
39650
- this.inner?.addNativeIndicator(type).remove();
42005
+ removeNative(handle) {
42006
+ handle.remove();
39651
42007
  this.refreshNativeCatalog();
39652
42008
  }
39653
- /** Sync mirror of the chart's present native types — the removal handler diffs
39654
- * against it to identify (and record) whichever type was just removed. */
42009
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
42010
+ * the removal index space (script instances follow them). */
42011
+ nativeHandles() {
42012
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
42013
+ }
42014
+ /** Sync mirror of the chart's native instances — the removal handler looks the
42015
+ * removed id up here (the registry has already forgotten it) to record its type. */
39655
42016
  syncPresentNatives() {
39656
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
42017
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
39657
42018
  }
39658
42019
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
39659
42020
  refreshNativeCatalog() {