@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
package/dist/widget.cjs CHANGED
@@ -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();
@@ -5167,9 +5174,9 @@ function factoryResetConfig(factory) {
5167
5174
  if (inst.enableKey) defaults2[inst.enableKey] = false;
5168
5175
  addRows(inst.rows);
5169
5176
  }
5170
- for (const sub of section.subsections ?? []) {
5171
- if (sub.enableKey) defaults2[sub.enableKey] = false;
5172
- addRows(sub.rows);
5177
+ for (const sub2 of section.subsections ?? []) {
5178
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
5179
+ addRows(sub2.rows);
5173
5180
  }
5174
5181
  if (!section.instances) addRows(section.rows);
5175
5182
  bag[t.id] = defaults2;
@@ -14098,17 +14105,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
14098
14105
  }
14099
14106
  var STYLE_ID7 = "vela-widget-symbolpicker";
14100
14107
  var CSS8 = `
14101
- /* Search + market tabs stay pinned while the result list scrolls underneath
14102
- (mobile fullscreen: the dialog body is the scroller). Negative margin eats
14103
- the body's padding so scrolled rows cannot peek around the island. */
14104
- .vela-sp-sticky {
14105
- position: sticky;
14106
- top: 0;
14107
- z-index: 1;
14108
- background: var(--vela-surface);
14109
- margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
14110
- padding: var(--vela-space-4) var(--vela-space-4) 0;
14111
- }
14108
+ /* The dialog body is flush (a flex column): search + market tabs are a fixed head
14109
+ and the result list is the ONLY scroller, in both layouts. A sticky head over a
14110
+ scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
14111
+ scroller's content box, so the head lands one padding below the top and covers
14112
+ the first row. */
14113
+ .vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
14112
14114
  .vela-sp-searchrow {
14113
14115
  display: flex;
14114
14116
  align-items: center;
@@ -14133,12 +14135,13 @@ var CSS8 = `
14133
14135
  .vela-sp-input::placeholder { text-transform: none; }
14134
14136
  .vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
14135
14137
  /* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
14136
- overflowing the body, and the result list stops capping itself \u2014 the body owns
14137
- the scrolling in the fullscreen presentation. */
14138
+ overflowing the body, and the result list stops capping itself \u2014 it fills the
14139
+ rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
14140
+ dialog body carries it, flush or not, so the list adds none of its own. */
14138
14141
  [data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
14139
14142
  [data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
14140
14143
  [data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
14141
- [data-layout='mobile'] .vela-sp-list { max-height: none; }
14144
+ [data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
14142
14145
  .vela-sp-tab {
14143
14146
  all: unset;
14144
14147
  padding: 3px 10px;
@@ -14150,7 +14153,12 @@ var CSS8 = `
14150
14153
  }
14151
14154
  .vela-sp-tab:hover { color: var(--vela-fg); }
14152
14155
  .vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
14153
- .vela-sp-list { margin-top: var(--vela-space-2); max-height: 46vh; overflow: auto; }
14156
+ .vela-sp-list {
14157
+ min-height: 0;
14158
+ margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
14159
+ max-height: 46vh;
14160
+ overflow: auto;
14161
+ }
14154
14162
  .vela-sp-list::-webkit-scrollbar { width: 8px; }
14155
14163
  .vela-sp-list::-webkit-scrollbar-thumb {
14156
14164
  background: var(--vela-scroll);
@@ -14243,8 +14251,8 @@ var SymbolPicker = class {
14243
14251
  searchRow.append(iconEl("search", doc), this.input);
14244
14252
  this.tabs = doc.createElement("div");
14245
14253
  this.tabs.className = "vela-sp-tabs";
14246
- const sticky = doc.createElement("div");
14247
- sticky.className = "vela-sp-sticky";
14254
+ const head = doc.createElement("div");
14255
+ head.className = "vela-sp-head";
14248
14256
  for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
14249
14257
  const b = doc.createElement("button");
14250
14258
  b.className = "vela-sp-tab";
@@ -14258,7 +14266,7 @@ var SymbolPicker = class {
14258
14266
  });
14259
14267
  this.tabs.appendChild(b);
14260
14268
  }
14261
- sticky.append(searchRow, this.tabs);
14269
+ head.append(searchRow, this.tabs);
14262
14270
  this.list = doc.createElement("div");
14263
14271
  this.list.className = "vela-sp-list";
14264
14272
  this.list.addEventListener("scroll", () => {
@@ -14271,7 +14279,8 @@ var SymbolPicker = class {
14271
14279
  title: "Symbol Search",
14272
14280
  host: opts.host,
14273
14281
  closeOnInteractOutside: true,
14274
- content: (body) => body.append(sticky, this.list),
14282
+ flush: true,
14283
+ content: (body) => body.append(head, this.list),
14275
14284
  onOpenChange: (open2) => {
14276
14285
  if (open2) {
14277
14286
  this.input.value = this.seed.toUpperCase();
@@ -16042,11 +16051,11 @@ var PriceScaleDrawer = class {
16042
16051
  for (const item of items) {
16043
16052
  if (item.id === "labels" && item.submenu) {
16044
16053
  section("Labels");
16045
- for (const sub of item.submenu) {
16046
- row(sub.label, {
16047
- checked: sub.checked,
16054
+ for (const sub2 of item.submenu) {
16055
+ row(sub2.label, {
16056
+ checked: sub2.checked,
16048
16057
  onClick: () => {
16049
- const feature = sub.id.slice("toggle:".length);
16058
+ const feature = sub2.id.slice("toggle:".length);
16050
16059
  chart.renderer.set(feature, !this.flag(feature));
16051
16060
  this.render();
16052
16061
  }
@@ -16056,11 +16065,11 @@ var PriceScaleDrawer = class {
16056
16065
  }
16057
16066
  if (item.id === "levels" && item.submenu) {
16058
16067
  section("Levels");
16059
- for (const sub of item.submenu) {
16060
- row(sub.label, {
16061
- checked: sub.checked,
16068
+ for (const sub2 of item.submenu) {
16069
+ row(sub2.label, {
16070
+ checked: sub2.checked,
16062
16071
  onClick: () => {
16063
- const feature = sub.id.slice("toggle:".length);
16072
+ const feature = sub2.id.slice("toggle:".length);
16064
16073
  chart.renderer.set(feature, !this.flag(feature));
16065
16074
  this.render();
16066
16075
  }
@@ -16634,8 +16643,8 @@ var DrawingController = class {
16634
16643
  this.port?.setExternalGhost?.(doc);
16635
16644
  }
16636
16645
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
16637
- setToolShortcuts(map) {
16638
- this.port?.setToolShortcuts?.(map);
16646
+ setToolShortcuts(map2) {
16647
+ this.port?.setToolShortcuts?.(map2);
16639
16648
  }
16640
16649
  // ── programmatic CRUD (facade-facing) ── each is one undo step
16641
16650
  add(type, init = {}) {
@@ -17306,8 +17315,8 @@ var DrawingToolbar = class {
17306
17315
  }
17307
17316
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
17308
17317
  * right edge of the flyout rows, beside the favorite star. */
17309
- setShortcuts(map) {
17310
- this.shortcuts = new Map(Object.entries(map));
17318
+ setShortcuts(map2) {
17319
+ this.shortcuts = new Map(Object.entries(map2));
17311
17320
  }
17312
17321
  /** Paint one star for the current favorite state (filled + gold, or outline). */
17313
17322
  paintStar(el, type) {
@@ -18254,7 +18263,7 @@ var LiveSession = class {
18254
18263
 
18255
18264
  // src/core/engine/IndicatorHandleImpl.ts
18256
18265
  var IndicatorHandleImpl = class {
18257
- constructor(id, title, controller, source) {
18266
+ constructor(id, title, controller, source, nativeType) {
18258
18267
  this.controller = controller;
18259
18268
  this.schema = [];
18260
18269
  this.propsSchema = [];
@@ -18263,6 +18272,7 @@ var IndicatorHandleImpl = class {
18263
18272
  this.id = id;
18264
18273
  this.title = title;
18265
18274
  if (source !== void 0) this.source = source;
18275
+ if (nativeType !== void 0) this.nativeType = nativeType;
18266
18276
  }
18267
18277
  get inputs() {
18268
18278
  return this.schema;
@@ -18353,24 +18363,24 @@ function summarizeModel(model) {
18353
18363
  }
18354
18364
  function inspectModels(models) {
18355
18365
  const indicators = models.map(summarizeModel);
18356
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18366
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18357
18367
  return {
18358
18368
  indicators,
18359
18369
  totals: {
18360
18370
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
18361
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18362
- fills: sum((s) => s.fills),
18363
- backgrounds: sum((s) => s.backgrounds),
18364
- priceLines: sum((s) => s.priceLines),
18365
- lines: sum((s) => s.lines),
18366
- boxes: sum((s) => s.boxes),
18367
- labels: sum((s) => s.labels),
18368
- polylines: sum((s) => s.polylines),
18369
- linefills: sum((s) => s.linefills),
18370
- tables: sum((s) => s.tables),
18371
- barColors: sum((s) => s.barColors),
18372
- trades: sum((s) => s.trades),
18373
- forcedOverlay: sum((s) => s.forcedOverlay)
18371
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18372
+ fills: sum2((s) => s.fills),
18373
+ backgrounds: sum2((s) => s.backgrounds),
18374
+ priceLines: sum2((s) => s.priceLines),
18375
+ lines: sum2((s) => s.lines),
18376
+ boxes: sum2((s) => s.boxes),
18377
+ labels: sum2((s) => s.labels),
18378
+ polylines: sum2((s) => s.polylines),
18379
+ linefills: sum2((s) => s.linefills),
18380
+ tables: sum2((s) => s.tables),
18381
+ barColors: sum2((s) => s.barColors),
18382
+ trades: sum2((s) => s.trades),
18383
+ forcedOverlay: sum2((s) => s.forcedOverlay)
18374
18384
  }
18375
18385
  };
18376
18386
  }
@@ -19439,16 +19449,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
19439
19449
  /**
19440
19450
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
19441
19451
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
19442
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
19443
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
19452
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
19453
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
19454
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
19455
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
19444
19456
  */
19445
19457
  addNativeIndicator(type, options = {}) {
19446
- const existing = this.registry.all().find((r) => r.native?.type === type);
19447
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
19448
19458
  const descriptor = getNativeIndicator(type);
19459
+ if (!descriptor?.multiInstance) {
19460
+ const existing = this.registry.all().find((r) => r.native?.type === type);
19461
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19462
+ }
19449
19463
  const id = this.registry.nextId("native");
19450
19464
  const title = descriptor?.title ?? type;
19451
- const handle = new IndicatorHandleImpl(id, title, this);
19465
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19452
19466
  this.handles.set(id, handle);
19453
19467
  if (!descriptor) {
19454
19468
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -19460,16 +19474,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
19460
19474
  void this.startNativeIndicator(id, handle);
19461
19475
  return handle;
19462
19476
  }
19463
- /**
19464
- * The catalog of registered native-indicator types with their live state on THIS chart: each
19465
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
19466
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
19467
- * because a descriptor's `isSupported` may probe the provider for a required capability.
19468
- */
19469
- /** The native types present on the chart — SYNC (registry state; no support probe). */
19477
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
19478
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
19470
19479
  presentNativeIndicators() {
19471
19480
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
19472
19481
  }
19482
+ /**
19483
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
19484
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
19485
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
19486
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
19487
+ * probe the provider for a required capability.
19488
+ */
19473
19489
  async availableNativeIndicators() {
19474
19490
  const symbol = this.qualifiedSymbol();
19475
19491
  const present = new Set(this.presentNativeIndicators());
@@ -19479,7 +19495,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
19479
19495
  title: d.title,
19480
19496
  supported: await this.isNativeSupported(d, symbol),
19481
19497
  present: present.has(d.type),
19482
- beta: d.beta
19498
+ beta: d.beta,
19499
+ ...d.multiInstance ? { multiInstance: true } : {}
19483
19500
  }))
19484
19501
  );
19485
19502
  }
@@ -20619,8 +20636,8 @@ var DrawingsControl = class {
20619
20636
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
20620
20637
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
20621
20638
  * platform formatting, so hints always match the host's actual bindings. */
20622
- setToolShortcuts(map) {
20623
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
20639
+ setToolShortcuts(map2) {
20640
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
20624
20641
  return this;
20625
20642
  }
20626
20643
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -22101,8 +22118,8 @@ var InputsUI = class {
22101
22118
  lg.style.top = `${top}px`;
22102
22119
  }
22103
22120
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22104
- setCollapsedPanes(map) {
22105
- this.paneCollapse = map;
22121
+ setCollapsedPanes(map2) {
22122
+ this.paneCollapse = map2;
22106
22123
  }
22107
22124
  /**
22108
22125
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -24111,11 +24128,11 @@ function mergeBarColors(indicators) {
24111
24128
  }
24112
24129
  }
24113
24130
  if (!any) return EMPTY_BARCOLORS;
24114
- const map = /* @__PURE__ */ new Map();
24131
+ const map2 = /* @__PURE__ */ new Map();
24115
24132
  for (const m of indicators.values()) {
24116
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24133
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24117
24134
  }
24118
- return map;
24135
+ return map2;
24119
24136
  }
24120
24137
 
24121
24138
  // src/renderers/native/core/ViewportState.ts
@@ -24412,9 +24429,9 @@ function isHorizontalWheel(deltaX, deltaY) {
24412
24429
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24413
24430
  return rightOffset + deltaX / barSpacing;
24414
24431
  }
24415
- function wheelPanDelta(deltaX, deltaY, shift) {
24432
+ function wheelPanDelta(deltaX, deltaY, shift2) {
24416
24433
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24417
- if (shift) return deltaY;
24434
+ if (shift2) return deltaY;
24418
24435
  return null;
24419
24436
  }
24420
24437
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -26036,11 +26053,11 @@ function mergeBarColors2(indicators) {
26036
26053
  }
26037
26054
  }
26038
26055
  if (!any) return EMPTY_BARCOLORS2;
26039
- const map = /* @__PURE__ */ new Map();
26056
+ const map2 = /* @__PURE__ */ new Map();
26040
26057
  for (const m of indicators.values()) {
26041
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26058
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
26042
26059
  }
26043
- return map;
26060
+ return map2;
26044
26061
  }
26045
26062
 
26046
26063
  // src/renderers/shared/drawing-geometry.ts
@@ -27679,9 +27696,9 @@ function settingsIdCatalog(hostSections) {
27679
27696
  };
27680
27697
  if (section.rows) addRows(section.rows);
27681
27698
  for (const inst of section.instances ?? []) addRows(inst.rows);
27682
- for (const sub of section.subsections ?? []) {
27683
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
27684
- addRows(sub.rows);
27699
+ for (const sub2 of section.subsections ?? []) {
27700
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27701
+ addRows(sub2.rows);
27685
27702
  }
27686
27703
  }
27687
27704
  for (const hs of hostSections) {
@@ -28240,14 +28257,14 @@ var SettingsDialog = class {
28240
28257
  };
28241
28258
  if (section.rows) seed(section.rows);
28242
28259
  for (const inst of section.instances ?? []) seed(inst.rows);
28243
- for (const sub of section.subsections ?? []) seed(sub.rows);
28260
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28244
28261
  for (const inst of section.instances ?? []) {
28245
28262
  if (!inst.enableKey || inst.enableKey in bag) continue;
28246
28263
  bag[inst.enableKey] = values[inst.enableKey] === true;
28247
28264
  }
28248
- for (const sub of section.subsections ?? []) {
28249
- if (!sub.enableKey || sub.enableKey in bag) continue;
28250
- bag[sub.enableKey] = values[sub.enableKey] === true;
28265
+ for (const sub2 of section.subsections ?? []) {
28266
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28267
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28251
28268
  }
28252
28269
  const refreshers = [];
28253
28270
  const put = (key, v) => {
@@ -28264,16 +28281,16 @@ var SettingsDialog = class {
28264
28281
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28265
28282
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28266
28283
  }
28267
- for (const sub of section.subsections ?? []) {
28268
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
28269
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
28284
+ for (const sub2 of section.subsections ?? []) {
28285
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28286
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28270
28287
  if (rows.length === 0) continue;
28271
- const subMarker = this.section(sub.title);
28288
+ const subMarker = this.section(sub2.title);
28272
28289
  subMarker.dataset.sdStyle = typeId;
28273
28290
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28274
28291
  subMarker.dataset.sdSub = "1";
28275
28292
  body.append(subMarker);
28276
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28293
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28277
28294
  }
28278
28295
  for (const r of refreshers) r();
28279
28296
  }
@@ -30368,11 +30385,11 @@ var DrawingInteraction = class {
30368
30385
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30369
30386
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30370
30387
  * while placing a line tool it locks the segment angle to 45° steps. */
30371
- down(x, y, mode = "off", shift = false) {
30388
+ down(x, y, mode = "off", shift2 = false) {
30372
30389
  const proj = this.deps.projector();
30373
30390
  if (this.state.kind === "placing") {
30374
30391
  if (this.state.draft.placementMode() !== "click") return;
30375
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30392
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30376
30393
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30377
30394
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30378
30395
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30391,7 +30408,7 @@ var DrawingInteraction = class {
30391
30408
  }
30392
30409
  const hit = this.hitAt(x, y);
30393
30410
  if (hit) {
30394
- if (shift) {
30411
+ if (shift2) {
30395
30412
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30396
30413
  return;
30397
30414
  }
@@ -30401,7 +30418,7 @@ var DrawingInteraction = class {
30401
30418
  }
30402
30419
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30403
30420
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30404
- move(x, y, mode = "off", shift = false) {
30421
+ move(x, y, mode = "off", shift2 = false) {
30405
30422
  if (this.state.kind === "idle") {
30406
30423
  const tool = this.deps.activeTool();
30407
30424
  const had = this.snapAt != null;
@@ -30415,7 +30432,7 @@ var DrawingInteraction = class {
30415
30432
  this.captureFreehand(x, y);
30416
30433
  return;
30417
30434
  }
30418
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30435
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30419
30436
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30420
30437
  this.deps.changed();
30421
30438
  return;
@@ -30428,7 +30445,7 @@ var DrawingInteraction = class {
30428
30445
  if (this.state.handle >= 0) {
30429
30446
  const a = d.anchors[this.state.handle];
30430
30447
  if (a) {
30431
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30448
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30432
30449
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30433
30450
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30434
30451
  }
@@ -31126,21 +31143,21 @@ var DrawingSettingsPopup = class {
31126
31143
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31127
31144
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31128
31145
  }
31129
- const vwap = drawing.vwap;
31130
- if (paths.has("vwap.midColor") && vwap) {
31146
+ const vwap2 = drawing.vwap;
31147
+ if (paths.has("vwap.midColor") && vwap2) {
31131
31148
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31132
31149
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
31133
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31134
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31150
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31151
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31135
31152
  bar.appendChild(
31136
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31153
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31137
31154
  label: (v) => `${v}\u03C3`,
31138
31155
  labelInTrigger: true
31139
31156
  })
31140
31157
  );
31141
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31142
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31143
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31158
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31159
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31160
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31144
31161
  }
31145
31162
  if (paths.has("maxCurvature")) {
31146
31163
  const cur = drawing.maxCurvature ?? 24;
@@ -32157,8 +32174,8 @@ var UserDrawingController = class {
32157
32174
  this.toolbar.setFavorites(types);
32158
32175
  }
32159
32176
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
32160
- setToolShortcuts(map) {
32161
- this.toolbar.setShortcuts(map);
32177
+ setToolShortcuts(map2) {
32178
+ this.toolbar.setShortcuts(map2);
32162
32179
  }
32163
32180
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
32164
32181
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -32276,7 +32293,7 @@ var UserDrawingController = class {
32276
32293
  this.render();
32277
32294
  }
32278
32295
  }
32279
- pointerDown(x, y, snap = "off", shift = false) {
32296
+ pointerDown(x, y, snap = "off", shift2 = false) {
32280
32297
  if (this.eraserMode) {
32281
32298
  this.erasing = true;
32282
32299
  this.deleteAt(x, y);
@@ -32296,9 +32313,9 @@ var UserDrawingController = class {
32296
32313
  return;
32297
32314
  }
32298
32315
  }
32299
- this.interaction.down(x, y, snap, shift);
32316
+ this.interaction.down(x, y, snap, shift2);
32300
32317
  }
32301
- pointerMove(x, y, snap = "off", shift = false) {
32318
+ pointerMove(x, y, snap = "off", shift2 = false) {
32302
32319
  if (this.eraserMode) {
32303
32320
  if (this.erasing) this.deleteAt(x, y);
32304
32321
  return;
@@ -32309,7 +32326,7 @@ var UserDrawingController = class {
32309
32326
  this.render();
32310
32327
  return;
32311
32328
  }
32312
- this.interaction.move(x, y, snap, shift);
32329
+ this.interaction.move(x, y, snap, shift2);
32313
32330
  this.updateHover(x, y);
32314
32331
  }
32315
32332
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -32986,14 +33003,14 @@ function layoutTable(ctx, t, args) {
32986
33003
  }
32987
33004
  for (const { box, w: w2, h: h2 } of spanning) {
32988
33005
  if (box.cs > 1) {
32989
- let sum = 0;
32990
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
32991
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
33006
+ let sum2 = 0;
33007
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
33008
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
32992
33009
  }
32993
33010
  if (box.rs > 1) {
32994
- let sum = 0;
32995
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
32996
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
33011
+ let sum2 = 0;
33012
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
33013
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
32997
33014
  }
32998
33015
  }
32999
33016
  let w = 0;
@@ -33580,24 +33597,24 @@ function rasterizeOverlay(ctx, root, frame) {
33580
33597
  drawSvgShapes(el, alpha);
33581
33598
  continue;
33582
33599
  }
33583
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33600
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33584
33601
  let path = null;
33585
33602
  if (tag === "path") {
33586
33603
  const d = el.getAttribute("d");
33587
33604
  if (d) path = new Path2D(d);
33588
33605
  } else if (tag === "circle") {
33589
33606
  path = new Path2D();
33590
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
33607
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
33591
33608
  } else if (tag === "ellipse") {
33592
33609
  path = new Path2D();
33593
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
33610
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
33594
33611
  } else if (tag === "rect") {
33595
33612
  path = new Path2D();
33596
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
33613
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
33597
33614
  } else if (tag === "line") {
33598
33615
  path = new Path2D();
33599
- path.moveTo(num3("x1"), num3("y1"));
33600
- path.lineTo(num3("x2"), num3("y2"));
33616
+ path.moveTo(num4("x1"), num4("y1"));
33617
+ path.lineTo(num4("x2"), num4("y2"));
33601
33618
  } else if (tag === "polyline" || tag === "polygon") {
33602
33619
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
33603
33620
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -35014,8 +35031,8 @@ var NativeRenderer = class {
35014
35031
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
35015
35032
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
35016
35033
  drawingsSnapMode: () => this.snapMode,
35017
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
35018
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
35034
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
35035
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
35019
35036
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
35020
35037
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
35021
35038
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -35332,18 +35349,18 @@ var NativeRenderer = class {
35332
35349
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
35333
35350
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
35334
35351
  const newHeadTime = this.bars[0].time;
35335
- let shift = 0;
35352
+ let shift2 = 0;
35336
35353
  if (newHeadTime !== prevHeadTime) {
35337
35354
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
35338
- if (prepended > 0) shift = prepended;
35355
+ if (prepended > 0) shift2 = prepended;
35339
35356
  else {
35340
35357
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
35341
- if (trimmed > 0) shift = -trimmed;
35358
+ if (trimmed > 0) shift2 = -trimmed;
35342
35359
  }
35343
35360
  }
35344
- if (shift !== 0) {
35345
- this.zoomAnchorLogical += shift;
35346
- if (this.hoverLogical != null) this.hoverLogical += shift;
35361
+ if (shift2 !== 0) {
35362
+ this.zoomAnchorLogical += shift2;
35363
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
35347
35364
  }
35348
35365
  }
35349
35366
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -36949,15 +36966,15 @@ var NativeRenderer = class {
36949
36966
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
36950
36967
  * strip — merged own-scale indicators are hidden while collapsed. */
36951
36968
  collapsedMasterMap() {
36952
- const map = /* @__PURE__ */ new Map();
36969
+ const map2 = /* @__PURE__ */ new Map();
36953
36970
  for (const pane of this.scene.panes.values()) {
36954
36971
  if (!pane.collapsed) continue;
36955
36972
  const models = this.scene.orderedIndicatorsForPane(pane.id);
36956
36973
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
36957
36974
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
36958
- map.set(pane.id, master?.id ?? null);
36975
+ map2.set(pane.id, master?.id ?? null);
36959
36976
  }
36960
- return map;
36977
+ return map2;
36961
36978
  }
36962
36979
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
36963
36980
  setToolbarGutter(px) {
@@ -37324,12 +37341,2328 @@ function registerVpvr() {
37324
37341
  registerNativeIndicator(vpvrDescriptor);
37325
37342
  }
37326
37343
 
37344
+ // src/core/model/identity.ts
37345
+ function stableSeriesId(parts) {
37346
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
37347
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
37348
+ }
37349
+
37350
+ // src/core/native-indicators/classics/define.ts
37351
+ function num3(inputs, key, fallback) {
37352
+ const v = inputs[key];
37353
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
37354
+ }
37355
+ function str3(inputs, key, fallback) {
37356
+ const v = inputs[key];
37357
+ return typeof v === "string" && v !== "" ? v : fallback;
37358
+ }
37359
+ function toPoints(bars, values, colors) {
37360
+ const pts = new Array(bars.length);
37361
+ for (let i = 0; i < bars.length; i++) {
37362
+ const v = values[i];
37363
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
37364
+ const c = colors?.[i];
37365
+ if (c != null) p.color = c;
37366
+ pts[i] = p;
37367
+ }
37368
+ return pts;
37369
+ }
37370
+ var ClassicIndicator = class {
37371
+ constructor(spec) {
37372
+ this.spec = spec;
37373
+ this.inputs = {};
37374
+ }
37375
+ start(ctx, inputs) {
37376
+ this.ctx = ctx;
37377
+ this.inputs = inputs;
37378
+ this.recompute();
37379
+ }
37380
+ onBars() {
37381
+ this.recompute();
37382
+ }
37383
+ onViewport() {
37384
+ }
37385
+ setInputs(inputs) {
37386
+ this.inputs = inputs;
37387
+ this.recompute();
37388
+ }
37389
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
37390
+ suspend() {
37391
+ }
37392
+ resume() {
37393
+ this.recompute();
37394
+ }
37395
+ stop() {
37396
+ }
37397
+ recompute() {
37398
+ const bars = this.ctx.bars();
37399
+ const out = this.spec.compute(bars, this.inputs);
37400
+ const instanceId = this.spec.type;
37401
+ const series = [];
37402
+ const idsByKey = /* @__PURE__ */ new Map();
37403
+ out.plots.forEach((plot, ordinal) => {
37404
+ const kind = plot.kind ?? "line";
37405
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
37406
+ idsByKey.set(plot.key, id);
37407
+ series.push({
37408
+ id,
37409
+ title: plot.title,
37410
+ paneId: "",
37411
+ // stamped by the orchestrator
37412
+ kind,
37413
+ points: toPoints(bars, plot.values, plot.colors),
37414
+ style: {
37415
+ color: plot.color,
37416
+ width: plot.width ?? 1,
37417
+ lineStyle: plot.lineStyle ?? "solid",
37418
+ ...plot.base != null ? { base: plot.base } : {}
37419
+ },
37420
+ ...plot.overlay ? { overlay: true } : {}
37421
+ });
37422
+ });
37423
+ if (out.markers && out.markers.length > 0) {
37424
+ series.push({
37425
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
37426
+ title: this.spec.shortTitle ?? this.spec.title,
37427
+ paneId: "",
37428
+ kind: "markers",
37429
+ markers: out.markers
37430
+ });
37431
+ }
37432
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
37433
+ const from = idsByKey.get(band.from);
37434
+ const to = idsByKey.get(band.to);
37435
+ if (!from || !to) return [];
37436
+ return [
37437
+ {
37438
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
37439
+ paneId: "",
37440
+ fromSeriesId: from,
37441
+ toSeriesId: to,
37442
+ color: band.color
37443
+ }
37444
+ ];
37445
+ });
37446
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
37447
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
37448
+ paneId: "",
37449
+ price: level.price,
37450
+ ...level.color != null ? { color: level.color } : {},
37451
+ lineStyle: level.lineStyle ?? "dashed",
37452
+ width: 1,
37453
+ ...level.title != null ? { title: level.title } : {}
37454
+ }));
37455
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
37456
+ const { key, ...rest } = poly;
37457
+ return {
37458
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
37459
+ paneId: "",
37460
+ ...rest
37461
+ };
37462
+ });
37463
+ this.ctx.emit({ series, fills, priceLines, polylines });
37464
+ this.ctx.setStatus("idle");
37465
+ }
37466
+ };
37467
+ function classicDescriptor(spec) {
37468
+ return {
37469
+ type: spec.type,
37470
+ title: spec.title,
37471
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
37472
+ paneHint: spec.overlay ? "price" : "new",
37473
+ overlay: spec.overlay,
37474
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
37475
+ multiInstance: true,
37476
+ inputsSchema: () => spec.inputs,
37477
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
37478
+ create: () => new ClassicIndicator(spec)
37479
+ };
37480
+ }
37481
+
37482
+ // src/core/native-indicators/classics/math.ts
37483
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
37484
+ function sourceValues(bars, source) {
37485
+ const out = new Array(bars.length);
37486
+ for (let i = 0; i < bars.length; i++) {
37487
+ const b = bars[i];
37488
+ switch (source) {
37489
+ case "Open":
37490
+ out[i] = b.open;
37491
+ break;
37492
+ case "High":
37493
+ out[i] = b.high;
37494
+ break;
37495
+ case "Low":
37496
+ out[i] = b.low;
37497
+ break;
37498
+ case "HL2":
37499
+ out[i] = (b.high + b.low) / 2;
37500
+ break;
37501
+ case "HLC3":
37502
+ out[i] = (b.high + b.low + b.close) / 3;
37503
+ break;
37504
+ case "OHLC4":
37505
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
37506
+ break;
37507
+ case "HLCC4":
37508
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
37509
+ break;
37510
+ default:
37511
+ out[i] = b.close;
37512
+ }
37513
+ }
37514
+ return out;
37515
+ }
37516
+ function volumes(bars) {
37517
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
37518
+ }
37519
+ function highs(bars) {
37520
+ return bars.map((b) => b.high);
37521
+ }
37522
+ function lows(bars) {
37523
+ return bars.map((b) => b.low);
37524
+ }
37525
+ function closes(bars) {
37526
+ return bars.map((b) => b.close);
37527
+ }
37528
+ function opens(bars) {
37529
+ return bars.map((b) => b.open);
37530
+ }
37531
+ function zip(a, b, f) {
37532
+ const out = new Array(a.length);
37533
+ for (let i = 0; i < a.length; i++) {
37534
+ const x = a[i];
37535
+ const y = b[i];
37536
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
37537
+ }
37538
+ return out;
37539
+ }
37540
+ function map(a, f) {
37541
+ const out = new Array(a.length);
37542
+ for (let i = 0; i < a.length; i++) {
37543
+ const x = a[i];
37544
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
37545
+ }
37546
+ return out;
37547
+ }
37548
+ function sub(a, b) {
37549
+ return zip(a, b, (x, y) => x - y);
37550
+ }
37551
+ function windowed(v, len, fold) {
37552
+ const out = new Array(v.length).fill(Number.NaN);
37553
+ if (len <= 0) return out;
37554
+ let run = 0;
37555
+ for (let i = 0; i < v.length; i++) {
37556
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
37557
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
37558
+ }
37559
+ return out;
37560
+ }
37561
+ function sum(v, len) {
37562
+ return windowed(v, len, (w, from, to) => {
37563
+ let s = 0;
37564
+ for (let k = from; k <= to; k++) s += w[k];
37565
+ return s;
37566
+ });
37567
+ }
37568
+ function sma(v, len) {
37569
+ return windowed(v, len, (w, from, to) => {
37570
+ let s = 0;
37571
+ for (let k = from; k <= to; k++) s += w[k];
37572
+ return s / len;
37573
+ });
37574
+ }
37575
+ function wma(v, len) {
37576
+ const norm = len * (len + 1) / 2;
37577
+ return windowed(v, len, (w, from, to) => {
37578
+ let s = 0;
37579
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
37580
+ return s / norm;
37581
+ });
37582
+ }
37583
+ function vwma(src, vol, len) {
37584
+ const pv = zip(src, vol, (p, q) => p * q);
37585
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
37586
+ }
37587
+ function stdev(v, len) {
37588
+ return windowed(v, len, (w, from, to) => {
37589
+ let s = 0;
37590
+ for (let k = from; k <= to; k++) s += w[k];
37591
+ const mean = s / len;
37592
+ let sq = 0;
37593
+ for (let k = from; k <= to; k++) {
37594
+ const d = w[k] - mean;
37595
+ sq += d * d;
37596
+ }
37597
+ return Math.sqrt(sq / len);
37598
+ });
37599
+ }
37600
+ function highest(v, len) {
37601
+ return windowed(v, len, (w, from, to) => {
37602
+ let m = -Infinity;
37603
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
37604
+ return m;
37605
+ });
37606
+ }
37607
+ function lowest(v, len) {
37608
+ return windowed(v, len, (w, from, to) => {
37609
+ let m = Infinity;
37610
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
37611
+ return m;
37612
+ });
37613
+ }
37614
+ function barsSinceHighest(v, len) {
37615
+ return windowed(v, len, (w, from, to) => {
37616
+ let m = -Infinity;
37617
+ let at = to;
37618
+ for (let k = from; k <= to; k++) {
37619
+ if (w[k] >= m) {
37620
+ m = w[k];
37621
+ at = k;
37622
+ }
37623
+ }
37624
+ return to - at;
37625
+ });
37626
+ }
37627
+ function barsSinceLowest(v, len) {
37628
+ return windowed(v, len, (w, from, to) => {
37629
+ let m = Infinity;
37630
+ let at = to;
37631
+ for (let k = from; k <= to; k++) {
37632
+ if (w[k] <= m) {
37633
+ m = w[k];
37634
+ at = k;
37635
+ }
37636
+ }
37637
+ return to - at;
37638
+ });
37639
+ }
37640
+ function recursive(v, len, alpha) {
37641
+ const out = new Array(v.length).fill(Number.NaN);
37642
+ let run = 0;
37643
+ let prev2 = Number.NaN;
37644
+ for (let i = 0; i < v.length; i++) {
37645
+ const x = v[i];
37646
+ if (!Number.isFinite(x)) {
37647
+ run = 0;
37648
+ prev2 = Number.NaN;
37649
+ continue;
37650
+ }
37651
+ run += 1;
37652
+ if (Number.isFinite(prev2)) {
37653
+ prev2 = alpha * x + (1 - alpha) * prev2;
37654
+ out[i] = prev2;
37655
+ } else if (run >= len) {
37656
+ let s = 0;
37657
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
37658
+ prev2 = s / len;
37659
+ out[i] = prev2;
37660
+ }
37661
+ }
37662
+ return out;
37663
+ }
37664
+ function ema(v, len) {
37665
+ return recursive(v, len, 2 / (len + 1));
37666
+ }
37667
+ function rma(v, len) {
37668
+ return recursive(v, len, 1 / len);
37669
+ }
37670
+ function change(v, n = 1) {
37671
+ const out = new Array(v.length).fill(Number.NaN);
37672
+ for (let i = n; i < v.length; i++) {
37673
+ const a = v[i];
37674
+ const b = v[i - n];
37675
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
37676
+ }
37677
+ return out;
37678
+ }
37679
+ function roc(v, len) {
37680
+ const out = new Array(v.length).fill(Number.NaN);
37681
+ for (let i = len; i < v.length; i++) {
37682
+ const a = v[i];
37683
+ const b = v[i - len];
37684
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
37685
+ }
37686
+ return out;
37687
+ }
37688
+ function shift(v, n) {
37689
+ const out = new Array(v.length).fill(Number.NaN);
37690
+ for (let i = 0; i < v.length; i++) {
37691
+ const j = i - n;
37692
+ if (j >= 0 && j < v.length) out[i] = v[j];
37693
+ }
37694
+ return out;
37695
+ }
37696
+ function trueRange(bars) {
37697
+ const out = new Array(bars.length).fill(Number.NaN);
37698
+ for (let i = 0; i < bars.length; i++) {
37699
+ const b = bars[i];
37700
+ if (i === 0) {
37701
+ out[i] = b.high - b.low;
37702
+ continue;
37703
+ }
37704
+ const pc = bars[i - 1].close;
37705
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
37706
+ }
37707
+ return out;
37708
+ }
37709
+ function atr(bars, len) {
37710
+ return rma(trueRange(bars), len);
37711
+ }
37712
+ function rsi(v, len) {
37713
+ const d = change(v);
37714
+ const up = map(d, (x) => Math.max(x, 0));
37715
+ const dn = map(d, (x) => Math.max(-x, 0));
37716
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
37717
+ }
37718
+ function stoch(v, hi, lo, len) {
37719
+ const hh = highest(hi, len);
37720
+ const ll = lowest(lo, len);
37721
+ const out = new Array(v.length).fill(Number.NaN);
37722
+ for (let i = 0; i < v.length; i++) {
37723
+ const x = v[i];
37724
+ const h = hh[i];
37725
+ const l = ll[i];
37726
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
37727
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
37728
+ }
37729
+ return out;
37730
+ }
37731
+ function linreg(v, len) {
37732
+ const sx = (len - 1) * len / 2;
37733
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
37734
+ const denom = len * sxx - sx * sx;
37735
+ return windowed(v, len, (w, from, to) => {
37736
+ let sy = 0;
37737
+ let sxy = 0;
37738
+ for (let k = from; k <= to; k++) {
37739
+ const x = k - from;
37740
+ sy += w[k];
37741
+ sxy += x * w[k];
37742
+ }
37743
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
37744
+ const intercept = (sy - slope * sx) / len;
37745
+ return intercept + slope * (len - 1);
37746
+ });
37747
+ }
37748
+ function swma(v) {
37749
+ const out = new Array(v.length).fill(Number.NaN);
37750
+ for (let i = 3; i < v.length; i++) {
37751
+ const a = v[i - 3];
37752
+ const b = v[i - 2];
37753
+ const c = v[i - 1];
37754
+ const d = v[i];
37755
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
37756
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
37757
+ }
37758
+ }
37759
+ return out;
37760
+ }
37761
+ function percentRank(v, len) {
37762
+ const out = new Array(v.length).fill(Number.NaN);
37763
+ for (let i = len; i < v.length; i++) {
37764
+ const x = v[i];
37765
+ if (!Number.isFinite(x)) continue;
37766
+ let below = 0;
37767
+ let count = 0;
37768
+ for (let k = i - len; k < i; k++) {
37769
+ const y = v[k];
37770
+ if (!Number.isFinite(y)) {
37771
+ count = -1;
37772
+ break;
37773
+ }
37774
+ count += 1;
37775
+ if (y <= x) below += 1;
37776
+ }
37777
+ if (count === len) out[i] = 100 * below / len;
37778
+ }
37779
+ return out;
37780
+ }
37781
+ function cumSum(v) {
37782
+ const out = new Array(v.length).fill(Number.NaN);
37783
+ let acc = 0;
37784
+ let seen = false;
37785
+ for (let i = 0; i < v.length; i++) {
37786
+ const x = v[i];
37787
+ if (Number.isFinite(x)) {
37788
+ acc += x;
37789
+ seen = true;
37790
+ }
37791
+ if (seen) out[i] = acc;
37792
+ }
37793
+ return out;
37794
+ }
37795
+
37796
+ // src/core/native-indicators/classics/shared.ts
37797
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
37798
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
37799
+ }
37800
+ function sourceInput(defval = "Close") {
37801
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
37802
+ }
37803
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
37804
+ return { key, title, type: "color", defval };
37805
+ }
37806
+ function withAlpha2(hex, alpha = 0.08) {
37807
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
37808
+ if (!m) return hex;
37809
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
37810
+ return `#${m[1]}${a}`;
37811
+ }
37812
+
37813
+ // src/core/native-indicators/classics/averages.ts
37814
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
37815
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
37816
+ function fixedAverage(type, title, shortTitle, smooth) {
37817
+ return {
37818
+ type,
37819
+ title,
37820
+ shortTitle,
37821
+ overlay: true,
37822
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
37823
+ compute: (bars, inputs) => {
37824
+ const len = num3(inputs, "length", 20);
37825
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
37826
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37827
+ if (offset !== 0) values = shift(values, offset);
37828
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37829
+ }
37830
+ };
37831
+ }
37832
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
37833
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
37834
+ var movingAverage = {
37835
+ type: "moving-average",
37836
+ title: "Moving Average",
37837
+ shortTitle: "MA",
37838
+ overlay: true,
37839
+ inputs: [
37840
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
37841
+ lengthInput(20),
37842
+ sourceInput(),
37843
+ offsetInput,
37844
+ colorInput()
37845
+ ],
37846
+ compute: (bars, inputs) => {
37847
+ const len = num3(inputs, "length", 20);
37848
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37849
+ const kind = str3(inputs, "maType", "SMA");
37850
+ let values;
37851
+ switch (kind) {
37852
+ case "EMA":
37853
+ values = ema(src, len);
37854
+ break;
37855
+ case "WMA":
37856
+ values = wma(src, len);
37857
+ break;
37858
+ case "RMA":
37859
+ values = rma(src, len);
37860
+ break;
37861
+ case "VWMA":
37862
+ values = vwma(src, volumes(bars), len);
37863
+ break;
37864
+ default:
37865
+ values = sma(src, len);
37866
+ }
37867
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37868
+ if (offset !== 0) values = shift(values, offset);
37869
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37870
+ }
37871
+ };
37872
+ var smoothedMa = {
37873
+ type: "rma",
37874
+ title: "Smoothed Moving Average",
37875
+ shortTitle: "RMA",
37876
+ overlay: true,
37877
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37878
+ compute: (bars, inputs) => ({
37879
+ plots: [
37880
+ {
37881
+ key: "rma",
37882
+ title: "RMA",
37883
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37884
+ color: str3(inputs, "color", SERIES_LINE),
37885
+ width: 2
37886
+ }
37887
+ ]
37888
+ })
37889
+ };
37890
+ var zlema = {
37891
+ type: "zlema",
37892
+ title: "Zero-Lag Exponential Moving Average",
37893
+ shortTitle: "ZLEMA",
37894
+ overlay: true,
37895
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37896
+ compute: (bars, inputs) => {
37897
+ const len = num3(inputs, "length", 14);
37898
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37899
+ const lag = Math.floor((len - 1) / 2);
37900
+ const lagged = shift(src, lag);
37901
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
37902
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37903
+ }
37904
+ };
37905
+ var vidya = {
37906
+ type: "vidya",
37907
+ title: "Variable Index Dynamic Average",
37908
+ shortTitle: "VIDYA",
37909
+ overlay: true,
37910
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
37911
+ compute: (bars, inputs) => {
37912
+ const len = num3(inputs, "length", 14);
37913
+ const cmoLen = num3(inputs, "cmoLength", 9);
37914
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37915
+ const d = change(src);
37916
+ const su = new Array(src.length).fill(Number.NaN);
37917
+ const sd = new Array(src.length).fill(Number.NaN);
37918
+ for (let i = cmoLen; i < src.length; i++) {
37919
+ let u = 0;
37920
+ let w = 0;
37921
+ let ok = true;
37922
+ for (let k = i - cmoLen + 1; k <= i; k++) {
37923
+ const x = d[k];
37924
+ if (!Number.isFinite(x)) {
37925
+ ok = false;
37926
+ break;
37927
+ }
37928
+ if (x > 0) u += x;
37929
+ else w -= x;
37930
+ }
37931
+ if (ok) {
37932
+ su[i] = u;
37933
+ sd[i] = w;
37934
+ }
37935
+ }
37936
+ const alpha = 2 / (len + 1);
37937
+ const out = new Array(src.length).fill(Number.NaN);
37938
+ let prev2 = Number.NaN;
37939
+ for (let i = 0; i < src.length; i++) {
37940
+ const x = src[i];
37941
+ const u = su[i];
37942
+ const w = sd[i];
37943
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
37944
+ const total = u + w;
37945
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
37946
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
37947
+ out[i] = prev2;
37948
+ }
37949
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37950
+ }
37951
+ };
37952
+ var maEnvelope = {
37953
+ type: "ma-envelope",
37954
+ title: "Moving Average Envelope",
37955
+ shortTitle: "MA Env",
37956
+ overlay: true,
37957
+ inputs: [
37958
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
37959
+ lengthInput(20),
37960
+ sourceInput(),
37961
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
37962
+ colorInput()
37963
+ ],
37964
+ compute: (bars, inputs) => {
37965
+ const len = num3(inputs, "length", 20);
37966
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37967
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
37968
+ const k = num3(inputs, "percent", 2.5) / 100;
37969
+ const ink = str3(inputs, "color", SERIES_LINE);
37970
+ return {
37971
+ plots: [
37972
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
37973
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
37974
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
37975
+ ],
37976
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
37977
+ };
37978
+ }
37979
+ };
37980
+ var linearRegression = {
37981
+ type: "linear-regression",
37982
+ title: "Linear Regression Curve",
37983
+ shortTitle: "LinReg",
37984
+ overlay: true,
37985
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37986
+ compute: (bars, inputs) => ({
37987
+ plots: [
37988
+ {
37989
+ key: "linreg",
37990
+ title: "LinReg",
37991
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37992
+ color: str3(inputs, "color", SERIES_LINE),
37993
+ width: 2
37994
+ }
37995
+ ]
37996
+ })
37997
+ };
37998
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
37999
+
38000
+ // src/core/native-indicators/classics/bands.ts
38001
+ function bollinger(bars, inputs) {
38002
+ const len = num3(inputs, "length", 20);
38003
+ const mult = num3(inputs, "mult", 2);
38004
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38005
+ const basis = sma(src, len);
38006
+ const dev = map(stdev(src, len), (x) => x * mult);
38007
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
38008
+ }
38009
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
38010
+ var bollingerBands = {
38011
+ type: "bollinger-bands",
38012
+ title: "Bollinger Bands",
38013
+ shortTitle: "BB",
38014
+ overlay: true,
38015
+ inputs: [...BB_INPUTS, colorInput()],
38016
+ compute: (bars, inputs) => {
38017
+ const { basis, upper, lower } = bollinger(bars, inputs);
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: upper, color: ink },
38023
+ { key: "lower", title: "Lower", values: lower, color: ink }
38024
+ ],
38025
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
38026
+ };
38027
+ }
38028
+ };
38029
+ var percentB = {
38030
+ type: "percent-b",
38031
+ title: "Bollinger Bands %B",
38032
+ shortTitle: "%B",
38033
+ overlay: false,
38034
+ inputs: [...BB_INPUTS, colorInput()],
38035
+ compute: (bars, inputs) => {
38036
+ const { upper, lower } = bollinger(bars, inputs);
38037
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38038
+ const values = src.map((x, i) => {
38039
+ const u = upper[i];
38040
+ const l = lower[i];
38041
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
38042
+ });
38043
+ return {
38044
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38045
+ levels: [
38046
+ { key: "upper", price: 1, color: NEUTRAL },
38047
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
38048
+ { key: "lower", price: 0, color: NEUTRAL }
38049
+ ]
38050
+ };
38051
+ }
38052
+ };
38053
+ var bandwidth = {
38054
+ type: "bandwidth",
38055
+ title: "Bollinger Bands Width",
38056
+ shortTitle: "BandWidth",
38057
+ overlay: false,
38058
+ inputs: [...BB_INPUTS, colorInput()],
38059
+ compute: (bars, inputs) => {
38060
+ const { basis, upper, lower } = bollinger(bars, inputs);
38061
+ const values = basis.map((b, i) => {
38062
+ const u = upper[i];
38063
+ const l = lower[i];
38064
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
38065
+ });
38066
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38067
+ }
38068
+ };
38069
+ var keltner = {
38070
+ type: "keltner-channels",
38071
+ title: "Keltner Channels",
38072
+ shortTitle: "KC",
38073
+ overlay: true,
38074
+ inputs: [
38075
+ lengthInput(20),
38076
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38077
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38078
+ sourceInput(),
38079
+ colorInput()
38080
+ ],
38081
+ compute: (bars, inputs) => {
38082
+ const len = num3(inputs, "length", 20);
38083
+ const mult = num3(inputs, "mult", 2);
38084
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
38085
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
38086
+ const ink = str3(inputs, "color", SERIES_LINE);
38087
+ return {
38088
+ plots: [
38089
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
38090
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
38091
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
38092
+ ],
38093
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38094
+ };
38095
+ }
38096
+ };
38097
+ var donchian = {
38098
+ type: "donchian-channels",
38099
+ title: "Donchian Channels",
38100
+ shortTitle: "DC",
38101
+ overlay: true,
38102
+ inputs: [lengthInput(20), colorInput()],
38103
+ compute: (bars, inputs) => {
38104
+ const len = num3(inputs, "length", 20);
38105
+ const upper = highest(highs(bars), len);
38106
+ const lower = lowest(lows(bars), len);
38107
+ const ink = str3(inputs, "color", SERIES_LINE);
38108
+ return {
38109
+ plots: [
38110
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
38111
+ { key: "upper", title: "Upper", values: upper, color: ink },
38112
+ { key: "lower", title: "Lower", values: lower, color: ink }
38113
+ ],
38114
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38115
+ };
38116
+ }
38117
+ };
38118
+ var chandelier = {
38119
+ type: "chandelier-exit",
38120
+ title: "Chandelier Exit",
38121
+ shortTitle: "CE",
38122
+ overlay: true,
38123
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
38124
+ compute: (bars, inputs) => {
38125
+ const len = num3(inputs, "length", 22);
38126
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
38127
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
38128
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
38129
+ return {
38130
+ plots: [
38131
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
38132
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
38133
+ ]
38134
+ };
38135
+ }
38136
+ };
38137
+ var chandeKroll = {
38138
+ type: "chande-kroll-stop",
38139
+ title: "Chande Kroll Stop",
38140
+ shortTitle: "CKS",
38141
+ overlay: true,
38142
+ inputs: [
38143
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38144
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
38145
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
38146
+ ],
38147
+ compute: (bars, inputs) => {
38148
+ const p = num3(inputs, "p", 10);
38149
+ const x = num3(inputs, "x", 1);
38150
+ const q = num3(inputs, "q", 9);
38151
+ const range = map(atr(bars, p), (v) => v * x);
38152
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
38153
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
38154
+ return {
38155
+ plots: [
38156
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
38157
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
38158
+ ]
38159
+ };
38160
+ }
38161
+ };
38162
+ var supertrend = {
38163
+ type: "supertrend",
38164
+ title: "SuperTrend",
38165
+ shortTitle: "SuperTrend",
38166
+ overlay: true,
38167
+ inputs: [
38168
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38169
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
38170
+ ],
38171
+ compute: (bars, inputs) => {
38172
+ const len = num3(inputs, "atrLength", 10);
38173
+ const mult = num3(inputs, "mult", 3);
38174
+ const range = atr(bars, len);
38175
+ const n = bars.length;
38176
+ const st = new Array(n).fill(Number.NaN);
38177
+ const dir = new Array(n).fill(1);
38178
+ let up = Number.NaN;
38179
+ let dn = Number.NaN;
38180
+ let trend = 1;
38181
+ for (let i = 0; i < n; i++) {
38182
+ const r = range[i];
38183
+ if (!Number.isFinite(r)) continue;
38184
+ const b = bars[i];
38185
+ const mid = (b.high + b.low) / 2;
38186
+ const basicUp = mid - mult * r;
38187
+ const basicDn = mid + mult * r;
38188
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
38189
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
38190
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
38191
+ if (trend === 1 && b.close < up) trend = -1;
38192
+ else if (trend === -1 && b.close > dn) trend = 1;
38193
+ dir[i] = trend;
38194
+ st[i] = trend === 1 ? up : dn;
38195
+ }
38196
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
38197
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
38198
+ }
38199
+ };
38200
+ var alligator = {
38201
+ type: "williams-alligator",
38202
+ title: "Williams Alligator",
38203
+ shortTitle: "Alligator",
38204
+ overlay: true,
38205
+ inputs: [
38206
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
38207
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
38208
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
38209
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
38210
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
38211
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
38212
+ ],
38213
+ compute: (bars, inputs) => {
38214
+ const hl2 = sourceValues(bars, "HL2");
38215
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
38216
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
38217
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
38218
+ return {
38219
+ plots: [
38220
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
38221
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
38222
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
38223
+ ]
38224
+ };
38225
+ }
38226
+ };
38227
+ var gator = {
38228
+ type: "gator-oscillator",
38229
+ title: "Gator Oscillator",
38230
+ shortTitle: "Gator Oscillator",
38231
+ overlay: false,
38232
+ inputs: [],
38233
+ compute: (bars) => {
38234
+ const hl2 = sourceValues(bars, "HL2");
38235
+ const jaw = shift(rma(hl2, 13), 8);
38236
+ const teeth = shift(rma(hl2, 8), 5);
38237
+ const lips = shift(rma(hl2, 5), 3);
38238
+ const upper = map(sub(jaw, teeth), Math.abs);
38239
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
38240
+ const colorByExpansion = (v) => v.map((x, i) => {
38241
+ if (!Number.isFinite(x)) return null;
38242
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
38243
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
38244
+ });
38245
+ return {
38246
+ plots: [
38247
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
38248
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
38249
+ ]
38250
+ };
38251
+ }
38252
+ };
38253
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
38254
+
38255
+ // src/core/native-indicators/classics/oscillators.ts
38256
+ function guideLevels(low, high, mid) {
38257
+ const levels = [
38258
+ { key: "upper", price: high, color: NEUTRAL },
38259
+ { key: "lower", price: low, color: NEUTRAL }
38260
+ ];
38261
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
38262
+ return levels;
38263
+ }
38264
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
38265
+ function histColors(values) {
38266
+ return values.map((x, i) => {
38267
+ if (!Number.isFinite(x)) return null;
38268
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
38269
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
38270
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
38271
+ return rising ? `${BEARISH}80` : BEARISH;
38272
+ });
38273
+ }
38274
+ var rsiSpec = {
38275
+ type: "rsi",
38276
+ title: "Relative Strength Index",
38277
+ shortTitle: "RSI",
38278
+ overlay: false,
38279
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38280
+ compute: (bars, inputs) => ({
38281
+ plots: [
38282
+ {
38283
+ key: "rsi",
38284
+ title: "RSI",
38285
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38286
+ color: str3(inputs, "color", SERIES_LINE),
38287
+ width: 2
38288
+ }
38289
+ ],
38290
+ levels: guideLevels(30, 70, 50)
38291
+ })
38292
+ };
38293
+ var stochastic = {
38294
+ type: "stochastic",
38295
+ title: "Stochastic",
38296
+ shortTitle: "Stoch",
38297
+ overlay: false,
38298
+ inputs: [
38299
+ lengthInput(14, "kLength", "%K length"),
38300
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
38301
+ lengthInput(3, "dLength", "%D smoothing", 500)
38302
+ ],
38303
+ compute: (bars, inputs) => {
38304
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
38305
+ const d = sma(k, num3(inputs, "dLength", 3));
38306
+ return {
38307
+ plots: [
38308
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38309
+ { key: "d", title: "%D", values: d, color: WARNING }
38310
+ ],
38311
+ levels: guideLevels(20, 80)
38312
+ };
38313
+ }
38314
+ };
38315
+ var stochasticRsi = {
38316
+ type: "stochastic-rsi",
38317
+ title: "Stochastic RSI",
38318
+ shortTitle: "Stoch RSI",
38319
+ overlay: false,
38320
+ inputs: [
38321
+ lengthInput(14, "rsiLength", "RSI length"),
38322
+ lengthInput(14, "stochLength", "Stochastic length"),
38323
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
38324
+ lengthInput(3, "dLength", "%D smoothing", 500),
38325
+ sourceInput()
38326
+ ],
38327
+ compute: (bars, inputs) => {
38328
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
38329
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
38330
+ const d = sma(k, num3(inputs, "dLength", 3));
38331
+ return {
38332
+ plots: [
38333
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38334
+ { key: "d", title: "%D", values: d, color: WARNING }
38335
+ ],
38336
+ levels: guideLevels(20, 80)
38337
+ };
38338
+ }
38339
+ };
38340
+ var macd = {
38341
+ type: "macd",
38342
+ title: "MACD",
38343
+ overlay: false,
38344
+ inputs: [
38345
+ lengthInput(12, "fastLength", "Fast length"),
38346
+ lengthInput(26, "slowLength", "Slow length"),
38347
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38348
+ sourceInput()
38349
+ ],
38350
+ compute: (bars, inputs) => {
38351
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38352
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
38353
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38354
+ const hist = sub(line, signal);
38355
+ return {
38356
+ plots: [
38357
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38358
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
38359
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38360
+ ],
38361
+ levels: ZERO_LEVEL
38362
+ };
38363
+ }
38364
+ };
38365
+ var ppo = {
38366
+ type: "ppo",
38367
+ title: "Percentage Price Oscillator",
38368
+ shortTitle: "PPO",
38369
+ overlay: false,
38370
+ inputs: [
38371
+ lengthInput(12, "fastLength", "Fast length"),
38372
+ lengthInput(26, "slowLength", "Slow length"),
38373
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38374
+ sourceInput()
38375
+ ],
38376
+ compute: (bars, inputs) => {
38377
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38378
+ const fast = ema(src, num3(inputs, "fastLength", 12));
38379
+ const slow = ema(src, num3(inputs, "slowLength", 26));
38380
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
38381
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38382
+ const hist = sub(line, signal);
38383
+ return {
38384
+ plots: [
38385
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38386
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
38387
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38388
+ ],
38389
+ levels: ZERO_LEVEL
38390
+ };
38391
+ }
38392
+ };
38393
+ var awesome = {
38394
+ type: "awesome-oscillator",
38395
+ title: "Awesome Oscillator",
38396
+ shortTitle: "AO",
38397
+ overlay: false,
38398
+ inputs: [],
38399
+ compute: (bars) => {
38400
+ const hl2 = sourceValues(bars, "HL2");
38401
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
38402
+ const colors = ao.map((x, i) => {
38403
+ if (!Number.isFinite(x)) return null;
38404
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
38405
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
38406
+ });
38407
+ return {
38408
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
38409
+ levels: ZERO_LEVEL
38410
+ };
38411
+ }
38412
+ };
38413
+ var cci = {
38414
+ type: "commodity-channel-index",
38415
+ title: "Commodity Channel Index",
38416
+ shortTitle: "CCI",
38417
+ overlay: false,
38418
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
38419
+ compute: (bars, inputs) => {
38420
+ const len = num3(inputs, "length", 20);
38421
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
38422
+ const basis = sma(src, len);
38423
+ const dev = new Array(src.length).fill(Number.NaN);
38424
+ for (let i = len - 1; i < src.length; i++) {
38425
+ const mean = basis[i];
38426
+ if (!Number.isFinite(mean)) continue;
38427
+ let d = 0;
38428
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
38429
+ dev[i] = d / len;
38430
+ }
38431
+ const values = src.map((x, i) => {
38432
+ const b = basis[i];
38433
+ const d = dev[i];
38434
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
38435
+ });
38436
+ return {
38437
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38438
+ levels: guideLevels(-100, 100, 0)
38439
+ };
38440
+ }
38441
+ };
38442
+ var williamsR = {
38443
+ type: "williams-percent-r",
38444
+ title: "Williams %R",
38445
+ shortTitle: "%R",
38446
+ overlay: false,
38447
+ inputs: [lengthInput(14), colorInput()],
38448
+ compute: (bars, inputs) => {
38449
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
38450
+ return {
38451
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38452
+ levels: guideLevels(-80, -20)
38453
+ };
38454
+ }
38455
+ };
38456
+ var cmo = {
38457
+ type: "chande-momentum-oscillator",
38458
+ title: "Chande Momentum Oscillator",
38459
+ shortTitle: "CMO",
38460
+ overlay: false,
38461
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38462
+ compute: (bars, inputs) => {
38463
+ const len = num3(inputs, "length", 9);
38464
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
38465
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
38466
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
38467
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
38468
+ return {
38469
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38470
+ levels: guideLevels(-50, 50, 0)
38471
+ };
38472
+ }
38473
+ };
38474
+ var connorsRsi = {
38475
+ type: "connors-rsi",
38476
+ title: "Connors RSI",
38477
+ shortTitle: "CRSI",
38478
+ overlay: false,
38479
+ inputs: [
38480
+ lengthInput(3, "rsiLength", "RSI length"),
38481
+ lengthInput(2, "streakLength", "Streak RSI length"),
38482
+ lengthInput(100, "rankLength", "Percent-rank length"),
38483
+ colorInput()
38484
+ ],
38485
+ compute: (bars, inputs) => {
38486
+ const c = closes(bars);
38487
+ const streak = new Array(c.length).fill(0);
38488
+ for (let i = 1; i < c.length; i++) {
38489
+ const d = c[i] - c[i - 1];
38490
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
38491
+ }
38492
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
38493
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
38494
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
38495
+ const values = c.map((_, i) => {
38496
+ const x = a[i];
38497
+ const y = b[i];
38498
+ const z = r[i];
38499
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
38500
+ });
38501
+ return {
38502
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38503
+ levels: guideLevels(20, 80, 50)
38504
+ };
38505
+ }
38506
+ };
38507
+ var fisher = {
38508
+ type: "fisher-transform",
38509
+ title: "Fisher Transform",
38510
+ shortTitle: "Fisher",
38511
+ overlay: false,
38512
+ inputs: [lengthInput(9)],
38513
+ compute: (bars, inputs) => {
38514
+ const len = num3(inputs, "length", 9);
38515
+ const hl2 = sourceValues(bars, "HL2");
38516
+ const hi = highest(hl2, len);
38517
+ const lo = lowest(hl2, len);
38518
+ const n = bars.length;
38519
+ const out = new Array(n).fill(Number.NaN);
38520
+ let value = 0;
38521
+ let fish = 0;
38522
+ for (let i = 0; i < n; i++) {
38523
+ const h = hi[i];
38524
+ const l = lo[i];
38525
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
38526
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
38527
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
38528
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
38529
+ out[i] = fish;
38530
+ }
38531
+ return {
38532
+ plots: [
38533
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
38534
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
38535
+ ],
38536
+ levels: ZERO_LEVEL
38537
+ };
38538
+ }
38539
+ };
38540
+ var trix = {
38541
+ type: "trix",
38542
+ title: "TRIX",
38543
+ overlay: false,
38544
+ inputs: [lengthInput(18), colorInput()],
38545
+ compute: (bars, inputs) => {
38546
+ const len = num3(inputs, "length", 18);
38547
+ const logClose = map(closes(bars), Math.log);
38548
+ const smooth = ema(ema(ema(logClose, len), len), len);
38549
+ const values = map(change(smooth), (x) => x * 1e4);
38550
+ return {
38551
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38552
+ levels: ZERO_LEVEL
38553
+ };
38554
+ }
38555
+ };
38556
+ function tsiLine(src, long, short) {
38557
+ const mom = change(src);
38558
+ const numer = ema(ema(mom, long), short);
38559
+ const denom = ema(ema(map(mom, Math.abs), long), short);
38560
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
38561
+ }
38562
+ var tsi = {
38563
+ type: "true-strength-index",
38564
+ title: "True Strength Index",
38565
+ shortTitle: "TSI",
38566
+ overlay: false,
38567
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
38568
+ compute: (bars, inputs) => {
38569
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
38570
+ return {
38571
+ plots: [
38572
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
38573
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
38574
+ ],
38575
+ levels: ZERO_LEVEL
38576
+ };
38577
+ }
38578
+ };
38579
+ var smiErgodic = {
38580
+ type: "smi-ergodic",
38581
+ title: "SMI Ergodic Oscillator",
38582
+ shortTitle: "SMIE",
38583
+ overlay: false,
38584
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
38585
+ compute: (bars, inputs) => {
38586
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
38587
+ const signal = ema(line, num3(inputs, "signalLength", 5));
38588
+ const osc = sub(line, signal);
38589
+ return {
38590
+ plots: [
38591
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
38592
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
38593
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38594
+ ],
38595
+ levels: ZERO_LEVEL
38596
+ };
38597
+ }
38598
+ };
38599
+ var stc = {
38600
+ type: "schaff-trend-cycle",
38601
+ title: "Schaff Trend Cycle",
38602
+ shortTitle: "Schaff Trend Cycle",
38603
+ overlay: false,
38604
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
38605
+ compute: (bars, inputs) => {
38606
+ const len = num3(inputs, "cycleLength", 10);
38607
+ const src = closes(bars);
38608
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
38609
+ const stochOf = (v) => {
38610
+ const lo = lowest(v, len);
38611
+ const hi = highest(v, len);
38612
+ const out = new Array(v.length).fill(Number.NaN);
38613
+ let prevRaw = Number.NaN;
38614
+ let smoothed = Number.NaN;
38615
+ for (let i = 0; i < v.length; i++) {
38616
+ const x = v[i];
38617
+ const l = lo[i];
38618
+ const h = hi[i];
38619
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
38620
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
38621
+ prevRaw = raw;
38622
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
38623
+ out[i] = smoothed;
38624
+ }
38625
+ return out;
38626
+ };
38627
+ const values = stochOf(stochOf(macdLine));
38628
+ return {
38629
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
38630
+ levels: guideLevels(25, 75)
38631
+ };
38632
+ }
38633
+ };
38634
+ var kst = {
38635
+ type: "know-sure-thing",
38636
+ title: "Know Sure Thing",
38637
+ shortTitle: "KST",
38638
+ overlay: false,
38639
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
38640
+ compute: (bars, inputs) => {
38641
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38642
+ const line = [
38643
+ sma(roc(src, 10), 10),
38644
+ sma(roc(src, 15), 10),
38645
+ sma(roc(src, 20), 10),
38646
+ sma(roc(src, 30), 15)
38647
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
38648
+ return {
38649
+ plots: [
38650
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
38651
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
38652
+ ],
38653
+ levels: ZERO_LEVEL
38654
+ };
38655
+ }
38656
+ };
38657
+ var coppock = {
38658
+ type: "coppock-curve",
38659
+ title: "Coppock Curve",
38660
+ shortTitle: "Coppock",
38661
+ overlay: false,
38662
+ inputs: [
38663
+ lengthInput(10, "wmaLength", "WMA length"),
38664
+ lengthInput(14, "longRoc", "Long RoC length"),
38665
+ lengthInput(11, "shortRoc", "Short RoC length"),
38666
+ sourceInput(),
38667
+ colorInput()
38668
+ ],
38669
+ compute: (bars, inputs) => {
38670
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38671
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
38672
+ return {
38673
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38674
+ levels: ZERO_LEVEL
38675
+ };
38676
+ }
38677
+ };
38678
+ var dpo = {
38679
+ type: "detrended-price-oscillator",
38680
+ title: "Detrended Price Oscillator",
38681
+ shortTitle: "DPO",
38682
+ overlay: false,
38683
+ inputs: [lengthInput(21), colorInput()],
38684
+ compute: (bars, inputs) => {
38685
+ const len = num3(inputs, "length", 21);
38686
+ const barsback = Math.floor(len / 2) + 1;
38687
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
38688
+ return {
38689
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38690
+ levels: ZERO_LEVEL
38691
+ };
38692
+ }
38693
+ };
38694
+ var rocSpec = {
38695
+ type: "rate-of-change",
38696
+ title: "Rate of Change",
38697
+ shortTitle: "ROC",
38698
+ overlay: false,
38699
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38700
+ compute: (bars, inputs) => ({
38701
+ plots: [
38702
+ {
38703
+ key: "roc",
38704
+ title: "RoC",
38705
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
38706
+ color: str3(inputs, "color", SERIES_LINE),
38707
+ width: 2
38708
+ }
38709
+ ],
38710
+ levels: ZERO_LEVEL
38711
+ })
38712
+ };
38713
+ var ultimate = {
38714
+ type: "ultimate-oscillator",
38715
+ title: "Ultimate Oscillator",
38716
+ shortTitle: "UO",
38717
+ overlay: false,
38718
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
38719
+ compute: (bars, inputs) => {
38720
+ const n = bars.length;
38721
+ const bp = new Array(n).fill(Number.NaN);
38722
+ const tr = new Array(n).fill(Number.NaN);
38723
+ for (let i = 0; i < n; i++) {
38724
+ const b = bars[i];
38725
+ const pc = i > 0 ? bars[i - 1].close : b.close;
38726
+ const lo = Math.min(b.low, pc);
38727
+ const hi = Math.max(b.high, pc);
38728
+ bp[i] = b.close - lo;
38729
+ tr[i] = hi - lo;
38730
+ }
38731
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
38732
+ const a7 = avg(num3(inputs, "fastLength", 7));
38733
+ const a14 = avg(num3(inputs, "middleLength", 14));
38734
+ const a28 = avg(num3(inputs, "slowLength", 28));
38735
+ const values = a7.map((x, i) => {
38736
+ const y = a14[i];
38737
+ const z = a28[i];
38738
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
38739
+ });
38740
+ return {
38741
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38742
+ levels: guideLevels(30, 70, 50)
38743
+ };
38744
+ }
38745
+ };
38746
+ var rvgi = {
38747
+ type: "relative-vigor-index",
38748
+ title: "Relative Vigor Index",
38749
+ shortTitle: "RVGI",
38750
+ overlay: false,
38751
+ inputs: [lengthInput(10)],
38752
+ compute: (bars, inputs) => {
38753
+ const len = num3(inputs, "length", 10);
38754
+ const co = swma(sub(closes(bars), opens(bars)));
38755
+ const hl = swma(sub(highs(bars), lows(bars)));
38756
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
38757
+ return {
38758
+ plots: [
38759
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
38760
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
38761
+ ],
38762
+ levels: ZERO_LEVEL
38763
+ };
38764
+ }
38765
+ };
38766
+ var relVolatility = {
38767
+ type: "relative-volatility-index",
38768
+ title: "Relative Volatility Index",
38769
+ shortTitle: "RVI",
38770
+ overlay: false,
38771
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
38772
+ compute: (bars, inputs) => {
38773
+ const smoothLen = num3(inputs, "smoothLength", 14);
38774
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38775
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
38776
+ const d = change(src);
38777
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
38778
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
38779
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
38780
+ return {
38781
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38782
+ levels: guideLevels(20, 80, 50)
38783
+ };
38784
+ }
38785
+ };
38786
+ var bop = {
38787
+ type: "balance-of-power",
38788
+ title: "Balance of Power",
38789
+ shortTitle: "BOP",
38790
+ overlay: false,
38791
+ inputs: [colorInput()],
38792
+ compute: (bars, inputs) => {
38793
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
38794
+ return {
38795
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38796
+ levels: ZERO_LEVEL
38797
+ };
38798
+ }
38799
+ };
38800
+ var elderRay = {
38801
+ type: "elder-ray",
38802
+ title: "Elder Ray",
38803
+ overlay: false,
38804
+ inputs: [lengthInput(13)],
38805
+ compute: (bars, inputs) => {
38806
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
38807
+ return {
38808
+ plots: [
38809
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
38810
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
38811
+ ],
38812
+ levels: ZERO_LEVEL
38813
+ };
38814
+ }
38815
+ };
38816
+ var ttmSqueeze = {
38817
+ type: "ttm-squeeze",
38818
+ title: "TTM Squeeze",
38819
+ overlay: false,
38820
+ inputs: [
38821
+ lengthInput(20),
38822
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38823
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
38824
+ ],
38825
+ compute: (bars, inputs) => {
38826
+ const len = num3(inputs, "length", 20);
38827
+ const c = closes(bars);
38828
+ const basis = sma(c, len);
38829
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
38830
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
38831
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
38832
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
38833
+ const mom = linreg(sub(c, anchor), len);
38834
+ const squeezed = c.map((_, i) => {
38835
+ const d = dev[i];
38836
+ const r = kcRange[i];
38837
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
38838
+ });
38839
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
38840
+ return {
38841
+ plots: [
38842
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
38843
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
38844
+ ]
38845
+ };
38846
+ }
38847
+ };
38848
+ var oscillatorSpecs = [
38849
+ rsiSpec,
38850
+ stochastic,
38851
+ stochasticRsi,
38852
+ macd,
38853
+ ppo,
38854
+ awesome,
38855
+ cci,
38856
+ williamsR,
38857
+ cmo,
38858
+ connorsRsi,
38859
+ fisher,
38860
+ trix,
38861
+ tsi,
38862
+ smiErgodic,
38863
+ stc,
38864
+ kst,
38865
+ coppock,
38866
+ dpo,
38867
+ rocSpec,
38868
+ ultimate,
38869
+ rvgi,
38870
+ relVolatility,
38871
+ bop,
38872
+ elderRay,
38873
+ ttmSqueeze
38874
+ ];
38875
+
38876
+ // src/core/native-indicators/classics/trend.ts
38877
+ var aroon = {
38878
+ type: "aroon",
38879
+ title: "Aroon",
38880
+ overlay: false,
38881
+ inputs: [lengthInput(14)],
38882
+ compute: (bars, inputs) => {
38883
+ const len = num3(inputs, "length", 14);
38884
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
38885
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
38886
+ return {
38887
+ plots: [
38888
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
38889
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
38890
+ ],
38891
+ levels: [
38892
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
38893
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
38894
+ ]
38895
+ };
38896
+ }
38897
+ };
38898
+ var adx = {
38899
+ type: "average-directional-index",
38900
+ title: "Average Directional Index",
38901
+ shortTitle: "ADX",
38902
+ overlay: false,
38903
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
38904
+ compute: (bars, inputs) => {
38905
+ const diLen = num3(inputs, "diLength", 14);
38906
+ const adxLen = num3(inputs, "adxLength", 14);
38907
+ const n = bars.length;
38908
+ const plusDm = new Array(n).fill(Number.NaN);
38909
+ const minusDm = new Array(n).fill(Number.NaN);
38910
+ for (let i = 1; i < n; i++) {
38911
+ const up = bars[i].high - bars[i - 1].high;
38912
+ const dn = bars[i - 1].low - bars[i].low;
38913
+ plusDm[i] = up > dn && up > 0 ? up : 0;
38914
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
38915
+ }
38916
+ const atrLine = rma(trueRange(bars), diLen);
38917
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38918
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38919
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
38920
+ return {
38921
+ plots: [
38922
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
38923
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
38924
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
38925
+ ],
38926
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
38927
+ };
38928
+ }
38929
+ };
38930
+ var vortex = {
38931
+ type: "vortex-indicator",
38932
+ title: "Vortex Indicator",
38933
+ shortTitle: "VI",
38934
+ overlay: false,
38935
+ inputs: [lengthInput(14)],
38936
+ compute: (bars, inputs) => {
38937
+ const len = num3(inputs, "length", 14);
38938
+ const n = bars.length;
38939
+ const vmPlus = new Array(n).fill(Number.NaN);
38940
+ const vmMinus = new Array(n).fill(Number.NaN);
38941
+ for (let i = 1; i < n; i++) {
38942
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
38943
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
38944
+ }
38945
+ const trSum = sum(trueRange(bars), len);
38946
+ return {
38947
+ plots: [
38948
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
38949
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
38950
+ ],
38951
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
38952
+ };
38953
+ }
38954
+ };
38955
+ var trendSpecs = [aroon, adx, vortex];
38956
+
38957
+ // src/core/native-indicators/classics/volatility.ts
38958
+ var atrSpec = {
38959
+ type: "average-true-range",
38960
+ title: "Average True Range",
38961
+ shortTitle: "ATR",
38962
+ overlay: false,
38963
+ inputs: [lengthInput(14), colorInput()],
38964
+ compute: (bars, inputs) => ({
38965
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
38966
+ })
38967
+ };
38968
+ var historicalVolatility = {
38969
+ type: "historical-volatility",
38970
+ title: "Historical Volatility",
38971
+ shortTitle: "HV",
38972
+ overlay: false,
38973
+ inputs: [
38974
+ lengthInput(10),
38975
+ { 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)" },
38976
+ colorInput()
38977
+ ],
38978
+ compute: (bars, inputs) => {
38979
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
38980
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
38981
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38982
+ }
38983
+ };
38984
+ var chaikinVolatility = {
38985
+ type: "chaikin-volatility",
38986
+ title: "Chaikin Volatility",
38987
+ shortTitle: "CHV",
38988
+ overlay: false,
38989
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
38990
+ compute: (bars, inputs) => {
38991
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
38992
+ return {
38993
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38994
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
38995
+ };
38996
+ }
38997
+ };
38998
+ var massIndex = {
38999
+ type: "mass-index",
39000
+ title: "Mass Index",
39001
+ shortTitle: "MI",
39002
+ overlay: false,
39003
+ inputs: [lengthInput(25), colorInput()],
39004
+ compute: (bars, inputs) => {
39005
+ const range = sub(highs(bars), lows(bars));
39006
+ const single = ema(range, 9);
39007
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
39008
+ return {
39009
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39010
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
39011
+ };
39012
+ }
39013
+ };
39014
+ var standardDeviation = {
39015
+ type: "standard-deviation",
39016
+ title: "Standard Deviation",
39017
+ shortTitle: "StdDev",
39018
+ overlay: false,
39019
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
39020
+ compute: (bars, inputs) => ({
39021
+ plots: [
39022
+ {
39023
+ key: "stdev",
39024
+ title: "StdDev",
39025
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
39026
+ color: str3(inputs, "color", SERIES_LINE),
39027
+ width: 2
39028
+ }
39029
+ ]
39030
+ })
39031
+ };
39032
+ var ulcerIndex = {
39033
+ type: "ulcer-index",
39034
+ title: "Ulcer Index",
39035
+ overlay: false,
39036
+ inputs: [lengthInput(14), colorInput()],
39037
+ compute: (bars, inputs) => {
39038
+ const len = num3(inputs, "length", 14);
39039
+ const c = closes(bars);
39040
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
39041
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
39042
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39043
+ }
39044
+ };
39045
+ var choppiness = {
39046
+ type: "choppiness-index",
39047
+ title: "Choppiness Index",
39048
+ shortTitle: "CHOP",
39049
+ overlay: false,
39050
+ inputs: [lengthInput(14), colorInput()],
39051
+ compute: (bars, inputs) => {
39052
+ const len = num3(inputs, "length", 14);
39053
+ const trSum = sum(trueRange(bars), len);
39054
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
39055
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
39056
+ return {
39057
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39058
+ levels: [
39059
+ { key: "upper", price: 61.8, color: NEUTRAL },
39060
+ { key: "lower", price: 38.2, color: NEUTRAL }
39061
+ ]
39062
+ };
39063
+ }
39064
+ };
39065
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
39066
+
39067
+ // src/core/native-indicators/classics/volume.ts
39068
+ function moneyFlowVolume(bars) {
39069
+ return bars.map((b) => {
39070
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39071
+ if (b.high === b.low) return 0;
39072
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
39073
+ });
39074
+ }
39075
+ var obv = {
39076
+ type: "on-balance-volume",
39077
+ title: "On Balance Volume",
39078
+ shortTitle: "OBV",
39079
+ overlay: false,
39080
+ inputs: [colorInput()],
39081
+ compute: (bars, inputs) => {
39082
+ const vol = volumes(bars);
39083
+ const d = change(closes(bars));
39084
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
39085
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39086
+ }
39087
+ };
39088
+ var accDist = {
39089
+ type: "accumulation-distribution",
39090
+ title: "Accumulation / Distribution",
39091
+ shortTitle: "A/D",
39092
+ overlay: false,
39093
+ inputs: [colorInput()],
39094
+ compute: (bars, inputs) => ({
39095
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
39096
+ })
39097
+ };
39098
+ var cmf = {
39099
+ type: "chaikin-money-flow",
39100
+ title: "Chaikin Money Flow",
39101
+ shortTitle: "CMF",
39102
+ overlay: false,
39103
+ inputs: [lengthInput(20), colorInput()],
39104
+ compute: (bars, inputs) => {
39105
+ const len = num3(inputs, "length", 20);
39106
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
39107
+ return {
39108
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39109
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39110
+ };
39111
+ }
39112
+ };
39113
+ var chaikinOscillator = {
39114
+ type: "chaikin-oscillator",
39115
+ title: "Chaikin Oscillator",
39116
+ shortTitle: "Chaikin Osc",
39117
+ overlay: false,
39118
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39119
+ compute: (bars, inputs) => {
39120
+ const ad = cumSum(moneyFlowVolume(bars));
39121
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
39122
+ return {
39123
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39124
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39125
+ };
39126
+ }
39127
+ };
39128
+ var eom = {
39129
+ type: "ease-of-movement",
39130
+ title: "Ease of Movement",
39131
+ shortTitle: "EOM",
39132
+ overlay: false,
39133
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
39134
+ compute: (bars, inputs) => {
39135
+ const div = num3(inputs, "divisor", 1e4);
39136
+ const hl2 = sourceValues(bars, "HL2");
39137
+ const move = change(hl2);
39138
+ const raw = bars.map((b, i) => {
39139
+ const m = move[i];
39140
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
39141
+ return div * m * (b.high - b.low) / b.volume;
39142
+ });
39143
+ return {
39144
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39145
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39146
+ };
39147
+ }
39148
+ };
39149
+ var forceIndex = {
39150
+ type: "force-index",
39151
+ title: "Force Index",
39152
+ shortTitle: "FI",
39153
+ overlay: false,
39154
+ inputs: [lengthInput(13), colorInput()],
39155
+ compute: (bars, inputs) => {
39156
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
39157
+ return {
39158
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39159
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39160
+ };
39161
+ }
39162
+ };
39163
+ var klinger = {
39164
+ type: "klinger-oscillator",
39165
+ title: "Klinger Oscillator",
39166
+ shortTitle: "KVO",
39167
+ overlay: false,
39168
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
39169
+ compute: (bars, inputs) => {
39170
+ const hlc3 = sourceValues(bars, "HLC3");
39171
+ const trend = change(hlc3);
39172
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
39173
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
39174
+ return {
39175
+ plots: [
39176
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
39177
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39178
+ ],
39179
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39180
+ };
39181
+ }
39182
+ };
39183
+ var mfi = {
39184
+ type: "money-flow-index",
39185
+ title: "Money Flow Index",
39186
+ shortTitle: "MFI",
39187
+ overlay: false,
39188
+ inputs: [lengthInput(14), colorInput()],
39189
+ compute: (bars, inputs) => {
39190
+ const len = num3(inputs, "length", 14);
39191
+ const tp = sourceValues(bars, "HLC3");
39192
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
39193
+ const d = change(tp);
39194
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
39195
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
39196
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
39197
+ return {
39198
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39199
+ levels: [
39200
+ { key: "upper", price: 80, color: NEUTRAL },
39201
+ { key: "lower", price: 20, color: NEUTRAL }
39202
+ ]
39203
+ };
39204
+ }
39205
+ };
39206
+ function volumeIndex(bars, when) {
39207
+ const out = new Array(bars.length).fill(Number.NaN);
39208
+ let index = 1e3;
39209
+ let started = false;
39210
+ for (let i = 1; i < bars.length; i++) {
39211
+ const v = bars[i].volume;
39212
+ const pv = bars[i - 1].volume;
39213
+ const pc = bars[i - 1].close;
39214
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
39215
+ if (started) out[i] = index;
39216
+ continue;
39217
+ }
39218
+ started = true;
39219
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
39220
+ out[i] = index;
39221
+ }
39222
+ return out;
39223
+ }
39224
+ var nvi = {
39225
+ type: "negative-volume-index",
39226
+ title: "Negative Volume Index",
39227
+ shortTitle: "Negative Volume Index",
39228
+ overlay: false,
39229
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39230
+ compute: (bars, inputs) => {
39231
+ const line = volumeIndex(bars, (v, pv) => v < pv);
39232
+ return {
39233
+ plots: [
39234
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39235
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39236
+ ]
39237
+ };
39238
+ }
39239
+ };
39240
+ var pvi = {
39241
+ type: "positive-volume-index",
39242
+ title: "Positive Volume Index",
39243
+ shortTitle: "Positive Volume Index",
39244
+ overlay: false,
39245
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39246
+ compute: (bars, inputs) => {
39247
+ const line = volumeIndex(bars, (v, pv) => v > pv);
39248
+ return {
39249
+ plots: [
39250
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39251
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39252
+ ]
39253
+ };
39254
+ }
39255
+ };
39256
+ var pvt = {
39257
+ type: "price-volume-trend",
39258
+ title: "Price Volume Trend",
39259
+ shortTitle: "PVT",
39260
+ overlay: false,
39261
+ inputs: [colorInput()],
39262
+ compute: (bars, inputs) => {
39263
+ const c = closes(bars);
39264
+ const raw = bars.map((b, i) => {
39265
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39266
+ const pc = c[i - 1];
39267
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
39268
+ });
39269
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39270
+ }
39271
+ };
39272
+ var volumeOscillator = {
39273
+ type: "volume-oscillator",
39274
+ title: "Volume Oscillator",
39275
+ shortTitle: "Vol Osc",
39276
+ overlay: false,
39277
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39278
+ compute: (bars, inputs) => {
39279
+ const vol = volumes(bars);
39280
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
39281
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
39282
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39283
+ return {
39284
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39285
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39286
+ };
39287
+ }
39288
+ };
39289
+ var pvo = {
39290
+ type: "pvo",
39291
+ title: "Percentage Volume Oscillator",
39292
+ shortTitle: "PVO",
39293
+ overlay: false,
39294
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
39295
+ compute: (bars, inputs) => {
39296
+ const vol = volumes(bars);
39297
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
39298
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
39299
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39300
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39301
+ const hist = sub(line, signal);
39302
+ return {
39303
+ plots: [
39304
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
39305
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
39306
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39307
+ ],
39308
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39309
+ };
39310
+ }
39311
+ };
39312
+ var vfi = {
39313
+ type: "volume-flow-indicator",
39314
+ title: "Volume Flow Indicator",
39315
+ shortTitle: "Volume Flow Indicator",
39316
+ overlay: false,
39317
+ inputs: [
39318
+ lengthInput(130),
39319
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
39320
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
39321
+ lengthInput(3, "smoothLength", "Smoothing length")
39322
+ ],
39323
+ compute: (bars, inputs) => {
39324
+ const len = num3(inputs, "length", 130);
39325
+ const coef = num3(inputs, "coef", 0.2);
39326
+ const volCoef = num3(inputs, "volCoef", 2.5);
39327
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
39328
+ const inter = change(typical);
39329
+ const vInter = stdev(inter, 30);
39330
+ const vol = volumes(bars);
39331
+ const vAve = sma(vol, len);
39332
+ const mf = change(sourceValues(bars, "HLC3"));
39333
+ const raw = bars.map((b, i) => {
39334
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
39335
+ const v = vol[i];
39336
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39337
+ const m = mf[i];
39338
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
39339
+ const capped = Math.min(v, ave * volCoef);
39340
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
39341
+ });
39342
+ const values = bars.map((_, i) => {
39343
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39344
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
39345
+ });
39346
+ const flowSum = sum(raw, len);
39347
+ const line = zip(flowSum, values, (f, a) => f / a);
39348
+ return {
39349
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
39350
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39351
+ };
39352
+ }
39353
+ };
39354
+ var intradayIntensity = {
39355
+ type: "intraday-intensity",
39356
+ title: "Intraday Intensity",
39357
+ shortTitle: "Intraday Intensity",
39358
+ overlay: false,
39359
+ inputs: [lengthInput(21), colorInput()],
39360
+ compute: (bars, inputs) => {
39361
+ const len = num3(inputs, "length", 21);
39362
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
39363
+ return {
39364
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39365
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39366
+ };
39367
+ }
39368
+ };
39369
+ var volumeSpecs = [
39370
+ obv,
39371
+ accDist,
39372
+ cmf,
39373
+ chaikinOscillator,
39374
+ eom,
39375
+ forceIndex,
39376
+ klinger,
39377
+ mfi,
39378
+ nvi,
39379
+ pvi,
39380
+ pvt,
39381
+ volumeOscillator,
39382
+ pvo,
39383
+ vfi,
39384
+ intradayIntensity
39385
+ ];
39386
+
39387
+ // src/core/native-indicators/classics/overlays.ts
39388
+ var DAY_MS2 = 864e5;
39389
+ function periodKey(anchor, time) {
39390
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
39391
+ if (anchor === "Month") {
39392
+ const d = new Date(time);
39393
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
39394
+ }
39395
+ return Math.floor(time / DAY_MS2);
39396
+ }
39397
+ var parabolicSar = {
39398
+ type: "parabolic-sar",
39399
+ title: "Parabolic SAR",
39400
+ shortTitle: "SAR",
39401
+ overlay: true,
39402
+ inputs: [
39403
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39404
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39405
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
39406
+ ],
39407
+ compute: (bars, inputs) => {
39408
+ const start = num3(inputs, "start", 0.02);
39409
+ const inc = num3(inputs, "increment", 0.02);
39410
+ const max = num3(inputs, "maximum", 0.2);
39411
+ const n = bars.length;
39412
+ const values = new Array(n).fill(Number.NaN);
39413
+ const colors = new Array(n).fill(null);
39414
+ if (n >= 2) {
39415
+ let long = bars[1].close >= bars[0].close;
39416
+ let sar = long ? bars[0].low : bars[0].high;
39417
+ let ep = long ? bars[0].high : bars[0].low;
39418
+ let af = start;
39419
+ for (let i = 1; i < n; i++) {
39420
+ const b = bars[i];
39421
+ sar += af * (ep - sar);
39422
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
39423
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
39424
+ const reversed = long ? b.low < sar : b.high > sar;
39425
+ if (reversed) {
39426
+ long = !long;
39427
+ sar = ep;
39428
+ ep = long ? b.high : b.low;
39429
+ af = start;
39430
+ } else if (long && b.high > ep) {
39431
+ ep = b.high;
39432
+ af = Math.min(max, af + inc);
39433
+ } else if (!long && b.low < ep) {
39434
+ ep = b.low;
39435
+ af = Math.min(max, af + inc);
39436
+ }
39437
+ values[i] = sar;
39438
+ colors[i] = long ? BULLISH : BEARISH;
39439
+ }
39440
+ }
39441
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
39442
+ }
39443
+ };
39444
+ var vwap = {
39445
+ type: "vwap",
39446
+ title: "Volume Weighted Average Price",
39447
+ shortTitle: "VWAP",
39448
+ overlay: true,
39449
+ inputs: [
39450
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
39451
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
39452
+ colorInput(INFO)
39453
+ ],
39454
+ compute: (bars, inputs) => {
39455
+ const anchor = str3(inputs, "anchor", "Day");
39456
+ const source = str3(inputs, "source", "HLC3");
39457
+ const values = new Array(bars.length).fill(Number.NaN);
39458
+ let period = Number.NaN;
39459
+ let cumPV = 0;
39460
+ let cumV = 0;
39461
+ for (let i = 0; i < bars.length; i++) {
39462
+ const b = bars[i];
39463
+ const key = periodKey(anchor, b.time);
39464
+ if (key !== period) {
39465
+ period = key;
39466
+ cumPV = 0;
39467
+ cumV = 0;
39468
+ }
39469
+ const v = b.volume;
39470
+ if (v != null && Number.isFinite(v) && v > 0) {
39471
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
39472
+ cumPV += tp * v;
39473
+ cumV += v;
39474
+ }
39475
+ if (cumV > 0) values[i] = cumPV / cumV;
39476
+ }
39477
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
39478
+ }
39479
+ };
39480
+ var pivotPoints = {
39481
+ type: "pivot-points",
39482
+ title: "Pivot Points",
39483
+ shortTitle: "Pivots",
39484
+ overlay: true,
39485
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
39486
+ compute: (bars, inputs) => {
39487
+ const anchor = str3(inputs, "anchor", "Day");
39488
+ const aggs = /* @__PURE__ */ new Map();
39489
+ for (const b of bars) {
39490
+ const key = periodKey(anchor, b.time);
39491
+ const a = aggs.get(key);
39492
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
39493
+ else {
39494
+ a.high = Math.max(a.high, b.high);
39495
+ a.low = Math.min(a.low, b.low);
39496
+ a.close = b.close;
39497
+ }
39498
+ }
39499
+ const n = bars.length;
39500
+ const mk = () => new Array(n).fill(Number.NaN);
39501
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
39502
+ for (let i = 0; i < n; i++) {
39503
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
39504
+ if (!prev2) continue;
39505
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
39506
+ levels.p[i] = p;
39507
+ levels.r1[i] = 2 * p - prev2.low;
39508
+ levels.s1[i] = 2 * p - prev2.high;
39509
+ levels.r2[i] = p + (prev2.high - prev2.low);
39510
+ levels.s2[i] = p - (prev2.high - prev2.low);
39511
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
39512
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
39513
+ }
39514
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
39515
+ return {
39516
+ plots: [
39517
+ plot("p", "P", WARNING),
39518
+ plot("r1", "R1", BEARISH),
39519
+ plot("s1", "S1", BULLISH),
39520
+ plot("r2", "R2", BEARISH),
39521
+ plot("s2", "S2", BULLISH),
39522
+ plot("r3", "R3", BEARISH),
39523
+ plot("s3", "S3", BULLISH)
39524
+ ]
39525
+ };
39526
+ }
39527
+ };
39528
+ var fiftyTwoWeek = {
39529
+ type: "52-week-high-low",
39530
+ title: "52 Week High/Low",
39531
+ shortTitle: "52W H/L",
39532
+ overlay: true,
39533
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
39534
+ compute: (bars, inputs) => {
39535
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
39536
+ const n = bars.length;
39537
+ const hi = new Array(n).fill(Number.NaN);
39538
+ const lo = new Array(n).fill(Number.NaN);
39539
+ const maxIdx = [];
39540
+ const minIdx = [];
39541
+ let from = 0;
39542
+ for (let i = 0; i < n; i++) {
39543
+ const b = bars[i];
39544
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
39545
+ maxIdx.push(i);
39546
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
39547
+ minIdx.push(i);
39548
+ while (bars[from].time < b.time - span) from += 1;
39549
+ while (maxIdx[0] < from) maxIdx.shift();
39550
+ while (minIdx[0] < from) minIdx.shift();
39551
+ hi[i] = bars[maxIdx[0]].high;
39552
+ lo[i] = bars[minIdx[0]].low;
39553
+ }
39554
+ return {
39555
+ plots: [
39556
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
39557
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
39558
+ ]
39559
+ };
39560
+ }
39561
+ };
39562
+ var zigzag = {
39563
+ type: "zigzag",
39564
+ title: "ZigZag",
39565
+ overlay: true,
39566
+ inputs: [
39567
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
39568
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39569
+ colorInput()
39570
+ ],
39571
+ compute: (bars, inputs) => {
39572
+ const dev = num3(inputs, "deviation", 5) / 100;
39573
+ const depth = num3(inputs, "depth", 10);
39574
+ const pivots = [];
39575
+ if (bars.length > 1) {
39576
+ let up = bars[1].close >= bars[0].close;
39577
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
39578
+ for (let i = 1; i < bars.length; i++) {
39579
+ const b = bars[i];
39580
+ if (up) {
39581
+ if (b.high >= ext.price) ext = { i, price: b.high };
39582
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39583
+ pivots.push(ext);
39584
+ up = false;
39585
+ ext = { i, price: b.low };
39586
+ }
39587
+ } else if (b.low <= ext.price) {
39588
+ ext = { i, price: b.low };
39589
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39590
+ pivots.push(ext);
39591
+ up = true;
39592
+ ext = { i, price: b.high };
39593
+ }
39594
+ }
39595
+ pivots.push(ext);
39596
+ }
39597
+ return {
39598
+ plots: [],
39599
+ polylines: [
39600
+ {
39601
+ key: "zigzag",
39602
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
39603
+ curved: false,
39604
+ closed: false,
39605
+ lineColor: str3(inputs, "color", SERIES_LINE),
39606
+ lineWidth: 2,
39607
+ lineStyle: "solid",
39608
+ arrowLeft: false,
39609
+ arrowRight: false
39610
+ }
39611
+ ]
39612
+ };
39613
+ }
39614
+ };
39615
+ var williamsFractal = {
39616
+ type: "williams-fractal",
39617
+ title: "Williams Fractal",
39618
+ shortTitle: "Fractals",
39619
+ overlay: true,
39620
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
39621
+ compute: (bars, inputs) => {
39622
+ const p = num3(inputs, "periods", 2);
39623
+ const markers = [];
39624
+ const isExtreme = (i, pick, better) => {
39625
+ const v = pick(bars[i]);
39626
+ for (let k = i - p; k <= i + p; k++) {
39627
+ if (k === i) continue;
39628
+ if (!better(v, pick(bars[k]))) return false;
39629
+ }
39630
+ return true;
39631
+ };
39632
+ for (let i = p; i < bars.length - p; i++) {
39633
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
39634
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
39635
+ }
39636
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
39637
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
39638
+ }
39639
+ }
39640
+ return { plots: [], markers };
39641
+ }
39642
+ };
39643
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
39644
+
39645
+ // src/core/native-indicators/classics/index.ts
39646
+ var classicSpecs = [
39647
+ ...averageSpecs,
39648
+ ...bandSpecs,
39649
+ ...oscillatorSpecs,
39650
+ ...trendSpecs,
39651
+ ...volatilitySpecs,
39652
+ ...volumeSpecs,
39653
+ ...overlaySpecs
39654
+ ];
39655
+ function registerClassicIndicators() {
39656
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
39657
+ }
39658
+
37327
39659
  // src/Vela.ts
37328
39660
  var Vela = class {
37329
39661
  constructor(container, options = {}, deps = {}) {
37330
39662
  registerBuiltinChartTypes();
37331
39663
  registerVolume();
37332
39664
  registerVpvr();
39665
+ registerClassicIndicators();
37333
39666
  const element = resolveElement(container);
37334
39667
  const theme = resolveTheme(options.theme);
37335
39668
  let animZoom = true;
@@ -37404,25 +39737,28 @@ var Vela = class {
37404
39737
  }
37405
39738
  /**
37406
39739
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
37407
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
37408
- * events) and is single-instance per type (a second call returns the existing handle). Returns
37409
- * a fail-soft handle for an unregistered type. Native renderer only.
39740
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
39741
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
39742
+ * call returns the existing handle; the classic studies allow several instances, so each call
39743
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
37410
39744
  */
37411
39745
  addNativeIndicator(type, options) {
37412
39746
  return this.orchestrator.addNativeIndicator(type, options);
37413
39747
  }
37414
39748
  /**
37415
39749
  * The catalog of built-in native indicators with their live state on this chart — each entry's
37416
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
37417
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
37418
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
37419
- * because support may probe the provider (a type may need data the symbol lacks).
39750
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
39751
+ * and whether it allows several instances (`multiInstance`; otherwise a second
39752
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
39753
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
39754
+ * provider (a type may need data the symbol lacks).
37420
39755
  */
37421
39756
  availableNativeIndicators() {
37422
39757
  return this.orchestrator.availableNativeIndicators();
37423
39758
  }
37424
39759
  /**
37425
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
39760
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
39761
+ * multi-instance type repeats) — the synchronous slice of
37426
39762
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
37427
39763
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
37428
39764
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -38165,7 +40501,7 @@ var Statusline = class {
38165
40501
  };
38166
40502
 
38167
40503
  // src/widget/session-shading.ts
38168
- var DAY_MS2 = 864e5;
40504
+ var DAY_MS3 = 864e5;
38169
40505
  var MINUTE_MS = 6e4;
38170
40506
  function parseWindow(text, allowOvernight = false) {
38171
40507
  if (typeof text !== "string") return null;
@@ -38244,7 +40580,7 @@ function expandSessionZones(spec, from, to) {
38244
40580
  const dtf = civilFormatter(spec.timezone);
38245
40581
  if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
38246
40582
  const seen = /* @__PURE__ */ new Set();
38247
- for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
40583
+ for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
38248
40584
  const civil = civilParts(dtf, cursor);
38249
40585
  const key = civil.year * 1e4 + civil.month * 100 + civil.day;
38250
40586
  if (!Number.isFinite(key) || seen.has(key)) continue;
@@ -38269,7 +40605,7 @@ function expandSessionZones(spec, from, to) {
38269
40605
  }
38270
40606
  return { pre, post, extended: mergeAbutting(extended) };
38271
40607
  }
38272
- var COVER_PAD_MIN_MS = 2 * DAY_MS2;
40608
+ var COVER_PAD_MIN_MS = 2 * DAY_MS3;
38273
40609
  var SessionShadingTracker = class {
38274
40610
  constructor(onZones) {
38275
40611
  this.onZones = onZones;
@@ -38293,7 +40629,7 @@ var SessionShadingTracker = class {
38293
40629
  this.covered = null;
38294
40630
  this.session = opts.session;
38295
40631
  const tfMs = timeframeMs(opts.timeframe);
38296
- this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
40632
+ this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
38297
40633
  this.lastRange = opts.range;
38298
40634
  void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
38299
40635
  if (my !== this.epoch) return;
@@ -38967,8 +41303,8 @@ var ChartCell = class {
38967
41303
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
38968
41304
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
38969
41305
  this.volumeMayBePending = true;
38970
- /** Sync mirror of the chart's present native types — the removal handler diffs
38971
- * against it to identify (and record) whichever type was just removed. */
41306
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
41307
+ * looks the removed id up here to learn which type an undo must re-add. */
38972
41308
  this.presentNatives = [];
38973
41309
  this.rangeBars = 0;
38974
41310
  this.pendingRange = null;
@@ -39140,15 +41476,18 @@ var ChartCell = class {
39140
41476
  redo: () => this.dropInstance(snapshot)
39141
41477
  });
39142
41478
  } else {
39143
- const now = this.inner?.presentNativeIndicators() ?? [];
39144
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
41479
+ const gone = this.presentNatives.find((n) => n.id === id2);
41480
+ if (gone) {
41481
+ const { type } = gone;
41482
+ let revived = null;
39145
41483
  this.history.push({
39146
41484
  undo: () => {
39147
- this.inner?.addNativeIndicator(type);
41485
+ revived = this.inner?.addNativeIndicator(type) ?? null;
39148
41486
  this.refreshNativeCatalog();
39149
41487
  },
39150
41488
  redo: () => {
39151
- this.inner?.addNativeIndicator(type).remove();
41489
+ revived?.remove();
41490
+ revived = null;
39152
41491
  this.refreshNativeCatalog();
39153
41492
  }
39154
41493
  });
@@ -39453,9 +41792,9 @@ var ChartCell = class {
39453
41792
  const live = this.inner?.renderer.get("priceStyle");
39454
41793
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
39455
41794
  }
39456
- /** Manifest instances + present natives — the topbar indicator count. */
41795
+ /** Manifest instances + native instances — the topbar indicator count. */
39457
41796
  get indicatorCount() {
39458
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
41797
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
39459
41798
  }
39460
41799
  /** Switch this cell's market in place (the chart instance survives). The projection
39461
41800
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -39587,13 +41926,15 @@ var ChartCell = class {
39587
41926
  if (!chart) return;
39588
41927
  this.volumeIntent = led.natives.includes("volume");
39589
41928
  this.history.silently(() => {
39590
- const present = chart.presentNativeIndicators();
39591
- for (const type of led.natives) {
39592
- if (!present.includes(type)) chart.addNativeIndicator(type);
39593
- }
39594
- for (const type of present) {
39595
- if (!led.natives.includes(type)) chart.addNativeIndicator(type).remove();
39596
- }
41929
+ const owed = /* @__PURE__ */ new Map();
41930
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
41931
+ for (const h of this.nativeHandles()) {
41932
+ const type = h.nativeType;
41933
+ const n = owed.get(type) ?? 0;
41934
+ if (n > 0) owed.set(type, n - 1);
41935
+ else h.remove();
41936
+ }
41937
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
39597
41938
  for (const it of [...this.instances]) this.dropInstance(it);
39598
41939
  if (this.manifest.length > 0) {
39599
41940
  for (const item of led.manifest) {
@@ -39611,34 +41952,42 @@ var ChartCell = class {
39611
41952
  this.syncPresentNatives();
39612
41953
  this.refreshNativeCatalog();
39613
41954
  }
39614
- /** The picker's library rows: supported natives first, then the manifest. */
41955
+ /** The supported natives in picker order: A→Z by title — registration order follows
41956
+ * the catalog's families, which is meaningless to the reader. This is the library
41957
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
41958
+ * read it — indexing the unsorted catalog on add would land on a different study. */
41959
+ supportedNatives() {
41960
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
41961
+ }
41962
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
41963
+ * then the manifest in the host's own order. */
39615
41964
  libraryRows() {
39616
41965
  return [
39617
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
41966
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
39618
41967
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
39619
41968
  ];
39620
41969
  }
39621
- /** The picker's on-chart rows: present natives first, then live instances. */
41970
+ /** The picker's on-chart rows: native instances first, then live script instances. */
39622
41971
  onChartRows() {
39623
41972
  return [
39624
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
41973
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
39625
41974
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
39626
41975
  ];
39627
41976
  }
39628
41977
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
39629
41978
  addFromLibrary(index) {
39630
- const natives = this.nativeCatalog.filter((n) => n.supported);
41979
+ const natives = this.supportedNatives();
39631
41980
  if (index < natives.length) this.addNative(natives[index].type);
39632
41981
  else {
39633
41982
  const entry = this.manifest[index - natives.length];
39634
41983
  if (entry) this.addManifestInstance(entry);
39635
41984
  }
39636
41985
  }
39637
- /** Remove by picker ON-CHART index (present natives precede instances). */
41986
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
39638
41987
  removeFromChart(index) {
39639
- const present = this.nativeCatalog.filter((n) => n.present);
39640
- if (index < present.length) this.removeNative(present[index].type);
39641
- else this.removeInstance(index - present.length);
41988
+ const natives = this.nativeHandles();
41989
+ if (index < natives.length) this.removeNative(natives[index]);
41990
+ else this.removeInstance(index - natives.length);
39642
41991
  }
39643
41992
  /**
39644
41993
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -39698,30 +42047,42 @@ var ChartCell = class {
39698
42047
  it.handle = null;
39699
42048
  this.deps.onIndicatorsChanged(this.id);
39700
42049
  }
39701
- /** Add a native indicator (single-instance per type — the core dedupes). */
42050
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
42051
+ * single-instance type already on the chart hands back its existing one — nothing
42052
+ * changed, so nothing enters the undo timeline. */
39702
42053
  addNative(type) {
39703
- this.inner?.addNativeIndicator(type);
42054
+ const chart = this.inner;
42055
+ if (!chart) return;
42056
+ const before = new Set(chart.indicators().map((h) => h.id));
42057
+ let added = chart.addNativeIndicator(type);
39704
42058
  this.syncPresentNatives();
39705
42059
  this.refreshNativeCatalog();
42060
+ if (before.has(added.id)) return;
39706
42061
  this.history.push({
39707
42062
  undo: () => {
39708
- this.inner?.addNativeIndicator(type).remove();
42063
+ added?.remove();
42064
+ added = null;
39709
42065
  this.refreshNativeCatalog();
39710
42066
  },
39711
42067
  redo: () => {
39712
- this.inner?.addNativeIndicator(type);
42068
+ added = this.inner?.addNativeIndicator(type) ?? null;
39713
42069
  this.refreshNativeCatalog();
39714
42070
  }
39715
42071
  });
39716
42072
  }
39717
- removeNative(type) {
39718
- this.inner?.addNativeIndicator(type).remove();
42073
+ removeNative(handle) {
42074
+ handle.remove();
39719
42075
  this.refreshNativeCatalog();
39720
42076
  }
39721
- /** Sync mirror of the chart's present native types — the removal handler diffs
39722
- * against it to identify (and record) whichever type was just removed. */
42077
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
42078
+ * the removal index space (script instances follow them). */
42079
+ nativeHandles() {
42080
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
42081
+ }
42082
+ /** Sync mirror of the chart's native instances — the removal handler looks the
42083
+ * removed id up here (the registry has already forgotten it) to record its type. */
39723
42084
  syncPresentNatives() {
39724
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
42085
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
39725
42086
  }
39726
42087
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
39727
42088
  refreshNativeCatalog() {