@luxalgo/vela 0.6.15 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +12 -1
  2. package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
  3. package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
  4. package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
  5. package/dist/chunk-FE6V2YAQ.js +1 -0
  6. package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
  7. package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
  8. package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
  9. package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
  10. package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
  11. package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
  12. package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
  13. package/dist/index.cjs +2746 -355
  14. package/dist/index.d.cts +15 -6
  15. package/dist/index.d.ts +15 -6
  16. package/dist/index.js +5 -5
  17. package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
  18. package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
  19. package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
  20. package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
  21. package/dist/plugin.cjs +1 -0
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +3 -3
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
  32. package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
  33. package/dist/ui.cjs +23 -15
  34. package/dist/ui.d.cts +1 -1
  35. package/dist/ui.d.ts +1 -1
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +2600 -209
  38. package/dist/vela.global.min.js +52 -55
  39. package/dist/widget.cjs +2663 -241
  40. package/dist/widget.d.cts +6 -6
  41. package/dist/widget.d.ts +6 -6
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +2663 -241
  44. package/dist/workspace.d.cts +25 -14
  45. package/dist/workspace.d.ts +25 -14
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. package/dist/chunk-H26BEHF4.js +0 -7
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) {
@@ -1234,6 +1234,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
1234
1234
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
1235
1235
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
1236
1236
  registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
1237
+ registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
1237
1238
  registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
1238
1239
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
1239
1240
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
@@ -1714,19 +1715,19 @@ var Surface = class _Surface {
1714
1715
  }
1715
1716
  setItems(items) {
1716
1717
  this.items = items;
1717
- for (const sub of this.subs.values()) sub.destroy();
1718
+ for (const sub2 of this.subs.values()) sub2.destroy();
1718
1719
  this.subs.clear();
1719
1720
  this.build();
1720
1721
  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);
1722
+ for (const sub2 of this.subs.values()) {
1723
+ api.setChild(sub2.handle.service);
1724
+ sub2.api.setParent(this.handle.service);
1724
1725
  }
1725
1726
  this.handle.flush();
1726
1727
  if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
1727
1728
  }
1728
1729
  destroy() {
1729
- for (const sub of this.subs.values()) sub.destroy();
1730
+ for (const sub2 of this.subs.values()) sub2.destroy();
1730
1731
  this.subs.clear();
1731
1732
  this.handle.stop();
1732
1733
  this.positioner.remove();
@@ -1740,9 +1741,9 @@ var Surface = class _Surface {
1740
1741
  for (const li of this.list.children) {
1741
1742
  const id = li.dataset.veiId;
1742
1743
  if (!id) continue;
1743
- const sub = this.subs.get(id);
1744
- if (sub) {
1745
- vanilla.spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
1744
+ const sub2 = this.subs.get(id);
1745
+ if (sub2) {
1746
+ vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
1746
1747
  continue;
1747
1748
  }
1748
1749
  const item = this.byId(id);
@@ -1819,7 +1820,7 @@ var Surface = class _Surface {
1819
1820
  const arrow = iconEl("chevron-right", doc);
1820
1821
  arrow.classList.add("vela-menu-arrow");
1821
1822
  li.appendChild(arrow);
1822
- const sub = new _Surface(doc, {
1823
+ const sub2 = new _Surface(doc, {
1823
1824
  host: this.host,
1824
1825
  placement: "right-start",
1825
1826
  onSelect: this.onSelect,
@@ -1827,8 +1828,8 @@ var Surface = class _Surface {
1827
1828
  checkmarks: this.checkmarks,
1828
1829
  id: `${this.mid}--${item.id}`
1829
1830
  });
1830
- sub.setItems(item.submenu ?? []);
1831
- this.subs.set(item.id, sub);
1831
+ sub2.setItems(item.submenu ?? []);
1832
+ this.subs.set(item.id, sub2);
1832
1833
  }
1833
1834
  this.list.appendChild(li);
1834
1835
  }
@@ -3528,8 +3529,21 @@ function buildColorPicker(color, theme, onChange) {
3528
3529
  };
3529
3530
  const recentRow = document.createElement("div");
3530
3531
  recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
3532
+ const recentSwatches = document.createElement("div");
3533
+ recentSwatches.style.cssText = "display:contents;";
3534
+ const add = document.createElement("label");
3535
+ 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;`;
3536
+ add.textContent = "+";
3537
+ const customInput = document.createElement("input");
3538
+ customInput.type = "color";
3539
+ customInput.value = curHex;
3540
+ customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
3541
+ customInput.addEventListener("input", () => previewHex(customInput.value));
3542
+ customInput.addEventListener("change", () => pickHex(customInput.value, true));
3543
+ add.appendChild(customInput);
3544
+ recentRow.append(recentSwatches, add);
3531
3545
  const renderRecents = () => {
3532
- recentRow.replaceChildren();
3546
+ recentSwatches.replaceChildren();
3533
3547
  for (const c of recents) {
3534
3548
  const sw = document.createElement("button");
3535
3549
  sw.type = "button";
@@ -3538,18 +3552,8 @@ function buildColorPicker(color, theme, onChange) {
3538
3552
  e.stopPropagation();
3539
3553
  pickHex(c);
3540
3554
  });
3541
- recentRow.appendChild(sw);
3555
+ recentSwatches.appendChild(sw);
3542
3556
  }
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
3557
  };
3554
3558
  const opLabel = document.createElement("div");
3555
3559
  opLabel.textContent = "Opacity";
@@ -3589,14 +3593,18 @@ function buildColorPicker(color, theme, onChange) {
3589
3593
  function emit() {
3590
3594
  onChange(combineColor(curHex, curAlpha));
3591
3595
  }
3592
- function pickHex(hex, custom = false) {
3596
+ function previewHex(hex) {
3593
3597
  curHex = splitColor(hex).hex6;
3594
- addRecent(curHex);
3595
3598
  paintSelection();
3596
3599
  paintOpacity();
3597
- if (custom) renderRecents();
3598
3600
  emit();
3599
3601
  }
3602
+ function pickHex(hex, custom = false) {
3603
+ previewHex(hex);
3604
+ addRecent(curHex);
3605
+ customInput.value = curHex;
3606
+ if (custom) renderRecents();
3607
+ }
3600
3608
  renderRecents();
3601
3609
  paintSelection();
3602
3610
  paintOpacity();
@@ -5167,9 +5175,9 @@ function factoryResetConfig(factory) {
5167
5175
  if (inst.enableKey) defaults2[inst.enableKey] = false;
5168
5176
  addRows(inst.rows);
5169
5177
  }
5170
- for (const sub of section.subsections ?? []) {
5171
- if (sub.enableKey) defaults2[sub.enableKey] = false;
5172
- addRows(sub.rows);
5178
+ for (const sub2 of section.subsections ?? []) {
5179
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
5180
+ addRows(sub2.rows);
5173
5181
  }
5174
5182
  if (!section.instances) addRows(section.rows);
5175
5183
  bag[t.id] = defaults2;
@@ -5238,7 +5246,8 @@ function mergeConfig(base, patch) {
5238
5246
  labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
5239
5247
  currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
5240
5248
  priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
5241
- countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
5249
+ countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
5250
+ animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
5242
5251
  },
5243
5252
  panes: {
5244
5253
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
@@ -14098,17 +14107,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
14098
14107
  }
14099
14108
  var STYLE_ID7 = "vela-widget-symbolpicker";
14100
14109
  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
- }
14110
+ /* The dialog body is flush (a flex column): search + market tabs are a fixed head
14111
+ and the result list is the ONLY scroller, in both layouts. A sticky head over a
14112
+ scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
14113
+ scroller's content box, so the head lands one padding below the top and covers
14114
+ the first row. */
14115
+ .vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
14112
14116
  .vela-sp-searchrow {
14113
14117
  display: flex;
14114
14118
  align-items: center;
@@ -14133,12 +14137,13 @@ var CSS8 = `
14133
14137
  .vela-sp-input::placeholder { text-transform: none; }
14134
14138
  .vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
14135
14139
  /* 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. */
14140
+ overflowing the body, and the result list stops capping itself \u2014 it fills the
14141
+ rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
14142
+ dialog body carries it, flush or not, so the list adds none of its own. */
14138
14143
  [data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
14139
14144
  [data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
14140
14145
  [data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
14141
- [data-layout='mobile'] .vela-sp-list { max-height: none; }
14146
+ [data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
14142
14147
  .vela-sp-tab {
14143
14148
  all: unset;
14144
14149
  padding: 3px 10px;
@@ -14150,7 +14155,12 @@ var CSS8 = `
14150
14155
  }
14151
14156
  .vela-sp-tab:hover { color: var(--vela-fg); }
14152
14157
  .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; }
14158
+ .vela-sp-list {
14159
+ min-height: 0;
14160
+ margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
14161
+ max-height: 46vh;
14162
+ overflow: auto;
14163
+ }
14154
14164
  .vela-sp-list::-webkit-scrollbar { width: 8px; }
14155
14165
  .vela-sp-list::-webkit-scrollbar-thumb {
14156
14166
  background: var(--vela-scroll);
@@ -14243,8 +14253,8 @@ var SymbolPicker = class {
14243
14253
  searchRow.append(iconEl("search", doc), this.input);
14244
14254
  this.tabs = doc.createElement("div");
14245
14255
  this.tabs.className = "vela-sp-tabs";
14246
- const sticky = doc.createElement("div");
14247
- sticky.className = "vela-sp-sticky";
14256
+ const head = doc.createElement("div");
14257
+ head.className = "vela-sp-head";
14248
14258
  for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
14249
14259
  const b = doc.createElement("button");
14250
14260
  b.className = "vela-sp-tab";
@@ -14258,7 +14268,7 @@ var SymbolPicker = class {
14258
14268
  });
14259
14269
  this.tabs.appendChild(b);
14260
14270
  }
14261
- sticky.append(searchRow, this.tabs);
14271
+ head.append(searchRow, this.tabs);
14262
14272
  this.list = doc.createElement("div");
14263
14273
  this.list.className = "vela-sp-list";
14264
14274
  this.list.addEventListener("scroll", () => {
@@ -14271,7 +14281,8 @@ var SymbolPicker = class {
14271
14281
  title: "Symbol Search",
14272
14282
  host: opts.host,
14273
14283
  closeOnInteractOutside: true,
14274
- content: (body) => body.append(sticky, this.list),
14284
+ flush: true,
14285
+ content: (body) => body.append(head, this.list),
14275
14286
  onOpenChange: (open2) => {
14276
14287
  if (open2) {
14277
14288
  this.input.value = this.seed.toUpperCase();
@@ -16042,11 +16053,11 @@ var PriceScaleDrawer = class {
16042
16053
  for (const item of items) {
16043
16054
  if (item.id === "labels" && item.submenu) {
16044
16055
  section("Labels");
16045
- for (const sub of item.submenu) {
16046
- row(sub.label, {
16047
- checked: sub.checked,
16056
+ for (const sub2 of item.submenu) {
16057
+ row(sub2.label, {
16058
+ checked: sub2.checked,
16048
16059
  onClick: () => {
16049
- const feature = sub.id.slice("toggle:".length);
16060
+ const feature = sub2.id.slice("toggle:".length);
16050
16061
  chart.renderer.set(feature, !this.flag(feature));
16051
16062
  this.render();
16052
16063
  }
@@ -16056,11 +16067,11 @@ var PriceScaleDrawer = class {
16056
16067
  }
16057
16068
  if (item.id === "levels" && item.submenu) {
16058
16069
  section("Levels");
16059
- for (const sub of item.submenu) {
16060
- row(sub.label, {
16061
- checked: sub.checked,
16070
+ for (const sub2 of item.submenu) {
16071
+ row(sub2.label, {
16072
+ checked: sub2.checked,
16062
16073
  onClick: () => {
16063
- const feature = sub.id.slice("toggle:".length);
16074
+ const feature = sub2.id.slice("toggle:".length);
16064
16075
  chart.renderer.set(feature, !this.flag(feature));
16065
16076
  this.render();
16066
16077
  }
@@ -16634,8 +16645,8 @@ var DrawingController = class {
16634
16645
  this.port?.setExternalGhost?.(doc);
16635
16646
  }
16636
16647
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
16637
- setToolShortcuts(map) {
16638
- this.port?.setToolShortcuts?.(map);
16648
+ setToolShortcuts(map2) {
16649
+ this.port?.setToolShortcuts?.(map2);
16639
16650
  }
16640
16651
  // ── programmatic CRUD (facade-facing) ── each is one undo step
16641
16652
  add(type, init = {}) {
@@ -17199,6 +17210,23 @@ function attachChromeTooltip(anchor, opts) {
17199
17210
  clear();
17200
17211
  };
17201
17212
  }
17213
+ function chromeHint(text, opts) {
17214
+ const px = opts.size ?? 16;
17215
+ const el = document.createElement("span");
17216
+ el.className = "vela-hint";
17217
+ el.setAttribute("role", "img");
17218
+ el.setAttribute("aria-label", text);
17219
+ el.innerHTML = iconAt("info", px);
17220
+ el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
17221
+ const hover = (on) => {
17222
+ el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
17223
+ el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
17224
+ };
17225
+ el.addEventListener("pointerenter", () => hover(true));
17226
+ el.addEventListener("pointerleave", () => hover(false));
17227
+ const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
17228
+ return { el, dispose };
17229
+ }
17202
17230
 
17203
17231
  // src/renderers/native/drawings/DrawingToolbar.ts
17204
17232
  var TOOLBAR_WIDTH = 44;
@@ -17306,8 +17334,8 @@ var DrawingToolbar = class {
17306
17334
  }
17307
17335
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
17308
17336
  * right edge of the flyout rows, beside the favorite star. */
17309
- setShortcuts(map) {
17310
- this.shortcuts = new Map(Object.entries(map));
17337
+ setShortcuts(map2) {
17338
+ this.shortcuts = new Map(Object.entries(map2));
17311
17339
  }
17312
17340
  /** Paint one star for the current favorite state (filled + gold, or outline). */
17313
17341
  paintStar(el, type) {
@@ -18190,6 +18218,24 @@ function localStorageAdapter(storageKey) {
18190
18218
  };
18191
18219
  }
18192
18220
 
18221
+ // src/core/options.ts
18222
+ var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
18223
+ var LIVE_BAR_EASE_DEFAULT_MS = 90;
18224
+ var LIVE_BAR_EASE_MAX_MS = 1e3;
18225
+ function resolveLiveBarEaseMs(value) {
18226
+ if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
18227
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
18228
+ return Math.min(value, LIVE_BAR_EASE_MAX_MS);
18229
+ }
18230
+ function resolveAnimations(animations) {
18231
+ if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
18232
+ return {
18233
+ animZoom: animations?.zoom ?? true,
18234
+ animPan: animations?.pan ?? true,
18235
+ animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
18236
+ };
18237
+ }
18238
+
18193
18239
  // src/core/model/series.ts
18194
18240
  function isLineLikeSeries(spec) {
18195
18241
  return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
@@ -18254,7 +18300,7 @@ var LiveSession = class {
18254
18300
 
18255
18301
  // src/core/engine/IndicatorHandleImpl.ts
18256
18302
  var IndicatorHandleImpl = class {
18257
- constructor(id, title, controller, source) {
18303
+ constructor(id, title, controller, source, nativeType) {
18258
18304
  this.controller = controller;
18259
18305
  this.schema = [];
18260
18306
  this.propsSchema = [];
@@ -18263,6 +18309,7 @@ var IndicatorHandleImpl = class {
18263
18309
  this.id = id;
18264
18310
  this.title = title;
18265
18311
  if (source !== void 0) this.source = source;
18312
+ if (nativeType !== void 0) this.nativeType = nativeType;
18266
18313
  }
18267
18314
  get inputs() {
18268
18315
  return this.schema;
@@ -18353,24 +18400,24 @@ function summarizeModel(model) {
18353
18400
  }
18354
18401
  function inspectModels(models) {
18355
18402
  const indicators = models.map(summarizeModel);
18356
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18403
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18357
18404
  return {
18358
18405
  indicators,
18359
18406
  totals: {
18360
18407
  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)
18408
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18409
+ fills: sum2((s) => s.fills),
18410
+ backgrounds: sum2((s) => s.backgrounds),
18411
+ priceLines: sum2((s) => s.priceLines),
18412
+ lines: sum2((s) => s.lines),
18413
+ boxes: sum2((s) => s.boxes),
18414
+ labels: sum2((s) => s.labels),
18415
+ polylines: sum2((s) => s.polylines),
18416
+ linefills: sum2((s) => s.linefills),
18417
+ tables: sum2((s) => s.tables),
18418
+ barColors: sum2((s) => s.barColors),
18419
+ trades: sum2((s) => s.trades),
18420
+ forcedOverlay: sum2((s) => s.forcedOverlay)
18374
18421
  }
18375
18422
  };
18376
18423
  }
@@ -19439,16 +19486,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
19439
19486
  /**
19440
19487
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
19441
19488
  * 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'`.
19489
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
19490
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
19491
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
19492
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
19444
19493
  */
19445
19494
  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
19495
  const descriptor = getNativeIndicator(type);
19496
+ if (!descriptor?.multiInstance) {
19497
+ const existing = this.registry.all().find((r) => r.native?.type === type);
19498
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19499
+ }
19449
19500
  const id = this.registry.nextId("native");
19450
19501
  const title = descriptor?.title ?? type;
19451
- const handle = new IndicatorHandleImpl(id, title, this);
19502
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19452
19503
  this.handles.set(id, handle);
19453
19504
  if (!descriptor) {
19454
19505
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -19460,16 +19511,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
19460
19511
  void this.startNativeIndicator(id, handle);
19461
19512
  return handle;
19462
19513
  }
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). */
19514
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
19515
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
19470
19516
  presentNativeIndicators() {
19471
19517
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
19472
19518
  }
19519
+ /**
19520
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
19521
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
19522
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
19523
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
19524
+ * probe the provider for a required capability.
19525
+ */
19473
19526
  async availableNativeIndicators() {
19474
19527
  const symbol = this.qualifiedSymbol();
19475
19528
  const present = new Set(this.presentNativeIndicators());
@@ -19479,7 +19532,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
19479
19532
  title: d.title,
19480
19533
  supported: await this.isNativeSupported(d, symbol),
19481
19534
  present: present.has(d.type),
19482
- beta: d.beta
19535
+ beta: d.beta,
19536
+ ...d.multiInstance ? { multiInstance: true } : {}
19483
19537
  }))
19484
19538
  );
19485
19539
  }
@@ -20619,8 +20673,8 @@ var DrawingsControl = class {
20619
20673
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
20620
20674
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
20621
20675
  * platform formatting, so hints always match the host's actual bindings. */
20622
- setToolShortcuts(map) {
20623
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
20676
+ setToolShortcuts(map2) {
20677
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
20624
20678
  return this;
20625
20679
  }
20626
20680
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -21147,14 +21201,11 @@ var IndicatorInputsDialog = class {
21147
21201
  buildTextField(id, current, emit) {
21148
21202
  return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
21149
21203
  }
21150
- /** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
21204
+ /** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
21151
21205
  infoButton(tooltip2) {
21152
- const b = document.createElement("span");
21153
- b.textContent = "i";
21154
- this.dialogTips.push(attachChromeTooltip(b, { host: this.host.container, theme: () => this.host.theme(), text: () => tooltip2, wrap: true, delayMs: 250 }));
21155
- b.className = "vela-ind-hint";
21156
- b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
21157
- return b;
21206
+ const { el, dispose } = chromeHint(tooltip2, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
21207
+ this.dialogTips.push(dispose);
21208
+ return el;
21158
21209
  }
21159
21210
  /** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
21160
21211
  select(id, options, current, emit) {
@@ -21591,8 +21642,6 @@ function ensureDialogStyles() {
21591
21642
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21592
21643
  .vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
21593
21644
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
21594
- .vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
21595
- .vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
21596
21645
  .vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
21597
21646
  .vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
21598
21647
  .vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
@@ -22101,8 +22150,8 @@ var InputsUI = class {
22101
22150
  lg.style.top = `${top}px`;
22102
22151
  }
22103
22152
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22104
- setCollapsedPanes(map) {
22105
- this.paneCollapse = map;
22153
+ setCollapsedPanes(map2) {
22154
+ this.paneCollapse = map2;
22106
22155
  }
22107
22156
  /**
22108
22157
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -24111,11 +24160,11 @@ function mergeBarColors(indicators) {
24111
24160
  }
24112
24161
  }
24113
24162
  if (!any) return EMPTY_BARCOLORS;
24114
- const map = /* @__PURE__ */ new Map();
24163
+ const map2 = /* @__PURE__ */ new Map();
24115
24164
  for (const m of indicators.values()) {
24116
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24165
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24117
24166
  }
24118
- return map;
24167
+ return map2;
24119
24168
  }
24120
24169
 
24121
24170
  // src/renderers/native/core/ViewportState.ts
@@ -24412,9 +24461,9 @@ function isHorizontalWheel(deltaX, deltaY) {
24412
24461
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24413
24462
  return rightOffset + deltaX / barSpacing;
24414
24463
  }
24415
- function wheelPanDelta(deltaX, deltaY, shift) {
24464
+ function wheelPanDelta(deltaX, deltaY, shift2) {
24416
24465
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24417
- if (shift) return deltaY;
24466
+ if (shift2) return deltaY;
24418
24467
  return null;
24419
24468
  }
24420
24469
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -26036,11 +26085,11 @@ function mergeBarColors2(indicators) {
26036
26085
  }
26037
26086
  }
26038
26087
  if (!any) return EMPTY_BARCOLORS2;
26039
- const map = /* @__PURE__ */ new Map();
26088
+ const map2 = /* @__PURE__ */ new Map();
26040
26089
  for (const m of indicators.values()) {
26041
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26090
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
26042
26091
  }
26043
- return map;
26092
+ return map2;
26044
26093
  }
26045
26094
 
26046
26095
  // src/renderers/shared/drawing-geometry.ts
@@ -27638,6 +27687,8 @@ var BUILTIN_SETTINGS_IDS = [
27638
27687
  "symbol.style.baseline.fill-bottom",
27639
27688
  "symbol.style.baseline.base-level",
27640
27689
  "symbol.style.baseline.width",
27690
+ "symbol.animation",
27691
+ "symbol.animation.price-changes",
27641
27692
  "symbol.timezone",
27642
27693
  "scales",
27643
27694
  "scales.price-scale",
@@ -27679,9 +27730,9 @@ function settingsIdCatalog(hostSections) {
27679
27730
  };
27680
27731
  if (section.rows) addRows(section.rows);
27681
27732
  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);
27733
+ for (const sub2 of section.subsections ?? []) {
27734
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27735
+ addRows(sub2.rows);
27685
27736
  }
27686
27737
  }
27687
27738
  for (const hs of hostSections) {
@@ -27780,8 +27831,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
27780
27831
  .vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
27781
27832
  .vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
27782
27833
  .vela-sd-mobile .vela-sd-btn{height:38px;}
27783
- .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
27784
- `;
27834
+ .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
27785
27835
  if (!existing) document.head.appendChild(st);
27786
27836
  }
27787
27837
  var SettingsDialog = class {
@@ -27802,6 +27852,8 @@ var SettingsDialog = class {
27802
27852
  this.themeControl = null;
27803
27853
  /** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
27804
27854
  this.tabs = [];
27855
+ this.hintTips = [];
27856
+ // ⓘ tooltip disposers of the open build (see hint)
27805
27857
  /** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
27806
27858
  this.typeActiveInstance = /* @__PURE__ */ new Map();
27807
27859
  /** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
@@ -27990,6 +28042,13 @@ var SettingsDialog = class {
27990
28042
  body.append(g);
27991
28043
  }
27992
28044
  showActive(config.series.style);
28045
+ body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
28046
+ body.append(sid(this.boolRow(
28047
+ "Animate price changes",
28048
+ config.priceScale.animateLastPrice,
28049
+ (v) => this.emit({ priceScale: { animateLastPrice: v } }),
28050
+ this.hint("Glide the live bar to each new price instead of snapping.")
28051
+ ), "symbol.animation.price-changes"));
27993
28052
  body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
27994
28053
  body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
27995
28054
  const renderHostSections = (placement) => {
@@ -28197,6 +28256,8 @@ var SettingsDialog = class {
28197
28256
  this.ui = null;
28198
28257
  this.root = null;
28199
28258
  this.tabs = [];
28259
+ for (const dispose of this.hintTips) dispose();
28260
+ this.hintTips = [];
28200
28261
  ui?.destroy();
28201
28262
  }
28202
28263
  destroy() {
@@ -28240,14 +28301,14 @@ var SettingsDialog = class {
28240
28301
  };
28241
28302
  if (section.rows) seed(section.rows);
28242
28303
  for (const inst of section.instances ?? []) seed(inst.rows);
28243
- for (const sub of section.subsections ?? []) seed(sub.rows);
28304
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28244
28305
  for (const inst of section.instances ?? []) {
28245
28306
  if (!inst.enableKey || inst.enableKey in bag) continue;
28246
28307
  bag[inst.enableKey] = values[inst.enableKey] === true;
28247
28308
  }
28248
- for (const sub of section.subsections ?? []) {
28249
- if (!sub.enableKey || sub.enableKey in bag) continue;
28250
- bag[sub.enableKey] = values[sub.enableKey] === true;
28309
+ for (const sub2 of section.subsections ?? []) {
28310
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28311
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28251
28312
  }
28252
28313
  const refreshers = [];
28253
28314
  const put = (key, v) => {
@@ -28264,16 +28325,16 @@ var SettingsDialog = class {
28264
28325
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28265
28326
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28266
28327
  }
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);
28328
+ for (const sub2 of section.subsections ?? []) {
28329
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28330
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28270
28331
  if (rows.length === 0) continue;
28271
- const subMarker = this.section(sub.title);
28332
+ const subMarker = this.section(sub2.title);
28272
28333
  subMarker.dataset.sdStyle = typeId;
28273
28334
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28274
28335
  subMarker.dataset.sdSub = "1";
28275
28336
  body.append(subMarker);
28276
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28337
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28277
28338
  }
28278
28339
  for (const r of refreshers) r();
28279
28340
  }
@@ -28610,23 +28671,31 @@ var SettingsDialog = class {
28610
28671
  }).el
28611
28672
  });
28612
28673
  }
28613
- /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area). */
28614
- boolRow(label, value, onChange) {
28615
- return this.toggleRow(label, value, onChange, []);
28674
+ /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
28675
+ * `info` (see {@link hint}) rides after the label. */
28676
+ boolRow(label, value, onChange, info) {
28677
+ return this.toggleRow(label, value, onChange, [], void 0, info);
28616
28678
  }
28617
28679
  /** An enable row: checkbox + label in the label column, dependent controls in the
28618
28680
  * shared control column; the control group dims and ignores input while the toggle
28619
28681
  * is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
28620
- toggleRow(label, value, onToggle, controls, get) {
28682
+ toggleRow(label, value, onToggle, controls, get, info) {
28621
28683
  return fieldRow({
28622
28684
  label,
28623
28685
  labelSize: "sm",
28624
28686
  bool: controls.length === 0,
28625
28687
  toggle: { checked: value, onChange: onToggle, get },
28626
28688
  control: controls,
28689
+ info,
28627
28690
  className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
28628
28691
  });
28629
28692
  }
28693
+ /** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
28694
+ hint(text) {
28695
+ const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
28696
+ this.hintTips.push(dispose);
28697
+ return el;
28698
+ }
28630
28699
  numberRow(label, value, min, max, step, onChange) {
28631
28700
  return fieldRow({
28632
28701
  label,
@@ -30368,11 +30437,11 @@ var DrawingInteraction = class {
30368
30437
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30369
30438
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30370
30439
  * while placing a line tool it locks the segment angle to 45° steps. */
30371
- down(x, y, mode = "off", shift = false) {
30440
+ down(x, y, mode = "off", shift2 = false) {
30372
30441
  const proj = this.deps.projector();
30373
30442
  if (this.state.kind === "placing") {
30374
30443
  if (this.state.draft.placementMode() !== "click") return;
30375
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30444
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30376
30445
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30377
30446
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30378
30447
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30391,7 +30460,7 @@ var DrawingInteraction = class {
30391
30460
  }
30392
30461
  const hit = this.hitAt(x, y);
30393
30462
  if (hit) {
30394
- if (shift) {
30463
+ if (shift2) {
30395
30464
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30396
30465
  return;
30397
30466
  }
@@ -30401,7 +30470,7 @@ var DrawingInteraction = class {
30401
30470
  }
30402
30471
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30403
30472
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30404
- move(x, y, mode = "off", shift = false) {
30473
+ move(x, y, mode = "off", shift2 = false) {
30405
30474
  if (this.state.kind === "idle") {
30406
30475
  const tool = this.deps.activeTool();
30407
30476
  const had = this.snapAt != null;
@@ -30415,7 +30484,7 @@ var DrawingInteraction = class {
30415
30484
  this.captureFreehand(x, y);
30416
30485
  return;
30417
30486
  }
30418
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30487
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30419
30488
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30420
30489
  this.deps.changed();
30421
30490
  return;
@@ -30428,7 +30497,7 @@ var DrawingInteraction = class {
30428
30497
  if (this.state.handle >= 0) {
30429
30498
  const a = d.anchors[this.state.handle];
30430
30499
  if (a) {
30431
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30500
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30432
30501
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30433
30502
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30434
30503
  }
@@ -31126,21 +31195,21 @@ var DrawingSettingsPopup = class {
31126
31195
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31127
31196
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31128
31197
  }
31129
- const vwap = drawing.vwap;
31130
- if (paths.has("vwap.midColor") && vwap) {
31198
+ const vwap2 = drawing.vwap;
31199
+ if (paths.has("vwap.midColor") && vwap2) {
31131
31200
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31132
31201
  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 }));
31202
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31203
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31135
31204
  bar.appendChild(
31136
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31205
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31137
31206
  label: (v) => `${v}\u03C3`,
31138
31207
  labelInTrigger: true
31139
31208
  })
31140
31209
  );
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 })));
31210
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31211
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31212
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31144
31213
  }
31145
31214
  if (paths.has("maxCurvature")) {
31146
31215
  const cur = drawing.maxCurvature ?? 24;
@@ -32157,8 +32226,8 @@ var UserDrawingController = class {
32157
32226
  this.toolbar.setFavorites(types);
32158
32227
  }
32159
32228
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
32160
- setToolShortcuts(map) {
32161
- this.toolbar.setShortcuts(map);
32229
+ setToolShortcuts(map2) {
32230
+ this.toolbar.setShortcuts(map2);
32162
32231
  }
32163
32232
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
32164
32233
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -32276,7 +32345,7 @@ var UserDrawingController = class {
32276
32345
  this.render();
32277
32346
  }
32278
32347
  }
32279
- pointerDown(x, y, snap = "off", shift = false) {
32348
+ pointerDown(x, y, snap = "off", shift2 = false) {
32280
32349
  if (this.eraserMode) {
32281
32350
  this.erasing = true;
32282
32351
  this.deleteAt(x, y);
@@ -32296,9 +32365,9 @@ var UserDrawingController = class {
32296
32365
  return;
32297
32366
  }
32298
32367
  }
32299
- this.interaction.down(x, y, snap, shift);
32368
+ this.interaction.down(x, y, snap, shift2);
32300
32369
  }
32301
- pointerMove(x, y, snap = "off", shift = false) {
32370
+ pointerMove(x, y, snap = "off", shift2 = false) {
32302
32371
  if (this.eraserMode) {
32303
32372
  if (this.erasing) this.deleteAt(x, y);
32304
32373
  return;
@@ -32309,7 +32378,7 @@ var UserDrawingController = class {
32309
32378
  this.render();
32310
32379
  return;
32311
32380
  }
32312
- this.interaction.move(x, y, snap, shift);
32381
+ this.interaction.move(x, y, snap, shift2);
32313
32382
  this.updateHover(x, y);
32314
32383
  }
32315
32384
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -32986,14 +33055,14 @@ function layoutTable(ctx, t, args) {
32986
33055
  }
32987
33056
  for (const { box, w: w2, h: h2 } of spanning) {
32988
33057
  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;
33058
+ let sum2 = 0;
33059
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
33060
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
32992
33061
  }
32993
33062
  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;
33063
+ let sum2 = 0;
33064
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
33065
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
32997
33066
  }
32998
33067
  }
32999
33068
  let w = 0;
@@ -33580,24 +33649,24 @@ function rasterizeOverlay(ctx, root, frame) {
33580
33649
  drawSvgShapes(el, alpha);
33581
33650
  continue;
33582
33651
  }
33583
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33652
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33584
33653
  let path = null;
33585
33654
  if (tag === "path") {
33586
33655
  const d = el.getAttribute("d");
33587
33656
  if (d) path = new Path2D(d);
33588
33657
  } else if (tag === "circle") {
33589
33658
  path = new Path2D();
33590
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
33659
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
33591
33660
  } else if (tag === "ellipse") {
33592
33661
  path = new Path2D();
33593
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
33662
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
33594
33663
  } else if (tag === "rect") {
33595
33664
  path = new Path2D();
33596
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
33665
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
33597
33666
  } else if (tag === "line") {
33598
33667
  path = new Path2D();
33599
- path.moveTo(num3("x1"), num3("y1"));
33600
- path.lineTo(num3("x2"), num3("y2"));
33668
+ path.moveTo(num4("x1"), num4("y1"));
33669
+ path.lineTo(num4("x2"), num4("y2"));
33601
33670
  } else if (tag === "polyline" || tag === "polygon") {
33602
33671
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
33603
33672
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -33858,7 +33927,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
33858
33927
  var ZOOM_TAU_MS = 70;
33859
33928
  var SCALE_TAU_MS = 80;
33860
33929
  var FLING_TAU_MS = 110;
33861
- var LIVE_BAR_TAU_MS = 90;
33862
33930
  var SCROLL_TO_TAU_MS = 130;
33863
33931
  var FLING_STOP_PX = 0.02;
33864
33932
  var PRICE_SCALE_K = 4e-3;
@@ -33929,9 +33997,13 @@ var NativeRenderer = class {
33929
33997
  /** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
33930
33998
  this.historyChordsEnabled = true;
33931
33999
  this.liveRegion = null;
33932
- // ── animation state (eased zoom + inertial pan) ──
34000
+ // ── animation state (eased zoom + inertial pan + live-bar glide) ──
33933
34001
  this.animZoom = true;
33934
34002
  this.animPan = true;
34003
+ this.animLiveBarMs = 0;
34004
+ // forming-bar OHLC glide time-constant; 0 = each tick snaps
34005
+ this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
34006
+ // the duration the settings-dialog on/off toggle restores (last non-zero value configured)
33935
34007
  // Brand default candles.
33936
34008
  this.candleUp = BULLISH;
33937
34009
  this.candleDown = BEARISH;
@@ -34056,7 +34128,7 @@ var NativeRenderer = class {
34056
34128
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
34057
34129
  this.priceStyleCbs = /* @__PURE__ */ new Set();
34058
34130
  this.name = "native";
34059
- this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
34131
+ this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
34060
34132
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
34061
34133
  * so moving onto the button doesn't count as leaving). */
34062
34134
  this.onScrollProximityMove = (e) => {
@@ -34090,6 +34162,7 @@ var NativeRenderer = class {
34090
34162
  this.backendMode = opts.nativeBackend;
34091
34163
  this.animZoom = opts.animZoom;
34092
34164
  this.animPan = opts.animPan;
34165
+ this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
34093
34166
  this.glowAmount = opts.glow;
34094
34167
  this.candleUp = opts.upColor;
34095
34168
  this.candleDown = opts.downColor;
@@ -34139,6 +34212,10 @@ var NativeRenderer = class {
34139
34212
  case "animPan":
34140
34213
  this.animPan = Boolean(value);
34141
34214
  return;
34215
+ case "animLiveBar":
34216
+ this.setLiveBarEase(resolveLiveBarEaseMs(value));
34217
+ return;
34218
+ // affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
34142
34219
  case "intro": {
34143
34220
  const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
34144
34221
  this.introStyle = s;
@@ -34275,6 +34352,8 @@ var NativeRenderer = class {
34275
34352
  return this.animZoom;
34276
34353
  case "animPan":
34277
34354
  return this.animPan;
34355
+ case "animLiveBar":
34356
+ return this.animLiveBarMs;
34278
34357
  case "intro":
34279
34358
  return this.introStyle;
34280
34359
  case "zoomAnchor":
@@ -34443,7 +34522,8 @@ var NativeRenderer = class {
34443
34522
  labelsVisible: this.scene.showAxisLabels,
34444
34523
  currentPriceLine: this.scene.showPriceLine,
34445
34524
  priceLabel: this.scene.showPriceLabel,
34446
- countdown: this.scene.showCountdown
34525
+ countdown: this.scene.showCountdown,
34526
+ animateLastPrice: this.animLiveBarMs > 0
34447
34527
  },
34448
34528
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
34449
34529
  trades: {
@@ -34550,6 +34630,7 @@ var NativeRenderer = class {
34550
34630
  this.scene.showPriceLabel = next.priceScale.priceLabel;
34551
34631
  this.scene.showCountdown = next.priceScale.countdown;
34552
34632
  this.syncCountdownTimer();
34633
+ this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
34553
34634
  s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
34554
34635
  this.scene.tradeMarkers = {
34555
34636
  visible: next.trades.visible,
@@ -35014,8 +35095,8 @@ var NativeRenderer = class {
35014
35095
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
35015
35096
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
35016
35097
  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),
35098
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
35099
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
35019
35100
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
35020
35101
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
35021
35102
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -35332,18 +35413,18 @@ var NativeRenderer = class {
35332
35413
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
35333
35414
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
35334
35415
  const newHeadTime = this.bars[0].time;
35335
- let shift = 0;
35416
+ let shift2 = 0;
35336
35417
  if (newHeadTime !== prevHeadTime) {
35337
35418
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
35338
- if (prepended > 0) shift = prepended;
35419
+ if (prepended > 0) shift2 = prepended;
35339
35420
  else {
35340
35421
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
35341
- if (trimmed > 0) shift = -trimmed;
35422
+ if (trimmed > 0) shift2 = -trimmed;
35342
35423
  }
35343
35424
  }
35344
- if (shift !== 0) {
35345
- this.zoomAnchorLogical += shift;
35346
- if (this.hoverLogical != null) this.hoverLogical += shift;
35425
+ if (shift2 !== 0) {
35426
+ this.zoomAnchorLogical += shift2;
35427
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
35347
35428
  }
35348
35429
  }
35349
35430
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -35366,8 +35447,11 @@ var NativeRenderer = class {
35366
35447
  const last = this.bars[n - 1];
35367
35448
  if (last && bar.time === last.time) {
35368
35449
  this.bars[n - 1] = bar;
35369
- if (this.liveEaseTime !== bar.time) this.syncLiveEase(bar);
35370
- this.animator.start();
35450
+ if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
35451
+ this.syncLiveEase(bar);
35452
+ } else {
35453
+ this.animator.start();
35454
+ }
35371
35455
  } else if (!last || bar.time > last.time) {
35372
35456
  this.bars.push(bar);
35373
35457
  this.syncLiveEase(bar);
@@ -35379,6 +35463,12 @@ var NativeRenderer = class {
35379
35463
  this.scene.bars = this.bars;
35380
35464
  this.scheduler.invalidate(4 /* Full */);
35381
35465
  }
35466
+ /** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
35467
+ * what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
35468
+ setLiveBarEase(ms) {
35469
+ this.animLiveBarMs = ms;
35470
+ if (ms > 0) this.animLiveBarOnMs = ms;
35471
+ }
35382
35472
  /** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
35383
35473
  syncLiveEase(bar) {
35384
35474
  this.liveEaseTime = bar.time;
@@ -35391,9 +35481,10 @@ var NativeRenderer = class {
35391
35481
  const target = this.bars[this.bars.length - 1];
35392
35482
  if (!target || this.liveEaseTime !== target.time) return false;
35393
35483
  const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
35394
- const nh = easeToward(this.liveEaseHigh, target.high, dtMs, LIVE_BAR_TAU_MS);
35395
- const nl = easeToward(this.liveEaseLow, target.low, dtMs, LIVE_BAR_TAU_MS);
35396
- const nc = easeToward(this.liveEaseClose, target.close, dtMs, LIVE_BAR_TAU_MS);
35484
+ const tau = this.animLiveBarMs;
35485
+ const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
35486
+ const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
35487
+ const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
35397
35488
  const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
35398
35489
  this.liveEaseHigh = active ? nh : target.high;
35399
35490
  this.liveEaseLow = active ? nl : target.low;
@@ -36949,15 +37040,15 @@ var NativeRenderer = class {
36949
37040
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
36950
37041
  * strip — merged own-scale indicators are hidden while collapsed. */
36951
37042
  collapsedMasterMap() {
36952
- const map = /* @__PURE__ */ new Map();
37043
+ const map2 = /* @__PURE__ */ new Map();
36953
37044
  for (const pane of this.scene.panes.values()) {
36954
37045
  if (!pane.collapsed) continue;
36955
37046
  const models = this.scene.orderedIndicatorsForPane(pane.id);
36956
37047
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
36957
37048
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
36958
- map.set(pane.id, master?.id ?? null);
37049
+ map2.set(pane.id, master?.id ?? null);
36959
37050
  }
36960
- return map;
37051
+ return map2;
36961
37052
  }
36962
37053
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
36963
37054
  setToolbarGutter(px) {
@@ -37324,29 +37415,2335 @@ function registerVpvr() {
37324
37415
  registerNativeIndicator(vpvrDescriptor);
37325
37416
  }
37326
37417
 
37418
+ // src/core/model/identity.ts
37419
+ function stableSeriesId(parts) {
37420
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
37421
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
37422
+ }
37423
+
37424
+ // src/core/native-indicators/classics/define.ts
37425
+ function num3(inputs, key, fallback) {
37426
+ const v = inputs[key];
37427
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
37428
+ }
37429
+ function str3(inputs, key, fallback) {
37430
+ const v = inputs[key];
37431
+ return typeof v === "string" && v !== "" ? v : fallback;
37432
+ }
37433
+ function toPoints(bars, values, colors) {
37434
+ const pts = new Array(bars.length);
37435
+ for (let i = 0; i < bars.length; i++) {
37436
+ const v = values[i];
37437
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
37438
+ const c = colors?.[i];
37439
+ if (c != null) p.color = c;
37440
+ pts[i] = p;
37441
+ }
37442
+ return pts;
37443
+ }
37444
+ var ClassicIndicator = class {
37445
+ constructor(spec) {
37446
+ this.spec = spec;
37447
+ this.inputs = {};
37448
+ }
37449
+ start(ctx, inputs) {
37450
+ this.ctx = ctx;
37451
+ this.inputs = inputs;
37452
+ this.recompute();
37453
+ }
37454
+ onBars() {
37455
+ this.recompute();
37456
+ }
37457
+ onViewport() {
37458
+ }
37459
+ setInputs(inputs) {
37460
+ this.inputs = inputs;
37461
+ this.recompute();
37462
+ }
37463
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
37464
+ suspend() {
37465
+ }
37466
+ resume() {
37467
+ this.recompute();
37468
+ }
37469
+ stop() {
37470
+ }
37471
+ recompute() {
37472
+ const bars = this.ctx.bars();
37473
+ const out = this.spec.compute(bars, this.inputs);
37474
+ const instanceId = this.spec.type;
37475
+ const series = [];
37476
+ const idsByKey = /* @__PURE__ */ new Map();
37477
+ out.plots.forEach((plot, ordinal) => {
37478
+ const kind = plot.kind ?? "line";
37479
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
37480
+ idsByKey.set(plot.key, id);
37481
+ series.push({
37482
+ id,
37483
+ title: plot.title,
37484
+ paneId: "",
37485
+ // stamped by the orchestrator
37486
+ kind,
37487
+ points: toPoints(bars, plot.values, plot.colors),
37488
+ style: {
37489
+ color: plot.color,
37490
+ width: plot.width ?? 1,
37491
+ lineStyle: plot.lineStyle ?? "solid",
37492
+ ...plot.base != null ? { base: plot.base } : {}
37493
+ },
37494
+ ...plot.overlay ? { overlay: true } : {}
37495
+ });
37496
+ });
37497
+ if (out.markers && out.markers.length > 0) {
37498
+ series.push({
37499
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
37500
+ title: this.spec.shortTitle ?? this.spec.title,
37501
+ paneId: "",
37502
+ kind: "markers",
37503
+ markers: out.markers
37504
+ });
37505
+ }
37506
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
37507
+ const from = idsByKey.get(band.from);
37508
+ const to = idsByKey.get(band.to);
37509
+ if (!from || !to) return [];
37510
+ return [
37511
+ {
37512
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
37513
+ paneId: "",
37514
+ fromSeriesId: from,
37515
+ toSeriesId: to,
37516
+ color: band.color
37517
+ }
37518
+ ];
37519
+ });
37520
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
37521
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
37522
+ paneId: "",
37523
+ price: level.price,
37524
+ ...level.color != null ? { color: level.color } : {},
37525
+ lineStyle: level.lineStyle ?? "dashed",
37526
+ width: 1,
37527
+ ...level.title != null ? { title: level.title } : {}
37528
+ }));
37529
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
37530
+ const { key, ...rest } = poly;
37531
+ return {
37532
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
37533
+ paneId: "",
37534
+ ...rest
37535
+ };
37536
+ });
37537
+ this.ctx.emit({ series, fills, priceLines, polylines });
37538
+ this.ctx.setStatus("idle");
37539
+ }
37540
+ };
37541
+ function classicDescriptor(spec) {
37542
+ return {
37543
+ type: spec.type,
37544
+ title: spec.title,
37545
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
37546
+ paneHint: spec.overlay ? "price" : "new",
37547
+ overlay: spec.overlay,
37548
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
37549
+ multiInstance: true,
37550
+ inputsSchema: () => spec.inputs,
37551
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
37552
+ create: () => new ClassicIndicator(spec)
37553
+ };
37554
+ }
37555
+
37556
+ // src/core/native-indicators/classics/math.ts
37557
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
37558
+ function sourceValues(bars, source) {
37559
+ const out = new Array(bars.length);
37560
+ for (let i = 0; i < bars.length; i++) {
37561
+ const b = bars[i];
37562
+ switch (source) {
37563
+ case "Open":
37564
+ out[i] = b.open;
37565
+ break;
37566
+ case "High":
37567
+ out[i] = b.high;
37568
+ break;
37569
+ case "Low":
37570
+ out[i] = b.low;
37571
+ break;
37572
+ case "HL2":
37573
+ out[i] = (b.high + b.low) / 2;
37574
+ break;
37575
+ case "HLC3":
37576
+ out[i] = (b.high + b.low + b.close) / 3;
37577
+ break;
37578
+ case "OHLC4":
37579
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
37580
+ break;
37581
+ case "HLCC4":
37582
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
37583
+ break;
37584
+ default:
37585
+ out[i] = b.close;
37586
+ }
37587
+ }
37588
+ return out;
37589
+ }
37590
+ function volumes(bars) {
37591
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
37592
+ }
37593
+ function highs(bars) {
37594
+ return bars.map((b) => b.high);
37595
+ }
37596
+ function lows(bars) {
37597
+ return bars.map((b) => b.low);
37598
+ }
37599
+ function closes(bars) {
37600
+ return bars.map((b) => b.close);
37601
+ }
37602
+ function opens(bars) {
37603
+ return bars.map((b) => b.open);
37604
+ }
37605
+ function zip(a, b, f) {
37606
+ const out = new Array(a.length);
37607
+ for (let i = 0; i < a.length; i++) {
37608
+ const x = a[i];
37609
+ const y = b[i];
37610
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
37611
+ }
37612
+ return out;
37613
+ }
37614
+ function map(a, f) {
37615
+ const out = new Array(a.length);
37616
+ for (let i = 0; i < a.length; i++) {
37617
+ const x = a[i];
37618
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
37619
+ }
37620
+ return out;
37621
+ }
37622
+ function sub(a, b) {
37623
+ return zip(a, b, (x, y) => x - y);
37624
+ }
37625
+ function windowed(v, len, fold) {
37626
+ const out = new Array(v.length).fill(Number.NaN);
37627
+ if (len <= 0) return out;
37628
+ let run = 0;
37629
+ for (let i = 0; i < v.length; i++) {
37630
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
37631
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
37632
+ }
37633
+ return out;
37634
+ }
37635
+ function sum(v, len) {
37636
+ return windowed(v, len, (w, from, to) => {
37637
+ let s = 0;
37638
+ for (let k = from; k <= to; k++) s += w[k];
37639
+ return s;
37640
+ });
37641
+ }
37642
+ function sma(v, len) {
37643
+ return windowed(v, len, (w, from, to) => {
37644
+ let s = 0;
37645
+ for (let k = from; k <= to; k++) s += w[k];
37646
+ return s / len;
37647
+ });
37648
+ }
37649
+ function wma(v, len) {
37650
+ const norm = len * (len + 1) / 2;
37651
+ return windowed(v, len, (w, from, to) => {
37652
+ let s = 0;
37653
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
37654
+ return s / norm;
37655
+ });
37656
+ }
37657
+ function vwma(src, vol, len) {
37658
+ const pv = zip(src, vol, (p, q) => p * q);
37659
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
37660
+ }
37661
+ function stdev(v, len) {
37662
+ return windowed(v, len, (w, from, to) => {
37663
+ let s = 0;
37664
+ for (let k = from; k <= to; k++) s += w[k];
37665
+ const mean = s / len;
37666
+ let sq = 0;
37667
+ for (let k = from; k <= to; k++) {
37668
+ const d = w[k] - mean;
37669
+ sq += d * d;
37670
+ }
37671
+ return Math.sqrt(sq / len);
37672
+ });
37673
+ }
37674
+ function highest(v, len) {
37675
+ return windowed(v, len, (w, from, to) => {
37676
+ let m = -Infinity;
37677
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
37678
+ return m;
37679
+ });
37680
+ }
37681
+ function lowest(v, len) {
37682
+ return windowed(v, len, (w, from, to) => {
37683
+ let m = Infinity;
37684
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
37685
+ return m;
37686
+ });
37687
+ }
37688
+ function barsSinceHighest(v, len) {
37689
+ return windowed(v, len, (w, from, to) => {
37690
+ let m = -Infinity;
37691
+ let at = to;
37692
+ for (let k = from; k <= to; k++) {
37693
+ if (w[k] >= m) {
37694
+ m = w[k];
37695
+ at = k;
37696
+ }
37697
+ }
37698
+ return to - at;
37699
+ });
37700
+ }
37701
+ function barsSinceLowest(v, len) {
37702
+ return windowed(v, len, (w, from, to) => {
37703
+ let m = Infinity;
37704
+ let at = to;
37705
+ for (let k = from; k <= to; k++) {
37706
+ if (w[k] <= m) {
37707
+ m = w[k];
37708
+ at = k;
37709
+ }
37710
+ }
37711
+ return to - at;
37712
+ });
37713
+ }
37714
+ function recursive(v, len, alpha) {
37715
+ const out = new Array(v.length).fill(Number.NaN);
37716
+ let run = 0;
37717
+ let prev2 = Number.NaN;
37718
+ for (let i = 0; i < v.length; i++) {
37719
+ const x = v[i];
37720
+ if (!Number.isFinite(x)) {
37721
+ run = 0;
37722
+ prev2 = Number.NaN;
37723
+ continue;
37724
+ }
37725
+ run += 1;
37726
+ if (Number.isFinite(prev2)) {
37727
+ prev2 = alpha * x + (1 - alpha) * prev2;
37728
+ out[i] = prev2;
37729
+ } else if (run >= len) {
37730
+ let s = 0;
37731
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
37732
+ prev2 = s / len;
37733
+ out[i] = prev2;
37734
+ }
37735
+ }
37736
+ return out;
37737
+ }
37738
+ function ema(v, len) {
37739
+ return recursive(v, len, 2 / (len + 1));
37740
+ }
37741
+ function rma(v, len) {
37742
+ return recursive(v, len, 1 / len);
37743
+ }
37744
+ function change(v, n = 1) {
37745
+ const out = new Array(v.length).fill(Number.NaN);
37746
+ for (let i = n; i < v.length; i++) {
37747
+ const a = v[i];
37748
+ const b = v[i - n];
37749
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
37750
+ }
37751
+ return out;
37752
+ }
37753
+ function roc(v, len) {
37754
+ const out = new Array(v.length).fill(Number.NaN);
37755
+ for (let i = len; i < v.length; i++) {
37756
+ const a = v[i];
37757
+ const b = v[i - len];
37758
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
37759
+ }
37760
+ return out;
37761
+ }
37762
+ function shift(v, n) {
37763
+ const out = new Array(v.length).fill(Number.NaN);
37764
+ for (let i = 0; i < v.length; i++) {
37765
+ const j = i - n;
37766
+ if (j >= 0 && j < v.length) out[i] = v[j];
37767
+ }
37768
+ return out;
37769
+ }
37770
+ function trueRange(bars) {
37771
+ const out = new Array(bars.length).fill(Number.NaN);
37772
+ for (let i = 0; i < bars.length; i++) {
37773
+ const b = bars[i];
37774
+ if (i === 0) {
37775
+ out[i] = b.high - b.low;
37776
+ continue;
37777
+ }
37778
+ const pc = bars[i - 1].close;
37779
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
37780
+ }
37781
+ return out;
37782
+ }
37783
+ function atr(bars, len) {
37784
+ return rma(trueRange(bars), len);
37785
+ }
37786
+ function rsi(v, len) {
37787
+ const d = change(v);
37788
+ const up = map(d, (x) => Math.max(x, 0));
37789
+ const dn = map(d, (x) => Math.max(-x, 0));
37790
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
37791
+ }
37792
+ function stoch(v, hi, lo, len) {
37793
+ const hh = highest(hi, len);
37794
+ const ll = lowest(lo, len);
37795
+ const out = new Array(v.length).fill(Number.NaN);
37796
+ for (let i = 0; i < v.length; i++) {
37797
+ const x = v[i];
37798
+ const h = hh[i];
37799
+ const l = ll[i];
37800
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
37801
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
37802
+ }
37803
+ return out;
37804
+ }
37805
+ function linreg(v, len) {
37806
+ const sx = (len - 1) * len / 2;
37807
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
37808
+ const denom = len * sxx - sx * sx;
37809
+ return windowed(v, len, (w, from, to) => {
37810
+ let sy = 0;
37811
+ let sxy = 0;
37812
+ for (let k = from; k <= to; k++) {
37813
+ const x = k - from;
37814
+ sy += w[k];
37815
+ sxy += x * w[k];
37816
+ }
37817
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
37818
+ const intercept = (sy - slope * sx) / len;
37819
+ return intercept + slope * (len - 1);
37820
+ });
37821
+ }
37822
+ function swma(v) {
37823
+ const out = new Array(v.length).fill(Number.NaN);
37824
+ for (let i = 3; i < v.length; i++) {
37825
+ const a = v[i - 3];
37826
+ const b = v[i - 2];
37827
+ const c = v[i - 1];
37828
+ const d = v[i];
37829
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
37830
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
37831
+ }
37832
+ }
37833
+ return out;
37834
+ }
37835
+ function percentRank(v, len) {
37836
+ const out = new Array(v.length).fill(Number.NaN);
37837
+ for (let i = len; i < v.length; i++) {
37838
+ const x = v[i];
37839
+ if (!Number.isFinite(x)) continue;
37840
+ let below = 0;
37841
+ let count = 0;
37842
+ for (let k = i - len; k < i; k++) {
37843
+ const y = v[k];
37844
+ if (!Number.isFinite(y)) {
37845
+ count = -1;
37846
+ break;
37847
+ }
37848
+ count += 1;
37849
+ if (y <= x) below += 1;
37850
+ }
37851
+ if (count === len) out[i] = 100 * below / len;
37852
+ }
37853
+ return out;
37854
+ }
37855
+ function cumSum(v) {
37856
+ const out = new Array(v.length).fill(Number.NaN);
37857
+ let acc = 0;
37858
+ let seen = false;
37859
+ for (let i = 0; i < v.length; i++) {
37860
+ const x = v[i];
37861
+ if (Number.isFinite(x)) {
37862
+ acc += x;
37863
+ seen = true;
37864
+ }
37865
+ if (seen) out[i] = acc;
37866
+ }
37867
+ return out;
37868
+ }
37869
+
37870
+ // src/core/native-indicators/classics/shared.ts
37871
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
37872
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
37873
+ }
37874
+ function sourceInput(defval = "Close") {
37875
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
37876
+ }
37877
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
37878
+ return { key, title, type: "color", defval };
37879
+ }
37880
+ function withAlpha2(hex, alpha = 0.08) {
37881
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
37882
+ if (!m) return hex;
37883
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
37884
+ return `#${m[1]}${a}`;
37885
+ }
37886
+
37887
+ // src/core/native-indicators/classics/averages.ts
37888
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
37889
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
37890
+ function fixedAverage(type, title, shortTitle, smooth) {
37891
+ return {
37892
+ type,
37893
+ title,
37894
+ shortTitle,
37895
+ overlay: true,
37896
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
37897
+ compute: (bars, inputs) => {
37898
+ const len = num3(inputs, "length", 20);
37899
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
37900
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37901
+ if (offset !== 0) values = shift(values, offset);
37902
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37903
+ }
37904
+ };
37905
+ }
37906
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
37907
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
37908
+ var movingAverage = {
37909
+ type: "moving-average",
37910
+ title: "Moving Average",
37911
+ shortTitle: "MA",
37912
+ overlay: true,
37913
+ inputs: [
37914
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
37915
+ lengthInput(20),
37916
+ sourceInput(),
37917
+ offsetInput,
37918
+ colorInput()
37919
+ ],
37920
+ compute: (bars, inputs) => {
37921
+ const len = num3(inputs, "length", 20);
37922
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37923
+ const kind = str3(inputs, "maType", "SMA");
37924
+ let values;
37925
+ switch (kind) {
37926
+ case "EMA":
37927
+ values = ema(src, len);
37928
+ break;
37929
+ case "WMA":
37930
+ values = wma(src, len);
37931
+ break;
37932
+ case "RMA":
37933
+ values = rma(src, len);
37934
+ break;
37935
+ case "VWMA":
37936
+ values = vwma(src, volumes(bars), len);
37937
+ break;
37938
+ default:
37939
+ values = sma(src, len);
37940
+ }
37941
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37942
+ if (offset !== 0) values = shift(values, offset);
37943
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37944
+ }
37945
+ };
37946
+ var smoothedMa = {
37947
+ type: "rma",
37948
+ title: "Smoothed Moving Average",
37949
+ shortTitle: "RMA",
37950
+ overlay: true,
37951
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37952
+ compute: (bars, inputs) => ({
37953
+ plots: [
37954
+ {
37955
+ key: "rma",
37956
+ title: "RMA",
37957
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37958
+ color: str3(inputs, "color", SERIES_LINE),
37959
+ width: 2
37960
+ }
37961
+ ]
37962
+ })
37963
+ };
37964
+ var zlema = {
37965
+ type: "zlema",
37966
+ title: "Zero-Lag Exponential Moving Average",
37967
+ shortTitle: "ZLEMA",
37968
+ overlay: true,
37969
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37970
+ compute: (bars, inputs) => {
37971
+ const len = num3(inputs, "length", 14);
37972
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37973
+ const lag = Math.floor((len - 1) / 2);
37974
+ const lagged = shift(src, lag);
37975
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
37976
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37977
+ }
37978
+ };
37979
+ var vidya = {
37980
+ type: "vidya",
37981
+ title: "Variable Index Dynamic Average",
37982
+ shortTitle: "VIDYA",
37983
+ overlay: true,
37984
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
37985
+ compute: (bars, inputs) => {
37986
+ const len = num3(inputs, "length", 14);
37987
+ const cmoLen = num3(inputs, "cmoLength", 9);
37988
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37989
+ const d = change(src);
37990
+ const su = new Array(src.length).fill(Number.NaN);
37991
+ const sd = new Array(src.length).fill(Number.NaN);
37992
+ for (let i = cmoLen; i < src.length; i++) {
37993
+ let u = 0;
37994
+ let w = 0;
37995
+ let ok = true;
37996
+ for (let k = i - cmoLen + 1; k <= i; k++) {
37997
+ const x = d[k];
37998
+ if (!Number.isFinite(x)) {
37999
+ ok = false;
38000
+ break;
38001
+ }
38002
+ if (x > 0) u += x;
38003
+ else w -= x;
38004
+ }
38005
+ if (ok) {
38006
+ su[i] = u;
38007
+ sd[i] = w;
38008
+ }
38009
+ }
38010
+ const alpha = 2 / (len + 1);
38011
+ const out = new Array(src.length).fill(Number.NaN);
38012
+ let prev2 = Number.NaN;
38013
+ for (let i = 0; i < src.length; i++) {
38014
+ const x = src[i];
38015
+ const u = su[i];
38016
+ const w = sd[i];
38017
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
38018
+ const total = u + w;
38019
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
38020
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
38021
+ out[i] = prev2;
38022
+ }
38023
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38024
+ }
38025
+ };
38026
+ var maEnvelope = {
38027
+ type: "ma-envelope",
38028
+ title: "Moving Average Envelope",
38029
+ shortTitle: "MA Env",
38030
+ overlay: true,
38031
+ inputs: [
38032
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
38033
+ lengthInput(20),
38034
+ sourceInput(),
38035
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
38036
+ colorInput()
38037
+ ],
38038
+ compute: (bars, inputs) => {
38039
+ const len = num3(inputs, "length", 20);
38040
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38041
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
38042
+ const k = num3(inputs, "percent", 2.5) / 100;
38043
+ const ink = str3(inputs, "color", SERIES_LINE);
38044
+ return {
38045
+ plots: [
38046
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
38047
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
38048
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
38049
+ ],
38050
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
38051
+ };
38052
+ }
38053
+ };
38054
+ var linearRegression = {
38055
+ type: "linear-regression",
38056
+ title: "Linear Regression Curve",
38057
+ shortTitle: "LinReg",
38058
+ overlay: true,
38059
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38060
+ compute: (bars, inputs) => ({
38061
+ plots: [
38062
+ {
38063
+ key: "linreg",
38064
+ title: "LinReg",
38065
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38066
+ color: str3(inputs, "color", SERIES_LINE),
38067
+ width: 2
38068
+ }
38069
+ ]
38070
+ })
38071
+ };
38072
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
38073
+
38074
+ // src/core/native-indicators/classics/bands.ts
38075
+ function bollinger(bars, inputs) {
38076
+ const len = num3(inputs, "length", 20);
38077
+ const mult = num3(inputs, "mult", 2);
38078
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38079
+ const basis = sma(src, len);
38080
+ const dev = map(stdev(src, len), (x) => x * mult);
38081
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
38082
+ }
38083
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
38084
+ var bollingerBands = {
38085
+ type: "bollinger-bands",
38086
+ title: "Bollinger Bands",
38087
+ shortTitle: "BB",
38088
+ overlay: true,
38089
+ inputs: [...BB_INPUTS, colorInput()],
38090
+ compute: (bars, inputs) => {
38091
+ const { basis, upper, lower } = bollinger(bars, inputs);
38092
+ const ink = str3(inputs, "color", SERIES_LINE);
38093
+ return {
38094
+ plots: [
38095
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
38096
+ { key: "upper", title: "Upper", values: upper, color: ink },
38097
+ { key: "lower", title: "Lower", values: lower, color: ink }
38098
+ ],
38099
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
38100
+ };
38101
+ }
38102
+ };
38103
+ var percentB = {
38104
+ type: "percent-b",
38105
+ title: "Bollinger Bands %B",
38106
+ shortTitle: "%B",
38107
+ overlay: false,
38108
+ inputs: [...BB_INPUTS, colorInput()],
38109
+ compute: (bars, inputs) => {
38110
+ const { upper, lower } = bollinger(bars, inputs);
38111
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38112
+ const values = src.map((x, i) => {
38113
+ const u = upper[i];
38114
+ const l = lower[i];
38115
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
38116
+ });
38117
+ return {
38118
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38119
+ levels: [
38120
+ { key: "upper", price: 1, color: NEUTRAL },
38121
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
38122
+ { key: "lower", price: 0, color: NEUTRAL }
38123
+ ]
38124
+ };
38125
+ }
38126
+ };
38127
+ var bandwidth = {
38128
+ type: "bandwidth",
38129
+ title: "Bollinger Bands Width",
38130
+ shortTitle: "BandWidth",
38131
+ overlay: false,
38132
+ inputs: [...BB_INPUTS, colorInput()],
38133
+ compute: (bars, inputs) => {
38134
+ const { basis, upper, lower } = bollinger(bars, inputs);
38135
+ const values = basis.map((b, i) => {
38136
+ const u = upper[i];
38137
+ const l = lower[i];
38138
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
38139
+ });
38140
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38141
+ }
38142
+ };
38143
+ var keltner = {
38144
+ type: "keltner-channels",
38145
+ title: "Keltner Channels",
38146
+ shortTitle: "KC",
38147
+ overlay: true,
38148
+ inputs: [
38149
+ lengthInput(20),
38150
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38151
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38152
+ sourceInput(),
38153
+ colorInput()
38154
+ ],
38155
+ compute: (bars, inputs) => {
38156
+ const len = num3(inputs, "length", 20);
38157
+ const mult = num3(inputs, "mult", 2);
38158
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
38159
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
38160
+ const ink = str3(inputs, "color", SERIES_LINE);
38161
+ return {
38162
+ plots: [
38163
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
38164
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
38165
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
38166
+ ],
38167
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38168
+ };
38169
+ }
38170
+ };
38171
+ var donchian = {
38172
+ type: "donchian-channels",
38173
+ title: "Donchian Channels",
38174
+ shortTitle: "DC",
38175
+ overlay: true,
38176
+ inputs: [lengthInput(20), colorInput()],
38177
+ compute: (bars, inputs) => {
38178
+ const len = num3(inputs, "length", 20);
38179
+ const upper = highest(highs(bars), len);
38180
+ const lower = lowest(lows(bars), len);
38181
+ const ink = str3(inputs, "color", SERIES_LINE);
38182
+ return {
38183
+ plots: [
38184
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
38185
+ { key: "upper", title: "Upper", values: upper, color: ink },
38186
+ { key: "lower", title: "Lower", values: lower, color: ink }
38187
+ ],
38188
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38189
+ };
38190
+ }
38191
+ };
38192
+ var chandelier = {
38193
+ type: "chandelier-exit",
38194
+ title: "Chandelier Exit",
38195
+ shortTitle: "CE",
38196
+ overlay: true,
38197
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
38198
+ compute: (bars, inputs) => {
38199
+ const len = num3(inputs, "length", 22);
38200
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
38201
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
38202
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
38203
+ return {
38204
+ plots: [
38205
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
38206
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
38207
+ ]
38208
+ };
38209
+ }
38210
+ };
38211
+ var chandeKroll = {
38212
+ type: "chande-kroll-stop",
38213
+ title: "Chande Kroll Stop",
38214
+ shortTitle: "CKS",
38215
+ overlay: true,
38216
+ inputs: [
38217
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38218
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
38219
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
38220
+ ],
38221
+ compute: (bars, inputs) => {
38222
+ const p = num3(inputs, "p", 10);
38223
+ const x = num3(inputs, "x", 1);
38224
+ const q = num3(inputs, "q", 9);
38225
+ const range = map(atr(bars, p), (v) => v * x);
38226
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
38227
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
38228
+ return {
38229
+ plots: [
38230
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
38231
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
38232
+ ]
38233
+ };
38234
+ }
38235
+ };
38236
+ var supertrend = {
38237
+ type: "supertrend",
38238
+ title: "SuperTrend",
38239
+ shortTitle: "SuperTrend",
38240
+ overlay: true,
38241
+ inputs: [
38242
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38243
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
38244
+ ],
38245
+ compute: (bars, inputs) => {
38246
+ const len = num3(inputs, "atrLength", 10);
38247
+ const mult = num3(inputs, "mult", 3);
38248
+ const range = atr(bars, len);
38249
+ const n = bars.length;
38250
+ const st = new Array(n).fill(Number.NaN);
38251
+ const dir = new Array(n).fill(1);
38252
+ let up = Number.NaN;
38253
+ let dn = Number.NaN;
38254
+ let trend = 1;
38255
+ for (let i = 0; i < n; i++) {
38256
+ const r = range[i];
38257
+ if (!Number.isFinite(r)) continue;
38258
+ const b = bars[i];
38259
+ const mid = (b.high + b.low) / 2;
38260
+ const basicUp = mid - mult * r;
38261
+ const basicDn = mid + mult * r;
38262
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
38263
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
38264
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
38265
+ if (trend === 1 && b.close < up) trend = -1;
38266
+ else if (trend === -1 && b.close > dn) trend = 1;
38267
+ dir[i] = trend;
38268
+ st[i] = trend === 1 ? up : dn;
38269
+ }
38270
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
38271
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
38272
+ }
38273
+ };
38274
+ var alligator = {
38275
+ type: "williams-alligator",
38276
+ title: "Williams Alligator",
38277
+ shortTitle: "Alligator",
38278
+ overlay: true,
38279
+ inputs: [
38280
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
38281
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
38282
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
38283
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
38284
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
38285
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
38286
+ ],
38287
+ compute: (bars, inputs) => {
38288
+ const hl2 = sourceValues(bars, "HL2");
38289
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
38290
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
38291
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
38292
+ return {
38293
+ plots: [
38294
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
38295
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
38296
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
38297
+ ]
38298
+ };
38299
+ }
38300
+ };
38301
+ var gator = {
38302
+ type: "gator-oscillator",
38303
+ title: "Gator Oscillator",
38304
+ shortTitle: "Gator Oscillator",
38305
+ overlay: false,
38306
+ inputs: [],
38307
+ compute: (bars) => {
38308
+ const hl2 = sourceValues(bars, "HL2");
38309
+ const jaw = shift(rma(hl2, 13), 8);
38310
+ const teeth = shift(rma(hl2, 8), 5);
38311
+ const lips = shift(rma(hl2, 5), 3);
38312
+ const upper = map(sub(jaw, teeth), Math.abs);
38313
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
38314
+ const colorByExpansion = (v) => v.map((x, i) => {
38315
+ if (!Number.isFinite(x)) return null;
38316
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
38317
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
38318
+ });
38319
+ return {
38320
+ plots: [
38321
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
38322
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
38323
+ ]
38324
+ };
38325
+ }
38326
+ };
38327
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
38328
+
38329
+ // src/core/native-indicators/classics/oscillators.ts
38330
+ function guideLevels(low, high, mid) {
38331
+ const levels = [
38332
+ { key: "upper", price: high, color: NEUTRAL },
38333
+ { key: "lower", price: low, color: NEUTRAL }
38334
+ ];
38335
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
38336
+ return levels;
38337
+ }
38338
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
38339
+ function histColors(values) {
38340
+ return values.map((x, i) => {
38341
+ if (!Number.isFinite(x)) return null;
38342
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
38343
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
38344
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
38345
+ return rising ? `${BEARISH}80` : BEARISH;
38346
+ });
38347
+ }
38348
+ var rsiSpec = {
38349
+ type: "rsi",
38350
+ title: "Relative Strength Index",
38351
+ shortTitle: "RSI",
38352
+ overlay: false,
38353
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38354
+ compute: (bars, inputs) => ({
38355
+ plots: [
38356
+ {
38357
+ key: "rsi",
38358
+ title: "RSI",
38359
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38360
+ color: str3(inputs, "color", SERIES_LINE),
38361
+ width: 2
38362
+ }
38363
+ ],
38364
+ levels: guideLevels(30, 70, 50)
38365
+ })
38366
+ };
38367
+ var stochastic = {
38368
+ type: "stochastic",
38369
+ title: "Stochastic",
38370
+ shortTitle: "Stoch",
38371
+ overlay: false,
38372
+ inputs: [
38373
+ lengthInput(14, "kLength", "%K length"),
38374
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
38375
+ lengthInput(3, "dLength", "%D smoothing", 500)
38376
+ ],
38377
+ compute: (bars, inputs) => {
38378
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
38379
+ const d = sma(k, num3(inputs, "dLength", 3));
38380
+ return {
38381
+ plots: [
38382
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38383
+ { key: "d", title: "%D", values: d, color: WARNING }
38384
+ ],
38385
+ levels: guideLevels(20, 80)
38386
+ };
38387
+ }
38388
+ };
38389
+ var stochasticRsi = {
38390
+ type: "stochastic-rsi",
38391
+ title: "Stochastic RSI",
38392
+ shortTitle: "Stoch RSI",
38393
+ overlay: false,
38394
+ inputs: [
38395
+ lengthInput(14, "rsiLength", "RSI length"),
38396
+ lengthInput(14, "stochLength", "Stochastic length"),
38397
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
38398
+ lengthInput(3, "dLength", "%D smoothing", 500),
38399
+ sourceInput()
38400
+ ],
38401
+ compute: (bars, inputs) => {
38402
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
38403
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
38404
+ const d = sma(k, num3(inputs, "dLength", 3));
38405
+ return {
38406
+ plots: [
38407
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38408
+ { key: "d", title: "%D", values: d, color: WARNING }
38409
+ ],
38410
+ levels: guideLevels(20, 80)
38411
+ };
38412
+ }
38413
+ };
38414
+ var macd = {
38415
+ type: "macd",
38416
+ title: "MACD",
38417
+ overlay: false,
38418
+ inputs: [
38419
+ lengthInput(12, "fastLength", "Fast length"),
38420
+ lengthInput(26, "slowLength", "Slow length"),
38421
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38422
+ sourceInput()
38423
+ ],
38424
+ compute: (bars, inputs) => {
38425
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38426
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
38427
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38428
+ const hist = sub(line, signal);
38429
+ return {
38430
+ plots: [
38431
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38432
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
38433
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38434
+ ],
38435
+ levels: ZERO_LEVEL
38436
+ };
38437
+ }
38438
+ };
38439
+ var ppo = {
38440
+ type: "ppo",
38441
+ title: "Percentage Price Oscillator",
38442
+ shortTitle: "PPO",
38443
+ overlay: false,
38444
+ inputs: [
38445
+ lengthInput(12, "fastLength", "Fast length"),
38446
+ lengthInput(26, "slowLength", "Slow length"),
38447
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38448
+ sourceInput()
38449
+ ],
38450
+ compute: (bars, inputs) => {
38451
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38452
+ const fast = ema(src, num3(inputs, "fastLength", 12));
38453
+ const slow = ema(src, num3(inputs, "slowLength", 26));
38454
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
38455
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38456
+ const hist = sub(line, signal);
38457
+ return {
38458
+ plots: [
38459
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38460
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
38461
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38462
+ ],
38463
+ levels: ZERO_LEVEL
38464
+ };
38465
+ }
38466
+ };
38467
+ var awesome = {
38468
+ type: "awesome-oscillator",
38469
+ title: "Awesome Oscillator",
38470
+ shortTitle: "AO",
38471
+ overlay: false,
38472
+ inputs: [],
38473
+ compute: (bars) => {
38474
+ const hl2 = sourceValues(bars, "HL2");
38475
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
38476
+ const colors = ao.map((x, i) => {
38477
+ if (!Number.isFinite(x)) return null;
38478
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
38479
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
38480
+ });
38481
+ return {
38482
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
38483
+ levels: ZERO_LEVEL
38484
+ };
38485
+ }
38486
+ };
38487
+ var cci = {
38488
+ type: "commodity-channel-index",
38489
+ title: "Commodity Channel Index",
38490
+ shortTitle: "CCI",
38491
+ overlay: false,
38492
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
38493
+ compute: (bars, inputs) => {
38494
+ const len = num3(inputs, "length", 20);
38495
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
38496
+ const basis = sma(src, len);
38497
+ const dev = new Array(src.length).fill(Number.NaN);
38498
+ for (let i = len - 1; i < src.length; i++) {
38499
+ const mean = basis[i];
38500
+ if (!Number.isFinite(mean)) continue;
38501
+ let d = 0;
38502
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
38503
+ dev[i] = d / len;
38504
+ }
38505
+ const values = src.map((x, i) => {
38506
+ const b = basis[i];
38507
+ const d = dev[i];
38508
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
38509
+ });
38510
+ return {
38511
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38512
+ levels: guideLevels(-100, 100, 0)
38513
+ };
38514
+ }
38515
+ };
38516
+ var williamsR = {
38517
+ type: "williams-percent-r",
38518
+ title: "Williams %R",
38519
+ shortTitle: "%R",
38520
+ overlay: false,
38521
+ inputs: [lengthInput(14), colorInput()],
38522
+ compute: (bars, inputs) => {
38523
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
38524
+ return {
38525
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38526
+ levels: guideLevels(-80, -20)
38527
+ };
38528
+ }
38529
+ };
38530
+ var cmo = {
38531
+ type: "chande-momentum-oscillator",
38532
+ title: "Chande Momentum Oscillator",
38533
+ shortTitle: "CMO",
38534
+ overlay: false,
38535
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38536
+ compute: (bars, inputs) => {
38537
+ const len = num3(inputs, "length", 9);
38538
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
38539
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
38540
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
38541
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
38542
+ return {
38543
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38544
+ levels: guideLevels(-50, 50, 0)
38545
+ };
38546
+ }
38547
+ };
38548
+ var connorsRsi = {
38549
+ type: "connors-rsi",
38550
+ title: "Connors RSI",
38551
+ shortTitle: "CRSI",
38552
+ overlay: false,
38553
+ inputs: [
38554
+ lengthInput(3, "rsiLength", "RSI length"),
38555
+ lengthInput(2, "streakLength", "Streak RSI length"),
38556
+ lengthInput(100, "rankLength", "Percent-rank length"),
38557
+ colorInput()
38558
+ ],
38559
+ compute: (bars, inputs) => {
38560
+ const c = closes(bars);
38561
+ const streak = new Array(c.length).fill(0);
38562
+ for (let i = 1; i < c.length; i++) {
38563
+ const d = c[i] - c[i - 1];
38564
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
38565
+ }
38566
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
38567
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
38568
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
38569
+ const values = c.map((_, i) => {
38570
+ const x = a[i];
38571
+ const y = b[i];
38572
+ const z = r[i];
38573
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
38574
+ });
38575
+ return {
38576
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38577
+ levels: guideLevels(20, 80, 50)
38578
+ };
38579
+ }
38580
+ };
38581
+ var fisher = {
38582
+ type: "fisher-transform",
38583
+ title: "Fisher Transform",
38584
+ shortTitle: "Fisher",
38585
+ overlay: false,
38586
+ inputs: [lengthInput(9)],
38587
+ compute: (bars, inputs) => {
38588
+ const len = num3(inputs, "length", 9);
38589
+ const hl2 = sourceValues(bars, "HL2");
38590
+ const hi = highest(hl2, len);
38591
+ const lo = lowest(hl2, len);
38592
+ const n = bars.length;
38593
+ const out = new Array(n).fill(Number.NaN);
38594
+ let value = 0;
38595
+ let fish = 0;
38596
+ for (let i = 0; i < n; i++) {
38597
+ const h = hi[i];
38598
+ const l = lo[i];
38599
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
38600
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
38601
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
38602
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
38603
+ out[i] = fish;
38604
+ }
38605
+ return {
38606
+ plots: [
38607
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
38608
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
38609
+ ],
38610
+ levels: ZERO_LEVEL
38611
+ };
38612
+ }
38613
+ };
38614
+ var trix = {
38615
+ type: "trix",
38616
+ title: "TRIX",
38617
+ overlay: false,
38618
+ inputs: [lengthInput(18), colorInput()],
38619
+ compute: (bars, inputs) => {
38620
+ const len = num3(inputs, "length", 18);
38621
+ const logClose = map(closes(bars), Math.log);
38622
+ const smooth = ema(ema(ema(logClose, len), len), len);
38623
+ const values = map(change(smooth), (x) => x * 1e4);
38624
+ return {
38625
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38626
+ levels: ZERO_LEVEL
38627
+ };
38628
+ }
38629
+ };
38630
+ function tsiLine(src, long, short) {
38631
+ const mom = change(src);
38632
+ const numer = ema(ema(mom, long), short);
38633
+ const denom = ema(ema(map(mom, Math.abs), long), short);
38634
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
38635
+ }
38636
+ var tsi = {
38637
+ type: "true-strength-index",
38638
+ title: "True Strength Index",
38639
+ shortTitle: "TSI",
38640
+ overlay: false,
38641
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
38642
+ compute: (bars, inputs) => {
38643
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
38644
+ return {
38645
+ plots: [
38646
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
38647
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
38648
+ ],
38649
+ levels: ZERO_LEVEL
38650
+ };
38651
+ }
38652
+ };
38653
+ var smiErgodic = {
38654
+ type: "smi-ergodic",
38655
+ title: "SMI Ergodic Oscillator",
38656
+ shortTitle: "SMIE",
38657
+ overlay: false,
38658
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
38659
+ compute: (bars, inputs) => {
38660
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
38661
+ const signal = ema(line, num3(inputs, "signalLength", 5));
38662
+ const osc = sub(line, signal);
38663
+ return {
38664
+ plots: [
38665
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
38666
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
38667
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38668
+ ],
38669
+ levels: ZERO_LEVEL
38670
+ };
38671
+ }
38672
+ };
38673
+ var stc = {
38674
+ type: "schaff-trend-cycle",
38675
+ title: "Schaff Trend Cycle",
38676
+ shortTitle: "Schaff Trend Cycle",
38677
+ overlay: false,
38678
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
38679
+ compute: (bars, inputs) => {
38680
+ const len = num3(inputs, "cycleLength", 10);
38681
+ const src = closes(bars);
38682
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
38683
+ const stochOf = (v) => {
38684
+ const lo = lowest(v, len);
38685
+ const hi = highest(v, len);
38686
+ const out = new Array(v.length).fill(Number.NaN);
38687
+ let prevRaw = Number.NaN;
38688
+ let smoothed = Number.NaN;
38689
+ for (let i = 0; i < v.length; i++) {
38690
+ const x = v[i];
38691
+ const l = lo[i];
38692
+ const h = hi[i];
38693
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
38694
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
38695
+ prevRaw = raw;
38696
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
38697
+ out[i] = smoothed;
38698
+ }
38699
+ return out;
38700
+ };
38701
+ const values = stochOf(stochOf(macdLine));
38702
+ return {
38703
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
38704
+ levels: guideLevels(25, 75)
38705
+ };
38706
+ }
38707
+ };
38708
+ var kst = {
38709
+ type: "know-sure-thing",
38710
+ title: "Know Sure Thing",
38711
+ shortTitle: "KST",
38712
+ overlay: false,
38713
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
38714
+ compute: (bars, inputs) => {
38715
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38716
+ const line = [
38717
+ sma(roc(src, 10), 10),
38718
+ sma(roc(src, 15), 10),
38719
+ sma(roc(src, 20), 10),
38720
+ sma(roc(src, 30), 15)
38721
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
38722
+ return {
38723
+ plots: [
38724
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
38725
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
38726
+ ],
38727
+ levels: ZERO_LEVEL
38728
+ };
38729
+ }
38730
+ };
38731
+ var coppock = {
38732
+ type: "coppock-curve",
38733
+ title: "Coppock Curve",
38734
+ shortTitle: "Coppock",
38735
+ overlay: false,
38736
+ inputs: [
38737
+ lengthInput(10, "wmaLength", "WMA length"),
38738
+ lengthInput(14, "longRoc", "Long RoC length"),
38739
+ lengthInput(11, "shortRoc", "Short RoC length"),
38740
+ sourceInput(),
38741
+ colorInput()
38742
+ ],
38743
+ compute: (bars, inputs) => {
38744
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38745
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
38746
+ return {
38747
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38748
+ levels: ZERO_LEVEL
38749
+ };
38750
+ }
38751
+ };
38752
+ var dpo = {
38753
+ type: "detrended-price-oscillator",
38754
+ title: "Detrended Price Oscillator",
38755
+ shortTitle: "DPO",
38756
+ overlay: false,
38757
+ inputs: [lengthInput(21), colorInput()],
38758
+ compute: (bars, inputs) => {
38759
+ const len = num3(inputs, "length", 21);
38760
+ const barsback = Math.floor(len / 2) + 1;
38761
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
38762
+ return {
38763
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38764
+ levels: ZERO_LEVEL
38765
+ };
38766
+ }
38767
+ };
38768
+ var rocSpec = {
38769
+ type: "rate-of-change",
38770
+ title: "Rate of Change",
38771
+ shortTitle: "ROC",
38772
+ overlay: false,
38773
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38774
+ compute: (bars, inputs) => ({
38775
+ plots: [
38776
+ {
38777
+ key: "roc",
38778
+ title: "RoC",
38779
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
38780
+ color: str3(inputs, "color", SERIES_LINE),
38781
+ width: 2
38782
+ }
38783
+ ],
38784
+ levels: ZERO_LEVEL
38785
+ })
38786
+ };
38787
+ var ultimate = {
38788
+ type: "ultimate-oscillator",
38789
+ title: "Ultimate Oscillator",
38790
+ shortTitle: "UO",
38791
+ overlay: false,
38792
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
38793
+ compute: (bars, inputs) => {
38794
+ const n = bars.length;
38795
+ const bp = new Array(n).fill(Number.NaN);
38796
+ const tr = new Array(n).fill(Number.NaN);
38797
+ for (let i = 0; i < n; i++) {
38798
+ const b = bars[i];
38799
+ const pc = i > 0 ? bars[i - 1].close : b.close;
38800
+ const lo = Math.min(b.low, pc);
38801
+ const hi = Math.max(b.high, pc);
38802
+ bp[i] = b.close - lo;
38803
+ tr[i] = hi - lo;
38804
+ }
38805
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
38806
+ const a7 = avg(num3(inputs, "fastLength", 7));
38807
+ const a14 = avg(num3(inputs, "middleLength", 14));
38808
+ const a28 = avg(num3(inputs, "slowLength", 28));
38809
+ const values = a7.map((x, i) => {
38810
+ const y = a14[i];
38811
+ const z = a28[i];
38812
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
38813
+ });
38814
+ return {
38815
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38816
+ levels: guideLevels(30, 70, 50)
38817
+ };
38818
+ }
38819
+ };
38820
+ var rvgi = {
38821
+ type: "relative-vigor-index",
38822
+ title: "Relative Vigor Index",
38823
+ shortTitle: "RVGI",
38824
+ overlay: false,
38825
+ inputs: [lengthInput(10)],
38826
+ compute: (bars, inputs) => {
38827
+ const len = num3(inputs, "length", 10);
38828
+ const co = swma(sub(closes(bars), opens(bars)));
38829
+ const hl = swma(sub(highs(bars), lows(bars)));
38830
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
38831
+ return {
38832
+ plots: [
38833
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
38834
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
38835
+ ],
38836
+ levels: ZERO_LEVEL
38837
+ };
38838
+ }
38839
+ };
38840
+ var relVolatility = {
38841
+ type: "relative-volatility-index",
38842
+ title: "Relative Volatility Index",
38843
+ shortTitle: "RVI",
38844
+ overlay: false,
38845
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
38846
+ compute: (bars, inputs) => {
38847
+ const smoothLen = num3(inputs, "smoothLength", 14);
38848
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38849
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
38850
+ const d = change(src);
38851
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
38852
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
38853
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
38854
+ return {
38855
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38856
+ levels: guideLevels(20, 80, 50)
38857
+ };
38858
+ }
38859
+ };
38860
+ var bop = {
38861
+ type: "balance-of-power",
38862
+ title: "Balance of Power",
38863
+ shortTitle: "BOP",
38864
+ overlay: false,
38865
+ inputs: [colorInput()],
38866
+ compute: (bars, inputs) => {
38867
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
38868
+ return {
38869
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38870
+ levels: ZERO_LEVEL
38871
+ };
38872
+ }
38873
+ };
38874
+ var elderRay = {
38875
+ type: "elder-ray",
38876
+ title: "Elder Ray",
38877
+ overlay: false,
38878
+ inputs: [lengthInput(13)],
38879
+ compute: (bars, inputs) => {
38880
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
38881
+ return {
38882
+ plots: [
38883
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
38884
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
38885
+ ],
38886
+ levels: ZERO_LEVEL
38887
+ };
38888
+ }
38889
+ };
38890
+ var ttmSqueeze = {
38891
+ type: "ttm-squeeze",
38892
+ title: "TTM Squeeze",
38893
+ overlay: false,
38894
+ inputs: [
38895
+ lengthInput(20),
38896
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38897
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
38898
+ ],
38899
+ compute: (bars, inputs) => {
38900
+ const len = num3(inputs, "length", 20);
38901
+ const c = closes(bars);
38902
+ const basis = sma(c, len);
38903
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
38904
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
38905
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
38906
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
38907
+ const mom = linreg(sub(c, anchor), len);
38908
+ const squeezed = c.map((_, i) => {
38909
+ const d = dev[i];
38910
+ const r = kcRange[i];
38911
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
38912
+ });
38913
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
38914
+ return {
38915
+ plots: [
38916
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
38917
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
38918
+ ]
38919
+ };
38920
+ }
38921
+ };
38922
+ var oscillatorSpecs = [
38923
+ rsiSpec,
38924
+ stochastic,
38925
+ stochasticRsi,
38926
+ macd,
38927
+ ppo,
38928
+ awesome,
38929
+ cci,
38930
+ williamsR,
38931
+ cmo,
38932
+ connorsRsi,
38933
+ fisher,
38934
+ trix,
38935
+ tsi,
38936
+ smiErgodic,
38937
+ stc,
38938
+ kst,
38939
+ coppock,
38940
+ dpo,
38941
+ rocSpec,
38942
+ ultimate,
38943
+ rvgi,
38944
+ relVolatility,
38945
+ bop,
38946
+ elderRay,
38947
+ ttmSqueeze
38948
+ ];
38949
+
38950
+ // src/core/native-indicators/classics/trend.ts
38951
+ var aroon = {
38952
+ type: "aroon",
38953
+ title: "Aroon",
38954
+ overlay: false,
38955
+ inputs: [lengthInput(14)],
38956
+ compute: (bars, inputs) => {
38957
+ const len = num3(inputs, "length", 14);
38958
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
38959
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
38960
+ return {
38961
+ plots: [
38962
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
38963
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
38964
+ ],
38965
+ levels: [
38966
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
38967
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
38968
+ ]
38969
+ };
38970
+ }
38971
+ };
38972
+ var adx = {
38973
+ type: "average-directional-index",
38974
+ title: "Average Directional Index",
38975
+ shortTitle: "ADX",
38976
+ overlay: false,
38977
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
38978
+ compute: (bars, inputs) => {
38979
+ const diLen = num3(inputs, "diLength", 14);
38980
+ const adxLen = num3(inputs, "adxLength", 14);
38981
+ const n = bars.length;
38982
+ const plusDm = new Array(n).fill(Number.NaN);
38983
+ const minusDm = new Array(n).fill(Number.NaN);
38984
+ for (let i = 1; i < n; i++) {
38985
+ const up = bars[i].high - bars[i - 1].high;
38986
+ const dn = bars[i - 1].low - bars[i].low;
38987
+ plusDm[i] = up > dn && up > 0 ? up : 0;
38988
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
38989
+ }
38990
+ const atrLine = rma(trueRange(bars), diLen);
38991
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38992
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38993
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
38994
+ return {
38995
+ plots: [
38996
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
38997
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
38998
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
38999
+ ],
39000
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
39001
+ };
39002
+ }
39003
+ };
39004
+ var vortex = {
39005
+ type: "vortex-indicator",
39006
+ title: "Vortex Indicator",
39007
+ shortTitle: "VI",
39008
+ overlay: false,
39009
+ inputs: [lengthInput(14)],
39010
+ compute: (bars, inputs) => {
39011
+ const len = num3(inputs, "length", 14);
39012
+ const n = bars.length;
39013
+ const vmPlus = new Array(n).fill(Number.NaN);
39014
+ const vmMinus = new Array(n).fill(Number.NaN);
39015
+ for (let i = 1; i < n; i++) {
39016
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
39017
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
39018
+ }
39019
+ const trSum = sum(trueRange(bars), len);
39020
+ return {
39021
+ plots: [
39022
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
39023
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
39024
+ ],
39025
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
39026
+ };
39027
+ }
39028
+ };
39029
+ var trendSpecs = [aroon, adx, vortex];
39030
+
39031
+ // src/core/native-indicators/classics/volatility.ts
39032
+ var atrSpec = {
39033
+ type: "average-true-range",
39034
+ title: "Average True Range",
39035
+ shortTitle: "ATR",
39036
+ overlay: false,
39037
+ inputs: [lengthInput(14), colorInput()],
39038
+ compute: (bars, inputs) => ({
39039
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
39040
+ })
39041
+ };
39042
+ var historicalVolatility = {
39043
+ type: "historical-volatility",
39044
+ title: "Historical Volatility",
39045
+ shortTitle: "HV",
39046
+ overlay: false,
39047
+ inputs: [
39048
+ lengthInput(10),
39049
+ { 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)" },
39050
+ colorInput()
39051
+ ],
39052
+ compute: (bars, inputs) => {
39053
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
39054
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
39055
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39056
+ }
39057
+ };
39058
+ var chaikinVolatility = {
39059
+ type: "chaikin-volatility",
39060
+ title: "Chaikin Volatility",
39061
+ shortTitle: "CHV",
39062
+ overlay: false,
39063
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
39064
+ compute: (bars, inputs) => {
39065
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
39066
+ return {
39067
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39068
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39069
+ };
39070
+ }
39071
+ };
39072
+ var massIndex = {
39073
+ type: "mass-index",
39074
+ title: "Mass Index",
39075
+ shortTitle: "MI",
39076
+ overlay: false,
39077
+ inputs: [lengthInput(25), colorInput()],
39078
+ compute: (bars, inputs) => {
39079
+ const range = sub(highs(bars), lows(bars));
39080
+ const single = ema(range, 9);
39081
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
39082
+ return {
39083
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39084
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
39085
+ };
39086
+ }
39087
+ };
39088
+ var standardDeviation = {
39089
+ type: "standard-deviation",
39090
+ title: "Standard Deviation",
39091
+ shortTitle: "StdDev",
39092
+ overlay: false,
39093
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
39094
+ compute: (bars, inputs) => ({
39095
+ plots: [
39096
+ {
39097
+ key: "stdev",
39098
+ title: "StdDev",
39099
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
39100
+ color: str3(inputs, "color", SERIES_LINE),
39101
+ width: 2
39102
+ }
39103
+ ]
39104
+ })
39105
+ };
39106
+ var ulcerIndex = {
39107
+ type: "ulcer-index",
39108
+ title: "Ulcer Index",
39109
+ overlay: false,
39110
+ inputs: [lengthInput(14), colorInput()],
39111
+ compute: (bars, inputs) => {
39112
+ const len = num3(inputs, "length", 14);
39113
+ const c = closes(bars);
39114
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
39115
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
39116
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39117
+ }
39118
+ };
39119
+ var choppiness = {
39120
+ type: "choppiness-index",
39121
+ title: "Choppiness Index",
39122
+ shortTitle: "CHOP",
39123
+ overlay: false,
39124
+ inputs: [lengthInput(14), colorInput()],
39125
+ compute: (bars, inputs) => {
39126
+ const len = num3(inputs, "length", 14);
39127
+ const trSum = sum(trueRange(bars), len);
39128
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
39129
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
39130
+ return {
39131
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39132
+ levels: [
39133
+ { key: "upper", price: 61.8, color: NEUTRAL },
39134
+ { key: "lower", price: 38.2, color: NEUTRAL }
39135
+ ]
39136
+ };
39137
+ }
39138
+ };
39139
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
39140
+
39141
+ // src/core/native-indicators/classics/volume.ts
39142
+ function moneyFlowVolume(bars) {
39143
+ return bars.map((b) => {
39144
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39145
+ if (b.high === b.low) return 0;
39146
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
39147
+ });
39148
+ }
39149
+ var obv = {
39150
+ type: "on-balance-volume",
39151
+ title: "On Balance Volume",
39152
+ shortTitle: "OBV",
39153
+ overlay: false,
39154
+ inputs: [colorInput()],
39155
+ compute: (bars, inputs) => {
39156
+ const vol = volumes(bars);
39157
+ const d = change(closes(bars));
39158
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
39159
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39160
+ }
39161
+ };
39162
+ var accDist = {
39163
+ type: "accumulation-distribution",
39164
+ title: "Accumulation / Distribution",
39165
+ shortTitle: "A/D",
39166
+ overlay: false,
39167
+ inputs: [colorInput()],
39168
+ compute: (bars, inputs) => ({
39169
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
39170
+ })
39171
+ };
39172
+ var cmf = {
39173
+ type: "chaikin-money-flow",
39174
+ title: "Chaikin Money Flow",
39175
+ shortTitle: "CMF",
39176
+ overlay: false,
39177
+ inputs: [lengthInput(20), colorInput()],
39178
+ compute: (bars, inputs) => {
39179
+ const len = num3(inputs, "length", 20);
39180
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
39181
+ return {
39182
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39183
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39184
+ };
39185
+ }
39186
+ };
39187
+ var chaikinOscillator = {
39188
+ type: "chaikin-oscillator",
39189
+ title: "Chaikin Oscillator",
39190
+ shortTitle: "Chaikin Osc",
39191
+ overlay: false,
39192
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39193
+ compute: (bars, inputs) => {
39194
+ const ad = cumSum(moneyFlowVolume(bars));
39195
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
39196
+ return {
39197
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39198
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39199
+ };
39200
+ }
39201
+ };
39202
+ var eom = {
39203
+ type: "ease-of-movement",
39204
+ title: "Ease of Movement",
39205
+ shortTitle: "EOM",
39206
+ overlay: false,
39207
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
39208
+ compute: (bars, inputs) => {
39209
+ const div = num3(inputs, "divisor", 1e4);
39210
+ const hl2 = sourceValues(bars, "HL2");
39211
+ const move = change(hl2);
39212
+ const raw = bars.map((b, i) => {
39213
+ const m = move[i];
39214
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
39215
+ return div * m * (b.high - b.low) / b.volume;
39216
+ });
39217
+ return {
39218
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39219
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39220
+ };
39221
+ }
39222
+ };
39223
+ var forceIndex = {
39224
+ type: "force-index",
39225
+ title: "Force Index",
39226
+ shortTitle: "FI",
39227
+ overlay: false,
39228
+ inputs: [lengthInput(13), colorInput()],
39229
+ compute: (bars, inputs) => {
39230
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
39231
+ return {
39232
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39233
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39234
+ };
39235
+ }
39236
+ };
39237
+ var klinger = {
39238
+ type: "klinger-oscillator",
39239
+ title: "Klinger Oscillator",
39240
+ shortTitle: "KVO",
39241
+ overlay: false,
39242
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
39243
+ compute: (bars, inputs) => {
39244
+ const hlc3 = sourceValues(bars, "HLC3");
39245
+ const trend = change(hlc3);
39246
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
39247
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
39248
+ return {
39249
+ plots: [
39250
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
39251
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39252
+ ],
39253
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39254
+ };
39255
+ }
39256
+ };
39257
+ var mfi = {
39258
+ type: "money-flow-index",
39259
+ title: "Money Flow Index",
39260
+ shortTitle: "MFI",
39261
+ overlay: false,
39262
+ inputs: [lengthInput(14), colorInput()],
39263
+ compute: (bars, inputs) => {
39264
+ const len = num3(inputs, "length", 14);
39265
+ const tp = sourceValues(bars, "HLC3");
39266
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
39267
+ const d = change(tp);
39268
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
39269
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
39270
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
39271
+ return {
39272
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39273
+ levels: [
39274
+ { key: "upper", price: 80, color: NEUTRAL },
39275
+ { key: "lower", price: 20, color: NEUTRAL }
39276
+ ]
39277
+ };
39278
+ }
39279
+ };
39280
+ function volumeIndex(bars, when) {
39281
+ const out = new Array(bars.length).fill(Number.NaN);
39282
+ let index = 1e3;
39283
+ let started = false;
39284
+ for (let i = 1; i < bars.length; i++) {
39285
+ const v = bars[i].volume;
39286
+ const pv = bars[i - 1].volume;
39287
+ const pc = bars[i - 1].close;
39288
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
39289
+ if (started) out[i] = index;
39290
+ continue;
39291
+ }
39292
+ started = true;
39293
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
39294
+ out[i] = index;
39295
+ }
39296
+ return out;
39297
+ }
39298
+ var nvi = {
39299
+ type: "negative-volume-index",
39300
+ title: "Negative Volume Index",
39301
+ shortTitle: "Negative Volume Index",
39302
+ overlay: false,
39303
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39304
+ compute: (bars, inputs) => {
39305
+ const line = volumeIndex(bars, (v, pv) => v < pv);
39306
+ return {
39307
+ plots: [
39308
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39309
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39310
+ ]
39311
+ };
39312
+ }
39313
+ };
39314
+ var pvi = {
39315
+ type: "positive-volume-index",
39316
+ title: "Positive Volume Index",
39317
+ shortTitle: "Positive Volume Index",
39318
+ overlay: false,
39319
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39320
+ compute: (bars, inputs) => {
39321
+ const line = volumeIndex(bars, (v, pv) => v > pv);
39322
+ return {
39323
+ plots: [
39324
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39325
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39326
+ ]
39327
+ };
39328
+ }
39329
+ };
39330
+ var pvt = {
39331
+ type: "price-volume-trend",
39332
+ title: "Price Volume Trend",
39333
+ shortTitle: "PVT",
39334
+ overlay: false,
39335
+ inputs: [colorInput()],
39336
+ compute: (bars, inputs) => {
39337
+ const c = closes(bars);
39338
+ const raw = bars.map((b, i) => {
39339
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39340
+ const pc = c[i - 1];
39341
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
39342
+ });
39343
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39344
+ }
39345
+ };
39346
+ var volumeOscillator = {
39347
+ type: "volume-oscillator",
39348
+ title: "Volume Oscillator",
39349
+ shortTitle: "Vol Osc",
39350
+ overlay: false,
39351
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39352
+ compute: (bars, inputs) => {
39353
+ const vol = volumes(bars);
39354
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
39355
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
39356
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39357
+ return {
39358
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39359
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39360
+ };
39361
+ }
39362
+ };
39363
+ var pvo = {
39364
+ type: "pvo",
39365
+ title: "Percentage Volume Oscillator",
39366
+ shortTitle: "PVO",
39367
+ overlay: false,
39368
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
39369
+ compute: (bars, inputs) => {
39370
+ const vol = volumes(bars);
39371
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
39372
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
39373
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39374
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39375
+ const hist = sub(line, signal);
39376
+ return {
39377
+ plots: [
39378
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
39379
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
39380
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39381
+ ],
39382
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39383
+ };
39384
+ }
39385
+ };
39386
+ var vfi = {
39387
+ type: "volume-flow-indicator",
39388
+ title: "Volume Flow Indicator",
39389
+ shortTitle: "Volume Flow Indicator",
39390
+ overlay: false,
39391
+ inputs: [
39392
+ lengthInput(130),
39393
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
39394
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
39395
+ lengthInput(3, "smoothLength", "Smoothing length")
39396
+ ],
39397
+ compute: (bars, inputs) => {
39398
+ const len = num3(inputs, "length", 130);
39399
+ const coef = num3(inputs, "coef", 0.2);
39400
+ const volCoef = num3(inputs, "volCoef", 2.5);
39401
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
39402
+ const inter = change(typical);
39403
+ const vInter = stdev(inter, 30);
39404
+ const vol = volumes(bars);
39405
+ const vAve = sma(vol, len);
39406
+ const mf = change(sourceValues(bars, "HLC3"));
39407
+ const raw = bars.map((b, i) => {
39408
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
39409
+ const v = vol[i];
39410
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39411
+ const m = mf[i];
39412
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
39413
+ const capped = Math.min(v, ave * volCoef);
39414
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
39415
+ });
39416
+ const values = bars.map((_, i) => {
39417
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39418
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
39419
+ });
39420
+ const flowSum = sum(raw, len);
39421
+ const line = zip(flowSum, values, (f, a) => f / a);
39422
+ return {
39423
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
39424
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39425
+ };
39426
+ }
39427
+ };
39428
+ var intradayIntensity = {
39429
+ type: "intraday-intensity",
39430
+ title: "Intraday Intensity",
39431
+ shortTitle: "Intraday Intensity",
39432
+ overlay: false,
39433
+ inputs: [lengthInput(21), colorInput()],
39434
+ compute: (bars, inputs) => {
39435
+ const len = num3(inputs, "length", 21);
39436
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
39437
+ return {
39438
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39439
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39440
+ };
39441
+ }
39442
+ };
39443
+ var volumeSpecs = [
39444
+ obv,
39445
+ accDist,
39446
+ cmf,
39447
+ chaikinOscillator,
39448
+ eom,
39449
+ forceIndex,
39450
+ klinger,
39451
+ mfi,
39452
+ nvi,
39453
+ pvi,
39454
+ pvt,
39455
+ volumeOscillator,
39456
+ pvo,
39457
+ vfi,
39458
+ intradayIntensity
39459
+ ];
39460
+
39461
+ // src/core/native-indicators/classics/overlays.ts
39462
+ var DAY_MS2 = 864e5;
39463
+ function periodKey(anchor, time) {
39464
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
39465
+ if (anchor === "Month") {
39466
+ const d = new Date(time);
39467
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
39468
+ }
39469
+ return Math.floor(time / DAY_MS2);
39470
+ }
39471
+ var parabolicSar = {
39472
+ type: "parabolic-sar",
39473
+ title: "Parabolic SAR",
39474
+ shortTitle: "SAR",
39475
+ overlay: true,
39476
+ inputs: [
39477
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39478
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39479
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
39480
+ ],
39481
+ compute: (bars, inputs) => {
39482
+ const start = num3(inputs, "start", 0.02);
39483
+ const inc = num3(inputs, "increment", 0.02);
39484
+ const max = num3(inputs, "maximum", 0.2);
39485
+ const n = bars.length;
39486
+ const values = new Array(n).fill(Number.NaN);
39487
+ const colors = new Array(n).fill(null);
39488
+ if (n >= 2) {
39489
+ let long = bars[1].close >= bars[0].close;
39490
+ let sar = long ? bars[0].low : bars[0].high;
39491
+ let ep = long ? bars[0].high : bars[0].low;
39492
+ let af = start;
39493
+ for (let i = 1; i < n; i++) {
39494
+ const b = bars[i];
39495
+ sar += af * (ep - sar);
39496
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
39497
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
39498
+ const reversed = long ? b.low < sar : b.high > sar;
39499
+ if (reversed) {
39500
+ long = !long;
39501
+ sar = ep;
39502
+ ep = long ? b.high : b.low;
39503
+ af = start;
39504
+ } else if (long && b.high > ep) {
39505
+ ep = b.high;
39506
+ af = Math.min(max, af + inc);
39507
+ } else if (!long && b.low < ep) {
39508
+ ep = b.low;
39509
+ af = Math.min(max, af + inc);
39510
+ }
39511
+ values[i] = sar;
39512
+ colors[i] = long ? BULLISH : BEARISH;
39513
+ }
39514
+ }
39515
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
39516
+ }
39517
+ };
39518
+ var vwap = {
39519
+ type: "vwap",
39520
+ title: "Volume Weighted Average Price",
39521
+ shortTitle: "VWAP",
39522
+ overlay: true,
39523
+ inputs: [
39524
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
39525
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
39526
+ colorInput(INFO)
39527
+ ],
39528
+ compute: (bars, inputs) => {
39529
+ const anchor = str3(inputs, "anchor", "Day");
39530
+ const source = str3(inputs, "source", "HLC3");
39531
+ const values = new Array(bars.length).fill(Number.NaN);
39532
+ let period = Number.NaN;
39533
+ let cumPV = 0;
39534
+ let cumV = 0;
39535
+ for (let i = 0; i < bars.length; i++) {
39536
+ const b = bars[i];
39537
+ const key = periodKey(anchor, b.time);
39538
+ if (key !== period) {
39539
+ period = key;
39540
+ cumPV = 0;
39541
+ cumV = 0;
39542
+ }
39543
+ const v = b.volume;
39544
+ if (v != null && Number.isFinite(v) && v > 0) {
39545
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
39546
+ cumPV += tp * v;
39547
+ cumV += v;
39548
+ }
39549
+ if (cumV > 0) values[i] = cumPV / cumV;
39550
+ }
39551
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
39552
+ }
39553
+ };
39554
+ var pivotPoints = {
39555
+ type: "pivot-points",
39556
+ title: "Pivot Points",
39557
+ shortTitle: "Pivots",
39558
+ overlay: true,
39559
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
39560
+ compute: (bars, inputs) => {
39561
+ const anchor = str3(inputs, "anchor", "Day");
39562
+ const aggs = /* @__PURE__ */ new Map();
39563
+ for (const b of bars) {
39564
+ const key = periodKey(anchor, b.time);
39565
+ const a = aggs.get(key);
39566
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
39567
+ else {
39568
+ a.high = Math.max(a.high, b.high);
39569
+ a.low = Math.min(a.low, b.low);
39570
+ a.close = b.close;
39571
+ }
39572
+ }
39573
+ const n = bars.length;
39574
+ const mk = () => new Array(n).fill(Number.NaN);
39575
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
39576
+ for (let i = 0; i < n; i++) {
39577
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
39578
+ if (!prev2) continue;
39579
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
39580
+ levels.p[i] = p;
39581
+ levels.r1[i] = 2 * p - prev2.low;
39582
+ levels.s1[i] = 2 * p - prev2.high;
39583
+ levels.r2[i] = p + (prev2.high - prev2.low);
39584
+ levels.s2[i] = p - (prev2.high - prev2.low);
39585
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
39586
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
39587
+ }
39588
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
39589
+ return {
39590
+ plots: [
39591
+ plot("p", "P", WARNING),
39592
+ plot("r1", "R1", BEARISH),
39593
+ plot("s1", "S1", BULLISH),
39594
+ plot("r2", "R2", BEARISH),
39595
+ plot("s2", "S2", BULLISH),
39596
+ plot("r3", "R3", BEARISH),
39597
+ plot("s3", "S3", BULLISH)
39598
+ ]
39599
+ };
39600
+ }
39601
+ };
39602
+ var fiftyTwoWeek = {
39603
+ type: "52-week-high-low",
39604
+ title: "52 Week High/Low",
39605
+ shortTitle: "52W H/L",
39606
+ overlay: true,
39607
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
39608
+ compute: (bars, inputs) => {
39609
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
39610
+ const n = bars.length;
39611
+ const hi = new Array(n).fill(Number.NaN);
39612
+ const lo = new Array(n).fill(Number.NaN);
39613
+ const maxIdx = [];
39614
+ const minIdx = [];
39615
+ let from = 0;
39616
+ for (let i = 0; i < n; i++) {
39617
+ const b = bars[i];
39618
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
39619
+ maxIdx.push(i);
39620
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
39621
+ minIdx.push(i);
39622
+ while (bars[from].time < b.time - span) from += 1;
39623
+ while (maxIdx[0] < from) maxIdx.shift();
39624
+ while (minIdx[0] < from) minIdx.shift();
39625
+ hi[i] = bars[maxIdx[0]].high;
39626
+ lo[i] = bars[minIdx[0]].low;
39627
+ }
39628
+ return {
39629
+ plots: [
39630
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
39631
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
39632
+ ]
39633
+ };
39634
+ }
39635
+ };
39636
+ var zigzag = {
39637
+ type: "zigzag",
39638
+ title: "ZigZag",
39639
+ overlay: true,
39640
+ inputs: [
39641
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
39642
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39643
+ colorInput()
39644
+ ],
39645
+ compute: (bars, inputs) => {
39646
+ const dev = num3(inputs, "deviation", 5) / 100;
39647
+ const depth = num3(inputs, "depth", 10);
39648
+ const pivots = [];
39649
+ if (bars.length > 1) {
39650
+ let up = bars[1].close >= bars[0].close;
39651
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
39652
+ for (let i = 1; i < bars.length; i++) {
39653
+ const b = bars[i];
39654
+ if (up) {
39655
+ if (b.high >= ext.price) ext = { i, price: b.high };
39656
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39657
+ pivots.push(ext);
39658
+ up = false;
39659
+ ext = { i, price: b.low };
39660
+ }
39661
+ } else if (b.low <= ext.price) {
39662
+ ext = { i, price: b.low };
39663
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39664
+ pivots.push(ext);
39665
+ up = true;
39666
+ ext = { i, price: b.high };
39667
+ }
39668
+ }
39669
+ pivots.push(ext);
39670
+ }
39671
+ return {
39672
+ plots: [],
39673
+ polylines: [
39674
+ {
39675
+ key: "zigzag",
39676
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
39677
+ curved: false,
39678
+ closed: false,
39679
+ lineColor: str3(inputs, "color", SERIES_LINE),
39680
+ lineWidth: 2,
39681
+ lineStyle: "solid",
39682
+ arrowLeft: false,
39683
+ arrowRight: false
39684
+ }
39685
+ ]
39686
+ };
39687
+ }
39688
+ };
39689
+ var williamsFractal = {
39690
+ type: "williams-fractal",
39691
+ title: "Williams Fractal",
39692
+ shortTitle: "Fractals",
39693
+ overlay: true,
39694
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
39695
+ compute: (bars, inputs) => {
39696
+ const p = num3(inputs, "periods", 2);
39697
+ const markers = [];
39698
+ const isExtreme = (i, pick, better) => {
39699
+ const v = pick(bars[i]);
39700
+ for (let k = i - p; k <= i + p; k++) {
39701
+ if (k === i) continue;
39702
+ if (!better(v, pick(bars[k]))) return false;
39703
+ }
39704
+ return true;
39705
+ };
39706
+ for (let i = p; i < bars.length - p; i++) {
39707
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
39708
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
39709
+ }
39710
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
39711
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
39712
+ }
39713
+ }
39714
+ return { plots: [], markers };
39715
+ }
39716
+ };
39717
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
39718
+
39719
+ // src/core/native-indicators/classics/index.ts
39720
+ var classicSpecs = [
39721
+ ...averageSpecs,
39722
+ ...bandSpecs,
39723
+ ...oscillatorSpecs,
39724
+ ...trendSpecs,
39725
+ ...volatilitySpecs,
39726
+ ...volumeSpecs,
39727
+ ...overlaySpecs
39728
+ ];
39729
+ function registerClassicIndicators() {
39730
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
39731
+ }
39732
+
37327
39733
  // src/Vela.ts
37328
39734
  var Vela = class {
37329
39735
  constructor(container, options = {}, deps = {}) {
37330
39736
  registerBuiltinChartTypes();
37331
39737
  registerVolume();
37332
39738
  registerVpvr();
39739
+ registerClassicIndicators();
37333
39740
  const element = resolveElement(container);
37334
39741
  const theme = resolveTheme(options.theme);
37335
- let animZoom = true;
37336
- let animPan = true;
37337
- if (typeof options.animations === "boolean") {
37338
- animZoom = options.animations;
37339
- animPan = options.animations;
37340
- } else if (options.animations) {
37341
- animZoom = options.animations.zoom ?? true;
37342
- animPan = options.animations.pan ?? true;
37343
- }
37344
39742
  const display = {
37345
39743
  currentPriceLine: options.currentPriceLine ?? true,
37346
39744
  logScale: options.logScale ?? false,
37347
39745
  nativeBackend: options.nativeBackend ?? "auto",
37348
- animZoom,
37349
- animPan,
39746
+ ...resolveAnimations(options.animations),
37350
39747
  glow: options.glow ?? 0,
37351
39748
  upColor: options.upColor ?? BULLISH,
37352
39749
  downColor: options.downColor ?? BEARISH,
@@ -37404,25 +39801,28 @@ var Vela = class {
37404
39801
  }
37405
39802
  /**
37406
39803
  * 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.
39804
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
39805
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
39806
+ * call returns the existing handle; the classic studies allow several instances, so each call
39807
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
37410
39808
  */
37411
39809
  addNativeIndicator(type, options) {
37412
39810
  return this.orchestrator.addNativeIndicator(type, options);
37413
39811
  }
37414
39812
  /**
37415
39813
  * 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).
39814
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
39815
+ * and whether it allows several instances (`multiInstance`; otherwise a second
39816
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
39817
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
39818
+ * provider (a type may need data the symbol lacks).
37420
39819
  */
37421
39820
  availableNativeIndicators() {
37422
39821
  return this.orchestrator.availableNativeIndicators();
37423
39822
  }
37424
39823
  /**
37425
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
39824
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
39825
+ * multi-instance type repeats) — the synchronous slice of
37426
39826
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
37427
39827
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
37428
39828
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -37658,9 +40058,6 @@ function resolveElement(container) {
37658
40058
  return element;
37659
40059
  }
37660
40060
 
37661
- // src/core/options.ts
37662
- var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
37663
-
37664
40061
  // src/widget/statusline-model.ts
37665
40062
  function segmentVisibility(parts, chartHidden = false) {
37666
40063
  return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
@@ -38165,7 +40562,7 @@ var Statusline = class {
38165
40562
  };
38166
40563
 
38167
40564
  // src/widget/session-shading.ts
38168
- var DAY_MS2 = 864e5;
40565
+ var DAY_MS3 = 864e5;
38169
40566
  var MINUTE_MS = 6e4;
38170
40567
  function parseWindow(text, allowOvernight = false) {
38171
40568
  if (typeof text !== "string") return null;
@@ -38244,7 +40641,7 @@ function expandSessionZones(spec, from, to) {
38244
40641
  const dtf = civilFormatter(spec.timezone);
38245
40642
  if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
38246
40643
  const seen = /* @__PURE__ */ new Set();
38247
- for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
40644
+ for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
38248
40645
  const civil = civilParts(dtf, cursor);
38249
40646
  const key = civil.year * 1e4 + civil.month * 100 + civil.day;
38250
40647
  if (!Number.isFinite(key) || seen.has(key)) continue;
@@ -38269,7 +40666,7 @@ function expandSessionZones(spec, from, to) {
38269
40666
  }
38270
40667
  return { pre, post, extended: mergeAbutting(extended) };
38271
40668
  }
38272
- var COVER_PAD_MIN_MS = 2 * DAY_MS2;
40669
+ var COVER_PAD_MIN_MS = 2 * DAY_MS3;
38273
40670
  var SessionShadingTracker = class {
38274
40671
  constructor(onZones) {
38275
40672
  this.onZones = onZones;
@@ -38293,7 +40690,7 @@ var SessionShadingTracker = class {
38293
40690
  this.covered = null;
38294
40691
  this.session = opts.session;
38295
40692
  const tfMs = timeframeMs(opts.timeframe);
38296
- this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
40693
+ this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
38297
40694
  this.lastRange = opts.range;
38298
40695
  void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
38299
40696
  if (my !== this.epoch) return;
@@ -38967,8 +41364,8 @@ var ChartCell = class {
38967
41364
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
38968
41365
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
38969
41366
  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. */
41367
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
41368
+ * looks the removed id up here to learn which type an undo must re-add. */
38972
41369
  this.presentNatives = [];
38973
41370
  this.rangeBars = 0;
38974
41371
  this.pendingRange = null;
@@ -39140,15 +41537,18 @@ var ChartCell = class {
39140
41537
  redo: () => this.dropInstance(snapshot)
39141
41538
  });
39142
41539
  } else {
39143
- const now = this.inner?.presentNativeIndicators() ?? [];
39144
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
41540
+ const gone = this.presentNatives.find((n) => n.id === id2);
41541
+ if (gone) {
41542
+ const { type } = gone;
41543
+ let revived = null;
39145
41544
  this.history.push({
39146
41545
  undo: () => {
39147
- this.inner?.addNativeIndicator(type);
41546
+ revived = this.inner?.addNativeIndicator(type) ?? null;
39148
41547
  this.refreshNativeCatalog();
39149
41548
  },
39150
41549
  redo: () => {
39151
- this.inner?.addNativeIndicator(type).remove();
41550
+ revived?.remove();
41551
+ revived = null;
39152
41552
  this.refreshNativeCatalog();
39153
41553
  }
39154
41554
  });
@@ -39453,9 +41853,9 @@ var ChartCell = class {
39453
41853
  const live = this.inner?.renderer.get("priceStyle");
39454
41854
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
39455
41855
  }
39456
- /** Manifest instances + present natives — the topbar indicator count. */
41856
+ /** Manifest instances + native instances — the topbar indicator count. */
39457
41857
  get indicatorCount() {
39458
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
41858
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
39459
41859
  }
39460
41860
  /** Switch this cell's market in place (the chart instance survives). The projection
39461
41861
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -39587,13 +41987,15 @@ var ChartCell = class {
39587
41987
  if (!chart) return;
39588
41988
  this.volumeIntent = led.natives.includes("volume");
39589
41989
  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
- }
41990
+ const owed = /* @__PURE__ */ new Map();
41991
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
41992
+ for (const h of this.nativeHandles()) {
41993
+ const type = h.nativeType;
41994
+ const n = owed.get(type) ?? 0;
41995
+ if (n > 0) owed.set(type, n - 1);
41996
+ else h.remove();
41997
+ }
41998
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
39597
41999
  for (const it of [...this.instances]) this.dropInstance(it);
39598
42000
  if (this.manifest.length > 0) {
39599
42001
  for (const item of led.manifest) {
@@ -39611,34 +42013,42 @@ var ChartCell = class {
39611
42013
  this.syncPresentNatives();
39612
42014
  this.refreshNativeCatalog();
39613
42015
  }
39614
- /** The picker's library rows: supported natives first, then the manifest. */
42016
+ /** The supported natives in picker order: A→Z by title — registration order follows
42017
+ * the catalog's families, which is meaningless to the reader. This is the library
42018
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
42019
+ * read it — indexing the unsorted catalog on add would land on a different study. */
42020
+ supportedNatives() {
42021
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
42022
+ }
42023
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
42024
+ * then the manifest in the host's own order. */
39615
42025
  libraryRows() {
39616
42026
  return [
39617
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
42027
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
39618
42028
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
39619
42029
  ];
39620
42030
  }
39621
- /** The picker's on-chart rows: present natives first, then live instances. */
42031
+ /** The picker's on-chart rows: native instances first, then live script instances. */
39622
42032
  onChartRows() {
39623
42033
  return [
39624
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
42034
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
39625
42035
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
39626
42036
  ];
39627
42037
  }
39628
42038
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
39629
42039
  addFromLibrary(index) {
39630
- const natives = this.nativeCatalog.filter((n) => n.supported);
42040
+ const natives = this.supportedNatives();
39631
42041
  if (index < natives.length) this.addNative(natives[index].type);
39632
42042
  else {
39633
42043
  const entry = this.manifest[index - natives.length];
39634
42044
  if (entry) this.addManifestInstance(entry);
39635
42045
  }
39636
42046
  }
39637
- /** Remove by picker ON-CHART index (present natives precede instances). */
42047
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
39638
42048
  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);
42049
+ const natives = this.nativeHandles();
42050
+ if (index < natives.length) this.removeNative(natives[index]);
42051
+ else this.removeInstance(index - natives.length);
39642
42052
  }
39643
42053
  /**
39644
42054
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -39698,30 +42108,42 @@ var ChartCell = class {
39698
42108
  it.handle = null;
39699
42109
  this.deps.onIndicatorsChanged(this.id);
39700
42110
  }
39701
- /** Add a native indicator (single-instance per type — the core dedupes). */
42111
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
42112
+ * single-instance type already on the chart hands back its existing one — nothing
42113
+ * changed, so nothing enters the undo timeline. */
39702
42114
  addNative(type) {
39703
- this.inner?.addNativeIndicator(type);
42115
+ const chart = this.inner;
42116
+ if (!chart) return;
42117
+ const before = new Set(chart.indicators().map((h) => h.id));
42118
+ let added = chart.addNativeIndicator(type);
39704
42119
  this.syncPresentNatives();
39705
42120
  this.refreshNativeCatalog();
42121
+ if (before.has(added.id)) return;
39706
42122
  this.history.push({
39707
42123
  undo: () => {
39708
- this.inner?.addNativeIndicator(type).remove();
42124
+ added?.remove();
42125
+ added = null;
39709
42126
  this.refreshNativeCatalog();
39710
42127
  },
39711
42128
  redo: () => {
39712
- this.inner?.addNativeIndicator(type);
42129
+ added = this.inner?.addNativeIndicator(type) ?? null;
39713
42130
  this.refreshNativeCatalog();
39714
42131
  }
39715
42132
  });
39716
42133
  }
39717
- removeNative(type) {
39718
- this.inner?.addNativeIndicator(type).remove();
42134
+ removeNative(handle) {
42135
+ handle.remove();
39719
42136
  this.refreshNativeCatalog();
39720
42137
  }
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. */
42138
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
42139
+ * the removal index space (script instances follow them). */
42140
+ nativeHandles() {
42141
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
42142
+ }
42143
+ /** Sync mirror of the chart's native instances — the removal handler looks the
42144
+ * removed id up here (the registry has already forgotten it) to record its type. */
39723
42145
  syncPresentNatives() {
39724
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
42146
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
39725
42147
  }
39726
42148
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
39727
42149
  refreshNativeCatalog() {