@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
@@ -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();
@@ -5113,9 +5121,9 @@ function factoryResetConfig(factory) {
5113
5121
  if (inst.enableKey) defaults2[inst.enableKey] = false;
5114
5122
  addRows(inst.rows);
5115
5123
  }
5116
- for (const sub of section.subsections ?? []) {
5117
- if (sub.enableKey) defaults2[sub.enableKey] = false;
5118
- addRows(sub.rows);
5124
+ for (const sub2 of section.subsections ?? []) {
5125
+ if (sub2.enableKey) defaults2[sub2.enableKey] = false;
5126
+ addRows(sub2.rows);
5119
5127
  }
5120
5128
  if (!section.instances) addRows(section.rows);
5121
5129
  bag[t.id] = defaults2;
@@ -5184,7 +5192,8 @@ function mergeConfig(base, patch) {
5184
5192
  labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
5185
5193
  currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
5186
5194
  priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
5187
- countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
5195
+ countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
5196
+ animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
5188
5197
  },
5189
5198
  panes: {
5190
5199
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
@@ -14031,17 +14040,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
14031
14040
  }
14032
14041
  var STYLE_ID7 = "vela-widget-symbolpicker";
14033
14042
  var CSS8 = `
14034
- /* Search + market tabs stay pinned while the result list scrolls underneath
14035
- (mobile fullscreen: the dialog body is the scroller). Negative margin eats
14036
- the body's padding so scrolled rows cannot peek around the island. */
14037
- .vela-sp-sticky {
14038
- position: sticky;
14039
- top: 0;
14040
- z-index: 1;
14041
- background: var(--vela-surface);
14042
- margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
14043
- padding: var(--vela-space-4) var(--vela-space-4) 0;
14044
- }
14043
+ /* The dialog body is flush (a flex column): search + market tabs are a fixed head
14044
+ and the result list is the ONLY scroller, in both layouts. A sticky head over a
14045
+ scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
14046
+ scroller's content box, so the head lands one padding below the top and covers
14047
+ the first row. */
14048
+ .vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
14045
14049
  .vela-sp-searchrow {
14046
14050
  display: flex;
14047
14051
  align-items: center;
@@ -14066,12 +14070,13 @@ var CSS8 = `
14066
14070
  .vela-sp-input::placeholder { text-transform: none; }
14067
14071
  .vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
14068
14072
  /* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
14069
- overflowing the body, and the result list stops capping itself \u2014 the body owns
14070
- the scrolling in the fullscreen presentation. */
14073
+ overflowing the body, and the result list stops capping itself \u2014 it fills the
14074
+ rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
14075
+ dialog body carries it, flush or not, so the list adds none of its own. */
14071
14076
  [data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
14072
14077
  [data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
14073
14078
  [data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
14074
- [data-layout='mobile'] .vela-sp-list { max-height: none; }
14079
+ [data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
14075
14080
  .vela-sp-tab {
14076
14081
  all: unset;
14077
14082
  padding: 3px 10px;
@@ -14083,7 +14088,12 @@ var CSS8 = `
14083
14088
  }
14084
14089
  .vela-sp-tab:hover { color: var(--vela-fg); }
14085
14090
  .vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
14086
- .vela-sp-list { margin-top: var(--vela-space-2); max-height: 46vh; overflow: auto; }
14091
+ .vela-sp-list {
14092
+ min-height: 0;
14093
+ margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
14094
+ max-height: 46vh;
14095
+ overflow: auto;
14096
+ }
14087
14097
  .vela-sp-list::-webkit-scrollbar { width: 8px; }
14088
14098
  .vela-sp-list::-webkit-scrollbar-thumb {
14089
14099
  background: var(--vela-scroll);
@@ -14176,8 +14186,8 @@ var SymbolPicker = class {
14176
14186
  searchRow.append(iconEl("search", doc), this.input);
14177
14187
  this.tabs = doc.createElement("div");
14178
14188
  this.tabs.className = "vela-sp-tabs";
14179
- const sticky = doc.createElement("div");
14180
- sticky.className = "vela-sp-sticky";
14189
+ const head = doc.createElement("div");
14190
+ head.className = "vela-sp-head";
14181
14191
  for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
14182
14192
  const b = doc.createElement("button");
14183
14193
  b.className = "vela-sp-tab";
@@ -14191,7 +14201,7 @@ var SymbolPicker = class {
14191
14201
  });
14192
14202
  this.tabs.appendChild(b);
14193
14203
  }
14194
- sticky.append(searchRow, this.tabs);
14204
+ head.append(searchRow, this.tabs);
14195
14205
  this.list = doc.createElement("div");
14196
14206
  this.list.className = "vela-sp-list";
14197
14207
  this.list.addEventListener("scroll", () => {
@@ -14204,7 +14214,8 @@ var SymbolPicker = class {
14204
14214
  title: "Symbol Search",
14205
14215
  host: opts.host,
14206
14216
  closeOnInteractOutside: true,
14207
- content: (body) => body.append(sticky, this.list),
14217
+ flush: true,
14218
+ content: (body) => body.append(head, this.list),
14208
14219
  onOpenChange: (open2) => {
14209
14220
  if (open2) {
14210
14221
  this.input.value = this.seed.toUpperCase();
@@ -15960,11 +15971,11 @@ var PriceScaleDrawer = class {
15960
15971
  for (const item of items) {
15961
15972
  if (item.id === "labels" && item.submenu) {
15962
15973
  section("Labels");
15963
- for (const sub of item.submenu) {
15964
- row(sub.label, {
15965
- checked: sub.checked,
15974
+ for (const sub2 of item.submenu) {
15975
+ row(sub2.label, {
15976
+ checked: sub2.checked,
15966
15977
  onClick: () => {
15967
- const feature = sub.id.slice("toggle:".length);
15978
+ const feature = sub2.id.slice("toggle:".length);
15968
15979
  chart.renderer.set(feature, !this.flag(feature));
15969
15980
  this.render();
15970
15981
  }
@@ -15974,11 +15985,11 @@ var PriceScaleDrawer = class {
15974
15985
  }
15975
15986
  if (item.id === "levels" && item.submenu) {
15976
15987
  section("Levels");
15977
- for (const sub of item.submenu) {
15978
- row(sub.label, {
15979
- checked: sub.checked,
15988
+ for (const sub2 of item.submenu) {
15989
+ row(sub2.label, {
15990
+ checked: sub2.checked,
15980
15991
  onClick: () => {
15981
- const feature = sub.id.slice("toggle:".length);
15992
+ const feature = sub2.id.slice("toggle:".length);
15982
15993
  chart.renderer.set(feature, !this.flag(feature));
15983
15994
  this.render();
15984
15995
  }
@@ -16552,8 +16563,8 @@ var DrawingController = class {
16552
16563
  this.port?.setExternalGhost?.(doc);
16553
16564
  }
16554
16565
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
16555
- setToolShortcuts(map) {
16556
- this.port?.setToolShortcuts?.(map);
16566
+ setToolShortcuts(map2) {
16567
+ this.port?.setToolShortcuts?.(map2);
16557
16568
  }
16558
16569
  // ── programmatic CRUD (facade-facing) ── each is one undo step
16559
16570
  add(type, init = {}) {
@@ -17117,6 +17128,23 @@ function attachChromeTooltip(anchor, opts) {
17117
17128
  clear();
17118
17129
  };
17119
17130
  }
17131
+ function chromeHint(text, opts) {
17132
+ const px = opts.size ?? 16;
17133
+ const el = document.createElement("span");
17134
+ el.className = "vela-hint";
17135
+ el.setAttribute("role", "img");
17136
+ el.setAttribute("aria-label", text);
17137
+ el.innerHTML = iconAt("info", px);
17138
+ 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;`;
17139
+ const hover = (on) => {
17140
+ el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
17141
+ el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
17142
+ };
17143
+ el.addEventListener("pointerenter", () => hover(true));
17144
+ el.addEventListener("pointerleave", () => hover(false));
17145
+ const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
17146
+ return { el, dispose };
17147
+ }
17120
17148
 
17121
17149
  // src/renderers/native/drawings/DrawingToolbar.ts
17122
17150
  var TOOLBAR_WIDTH = 44;
@@ -17224,8 +17252,8 @@ var DrawingToolbar = class {
17224
17252
  }
17225
17253
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
17226
17254
  * right edge of the flyout rows, beside the favorite star. */
17227
- setShortcuts(map) {
17228
- this.shortcuts = new Map(Object.entries(map));
17255
+ setShortcuts(map2) {
17256
+ this.shortcuts = new Map(Object.entries(map2));
17229
17257
  }
17230
17258
  /** Paint one star for the current favorite state (filled + gold, or outline). */
17231
17259
  paintStar(el, type) {
@@ -18122,6 +18150,24 @@ function localStorageAdapter(storageKey) {
18122
18150
  };
18123
18151
  }
18124
18152
 
18153
+ // src/core/options.ts
18154
+ var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
18155
+ var LIVE_BAR_EASE_DEFAULT_MS = 90;
18156
+ var LIVE_BAR_EASE_MAX_MS = 1e3;
18157
+ function resolveLiveBarEaseMs(value) {
18158
+ if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
18159
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
18160
+ return Math.min(value, LIVE_BAR_EASE_MAX_MS);
18161
+ }
18162
+ function resolveAnimations(animations) {
18163
+ if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
18164
+ return {
18165
+ animZoom: animations?.zoom ?? true,
18166
+ animPan: animations?.pan ?? true,
18167
+ animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
18168
+ };
18169
+ }
18170
+
18125
18171
  // src/core/model/series.ts
18126
18172
  function isLineLikeSeries(spec) {
18127
18173
  return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
@@ -18186,7 +18232,7 @@ var LiveSession = class {
18186
18232
 
18187
18233
  // src/core/engine/IndicatorHandleImpl.ts
18188
18234
  var IndicatorHandleImpl = class {
18189
- constructor(id, title, controller, source) {
18235
+ constructor(id, title, controller, source, nativeType) {
18190
18236
  this.controller = controller;
18191
18237
  this.schema = [];
18192
18238
  this.propsSchema = [];
@@ -18195,6 +18241,7 @@ var IndicatorHandleImpl = class {
18195
18241
  this.id = id;
18196
18242
  this.title = title;
18197
18243
  if (source !== void 0) this.source = source;
18244
+ if (nativeType !== void 0) this.nativeType = nativeType;
18198
18245
  }
18199
18246
  get inputs() {
18200
18247
  return this.schema;
@@ -18285,24 +18332,24 @@ function summarizeModel(model) {
18285
18332
  }
18286
18333
  function inspectModels(models) {
18287
18334
  const indicators = models.map(summarizeModel);
18288
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18335
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
18289
18336
  return {
18290
18337
  indicators,
18291
18338
  totals: {
18292
18339
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
18293
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18294
- fills: sum((s) => s.fills),
18295
- backgrounds: sum((s) => s.backgrounds),
18296
- priceLines: sum((s) => s.priceLines),
18297
- lines: sum((s) => s.lines),
18298
- boxes: sum((s) => s.boxes),
18299
- labels: sum((s) => s.labels),
18300
- polylines: sum((s) => s.polylines),
18301
- linefills: sum((s) => s.linefills),
18302
- tables: sum((s) => s.tables),
18303
- barColors: sum((s) => s.barColors),
18304
- trades: sum((s) => s.trades),
18305
- forcedOverlay: sum((s) => s.forcedOverlay)
18340
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
18341
+ fills: sum2((s) => s.fills),
18342
+ backgrounds: sum2((s) => s.backgrounds),
18343
+ priceLines: sum2((s) => s.priceLines),
18344
+ lines: sum2((s) => s.lines),
18345
+ boxes: sum2((s) => s.boxes),
18346
+ labels: sum2((s) => s.labels),
18347
+ polylines: sum2((s) => s.polylines),
18348
+ linefills: sum2((s) => s.linefills),
18349
+ tables: sum2((s) => s.tables),
18350
+ barColors: sum2((s) => s.barColors),
18351
+ trades: sum2((s) => s.trades),
18352
+ forcedOverlay: sum2((s) => s.forcedOverlay)
18306
18353
  }
18307
18354
  };
18308
18355
  }
@@ -19371,16 +19418,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
19371
19418
  /**
19372
19419
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
19373
19420
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
19374
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
19375
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
19421
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
19422
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
19423
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
19424
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
19376
19425
  */
19377
19426
  addNativeIndicator(type, options = {}) {
19378
- const existing = this.registry.all().find((r) => r.native?.type === type);
19379
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
19380
19427
  const descriptor = getNativeIndicator(type);
19428
+ if (!descriptor?.multiInstance) {
19429
+ const existing = this.registry.all().find((r) => r.native?.type === type);
19430
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19431
+ }
19381
19432
  const id = this.registry.nextId("native");
19382
19433
  const title = descriptor?.title ?? type;
19383
- const handle = new IndicatorHandleImpl(id, title, this);
19434
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19384
19435
  this.handles.set(id, handle);
19385
19436
  if (!descriptor) {
19386
19437
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -19392,16 +19443,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
19392
19443
  void this.startNativeIndicator(id, handle);
19393
19444
  return handle;
19394
19445
  }
19395
- /**
19396
- * The catalog of registered native-indicator types with their live state on THIS chart: each
19397
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
19398
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
19399
- * because a descriptor's `isSupported` may probe the provider for a required capability.
19400
- */
19401
- /** The native types present on the chart — SYNC (registry state; no support probe). */
19446
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
19447
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
19402
19448
  presentNativeIndicators() {
19403
19449
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
19404
19450
  }
19451
+ /**
19452
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
19453
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
19454
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
19455
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
19456
+ * probe the provider for a required capability.
19457
+ */
19405
19458
  async availableNativeIndicators() {
19406
19459
  const symbol = this.qualifiedSymbol();
19407
19460
  const present = new Set(this.presentNativeIndicators());
@@ -19411,7 +19464,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
19411
19464
  title: d.title,
19412
19465
  supported: await this.isNativeSupported(d, symbol),
19413
19466
  present: present.has(d.type),
19414
- beta: d.beta
19467
+ beta: d.beta,
19468
+ ...d.multiInstance ? { multiInstance: true } : {}
19415
19469
  }))
19416
19470
  );
19417
19471
  }
@@ -20551,8 +20605,8 @@ var DrawingsControl = class {
20551
20605
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
20552
20606
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
20553
20607
  * platform formatting, so hints always match the host's actual bindings. */
20554
- setToolShortcuts(map) {
20555
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
20608
+ setToolShortcuts(map2) {
20609
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
20556
20610
  return this;
20557
20611
  }
20558
20612
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -21079,14 +21133,11 @@ var IndicatorInputsDialog = class {
21079
21133
  buildTextField(id, current, emit) {
21080
21134
  return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
21081
21135
  }
21082
- /** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
21136
+ /** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
21083
21137
  infoButton(tooltip2) {
21084
- const b = document.createElement("span");
21085
- b.textContent = "i";
21086
- this.dialogTips.push(attachChromeTooltip(b, { host: this.host.container, theme: () => this.host.theme(), text: () => tooltip2, wrap: true, delayMs: 250 }));
21087
- b.className = "vela-ind-hint";
21088
- 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;";
21089
- return b;
21138
+ const { el, dispose } = chromeHint(tooltip2, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
21139
+ this.dialogTips.push(dispose);
21140
+ return el;
21090
21141
  }
21091
21142
  /** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
21092
21143
  select(id, options, current, emit) {
@@ -21523,8 +21574,6 @@ function ensureDialogStyles() {
21523
21574
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21524
21575
  .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;}
21525
21576
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
21526
- .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;}
21527
- .vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
21528
21577
  .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;}
21529
21578
  .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;}
21530
21579
  .vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
@@ -22033,8 +22082,8 @@ var InputsUI = class {
22033
22082
  lg.style.top = `${top}px`;
22034
22083
  }
22035
22084
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
22036
- setCollapsedPanes(map) {
22037
- this.paneCollapse = map;
22085
+ setCollapsedPanes(map2) {
22086
+ this.paneCollapse = map2;
22038
22087
  }
22039
22088
  /**
22040
22089
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -24043,11 +24092,11 @@ function mergeBarColors(indicators) {
24043
24092
  }
24044
24093
  }
24045
24094
  if (!any) return EMPTY_BARCOLORS;
24046
- const map = /* @__PURE__ */ new Map();
24095
+ const map2 = /* @__PURE__ */ new Map();
24047
24096
  for (const m of indicators.values()) {
24048
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
24097
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
24049
24098
  }
24050
- return map;
24099
+ return map2;
24051
24100
  }
24052
24101
 
24053
24102
  // src/renderers/native/core/ViewportState.ts
@@ -24344,9 +24393,9 @@ function isHorizontalWheel(deltaX, deltaY) {
24344
24393
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
24345
24394
  return rightOffset + deltaX / barSpacing;
24346
24395
  }
24347
- function wheelPanDelta(deltaX, deltaY, shift) {
24396
+ function wheelPanDelta(deltaX, deltaY, shift2) {
24348
24397
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
24349
- if (shift) return deltaY;
24398
+ if (shift2) return deltaY;
24350
24399
  return null;
24351
24400
  }
24352
24401
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -25968,11 +26017,11 @@ function mergeBarColors2(indicators) {
25968
26017
  }
25969
26018
  }
25970
26019
  if (!any) return EMPTY_BARCOLORS2;
25971
- const map = /* @__PURE__ */ new Map();
26020
+ const map2 = /* @__PURE__ */ new Map();
25972
26021
  for (const m of indicators.values()) {
25973
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
26022
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
25974
26023
  }
25975
- return map;
26024
+ return map2;
25976
26025
  }
25977
26026
 
25978
26027
  // src/renderers/shared/drawing-geometry.ts
@@ -27570,6 +27619,8 @@ var BUILTIN_SETTINGS_IDS = [
27570
27619
  "symbol.style.baseline.fill-bottom",
27571
27620
  "symbol.style.baseline.base-level",
27572
27621
  "symbol.style.baseline.width",
27622
+ "symbol.animation",
27623
+ "symbol.animation.price-changes",
27573
27624
  "symbol.timezone",
27574
27625
  "scales",
27575
27626
  "scales.price-scale",
@@ -27611,9 +27662,9 @@ function settingsIdCatalog(hostSections) {
27611
27662
  };
27612
27663
  if (section.rows) addRows(section.rows);
27613
27664
  for (const inst of section.instances ?? []) addRows(inst.rows);
27614
- for (const sub of section.subsections ?? []) {
27615
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
27616
- addRows(sub.rows);
27665
+ for (const sub2 of section.subsections ?? []) {
27666
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
27667
+ addRows(sub2.rows);
27617
27668
  }
27618
27669
  }
27619
27670
  for (const hs of hostSections) {
@@ -27712,8 +27763,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
27712
27763
  .vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
27713
27764
  .vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
27714
27765
  .vela-sd-mobile .vela-sd-btn{height:38px;}
27715
- .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
27716
- `;
27766
+ .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
27717
27767
  if (!existing) document.head.appendChild(st);
27718
27768
  }
27719
27769
  var SettingsDialog = class {
@@ -27734,6 +27784,8 @@ var SettingsDialog = class {
27734
27784
  this.themeControl = null;
27735
27785
  /** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
27736
27786
  this.tabs = [];
27787
+ this.hintTips = [];
27788
+ // ⓘ tooltip disposers of the open build (see hint)
27737
27789
  /** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
27738
27790
  this.typeActiveInstance = /* @__PURE__ */ new Map();
27739
27791
  /** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
@@ -27922,6 +27974,13 @@ var SettingsDialog = class {
27922
27974
  body.append(g);
27923
27975
  }
27924
27976
  showActive(config.series.style);
27977
+ body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
27978
+ body.append(sid(this.boolRow(
27979
+ "Animate price changes",
27980
+ config.priceScale.animateLastPrice,
27981
+ (v) => this.emit({ priceScale: { animateLastPrice: v } }),
27982
+ this.hint("Glide the live bar to each new price instead of snapping.")
27983
+ ), "symbol.animation.price-changes"));
27925
27984
  body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
27926
27985
  body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
27927
27986
  const renderHostSections = (placement) => {
@@ -28129,6 +28188,8 @@ var SettingsDialog = class {
28129
28188
  this.ui = null;
28130
28189
  this.root = null;
28131
28190
  this.tabs = [];
28191
+ for (const dispose of this.hintTips) dispose();
28192
+ this.hintTips = [];
28132
28193
  ui?.destroy();
28133
28194
  }
28134
28195
  destroy() {
@@ -28172,14 +28233,14 @@ var SettingsDialog = class {
28172
28233
  };
28173
28234
  if (section.rows) seed(section.rows);
28174
28235
  for (const inst of section.instances ?? []) seed(inst.rows);
28175
- for (const sub of section.subsections ?? []) seed(sub.rows);
28236
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
28176
28237
  for (const inst of section.instances ?? []) {
28177
28238
  if (!inst.enableKey || inst.enableKey in bag) continue;
28178
28239
  bag[inst.enableKey] = values[inst.enableKey] === true;
28179
28240
  }
28180
- for (const sub of section.subsections ?? []) {
28181
- if (!sub.enableKey || sub.enableKey in bag) continue;
28182
- bag[sub.enableKey] = values[sub.enableKey] === true;
28241
+ for (const sub2 of section.subsections ?? []) {
28242
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
28243
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
28183
28244
  }
28184
28245
  const refreshers = [];
28185
28246
  const put = (key, v) => {
@@ -28196,16 +28257,16 @@ var SettingsDialog = class {
28196
28257
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
28197
28258
  else this.flatTypeRows(rows, bag, put, refreshers, body);
28198
28259
  }
28199
- for (const sub of section.subsections ?? []) {
28200
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
28201
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
28260
+ for (const sub2 of section.subsections ?? []) {
28261
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
28262
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
28202
28263
  if (rows.length === 0) continue;
28203
- const subMarker = this.section(sub.title);
28264
+ const subMarker = this.section(sub2.title);
28204
28265
  subMarker.dataset.sdStyle = typeId;
28205
28266
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
28206
28267
  subMarker.dataset.sdSub = "1";
28207
28268
  body.append(subMarker);
28208
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
28269
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
28209
28270
  }
28210
28271
  for (const r of refreshers) r();
28211
28272
  }
@@ -28542,23 +28603,31 @@ var SettingsDialog = class {
28542
28603
  }).el
28543
28604
  });
28544
28605
  }
28545
- /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area). */
28546
- boolRow(label, value, onChange) {
28547
- return this.toggleRow(label, value, onChange, []);
28606
+ /** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
28607
+ * `info` (see {@link hint}) rides after the label. */
28608
+ boolRow(label, value, onChange, info) {
28609
+ return this.toggleRow(label, value, onChange, [], void 0, info);
28548
28610
  }
28549
28611
  /** An enable row: checkbox + label in the label column, dependent controls in the
28550
28612
  * shared control column; the control group dims and ignores input while the toggle
28551
28613
  * is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
28552
- toggleRow(label, value, onToggle, controls, get) {
28614
+ toggleRow(label, value, onToggle, controls, get, info) {
28553
28615
  return fieldRow({
28554
28616
  label,
28555
28617
  labelSize: "sm",
28556
28618
  bool: controls.length === 0,
28557
28619
  toggle: { checked: value, onChange: onToggle, get },
28558
28620
  control: controls,
28621
+ info,
28559
28622
  className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
28560
28623
  });
28561
28624
  }
28625
+ /** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
28626
+ hint(text) {
28627
+ const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
28628
+ this.hintTips.push(dispose);
28629
+ return el;
28630
+ }
28562
28631
  numberRow(label, value, min, max, step, onChange) {
28563
28632
  return fieldRow({
28564
28633
  label,
@@ -30300,11 +30369,11 @@ var DrawingInteraction = class {
30300
30369
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
30301
30370
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
30302
30371
  * while placing a line tool it locks the segment angle to 45° steps. */
30303
- down(x, y, mode = "off", shift = false) {
30372
+ down(x, y, mode = "off", shift2 = false) {
30304
30373
  const proj = this.deps.projector();
30305
30374
  if (this.state.kind === "placing") {
30306
30375
  if (this.state.draft.placementMode() !== "click") return;
30307
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30376
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30308
30377
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30309
30378
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
30310
30379
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -30323,7 +30392,7 @@ var DrawingInteraction = class {
30323
30392
  }
30324
30393
  const hit = this.hitAt(x, y);
30325
30394
  if (hit) {
30326
- if (shift) {
30395
+ if (shift2) {
30327
30396
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
30328
30397
  return;
30329
30398
  }
@@ -30333,7 +30402,7 @@ var DrawingInteraction = class {
30333
30402
  }
30334
30403
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
30335
30404
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
30336
- move(x, y, mode = "off", shift = false) {
30405
+ move(x, y, mode = "off", shift2 = false) {
30337
30406
  if (this.state.kind === "idle") {
30338
30407
  const tool = this.deps.activeTool();
30339
30408
  const had = this.snapAt != null;
@@ -30347,7 +30416,7 @@ var DrawingInteraction = class {
30347
30416
  this.captureFreehand(x, y);
30348
30417
  return;
30349
30418
  }
30350
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
30419
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
30351
30420
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
30352
30421
  this.deps.changed();
30353
30422
  return;
@@ -30360,7 +30429,7 @@ var DrawingInteraction = class {
30360
30429
  if (this.state.handle >= 0) {
30361
30430
  const a = d.anchors[this.state.handle];
30362
30431
  if (a) {
30363
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30432
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
30364
30433
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
30365
30434
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
30366
30435
  }
@@ -31058,21 +31127,21 @@ var DrawingSettingsPopup = class {
31058
31127
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
31059
31128
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
31060
31129
  }
31061
- const vwap = drawing.vwap;
31062
- if (paths.has("vwap.midColor") && vwap) {
31130
+ const vwap2 = drawing.vwap;
31131
+ if (paths.has("vwap.midColor") && vwap2) {
31063
31132
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
31064
31133
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
31065
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31066
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31134
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
31135
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
31067
31136
  bar.appendChild(
31068
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31137
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
31069
31138
  label: (v) => `${v}\u03C3`,
31070
31139
  labelInTrigger: true
31071
31140
  })
31072
31141
  );
31073
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31074
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31075
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31142
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
31143
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
31144
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
31076
31145
  }
31077
31146
  if (paths.has("maxCurvature")) {
31078
31147
  const cur = drawing.maxCurvature ?? 24;
@@ -32089,8 +32158,8 @@ var UserDrawingController = class {
32089
32158
  this.toolbar.setFavorites(types);
32090
32159
  }
32091
32160
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
32092
- setToolShortcuts(map) {
32093
- this.toolbar.setShortcuts(map);
32161
+ setToolShortcuts(map2) {
32162
+ this.toolbar.setShortcuts(map2);
32094
32163
  }
32095
32164
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
32096
32165
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -32208,7 +32277,7 @@ var UserDrawingController = class {
32208
32277
  this.render();
32209
32278
  }
32210
32279
  }
32211
- pointerDown(x, y, snap = "off", shift = false) {
32280
+ pointerDown(x, y, snap = "off", shift2 = false) {
32212
32281
  if (this.eraserMode) {
32213
32282
  this.erasing = true;
32214
32283
  this.deleteAt(x, y);
@@ -32228,9 +32297,9 @@ var UserDrawingController = class {
32228
32297
  return;
32229
32298
  }
32230
32299
  }
32231
- this.interaction.down(x, y, snap, shift);
32300
+ this.interaction.down(x, y, snap, shift2);
32232
32301
  }
32233
- pointerMove(x, y, snap = "off", shift = false) {
32302
+ pointerMove(x, y, snap = "off", shift2 = false) {
32234
32303
  if (this.eraserMode) {
32235
32304
  if (this.erasing) this.deleteAt(x, y);
32236
32305
  return;
@@ -32241,7 +32310,7 @@ var UserDrawingController = class {
32241
32310
  this.render();
32242
32311
  return;
32243
32312
  }
32244
- this.interaction.move(x, y, snap, shift);
32313
+ this.interaction.move(x, y, snap, shift2);
32245
32314
  this.updateHover(x, y);
32246
32315
  }
32247
32316
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -32918,14 +32987,14 @@ function layoutTable(ctx, t, args) {
32918
32987
  }
32919
32988
  for (const { box, w: w2, h: h2 } of spanning) {
32920
32989
  if (box.cs > 1) {
32921
- let sum = 0;
32922
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
32923
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
32990
+ let sum2 = 0;
32991
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
32992
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
32924
32993
  }
32925
32994
  if (box.rs > 1) {
32926
- let sum = 0;
32927
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
32928
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
32995
+ let sum2 = 0;
32996
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
32997
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
32929
32998
  }
32930
32999
  }
32931
33000
  let w = 0;
@@ -33512,24 +33581,24 @@ function rasterizeOverlay(ctx, root, frame) {
33512
33581
  drawSvgShapes(el, alpha);
33513
33582
  continue;
33514
33583
  }
33515
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33584
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
33516
33585
  let path = null;
33517
33586
  if (tag === "path") {
33518
33587
  const d = el.getAttribute("d");
33519
33588
  if (d) path = new Path2D(d);
33520
33589
  } else if (tag === "circle") {
33521
33590
  path = new Path2D();
33522
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
33591
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
33523
33592
  } else if (tag === "ellipse") {
33524
33593
  path = new Path2D();
33525
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
33594
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
33526
33595
  } else if (tag === "rect") {
33527
33596
  path = new Path2D();
33528
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
33597
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
33529
33598
  } else if (tag === "line") {
33530
33599
  path = new Path2D();
33531
- path.moveTo(num3("x1"), num3("y1"));
33532
- path.lineTo(num3("x2"), num3("y2"));
33600
+ path.moveTo(num4("x1"), num4("y1"));
33601
+ path.lineTo(num4("x2"), num4("y2"));
33533
33602
  } else if (tag === "polyline" || tag === "polygon") {
33534
33603
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
33535
33604
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -33790,7 +33859,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
33790
33859
  var ZOOM_TAU_MS = 70;
33791
33860
  var SCALE_TAU_MS = 80;
33792
33861
  var FLING_TAU_MS = 110;
33793
- var LIVE_BAR_TAU_MS = 90;
33794
33862
  var SCROLL_TO_TAU_MS = 130;
33795
33863
  var FLING_STOP_PX = 0.02;
33796
33864
  var PRICE_SCALE_K = 4e-3;
@@ -33861,9 +33929,13 @@ var NativeRenderer = class {
33861
33929
  /** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
33862
33930
  this.historyChordsEnabled = true;
33863
33931
  this.liveRegion = null;
33864
- // ── animation state (eased zoom + inertial pan) ──
33932
+ // ── animation state (eased zoom + inertial pan + live-bar glide) ──
33865
33933
  this.animZoom = true;
33866
33934
  this.animPan = true;
33935
+ this.animLiveBarMs = 0;
33936
+ // forming-bar OHLC glide time-constant; 0 = each tick snaps
33937
+ this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
33938
+ // the duration the settings-dialog on/off toggle restores (last non-zero value configured)
33867
33939
  // Brand default candles.
33868
33940
  this.candleUp = BULLISH;
33869
33941
  this.candleDown = BEARISH;
@@ -33988,7 +34060,7 @@ var NativeRenderer = class {
33988
34060
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
33989
34061
  this.priceStyleCbs = /* @__PURE__ */ new Set();
33990
34062
  this.name = "native";
33991
- 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"];
34063
+ 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"];
33992
34064
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
33993
34065
  * so moving onto the button doesn't count as leaving). */
33994
34066
  this.onScrollProximityMove = (e) => {
@@ -34022,6 +34094,7 @@ var NativeRenderer = class {
34022
34094
  this.backendMode = opts.nativeBackend;
34023
34095
  this.animZoom = opts.animZoom;
34024
34096
  this.animPan = opts.animPan;
34097
+ this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
34025
34098
  this.glowAmount = opts.glow;
34026
34099
  this.candleUp = opts.upColor;
34027
34100
  this.candleDown = opts.downColor;
@@ -34071,6 +34144,10 @@ var NativeRenderer = class {
34071
34144
  case "animPan":
34072
34145
  this.animPan = Boolean(value);
34073
34146
  return;
34147
+ case "animLiveBar":
34148
+ this.setLiveBarEase(resolveLiveBarEaseMs(value));
34149
+ return;
34150
+ // affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
34074
34151
  case "intro": {
34075
34152
  const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
34076
34153
  this.introStyle = s;
@@ -34207,6 +34284,8 @@ var NativeRenderer = class {
34207
34284
  return this.animZoom;
34208
34285
  case "animPan":
34209
34286
  return this.animPan;
34287
+ case "animLiveBar":
34288
+ return this.animLiveBarMs;
34210
34289
  case "intro":
34211
34290
  return this.introStyle;
34212
34291
  case "zoomAnchor":
@@ -34375,7 +34454,8 @@ var NativeRenderer = class {
34375
34454
  labelsVisible: this.scene.showAxisLabels,
34376
34455
  currentPriceLine: this.scene.showPriceLine,
34377
34456
  priceLabel: this.scene.showPriceLabel,
34378
- countdown: this.scene.showCountdown
34457
+ countdown: this.scene.showCountdown,
34458
+ animateLastPrice: this.animLiveBarMs > 0
34379
34459
  },
34380
34460
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
34381
34461
  trades: {
@@ -34482,6 +34562,7 @@ var NativeRenderer = class {
34482
34562
  this.scene.showPriceLabel = next.priceScale.priceLabel;
34483
34563
  this.scene.showCountdown = next.priceScale.countdown;
34484
34564
  this.syncCountdownTimer();
34565
+ this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
34485
34566
  s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
34486
34567
  this.scene.tradeMarkers = {
34487
34568
  visible: next.trades.visible,
@@ -34946,8 +35027,8 @@ var NativeRenderer = class {
34946
35027
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
34947
35028
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
34948
35029
  drawingsSnapMode: () => this.snapMode,
34949
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
34950
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
35030
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
35031
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
34951
35032
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
34952
35033
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
34953
35034
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -35264,18 +35345,18 @@ var NativeRenderer = class {
35264
35345
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
35265
35346
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
35266
35347
  const newHeadTime = this.bars[0].time;
35267
- let shift = 0;
35348
+ let shift2 = 0;
35268
35349
  if (newHeadTime !== prevHeadTime) {
35269
35350
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
35270
- if (prepended > 0) shift = prepended;
35351
+ if (prepended > 0) shift2 = prepended;
35271
35352
  else {
35272
35353
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
35273
- if (trimmed > 0) shift = -trimmed;
35354
+ if (trimmed > 0) shift2 = -trimmed;
35274
35355
  }
35275
35356
  }
35276
- if (shift !== 0) {
35277
- this.zoomAnchorLogical += shift;
35278
- if (this.hoverLogical != null) this.hoverLogical += shift;
35357
+ if (shift2 !== 0) {
35358
+ this.zoomAnchorLogical += shift2;
35359
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
35279
35360
  }
35280
35361
  }
35281
35362
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -35298,8 +35379,11 @@ var NativeRenderer = class {
35298
35379
  const last = this.bars[n - 1];
35299
35380
  if (last && bar.time === last.time) {
35300
35381
  this.bars[n - 1] = bar;
35301
- if (this.liveEaseTime !== bar.time) this.syncLiveEase(bar);
35302
- this.animator.start();
35382
+ if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
35383
+ this.syncLiveEase(bar);
35384
+ } else {
35385
+ this.animator.start();
35386
+ }
35303
35387
  } else if (!last || bar.time > last.time) {
35304
35388
  this.bars.push(bar);
35305
35389
  this.syncLiveEase(bar);
@@ -35311,6 +35395,12 @@ var NativeRenderer = class {
35311
35395
  this.scene.bars = this.bars;
35312
35396
  this.scheduler.invalidate(4 /* Full */);
35313
35397
  }
35398
+ /** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
35399
+ * what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
35400
+ setLiveBarEase(ms) {
35401
+ this.animLiveBarMs = ms;
35402
+ if (ms > 0) this.animLiveBarOnMs = ms;
35403
+ }
35314
35404
  /** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
35315
35405
  syncLiveEase(bar) {
35316
35406
  this.liveEaseTime = bar.time;
@@ -35323,9 +35413,10 @@ var NativeRenderer = class {
35323
35413
  const target = this.bars[this.bars.length - 1];
35324
35414
  if (!target || this.liveEaseTime !== target.time) return false;
35325
35415
  const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
35326
- const nh = easeToward(this.liveEaseHigh, target.high, dtMs, LIVE_BAR_TAU_MS);
35327
- const nl = easeToward(this.liveEaseLow, target.low, dtMs, LIVE_BAR_TAU_MS);
35328
- const nc = easeToward(this.liveEaseClose, target.close, dtMs, LIVE_BAR_TAU_MS);
35416
+ const tau = this.animLiveBarMs;
35417
+ const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
35418
+ const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
35419
+ const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
35329
35420
  const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
35330
35421
  this.liveEaseHigh = active ? nh : target.high;
35331
35422
  this.liveEaseLow = active ? nl : target.low;
@@ -36881,15 +36972,15 @@ var NativeRenderer = class {
36881
36972
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
36882
36973
  * strip — merged own-scale indicators are hidden while collapsed. */
36883
36974
  collapsedMasterMap() {
36884
- const map = /* @__PURE__ */ new Map();
36975
+ const map2 = /* @__PURE__ */ new Map();
36885
36976
  for (const pane of this.scene.panes.values()) {
36886
36977
  if (!pane.collapsed) continue;
36887
36978
  const models = this.scene.orderedIndicatorsForPane(pane.id);
36888
36979
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
36889
36980
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
36890
- map.set(pane.id, master?.id ?? null);
36981
+ map2.set(pane.id, master?.id ?? null);
36891
36982
  }
36892
- return map;
36983
+ return map2;
36893
36984
  }
36894
36985
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
36895
36986
  setToolbarGutter(px) {
@@ -37256,29 +37347,2335 @@ function registerVpvr() {
37256
37347
  registerNativeIndicator(vpvrDescriptor);
37257
37348
  }
37258
37349
 
37350
+ // src/core/model/identity.ts
37351
+ function stableSeriesId(parts) {
37352
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
37353
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
37354
+ }
37355
+
37356
+ // src/core/native-indicators/classics/define.ts
37357
+ function num3(inputs, key, fallback) {
37358
+ const v = inputs[key];
37359
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
37360
+ }
37361
+ function str3(inputs, key, fallback) {
37362
+ const v = inputs[key];
37363
+ return typeof v === "string" && v !== "" ? v : fallback;
37364
+ }
37365
+ function toPoints(bars, values, colors) {
37366
+ const pts = new Array(bars.length);
37367
+ for (let i = 0; i < bars.length; i++) {
37368
+ const v = values[i];
37369
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
37370
+ const c = colors?.[i];
37371
+ if (c != null) p.color = c;
37372
+ pts[i] = p;
37373
+ }
37374
+ return pts;
37375
+ }
37376
+ var ClassicIndicator = class {
37377
+ constructor(spec) {
37378
+ this.spec = spec;
37379
+ this.inputs = {};
37380
+ }
37381
+ start(ctx, inputs) {
37382
+ this.ctx = ctx;
37383
+ this.inputs = inputs;
37384
+ this.recompute();
37385
+ }
37386
+ onBars() {
37387
+ this.recompute();
37388
+ }
37389
+ onViewport() {
37390
+ }
37391
+ setInputs(inputs) {
37392
+ this.inputs = inputs;
37393
+ this.recompute();
37394
+ }
37395
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
37396
+ suspend() {
37397
+ }
37398
+ resume() {
37399
+ this.recompute();
37400
+ }
37401
+ stop() {
37402
+ }
37403
+ recompute() {
37404
+ const bars = this.ctx.bars();
37405
+ const out = this.spec.compute(bars, this.inputs);
37406
+ const instanceId = this.spec.type;
37407
+ const series = [];
37408
+ const idsByKey = /* @__PURE__ */ new Map();
37409
+ out.plots.forEach((plot, ordinal) => {
37410
+ const kind = plot.kind ?? "line";
37411
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
37412
+ idsByKey.set(plot.key, id);
37413
+ series.push({
37414
+ id,
37415
+ title: plot.title,
37416
+ paneId: "",
37417
+ // stamped by the orchestrator
37418
+ kind,
37419
+ points: toPoints(bars, plot.values, plot.colors),
37420
+ style: {
37421
+ color: plot.color,
37422
+ width: plot.width ?? 1,
37423
+ lineStyle: plot.lineStyle ?? "solid",
37424
+ ...plot.base != null ? { base: plot.base } : {}
37425
+ },
37426
+ ...plot.overlay ? { overlay: true } : {}
37427
+ });
37428
+ });
37429
+ if (out.markers && out.markers.length > 0) {
37430
+ series.push({
37431
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
37432
+ title: this.spec.shortTitle ?? this.spec.title,
37433
+ paneId: "",
37434
+ kind: "markers",
37435
+ markers: out.markers
37436
+ });
37437
+ }
37438
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
37439
+ const from = idsByKey.get(band.from);
37440
+ const to = idsByKey.get(band.to);
37441
+ if (!from || !to) return [];
37442
+ return [
37443
+ {
37444
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
37445
+ paneId: "",
37446
+ fromSeriesId: from,
37447
+ toSeriesId: to,
37448
+ color: band.color
37449
+ }
37450
+ ];
37451
+ });
37452
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
37453
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
37454
+ paneId: "",
37455
+ price: level.price,
37456
+ ...level.color != null ? { color: level.color } : {},
37457
+ lineStyle: level.lineStyle ?? "dashed",
37458
+ width: 1,
37459
+ ...level.title != null ? { title: level.title } : {}
37460
+ }));
37461
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
37462
+ const { key, ...rest } = poly;
37463
+ return {
37464
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
37465
+ paneId: "",
37466
+ ...rest
37467
+ };
37468
+ });
37469
+ this.ctx.emit({ series, fills, priceLines, polylines });
37470
+ this.ctx.setStatus("idle");
37471
+ }
37472
+ };
37473
+ function classicDescriptor(spec) {
37474
+ return {
37475
+ type: spec.type,
37476
+ title: spec.title,
37477
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
37478
+ paneHint: spec.overlay ? "price" : "new",
37479
+ overlay: spec.overlay,
37480
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
37481
+ multiInstance: true,
37482
+ inputsSchema: () => spec.inputs,
37483
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
37484
+ create: () => new ClassicIndicator(spec)
37485
+ };
37486
+ }
37487
+
37488
+ // src/core/native-indicators/classics/math.ts
37489
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
37490
+ function sourceValues(bars, source) {
37491
+ const out = new Array(bars.length);
37492
+ for (let i = 0; i < bars.length; i++) {
37493
+ const b = bars[i];
37494
+ switch (source) {
37495
+ case "Open":
37496
+ out[i] = b.open;
37497
+ break;
37498
+ case "High":
37499
+ out[i] = b.high;
37500
+ break;
37501
+ case "Low":
37502
+ out[i] = b.low;
37503
+ break;
37504
+ case "HL2":
37505
+ out[i] = (b.high + b.low) / 2;
37506
+ break;
37507
+ case "HLC3":
37508
+ out[i] = (b.high + b.low + b.close) / 3;
37509
+ break;
37510
+ case "OHLC4":
37511
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
37512
+ break;
37513
+ case "HLCC4":
37514
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
37515
+ break;
37516
+ default:
37517
+ out[i] = b.close;
37518
+ }
37519
+ }
37520
+ return out;
37521
+ }
37522
+ function volumes(bars) {
37523
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
37524
+ }
37525
+ function highs(bars) {
37526
+ return bars.map((b) => b.high);
37527
+ }
37528
+ function lows(bars) {
37529
+ return bars.map((b) => b.low);
37530
+ }
37531
+ function closes(bars) {
37532
+ return bars.map((b) => b.close);
37533
+ }
37534
+ function opens(bars) {
37535
+ return bars.map((b) => b.open);
37536
+ }
37537
+ function zip(a, b, f) {
37538
+ const out = new Array(a.length);
37539
+ for (let i = 0; i < a.length; i++) {
37540
+ const x = a[i];
37541
+ const y = b[i];
37542
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
37543
+ }
37544
+ return out;
37545
+ }
37546
+ function map(a, f) {
37547
+ const out = new Array(a.length);
37548
+ for (let i = 0; i < a.length; i++) {
37549
+ const x = a[i];
37550
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
37551
+ }
37552
+ return out;
37553
+ }
37554
+ function sub(a, b) {
37555
+ return zip(a, b, (x, y) => x - y);
37556
+ }
37557
+ function windowed(v, len, fold) {
37558
+ const out = new Array(v.length).fill(Number.NaN);
37559
+ if (len <= 0) return out;
37560
+ let run = 0;
37561
+ for (let i = 0; i < v.length; i++) {
37562
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
37563
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
37564
+ }
37565
+ return out;
37566
+ }
37567
+ function sum(v, len) {
37568
+ return windowed(v, len, (w, from, to) => {
37569
+ let s = 0;
37570
+ for (let k = from; k <= to; k++) s += w[k];
37571
+ return s;
37572
+ });
37573
+ }
37574
+ function sma(v, len) {
37575
+ return windowed(v, len, (w, from, to) => {
37576
+ let s = 0;
37577
+ for (let k = from; k <= to; k++) s += w[k];
37578
+ return s / len;
37579
+ });
37580
+ }
37581
+ function wma(v, len) {
37582
+ const norm = len * (len + 1) / 2;
37583
+ return windowed(v, len, (w, from, to) => {
37584
+ let s = 0;
37585
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
37586
+ return s / norm;
37587
+ });
37588
+ }
37589
+ function vwma(src, vol, len) {
37590
+ const pv = zip(src, vol, (p, q) => p * q);
37591
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
37592
+ }
37593
+ function stdev(v, len) {
37594
+ return windowed(v, len, (w, from, to) => {
37595
+ let s = 0;
37596
+ for (let k = from; k <= to; k++) s += w[k];
37597
+ const mean = s / len;
37598
+ let sq = 0;
37599
+ for (let k = from; k <= to; k++) {
37600
+ const d = w[k] - mean;
37601
+ sq += d * d;
37602
+ }
37603
+ return Math.sqrt(sq / len);
37604
+ });
37605
+ }
37606
+ function highest(v, len) {
37607
+ return windowed(v, len, (w, from, to) => {
37608
+ let m = -Infinity;
37609
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
37610
+ return m;
37611
+ });
37612
+ }
37613
+ function lowest(v, len) {
37614
+ return windowed(v, len, (w, from, to) => {
37615
+ let m = Infinity;
37616
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
37617
+ return m;
37618
+ });
37619
+ }
37620
+ function barsSinceHighest(v, len) {
37621
+ return windowed(v, len, (w, from, to) => {
37622
+ let m = -Infinity;
37623
+ let at = to;
37624
+ for (let k = from; k <= to; k++) {
37625
+ if (w[k] >= m) {
37626
+ m = w[k];
37627
+ at = k;
37628
+ }
37629
+ }
37630
+ return to - at;
37631
+ });
37632
+ }
37633
+ function barsSinceLowest(v, len) {
37634
+ return windowed(v, len, (w, from, to) => {
37635
+ let m = Infinity;
37636
+ let at = to;
37637
+ for (let k = from; k <= to; k++) {
37638
+ if (w[k] <= m) {
37639
+ m = w[k];
37640
+ at = k;
37641
+ }
37642
+ }
37643
+ return to - at;
37644
+ });
37645
+ }
37646
+ function recursive(v, len, alpha) {
37647
+ const out = new Array(v.length).fill(Number.NaN);
37648
+ let run = 0;
37649
+ let prev2 = Number.NaN;
37650
+ for (let i = 0; i < v.length; i++) {
37651
+ const x = v[i];
37652
+ if (!Number.isFinite(x)) {
37653
+ run = 0;
37654
+ prev2 = Number.NaN;
37655
+ continue;
37656
+ }
37657
+ run += 1;
37658
+ if (Number.isFinite(prev2)) {
37659
+ prev2 = alpha * x + (1 - alpha) * prev2;
37660
+ out[i] = prev2;
37661
+ } else if (run >= len) {
37662
+ let s = 0;
37663
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
37664
+ prev2 = s / len;
37665
+ out[i] = prev2;
37666
+ }
37667
+ }
37668
+ return out;
37669
+ }
37670
+ function ema(v, len) {
37671
+ return recursive(v, len, 2 / (len + 1));
37672
+ }
37673
+ function rma(v, len) {
37674
+ return recursive(v, len, 1 / len);
37675
+ }
37676
+ function change(v, n = 1) {
37677
+ const out = new Array(v.length).fill(Number.NaN);
37678
+ for (let i = n; i < v.length; i++) {
37679
+ const a = v[i];
37680
+ const b = v[i - n];
37681
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
37682
+ }
37683
+ return out;
37684
+ }
37685
+ function roc(v, len) {
37686
+ const out = new Array(v.length).fill(Number.NaN);
37687
+ for (let i = len; i < v.length; i++) {
37688
+ const a = v[i];
37689
+ const b = v[i - len];
37690
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
37691
+ }
37692
+ return out;
37693
+ }
37694
+ function shift(v, n) {
37695
+ const out = new Array(v.length).fill(Number.NaN);
37696
+ for (let i = 0; i < v.length; i++) {
37697
+ const j = i - n;
37698
+ if (j >= 0 && j < v.length) out[i] = v[j];
37699
+ }
37700
+ return out;
37701
+ }
37702
+ function trueRange(bars) {
37703
+ const out = new Array(bars.length).fill(Number.NaN);
37704
+ for (let i = 0; i < bars.length; i++) {
37705
+ const b = bars[i];
37706
+ if (i === 0) {
37707
+ out[i] = b.high - b.low;
37708
+ continue;
37709
+ }
37710
+ const pc = bars[i - 1].close;
37711
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
37712
+ }
37713
+ return out;
37714
+ }
37715
+ function atr(bars, len) {
37716
+ return rma(trueRange(bars), len);
37717
+ }
37718
+ function rsi(v, len) {
37719
+ const d = change(v);
37720
+ const up = map(d, (x) => Math.max(x, 0));
37721
+ const dn = map(d, (x) => Math.max(-x, 0));
37722
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
37723
+ }
37724
+ function stoch(v, hi, lo, len) {
37725
+ const hh = highest(hi, len);
37726
+ const ll = lowest(lo, len);
37727
+ const out = new Array(v.length).fill(Number.NaN);
37728
+ for (let i = 0; i < v.length; i++) {
37729
+ const x = v[i];
37730
+ const h = hh[i];
37731
+ const l = ll[i];
37732
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
37733
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
37734
+ }
37735
+ return out;
37736
+ }
37737
+ function linreg(v, len) {
37738
+ const sx = (len - 1) * len / 2;
37739
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
37740
+ const denom = len * sxx - sx * sx;
37741
+ return windowed(v, len, (w, from, to) => {
37742
+ let sy = 0;
37743
+ let sxy = 0;
37744
+ for (let k = from; k <= to; k++) {
37745
+ const x = k - from;
37746
+ sy += w[k];
37747
+ sxy += x * w[k];
37748
+ }
37749
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
37750
+ const intercept = (sy - slope * sx) / len;
37751
+ return intercept + slope * (len - 1);
37752
+ });
37753
+ }
37754
+ function swma(v) {
37755
+ const out = new Array(v.length).fill(Number.NaN);
37756
+ for (let i = 3; i < v.length; i++) {
37757
+ const a = v[i - 3];
37758
+ const b = v[i - 2];
37759
+ const c = v[i - 1];
37760
+ const d = v[i];
37761
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
37762
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
37763
+ }
37764
+ }
37765
+ return out;
37766
+ }
37767
+ function percentRank(v, len) {
37768
+ const out = new Array(v.length).fill(Number.NaN);
37769
+ for (let i = len; i < v.length; i++) {
37770
+ const x = v[i];
37771
+ if (!Number.isFinite(x)) continue;
37772
+ let below = 0;
37773
+ let count = 0;
37774
+ for (let k = i - len; k < i; k++) {
37775
+ const y = v[k];
37776
+ if (!Number.isFinite(y)) {
37777
+ count = -1;
37778
+ break;
37779
+ }
37780
+ count += 1;
37781
+ if (y <= x) below += 1;
37782
+ }
37783
+ if (count === len) out[i] = 100 * below / len;
37784
+ }
37785
+ return out;
37786
+ }
37787
+ function cumSum(v) {
37788
+ const out = new Array(v.length).fill(Number.NaN);
37789
+ let acc = 0;
37790
+ let seen = false;
37791
+ for (let i = 0; i < v.length; i++) {
37792
+ const x = v[i];
37793
+ if (Number.isFinite(x)) {
37794
+ acc += x;
37795
+ seen = true;
37796
+ }
37797
+ if (seen) out[i] = acc;
37798
+ }
37799
+ return out;
37800
+ }
37801
+
37802
+ // src/core/native-indicators/classics/shared.ts
37803
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
37804
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
37805
+ }
37806
+ function sourceInput(defval = "Close") {
37807
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
37808
+ }
37809
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
37810
+ return { key, title, type: "color", defval };
37811
+ }
37812
+ function withAlpha2(hex, alpha = 0.08) {
37813
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
37814
+ if (!m) return hex;
37815
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
37816
+ return `#${m[1]}${a}`;
37817
+ }
37818
+
37819
+ // src/core/native-indicators/classics/averages.ts
37820
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
37821
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
37822
+ function fixedAverage(type, title, shortTitle, smooth) {
37823
+ return {
37824
+ type,
37825
+ title,
37826
+ shortTitle,
37827
+ overlay: true,
37828
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
37829
+ compute: (bars, inputs) => {
37830
+ const len = num3(inputs, "length", 20);
37831
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
37832
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37833
+ if (offset !== 0) values = shift(values, offset);
37834
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37835
+ }
37836
+ };
37837
+ }
37838
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
37839
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
37840
+ var movingAverage = {
37841
+ type: "moving-average",
37842
+ title: "Moving Average",
37843
+ shortTitle: "MA",
37844
+ overlay: true,
37845
+ inputs: [
37846
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
37847
+ lengthInput(20),
37848
+ sourceInput(),
37849
+ offsetInput,
37850
+ colorInput()
37851
+ ],
37852
+ compute: (bars, inputs) => {
37853
+ const len = num3(inputs, "length", 20);
37854
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37855
+ const kind = str3(inputs, "maType", "SMA");
37856
+ let values;
37857
+ switch (kind) {
37858
+ case "EMA":
37859
+ values = ema(src, len);
37860
+ break;
37861
+ case "WMA":
37862
+ values = wma(src, len);
37863
+ break;
37864
+ case "RMA":
37865
+ values = rma(src, len);
37866
+ break;
37867
+ case "VWMA":
37868
+ values = vwma(src, volumes(bars), len);
37869
+ break;
37870
+ default:
37871
+ values = sma(src, len);
37872
+ }
37873
+ const offset = Math.trunc(num3(inputs, "offset", 0));
37874
+ if (offset !== 0) values = shift(values, offset);
37875
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37876
+ }
37877
+ };
37878
+ var smoothedMa = {
37879
+ type: "rma",
37880
+ title: "Smoothed Moving Average",
37881
+ shortTitle: "RMA",
37882
+ overlay: true,
37883
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37884
+ compute: (bars, inputs) => ({
37885
+ plots: [
37886
+ {
37887
+ key: "rma",
37888
+ title: "RMA",
37889
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37890
+ color: str3(inputs, "color", SERIES_LINE),
37891
+ width: 2
37892
+ }
37893
+ ]
37894
+ })
37895
+ };
37896
+ var zlema = {
37897
+ type: "zlema",
37898
+ title: "Zero-Lag Exponential Moving Average",
37899
+ shortTitle: "ZLEMA",
37900
+ overlay: true,
37901
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37902
+ compute: (bars, inputs) => {
37903
+ const len = num3(inputs, "length", 14);
37904
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37905
+ const lag = Math.floor((len - 1) / 2);
37906
+ const lagged = shift(src, lag);
37907
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
37908
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37909
+ }
37910
+ };
37911
+ var vidya = {
37912
+ type: "vidya",
37913
+ title: "Variable Index Dynamic Average",
37914
+ shortTitle: "VIDYA",
37915
+ overlay: true,
37916
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
37917
+ compute: (bars, inputs) => {
37918
+ const len = num3(inputs, "length", 14);
37919
+ const cmoLen = num3(inputs, "cmoLength", 9);
37920
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37921
+ const d = change(src);
37922
+ const su = new Array(src.length).fill(Number.NaN);
37923
+ const sd = new Array(src.length).fill(Number.NaN);
37924
+ for (let i = cmoLen; i < src.length; i++) {
37925
+ let u = 0;
37926
+ let w = 0;
37927
+ let ok = true;
37928
+ for (let k = i - cmoLen + 1; k <= i; k++) {
37929
+ const x = d[k];
37930
+ if (!Number.isFinite(x)) {
37931
+ ok = false;
37932
+ break;
37933
+ }
37934
+ if (x > 0) u += x;
37935
+ else w -= x;
37936
+ }
37937
+ if (ok) {
37938
+ su[i] = u;
37939
+ sd[i] = w;
37940
+ }
37941
+ }
37942
+ const alpha = 2 / (len + 1);
37943
+ const out = new Array(src.length).fill(Number.NaN);
37944
+ let prev2 = Number.NaN;
37945
+ for (let i = 0; i < src.length; i++) {
37946
+ const x = src[i];
37947
+ const u = su[i];
37948
+ const w = sd[i];
37949
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
37950
+ const total = u + w;
37951
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
37952
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
37953
+ out[i] = prev2;
37954
+ }
37955
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
37956
+ }
37957
+ };
37958
+ var maEnvelope = {
37959
+ type: "ma-envelope",
37960
+ title: "Moving Average Envelope",
37961
+ shortTitle: "MA Env",
37962
+ overlay: true,
37963
+ inputs: [
37964
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
37965
+ lengthInput(20),
37966
+ sourceInput(),
37967
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
37968
+ colorInput()
37969
+ ],
37970
+ compute: (bars, inputs) => {
37971
+ const len = num3(inputs, "length", 20);
37972
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
37973
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
37974
+ const k = num3(inputs, "percent", 2.5) / 100;
37975
+ const ink = str3(inputs, "color", SERIES_LINE);
37976
+ return {
37977
+ plots: [
37978
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
37979
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
37980
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
37981
+ ],
37982
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
37983
+ };
37984
+ }
37985
+ };
37986
+ var linearRegression = {
37987
+ type: "linear-regression",
37988
+ title: "Linear Regression Curve",
37989
+ shortTitle: "LinReg",
37990
+ overlay: true,
37991
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
37992
+ compute: (bars, inputs) => ({
37993
+ plots: [
37994
+ {
37995
+ key: "linreg",
37996
+ title: "LinReg",
37997
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
37998
+ color: str3(inputs, "color", SERIES_LINE),
37999
+ width: 2
38000
+ }
38001
+ ]
38002
+ })
38003
+ };
38004
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
38005
+
38006
+ // src/core/native-indicators/classics/bands.ts
38007
+ function bollinger(bars, inputs) {
38008
+ const len = num3(inputs, "length", 20);
38009
+ const mult = num3(inputs, "mult", 2);
38010
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38011
+ const basis = sma(src, len);
38012
+ const dev = map(stdev(src, len), (x) => x * mult);
38013
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
38014
+ }
38015
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
38016
+ var bollingerBands = {
38017
+ type: "bollinger-bands",
38018
+ title: "Bollinger Bands",
38019
+ shortTitle: "BB",
38020
+ overlay: true,
38021
+ inputs: [...BB_INPUTS, colorInput()],
38022
+ compute: (bars, inputs) => {
38023
+ const { basis, upper, lower } = bollinger(bars, inputs);
38024
+ const ink = str3(inputs, "color", SERIES_LINE);
38025
+ return {
38026
+ plots: [
38027
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
38028
+ { key: "upper", title: "Upper", values: upper, color: ink },
38029
+ { key: "lower", title: "Lower", values: lower, color: ink }
38030
+ ],
38031
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
38032
+ };
38033
+ }
38034
+ };
38035
+ var percentB = {
38036
+ type: "percent-b",
38037
+ title: "Bollinger Bands %B",
38038
+ shortTitle: "%B",
38039
+ overlay: false,
38040
+ inputs: [...BB_INPUTS, colorInput()],
38041
+ compute: (bars, inputs) => {
38042
+ const { upper, lower } = bollinger(bars, inputs);
38043
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38044
+ const values = src.map((x, i) => {
38045
+ const u = upper[i];
38046
+ const l = lower[i];
38047
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
38048
+ });
38049
+ return {
38050
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38051
+ levels: [
38052
+ { key: "upper", price: 1, color: NEUTRAL },
38053
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
38054
+ { key: "lower", price: 0, color: NEUTRAL }
38055
+ ]
38056
+ };
38057
+ }
38058
+ };
38059
+ var bandwidth = {
38060
+ type: "bandwidth",
38061
+ title: "Bollinger Bands Width",
38062
+ shortTitle: "BandWidth",
38063
+ overlay: false,
38064
+ inputs: [...BB_INPUTS, colorInput()],
38065
+ compute: (bars, inputs) => {
38066
+ const { basis, upper, lower } = bollinger(bars, inputs);
38067
+ const values = basis.map((b, i) => {
38068
+ const u = upper[i];
38069
+ const l = lower[i];
38070
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
38071
+ });
38072
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38073
+ }
38074
+ };
38075
+ var keltner = {
38076
+ type: "keltner-channels",
38077
+ title: "Keltner Channels",
38078
+ shortTitle: "KC",
38079
+ overlay: true,
38080
+ inputs: [
38081
+ lengthInput(20),
38082
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38083
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38084
+ sourceInput(),
38085
+ colorInput()
38086
+ ],
38087
+ compute: (bars, inputs) => {
38088
+ const len = num3(inputs, "length", 20);
38089
+ const mult = num3(inputs, "mult", 2);
38090
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
38091
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
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: zip(basis, range, (b, r) => b + r), color: ink },
38097
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
38098
+ ],
38099
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38100
+ };
38101
+ }
38102
+ };
38103
+ var donchian = {
38104
+ type: "donchian-channels",
38105
+ title: "Donchian Channels",
38106
+ shortTitle: "DC",
38107
+ overlay: true,
38108
+ inputs: [lengthInput(20), colorInput()],
38109
+ compute: (bars, inputs) => {
38110
+ const len = num3(inputs, "length", 20);
38111
+ const upper = highest(highs(bars), len);
38112
+ const lower = lowest(lows(bars), len);
38113
+ const ink = str3(inputs, "color", SERIES_LINE);
38114
+ return {
38115
+ plots: [
38116
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
38117
+ { key: "upper", title: "Upper", values: upper, color: ink },
38118
+ { key: "lower", title: "Lower", values: lower, color: ink }
38119
+ ],
38120
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
38121
+ };
38122
+ }
38123
+ };
38124
+ var chandelier = {
38125
+ type: "chandelier-exit",
38126
+ title: "Chandelier Exit",
38127
+ shortTitle: "CE",
38128
+ overlay: true,
38129
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
38130
+ compute: (bars, inputs) => {
38131
+ const len = num3(inputs, "length", 22);
38132
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
38133
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
38134
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
38135
+ return {
38136
+ plots: [
38137
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
38138
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
38139
+ ]
38140
+ };
38141
+ }
38142
+ };
38143
+ var chandeKroll = {
38144
+ type: "chande-kroll-stop",
38145
+ title: "Chande Kroll Stop",
38146
+ shortTitle: "CKS",
38147
+ overlay: true,
38148
+ inputs: [
38149
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38150
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
38151
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
38152
+ ],
38153
+ compute: (bars, inputs) => {
38154
+ const p = num3(inputs, "p", 10);
38155
+ const x = num3(inputs, "x", 1);
38156
+ const q = num3(inputs, "q", 9);
38157
+ const range = map(atr(bars, p), (v) => v * x);
38158
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
38159
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
38160
+ return {
38161
+ plots: [
38162
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
38163
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
38164
+ ]
38165
+ };
38166
+ }
38167
+ };
38168
+ var supertrend = {
38169
+ type: "supertrend",
38170
+ title: "SuperTrend",
38171
+ shortTitle: "SuperTrend",
38172
+ overlay: true,
38173
+ inputs: [
38174
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
38175
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
38176
+ ],
38177
+ compute: (bars, inputs) => {
38178
+ const len = num3(inputs, "atrLength", 10);
38179
+ const mult = num3(inputs, "mult", 3);
38180
+ const range = atr(bars, len);
38181
+ const n = bars.length;
38182
+ const st = new Array(n).fill(Number.NaN);
38183
+ const dir = new Array(n).fill(1);
38184
+ let up = Number.NaN;
38185
+ let dn = Number.NaN;
38186
+ let trend = 1;
38187
+ for (let i = 0; i < n; i++) {
38188
+ const r = range[i];
38189
+ if (!Number.isFinite(r)) continue;
38190
+ const b = bars[i];
38191
+ const mid = (b.high + b.low) / 2;
38192
+ const basicUp = mid - mult * r;
38193
+ const basicDn = mid + mult * r;
38194
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
38195
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
38196
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
38197
+ if (trend === 1 && b.close < up) trend = -1;
38198
+ else if (trend === -1 && b.close > dn) trend = 1;
38199
+ dir[i] = trend;
38200
+ st[i] = trend === 1 ? up : dn;
38201
+ }
38202
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
38203
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
38204
+ }
38205
+ };
38206
+ var alligator = {
38207
+ type: "williams-alligator",
38208
+ title: "Williams Alligator",
38209
+ shortTitle: "Alligator",
38210
+ overlay: true,
38211
+ inputs: [
38212
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
38213
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
38214
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
38215
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
38216
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
38217
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
38218
+ ],
38219
+ compute: (bars, inputs) => {
38220
+ const hl2 = sourceValues(bars, "HL2");
38221
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
38222
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
38223
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
38224
+ return {
38225
+ plots: [
38226
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
38227
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
38228
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
38229
+ ]
38230
+ };
38231
+ }
38232
+ };
38233
+ var gator = {
38234
+ type: "gator-oscillator",
38235
+ title: "Gator Oscillator",
38236
+ shortTitle: "Gator Oscillator",
38237
+ overlay: false,
38238
+ inputs: [],
38239
+ compute: (bars) => {
38240
+ const hl2 = sourceValues(bars, "HL2");
38241
+ const jaw = shift(rma(hl2, 13), 8);
38242
+ const teeth = shift(rma(hl2, 8), 5);
38243
+ const lips = shift(rma(hl2, 5), 3);
38244
+ const upper = map(sub(jaw, teeth), Math.abs);
38245
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
38246
+ const colorByExpansion = (v) => v.map((x, i) => {
38247
+ if (!Number.isFinite(x)) return null;
38248
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
38249
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
38250
+ });
38251
+ return {
38252
+ plots: [
38253
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
38254
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
38255
+ ]
38256
+ };
38257
+ }
38258
+ };
38259
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
38260
+
38261
+ // src/core/native-indicators/classics/oscillators.ts
38262
+ function guideLevels(low, high, mid) {
38263
+ const levels = [
38264
+ { key: "upper", price: high, color: NEUTRAL },
38265
+ { key: "lower", price: low, color: NEUTRAL }
38266
+ ];
38267
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
38268
+ return levels;
38269
+ }
38270
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
38271
+ function histColors(values) {
38272
+ return values.map((x, i) => {
38273
+ if (!Number.isFinite(x)) return null;
38274
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
38275
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
38276
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
38277
+ return rising ? `${BEARISH}80` : BEARISH;
38278
+ });
38279
+ }
38280
+ var rsiSpec = {
38281
+ type: "rsi",
38282
+ title: "Relative Strength Index",
38283
+ shortTitle: "RSI",
38284
+ overlay: false,
38285
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
38286
+ compute: (bars, inputs) => ({
38287
+ plots: [
38288
+ {
38289
+ key: "rsi",
38290
+ title: "RSI",
38291
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
38292
+ color: str3(inputs, "color", SERIES_LINE),
38293
+ width: 2
38294
+ }
38295
+ ],
38296
+ levels: guideLevels(30, 70, 50)
38297
+ })
38298
+ };
38299
+ var stochastic = {
38300
+ type: "stochastic",
38301
+ title: "Stochastic",
38302
+ shortTitle: "Stoch",
38303
+ overlay: false,
38304
+ inputs: [
38305
+ lengthInput(14, "kLength", "%K length"),
38306
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
38307
+ lengthInput(3, "dLength", "%D smoothing", 500)
38308
+ ],
38309
+ compute: (bars, inputs) => {
38310
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
38311
+ const d = sma(k, num3(inputs, "dLength", 3));
38312
+ return {
38313
+ plots: [
38314
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38315
+ { key: "d", title: "%D", values: d, color: WARNING }
38316
+ ],
38317
+ levels: guideLevels(20, 80)
38318
+ };
38319
+ }
38320
+ };
38321
+ var stochasticRsi = {
38322
+ type: "stochastic-rsi",
38323
+ title: "Stochastic RSI",
38324
+ shortTitle: "Stoch RSI",
38325
+ overlay: false,
38326
+ inputs: [
38327
+ lengthInput(14, "rsiLength", "RSI length"),
38328
+ lengthInput(14, "stochLength", "Stochastic length"),
38329
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
38330
+ lengthInput(3, "dLength", "%D smoothing", 500),
38331
+ sourceInput()
38332
+ ],
38333
+ compute: (bars, inputs) => {
38334
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
38335
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
38336
+ const d = sma(k, num3(inputs, "dLength", 3));
38337
+ return {
38338
+ plots: [
38339
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
38340
+ { key: "d", title: "%D", values: d, color: WARNING }
38341
+ ],
38342
+ levels: guideLevels(20, 80)
38343
+ };
38344
+ }
38345
+ };
38346
+ var macd = {
38347
+ type: "macd",
38348
+ title: "MACD",
38349
+ overlay: false,
38350
+ inputs: [
38351
+ lengthInput(12, "fastLength", "Fast length"),
38352
+ lengthInput(26, "slowLength", "Slow length"),
38353
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38354
+ sourceInput()
38355
+ ],
38356
+ compute: (bars, inputs) => {
38357
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38358
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
38359
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38360
+ const hist = sub(line, signal);
38361
+ return {
38362
+ plots: [
38363
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38364
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
38365
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38366
+ ],
38367
+ levels: ZERO_LEVEL
38368
+ };
38369
+ }
38370
+ };
38371
+ var ppo = {
38372
+ type: "ppo",
38373
+ title: "Percentage Price Oscillator",
38374
+ shortTitle: "PPO",
38375
+ overlay: false,
38376
+ inputs: [
38377
+ lengthInput(12, "fastLength", "Fast length"),
38378
+ lengthInput(26, "slowLength", "Slow length"),
38379
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
38380
+ sourceInput()
38381
+ ],
38382
+ compute: (bars, inputs) => {
38383
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38384
+ const fast = ema(src, num3(inputs, "fastLength", 12));
38385
+ const slow = ema(src, num3(inputs, "slowLength", 26));
38386
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
38387
+ const signal = ema(line, num3(inputs, "signalLength", 9));
38388
+ const hist = sub(line, signal);
38389
+ return {
38390
+ plots: [
38391
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
38392
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
38393
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38394
+ ],
38395
+ levels: ZERO_LEVEL
38396
+ };
38397
+ }
38398
+ };
38399
+ var awesome = {
38400
+ type: "awesome-oscillator",
38401
+ title: "Awesome Oscillator",
38402
+ shortTitle: "AO",
38403
+ overlay: false,
38404
+ inputs: [],
38405
+ compute: (bars) => {
38406
+ const hl2 = sourceValues(bars, "HL2");
38407
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
38408
+ const colors = ao.map((x, i) => {
38409
+ if (!Number.isFinite(x)) return null;
38410
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
38411
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
38412
+ });
38413
+ return {
38414
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
38415
+ levels: ZERO_LEVEL
38416
+ };
38417
+ }
38418
+ };
38419
+ var cci = {
38420
+ type: "commodity-channel-index",
38421
+ title: "Commodity Channel Index",
38422
+ shortTitle: "CCI",
38423
+ overlay: false,
38424
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
38425
+ compute: (bars, inputs) => {
38426
+ const len = num3(inputs, "length", 20);
38427
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
38428
+ const basis = sma(src, len);
38429
+ const dev = new Array(src.length).fill(Number.NaN);
38430
+ for (let i = len - 1; i < src.length; i++) {
38431
+ const mean = basis[i];
38432
+ if (!Number.isFinite(mean)) continue;
38433
+ let d = 0;
38434
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
38435
+ dev[i] = d / len;
38436
+ }
38437
+ const values = src.map((x, i) => {
38438
+ const b = basis[i];
38439
+ const d = dev[i];
38440
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
38441
+ });
38442
+ return {
38443
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38444
+ levels: guideLevels(-100, 100, 0)
38445
+ };
38446
+ }
38447
+ };
38448
+ var williamsR = {
38449
+ type: "williams-percent-r",
38450
+ title: "Williams %R",
38451
+ shortTitle: "%R",
38452
+ overlay: false,
38453
+ inputs: [lengthInput(14), colorInput()],
38454
+ compute: (bars, inputs) => {
38455
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
38456
+ return {
38457
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38458
+ levels: guideLevels(-80, -20)
38459
+ };
38460
+ }
38461
+ };
38462
+ var cmo = {
38463
+ type: "chande-momentum-oscillator",
38464
+ title: "Chande Momentum Oscillator",
38465
+ shortTitle: "CMO",
38466
+ overlay: false,
38467
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38468
+ compute: (bars, inputs) => {
38469
+ const len = num3(inputs, "length", 9);
38470
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
38471
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
38472
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
38473
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
38474
+ return {
38475
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38476
+ levels: guideLevels(-50, 50, 0)
38477
+ };
38478
+ }
38479
+ };
38480
+ var connorsRsi = {
38481
+ type: "connors-rsi",
38482
+ title: "Connors RSI",
38483
+ shortTitle: "CRSI",
38484
+ overlay: false,
38485
+ inputs: [
38486
+ lengthInput(3, "rsiLength", "RSI length"),
38487
+ lengthInput(2, "streakLength", "Streak RSI length"),
38488
+ lengthInput(100, "rankLength", "Percent-rank length"),
38489
+ colorInput()
38490
+ ],
38491
+ compute: (bars, inputs) => {
38492
+ const c = closes(bars);
38493
+ const streak = new Array(c.length).fill(0);
38494
+ for (let i = 1; i < c.length; i++) {
38495
+ const d = c[i] - c[i - 1];
38496
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
38497
+ }
38498
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
38499
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
38500
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
38501
+ const values = c.map((_, i) => {
38502
+ const x = a[i];
38503
+ const y = b[i];
38504
+ const z = r[i];
38505
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
38506
+ });
38507
+ return {
38508
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38509
+ levels: guideLevels(20, 80, 50)
38510
+ };
38511
+ }
38512
+ };
38513
+ var fisher = {
38514
+ type: "fisher-transform",
38515
+ title: "Fisher Transform",
38516
+ shortTitle: "Fisher",
38517
+ overlay: false,
38518
+ inputs: [lengthInput(9)],
38519
+ compute: (bars, inputs) => {
38520
+ const len = num3(inputs, "length", 9);
38521
+ const hl2 = sourceValues(bars, "HL2");
38522
+ const hi = highest(hl2, len);
38523
+ const lo = lowest(hl2, len);
38524
+ const n = bars.length;
38525
+ const out = new Array(n).fill(Number.NaN);
38526
+ let value = 0;
38527
+ let fish = 0;
38528
+ for (let i = 0; i < n; i++) {
38529
+ const h = hi[i];
38530
+ const l = lo[i];
38531
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
38532
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
38533
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
38534
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
38535
+ out[i] = fish;
38536
+ }
38537
+ return {
38538
+ plots: [
38539
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
38540
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
38541
+ ],
38542
+ levels: ZERO_LEVEL
38543
+ };
38544
+ }
38545
+ };
38546
+ var trix = {
38547
+ type: "trix",
38548
+ title: "TRIX",
38549
+ overlay: false,
38550
+ inputs: [lengthInput(18), colorInput()],
38551
+ compute: (bars, inputs) => {
38552
+ const len = num3(inputs, "length", 18);
38553
+ const logClose = map(closes(bars), Math.log);
38554
+ const smooth = ema(ema(ema(logClose, len), len), len);
38555
+ const values = map(change(smooth), (x) => x * 1e4);
38556
+ return {
38557
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38558
+ levels: ZERO_LEVEL
38559
+ };
38560
+ }
38561
+ };
38562
+ function tsiLine(src, long, short) {
38563
+ const mom = change(src);
38564
+ const numer = ema(ema(mom, long), short);
38565
+ const denom = ema(ema(map(mom, Math.abs), long), short);
38566
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
38567
+ }
38568
+ var tsi = {
38569
+ type: "true-strength-index",
38570
+ title: "True Strength Index",
38571
+ shortTitle: "TSI",
38572
+ overlay: false,
38573
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
38574
+ compute: (bars, inputs) => {
38575
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
38576
+ return {
38577
+ plots: [
38578
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
38579
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
38580
+ ],
38581
+ levels: ZERO_LEVEL
38582
+ };
38583
+ }
38584
+ };
38585
+ var smiErgodic = {
38586
+ type: "smi-ergodic",
38587
+ title: "SMI Ergodic Oscillator",
38588
+ shortTitle: "SMIE",
38589
+ overlay: false,
38590
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
38591
+ compute: (bars, inputs) => {
38592
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
38593
+ const signal = ema(line, num3(inputs, "signalLength", 5));
38594
+ const osc = sub(line, signal);
38595
+ return {
38596
+ plots: [
38597
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
38598
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
38599
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
38600
+ ],
38601
+ levels: ZERO_LEVEL
38602
+ };
38603
+ }
38604
+ };
38605
+ var stc = {
38606
+ type: "schaff-trend-cycle",
38607
+ title: "Schaff Trend Cycle",
38608
+ shortTitle: "Schaff Trend Cycle",
38609
+ overlay: false,
38610
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
38611
+ compute: (bars, inputs) => {
38612
+ const len = num3(inputs, "cycleLength", 10);
38613
+ const src = closes(bars);
38614
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
38615
+ const stochOf = (v) => {
38616
+ const lo = lowest(v, len);
38617
+ const hi = highest(v, len);
38618
+ const out = new Array(v.length).fill(Number.NaN);
38619
+ let prevRaw = Number.NaN;
38620
+ let smoothed = Number.NaN;
38621
+ for (let i = 0; i < v.length; i++) {
38622
+ const x = v[i];
38623
+ const l = lo[i];
38624
+ const h = hi[i];
38625
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
38626
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
38627
+ prevRaw = raw;
38628
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
38629
+ out[i] = smoothed;
38630
+ }
38631
+ return out;
38632
+ };
38633
+ const values = stochOf(stochOf(macdLine));
38634
+ return {
38635
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
38636
+ levels: guideLevels(25, 75)
38637
+ };
38638
+ }
38639
+ };
38640
+ var kst = {
38641
+ type: "know-sure-thing",
38642
+ title: "Know Sure Thing",
38643
+ shortTitle: "KST",
38644
+ overlay: false,
38645
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
38646
+ compute: (bars, inputs) => {
38647
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38648
+ const line = [
38649
+ sma(roc(src, 10), 10),
38650
+ sma(roc(src, 15), 10),
38651
+ sma(roc(src, 20), 10),
38652
+ sma(roc(src, 30), 15)
38653
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
38654
+ return {
38655
+ plots: [
38656
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
38657
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
38658
+ ],
38659
+ levels: ZERO_LEVEL
38660
+ };
38661
+ }
38662
+ };
38663
+ var coppock = {
38664
+ type: "coppock-curve",
38665
+ title: "Coppock Curve",
38666
+ shortTitle: "Coppock",
38667
+ overlay: false,
38668
+ inputs: [
38669
+ lengthInput(10, "wmaLength", "WMA length"),
38670
+ lengthInput(14, "longRoc", "Long RoC length"),
38671
+ lengthInput(11, "shortRoc", "Short RoC length"),
38672
+ sourceInput(),
38673
+ colorInput()
38674
+ ],
38675
+ compute: (bars, inputs) => {
38676
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38677
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
38678
+ return {
38679
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38680
+ levels: ZERO_LEVEL
38681
+ };
38682
+ }
38683
+ };
38684
+ var dpo = {
38685
+ type: "detrended-price-oscillator",
38686
+ title: "Detrended Price Oscillator",
38687
+ shortTitle: "DPO",
38688
+ overlay: false,
38689
+ inputs: [lengthInput(21), colorInput()],
38690
+ compute: (bars, inputs) => {
38691
+ const len = num3(inputs, "length", 21);
38692
+ const barsback = Math.floor(len / 2) + 1;
38693
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
38694
+ return {
38695
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38696
+ levels: ZERO_LEVEL
38697
+ };
38698
+ }
38699
+ };
38700
+ var rocSpec = {
38701
+ type: "rate-of-change",
38702
+ title: "Rate of Change",
38703
+ shortTitle: "ROC",
38704
+ overlay: false,
38705
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
38706
+ compute: (bars, inputs) => ({
38707
+ plots: [
38708
+ {
38709
+ key: "roc",
38710
+ title: "RoC",
38711
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
38712
+ color: str3(inputs, "color", SERIES_LINE),
38713
+ width: 2
38714
+ }
38715
+ ],
38716
+ levels: ZERO_LEVEL
38717
+ })
38718
+ };
38719
+ var ultimate = {
38720
+ type: "ultimate-oscillator",
38721
+ title: "Ultimate Oscillator",
38722
+ shortTitle: "UO",
38723
+ overlay: false,
38724
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
38725
+ compute: (bars, inputs) => {
38726
+ const n = bars.length;
38727
+ const bp = new Array(n).fill(Number.NaN);
38728
+ const tr = new Array(n).fill(Number.NaN);
38729
+ for (let i = 0; i < n; i++) {
38730
+ const b = bars[i];
38731
+ const pc = i > 0 ? bars[i - 1].close : b.close;
38732
+ const lo = Math.min(b.low, pc);
38733
+ const hi = Math.max(b.high, pc);
38734
+ bp[i] = b.close - lo;
38735
+ tr[i] = hi - lo;
38736
+ }
38737
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
38738
+ const a7 = avg(num3(inputs, "fastLength", 7));
38739
+ const a14 = avg(num3(inputs, "middleLength", 14));
38740
+ const a28 = avg(num3(inputs, "slowLength", 28));
38741
+ const values = a7.map((x, i) => {
38742
+ const y = a14[i];
38743
+ const z = a28[i];
38744
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
38745
+ });
38746
+ return {
38747
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38748
+ levels: guideLevels(30, 70, 50)
38749
+ };
38750
+ }
38751
+ };
38752
+ var rvgi = {
38753
+ type: "relative-vigor-index",
38754
+ title: "Relative Vigor Index",
38755
+ shortTitle: "RVGI",
38756
+ overlay: false,
38757
+ inputs: [lengthInput(10)],
38758
+ compute: (bars, inputs) => {
38759
+ const len = num3(inputs, "length", 10);
38760
+ const co = swma(sub(closes(bars), opens(bars)));
38761
+ const hl = swma(sub(highs(bars), lows(bars)));
38762
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
38763
+ return {
38764
+ plots: [
38765
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
38766
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
38767
+ ],
38768
+ levels: ZERO_LEVEL
38769
+ };
38770
+ }
38771
+ };
38772
+ var relVolatility = {
38773
+ type: "relative-volatility-index",
38774
+ title: "Relative Volatility Index",
38775
+ shortTitle: "RVI",
38776
+ overlay: false,
38777
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
38778
+ compute: (bars, inputs) => {
38779
+ const smoothLen = num3(inputs, "smoothLength", 14);
38780
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
38781
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
38782
+ const d = change(src);
38783
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
38784
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
38785
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
38786
+ return {
38787
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38788
+ levels: guideLevels(20, 80, 50)
38789
+ };
38790
+ }
38791
+ };
38792
+ var bop = {
38793
+ type: "balance-of-power",
38794
+ title: "Balance of Power",
38795
+ shortTitle: "BOP",
38796
+ overlay: false,
38797
+ inputs: [colorInput()],
38798
+ compute: (bars, inputs) => {
38799
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
38800
+ return {
38801
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
38802
+ levels: ZERO_LEVEL
38803
+ };
38804
+ }
38805
+ };
38806
+ var elderRay = {
38807
+ type: "elder-ray",
38808
+ title: "Elder Ray",
38809
+ overlay: false,
38810
+ inputs: [lengthInput(13)],
38811
+ compute: (bars, inputs) => {
38812
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
38813
+ return {
38814
+ plots: [
38815
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
38816
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
38817
+ ],
38818
+ levels: ZERO_LEVEL
38819
+ };
38820
+ }
38821
+ };
38822
+ var ttmSqueeze = {
38823
+ type: "ttm-squeeze",
38824
+ title: "TTM Squeeze",
38825
+ overlay: false,
38826
+ inputs: [
38827
+ lengthInput(20),
38828
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
38829
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
38830
+ ],
38831
+ compute: (bars, inputs) => {
38832
+ const len = num3(inputs, "length", 20);
38833
+ const c = closes(bars);
38834
+ const basis = sma(c, len);
38835
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
38836
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
38837
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
38838
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
38839
+ const mom = linreg(sub(c, anchor), len);
38840
+ const squeezed = c.map((_, i) => {
38841
+ const d = dev[i];
38842
+ const r = kcRange[i];
38843
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
38844
+ });
38845
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
38846
+ return {
38847
+ plots: [
38848
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
38849
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
38850
+ ]
38851
+ };
38852
+ }
38853
+ };
38854
+ var oscillatorSpecs = [
38855
+ rsiSpec,
38856
+ stochastic,
38857
+ stochasticRsi,
38858
+ macd,
38859
+ ppo,
38860
+ awesome,
38861
+ cci,
38862
+ williamsR,
38863
+ cmo,
38864
+ connorsRsi,
38865
+ fisher,
38866
+ trix,
38867
+ tsi,
38868
+ smiErgodic,
38869
+ stc,
38870
+ kst,
38871
+ coppock,
38872
+ dpo,
38873
+ rocSpec,
38874
+ ultimate,
38875
+ rvgi,
38876
+ relVolatility,
38877
+ bop,
38878
+ elderRay,
38879
+ ttmSqueeze
38880
+ ];
38881
+
38882
+ // src/core/native-indicators/classics/trend.ts
38883
+ var aroon = {
38884
+ type: "aroon",
38885
+ title: "Aroon",
38886
+ overlay: false,
38887
+ inputs: [lengthInput(14)],
38888
+ compute: (bars, inputs) => {
38889
+ const len = num3(inputs, "length", 14);
38890
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
38891
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
38892
+ return {
38893
+ plots: [
38894
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
38895
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
38896
+ ],
38897
+ levels: [
38898
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
38899
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
38900
+ ]
38901
+ };
38902
+ }
38903
+ };
38904
+ var adx = {
38905
+ type: "average-directional-index",
38906
+ title: "Average Directional Index",
38907
+ shortTitle: "ADX",
38908
+ overlay: false,
38909
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
38910
+ compute: (bars, inputs) => {
38911
+ const diLen = num3(inputs, "diLength", 14);
38912
+ const adxLen = num3(inputs, "adxLength", 14);
38913
+ const n = bars.length;
38914
+ const plusDm = new Array(n).fill(Number.NaN);
38915
+ const minusDm = new Array(n).fill(Number.NaN);
38916
+ for (let i = 1; i < n; i++) {
38917
+ const up = bars[i].high - bars[i - 1].high;
38918
+ const dn = bars[i - 1].low - bars[i].low;
38919
+ plusDm[i] = up > dn && up > 0 ? up : 0;
38920
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
38921
+ }
38922
+ const atrLine = rma(trueRange(bars), diLen);
38923
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38924
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
38925
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
38926
+ return {
38927
+ plots: [
38928
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
38929
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
38930
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
38931
+ ],
38932
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
38933
+ };
38934
+ }
38935
+ };
38936
+ var vortex = {
38937
+ type: "vortex-indicator",
38938
+ title: "Vortex Indicator",
38939
+ shortTitle: "VI",
38940
+ overlay: false,
38941
+ inputs: [lengthInput(14)],
38942
+ compute: (bars, inputs) => {
38943
+ const len = num3(inputs, "length", 14);
38944
+ const n = bars.length;
38945
+ const vmPlus = new Array(n).fill(Number.NaN);
38946
+ const vmMinus = new Array(n).fill(Number.NaN);
38947
+ for (let i = 1; i < n; i++) {
38948
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
38949
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
38950
+ }
38951
+ const trSum = sum(trueRange(bars), len);
38952
+ return {
38953
+ plots: [
38954
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
38955
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
38956
+ ],
38957
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
38958
+ };
38959
+ }
38960
+ };
38961
+ var trendSpecs = [aroon, adx, vortex];
38962
+
38963
+ // src/core/native-indicators/classics/volatility.ts
38964
+ var atrSpec = {
38965
+ type: "average-true-range",
38966
+ title: "Average True Range",
38967
+ shortTitle: "ATR",
38968
+ overlay: false,
38969
+ inputs: [lengthInput(14), colorInput()],
38970
+ compute: (bars, inputs) => ({
38971
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
38972
+ })
38973
+ };
38974
+ var historicalVolatility = {
38975
+ type: "historical-volatility",
38976
+ title: "Historical Volatility",
38977
+ shortTitle: "HV",
38978
+ overlay: false,
38979
+ inputs: [
38980
+ lengthInput(10),
38981
+ { 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)" },
38982
+ colorInput()
38983
+ ],
38984
+ compute: (bars, inputs) => {
38985
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
38986
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
38987
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
38988
+ }
38989
+ };
38990
+ var chaikinVolatility = {
38991
+ type: "chaikin-volatility",
38992
+ title: "Chaikin Volatility",
38993
+ shortTitle: "CHV",
38994
+ overlay: false,
38995
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
38996
+ compute: (bars, inputs) => {
38997
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
38998
+ return {
38999
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39000
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39001
+ };
39002
+ }
39003
+ };
39004
+ var massIndex = {
39005
+ type: "mass-index",
39006
+ title: "Mass Index",
39007
+ shortTitle: "MI",
39008
+ overlay: false,
39009
+ inputs: [lengthInput(25), colorInput()],
39010
+ compute: (bars, inputs) => {
39011
+ const range = sub(highs(bars), lows(bars));
39012
+ const single = ema(range, 9);
39013
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
39014
+ return {
39015
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39016
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
39017
+ };
39018
+ }
39019
+ };
39020
+ var standardDeviation = {
39021
+ type: "standard-deviation",
39022
+ title: "Standard Deviation",
39023
+ shortTitle: "StdDev",
39024
+ overlay: false,
39025
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
39026
+ compute: (bars, inputs) => ({
39027
+ plots: [
39028
+ {
39029
+ key: "stdev",
39030
+ title: "StdDev",
39031
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
39032
+ color: str3(inputs, "color", SERIES_LINE),
39033
+ width: 2
39034
+ }
39035
+ ]
39036
+ })
39037
+ };
39038
+ var ulcerIndex = {
39039
+ type: "ulcer-index",
39040
+ title: "Ulcer Index",
39041
+ overlay: false,
39042
+ inputs: [lengthInput(14), colorInput()],
39043
+ compute: (bars, inputs) => {
39044
+ const len = num3(inputs, "length", 14);
39045
+ const c = closes(bars);
39046
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
39047
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
39048
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39049
+ }
39050
+ };
39051
+ var choppiness = {
39052
+ type: "choppiness-index",
39053
+ title: "Choppiness Index",
39054
+ shortTitle: "CHOP",
39055
+ overlay: false,
39056
+ inputs: [lengthInput(14), colorInput()],
39057
+ compute: (bars, inputs) => {
39058
+ const len = num3(inputs, "length", 14);
39059
+ const trSum = sum(trueRange(bars), len);
39060
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
39061
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
39062
+ return {
39063
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39064
+ levels: [
39065
+ { key: "upper", price: 61.8, color: NEUTRAL },
39066
+ { key: "lower", price: 38.2, color: NEUTRAL }
39067
+ ]
39068
+ };
39069
+ }
39070
+ };
39071
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
39072
+
39073
+ // src/core/native-indicators/classics/volume.ts
39074
+ function moneyFlowVolume(bars) {
39075
+ return bars.map((b) => {
39076
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39077
+ if (b.high === b.low) return 0;
39078
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
39079
+ });
39080
+ }
39081
+ var obv = {
39082
+ type: "on-balance-volume",
39083
+ title: "On Balance Volume",
39084
+ shortTitle: "OBV",
39085
+ overlay: false,
39086
+ inputs: [colorInput()],
39087
+ compute: (bars, inputs) => {
39088
+ const vol = volumes(bars);
39089
+ const d = change(closes(bars));
39090
+ const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
39091
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39092
+ }
39093
+ };
39094
+ var accDist = {
39095
+ type: "accumulation-distribution",
39096
+ title: "Accumulation / Distribution",
39097
+ shortTitle: "A/D",
39098
+ overlay: false,
39099
+ inputs: [colorInput()],
39100
+ compute: (bars, inputs) => ({
39101
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
39102
+ })
39103
+ };
39104
+ var cmf = {
39105
+ type: "chaikin-money-flow",
39106
+ title: "Chaikin Money Flow",
39107
+ shortTitle: "CMF",
39108
+ overlay: false,
39109
+ inputs: [lengthInput(20), colorInput()],
39110
+ compute: (bars, inputs) => {
39111
+ const len = num3(inputs, "length", 20);
39112
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
39113
+ return {
39114
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39115
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39116
+ };
39117
+ }
39118
+ };
39119
+ var chaikinOscillator = {
39120
+ type: "chaikin-oscillator",
39121
+ title: "Chaikin Oscillator",
39122
+ shortTitle: "Chaikin Osc",
39123
+ overlay: false,
39124
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39125
+ compute: (bars, inputs) => {
39126
+ const ad = cumSum(moneyFlowVolume(bars));
39127
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
39128
+ return {
39129
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39130
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39131
+ };
39132
+ }
39133
+ };
39134
+ var eom = {
39135
+ type: "ease-of-movement",
39136
+ title: "Ease of Movement",
39137
+ shortTitle: "EOM",
39138
+ overlay: false,
39139
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
39140
+ compute: (bars, inputs) => {
39141
+ const div = num3(inputs, "divisor", 1e4);
39142
+ const hl2 = sourceValues(bars, "HL2");
39143
+ const move = change(hl2);
39144
+ const raw = bars.map((b, i) => {
39145
+ const m = move[i];
39146
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
39147
+ return div * m * (b.high - b.low) / b.volume;
39148
+ });
39149
+ return {
39150
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39151
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39152
+ };
39153
+ }
39154
+ };
39155
+ var forceIndex = {
39156
+ type: "force-index",
39157
+ title: "Force Index",
39158
+ shortTitle: "FI",
39159
+ overlay: false,
39160
+ inputs: [lengthInput(13), colorInput()],
39161
+ compute: (bars, inputs) => {
39162
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
39163
+ return {
39164
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39165
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39166
+ };
39167
+ }
39168
+ };
39169
+ var klinger = {
39170
+ type: "klinger-oscillator",
39171
+ title: "Klinger Oscillator",
39172
+ shortTitle: "KVO",
39173
+ overlay: false,
39174
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
39175
+ compute: (bars, inputs) => {
39176
+ const hlc3 = sourceValues(bars, "HLC3");
39177
+ const trend = change(hlc3);
39178
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
39179
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
39180
+ return {
39181
+ plots: [
39182
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
39183
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
39184
+ ],
39185
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39186
+ };
39187
+ }
39188
+ };
39189
+ var mfi = {
39190
+ type: "money-flow-index",
39191
+ title: "Money Flow Index",
39192
+ shortTitle: "MFI",
39193
+ overlay: false,
39194
+ inputs: [lengthInput(14), colorInput()],
39195
+ compute: (bars, inputs) => {
39196
+ const len = num3(inputs, "length", 14);
39197
+ const tp = sourceValues(bars, "HLC3");
39198
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
39199
+ const d = change(tp);
39200
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
39201
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
39202
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
39203
+ return {
39204
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39205
+ levels: [
39206
+ { key: "upper", price: 80, color: NEUTRAL },
39207
+ { key: "lower", price: 20, color: NEUTRAL }
39208
+ ]
39209
+ };
39210
+ }
39211
+ };
39212
+ function volumeIndex(bars, when) {
39213
+ const out = new Array(bars.length).fill(Number.NaN);
39214
+ let index = 1e3;
39215
+ let started = false;
39216
+ for (let i = 1; i < bars.length; i++) {
39217
+ const v = bars[i].volume;
39218
+ const pv = bars[i - 1].volume;
39219
+ const pc = bars[i - 1].close;
39220
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
39221
+ if (started) out[i] = index;
39222
+ continue;
39223
+ }
39224
+ started = true;
39225
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
39226
+ out[i] = index;
39227
+ }
39228
+ return out;
39229
+ }
39230
+ var nvi = {
39231
+ type: "negative-volume-index",
39232
+ title: "Negative Volume Index",
39233
+ shortTitle: "Negative Volume Index",
39234
+ overlay: false,
39235
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39236
+ compute: (bars, inputs) => {
39237
+ const line = volumeIndex(bars, (v, pv) => v < pv);
39238
+ return {
39239
+ plots: [
39240
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39241
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39242
+ ]
39243
+ };
39244
+ }
39245
+ };
39246
+ var pvi = {
39247
+ type: "positive-volume-index",
39248
+ title: "Positive Volume Index",
39249
+ shortTitle: "Positive Volume Index",
39250
+ overlay: false,
39251
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
39252
+ compute: (bars, inputs) => {
39253
+ const line = volumeIndex(bars, (v, pv) => v > pv);
39254
+ return {
39255
+ plots: [
39256
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
39257
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
39258
+ ]
39259
+ };
39260
+ }
39261
+ };
39262
+ var pvt = {
39263
+ type: "price-volume-trend",
39264
+ title: "Price Volume Trend",
39265
+ shortTitle: "PVT",
39266
+ overlay: false,
39267
+ inputs: [colorInput()],
39268
+ compute: (bars, inputs) => {
39269
+ const c = closes(bars);
39270
+ const raw = bars.map((b, i) => {
39271
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
39272
+ const pc = c[i - 1];
39273
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
39274
+ });
39275
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
39276
+ }
39277
+ };
39278
+ var volumeOscillator = {
39279
+ type: "volume-oscillator",
39280
+ title: "Volume Oscillator",
39281
+ shortTitle: "Vol Osc",
39282
+ overlay: false,
39283
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
39284
+ compute: (bars, inputs) => {
39285
+ const vol = volumes(bars);
39286
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
39287
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
39288
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39289
+ return {
39290
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39291
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39292
+ };
39293
+ }
39294
+ };
39295
+ var pvo = {
39296
+ type: "pvo",
39297
+ title: "Percentage Volume Oscillator",
39298
+ shortTitle: "PVO",
39299
+ overlay: false,
39300
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
39301
+ compute: (bars, inputs) => {
39302
+ const vol = volumes(bars);
39303
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
39304
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
39305
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
39306
+ const signal = ema(line, num3(inputs, "signalLength", 9));
39307
+ const hist = sub(line, signal);
39308
+ return {
39309
+ plots: [
39310
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
39311
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
39312
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
39313
+ ],
39314
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39315
+ };
39316
+ }
39317
+ };
39318
+ var vfi = {
39319
+ type: "volume-flow-indicator",
39320
+ title: "Volume Flow Indicator",
39321
+ shortTitle: "Volume Flow Indicator",
39322
+ overlay: false,
39323
+ inputs: [
39324
+ lengthInput(130),
39325
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
39326
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
39327
+ lengthInput(3, "smoothLength", "Smoothing length")
39328
+ ],
39329
+ compute: (bars, inputs) => {
39330
+ const len = num3(inputs, "length", 130);
39331
+ const coef = num3(inputs, "coef", 0.2);
39332
+ const volCoef = num3(inputs, "volCoef", 2.5);
39333
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
39334
+ const inter = change(typical);
39335
+ const vInter = stdev(inter, 30);
39336
+ const vol = volumes(bars);
39337
+ const vAve = sma(vol, len);
39338
+ const mf = change(sourceValues(bars, "HLC3"));
39339
+ const raw = bars.map((b, i) => {
39340
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
39341
+ const v = vol[i];
39342
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39343
+ const m = mf[i];
39344
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
39345
+ const capped = Math.min(v, ave * volCoef);
39346
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
39347
+ });
39348
+ const values = bars.map((_, i) => {
39349
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
39350
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
39351
+ });
39352
+ const flowSum = sum(raw, len);
39353
+ const line = zip(flowSum, values, (f, a) => f / a);
39354
+ return {
39355
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
39356
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39357
+ };
39358
+ }
39359
+ };
39360
+ var intradayIntensity = {
39361
+ type: "intraday-intensity",
39362
+ title: "Intraday Intensity",
39363
+ shortTitle: "Intraday Intensity",
39364
+ overlay: false,
39365
+ inputs: [lengthInput(21), colorInput()],
39366
+ compute: (bars, inputs) => {
39367
+ const len = num3(inputs, "length", 21);
39368
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
39369
+ return {
39370
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
39371
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
39372
+ };
39373
+ }
39374
+ };
39375
+ var volumeSpecs = [
39376
+ obv,
39377
+ accDist,
39378
+ cmf,
39379
+ chaikinOscillator,
39380
+ eom,
39381
+ forceIndex,
39382
+ klinger,
39383
+ mfi,
39384
+ nvi,
39385
+ pvi,
39386
+ pvt,
39387
+ volumeOscillator,
39388
+ pvo,
39389
+ vfi,
39390
+ intradayIntensity
39391
+ ];
39392
+
39393
+ // src/core/native-indicators/classics/overlays.ts
39394
+ var DAY_MS2 = 864e5;
39395
+ function periodKey(anchor, time) {
39396
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
39397
+ if (anchor === "Month") {
39398
+ const d = new Date(time);
39399
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
39400
+ }
39401
+ return Math.floor(time / DAY_MS2);
39402
+ }
39403
+ var parabolicSar = {
39404
+ type: "parabolic-sar",
39405
+ title: "Parabolic SAR",
39406
+ shortTitle: "SAR",
39407
+ overlay: true,
39408
+ inputs: [
39409
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39410
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
39411
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
39412
+ ],
39413
+ compute: (bars, inputs) => {
39414
+ const start = num3(inputs, "start", 0.02);
39415
+ const inc = num3(inputs, "increment", 0.02);
39416
+ const max = num3(inputs, "maximum", 0.2);
39417
+ const n = bars.length;
39418
+ const values = new Array(n).fill(Number.NaN);
39419
+ const colors = new Array(n).fill(null);
39420
+ if (n >= 2) {
39421
+ let long = bars[1].close >= bars[0].close;
39422
+ let sar = long ? bars[0].low : bars[0].high;
39423
+ let ep = long ? bars[0].high : bars[0].low;
39424
+ let af = start;
39425
+ for (let i = 1; i < n; i++) {
39426
+ const b = bars[i];
39427
+ sar += af * (ep - sar);
39428
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
39429
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
39430
+ const reversed = long ? b.low < sar : b.high > sar;
39431
+ if (reversed) {
39432
+ long = !long;
39433
+ sar = ep;
39434
+ ep = long ? b.high : b.low;
39435
+ af = start;
39436
+ } else if (long && b.high > ep) {
39437
+ ep = b.high;
39438
+ af = Math.min(max, af + inc);
39439
+ } else if (!long && b.low < ep) {
39440
+ ep = b.low;
39441
+ af = Math.min(max, af + inc);
39442
+ }
39443
+ values[i] = sar;
39444
+ colors[i] = long ? BULLISH : BEARISH;
39445
+ }
39446
+ }
39447
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
39448
+ }
39449
+ };
39450
+ var vwap = {
39451
+ type: "vwap",
39452
+ title: "Volume Weighted Average Price",
39453
+ shortTitle: "VWAP",
39454
+ overlay: true,
39455
+ inputs: [
39456
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
39457
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
39458
+ colorInput(INFO)
39459
+ ],
39460
+ compute: (bars, inputs) => {
39461
+ const anchor = str3(inputs, "anchor", "Day");
39462
+ const source = str3(inputs, "source", "HLC3");
39463
+ const values = new Array(bars.length).fill(Number.NaN);
39464
+ let period = Number.NaN;
39465
+ let cumPV = 0;
39466
+ let cumV = 0;
39467
+ for (let i = 0; i < bars.length; i++) {
39468
+ const b = bars[i];
39469
+ const key = periodKey(anchor, b.time);
39470
+ if (key !== period) {
39471
+ period = key;
39472
+ cumPV = 0;
39473
+ cumV = 0;
39474
+ }
39475
+ const v = b.volume;
39476
+ if (v != null && Number.isFinite(v) && v > 0) {
39477
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
39478
+ cumPV += tp * v;
39479
+ cumV += v;
39480
+ }
39481
+ if (cumV > 0) values[i] = cumPV / cumV;
39482
+ }
39483
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
39484
+ }
39485
+ };
39486
+ var pivotPoints = {
39487
+ type: "pivot-points",
39488
+ title: "Pivot Points",
39489
+ shortTitle: "Pivots",
39490
+ overlay: true,
39491
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
39492
+ compute: (bars, inputs) => {
39493
+ const anchor = str3(inputs, "anchor", "Day");
39494
+ const aggs = /* @__PURE__ */ new Map();
39495
+ for (const b of bars) {
39496
+ const key = periodKey(anchor, b.time);
39497
+ const a = aggs.get(key);
39498
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
39499
+ else {
39500
+ a.high = Math.max(a.high, b.high);
39501
+ a.low = Math.min(a.low, b.low);
39502
+ a.close = b.close;
39503
+ }
39504
+ }
39505
+ const n = bars.length;
39506
+ const mk = () => new Array(n).fill(Number.NaN);
39507
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
39508
+ for (let i = 0; i < n; i++) {
39509
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
39510
+ if (!prev2) continue;
39511
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
39512
+ levels.p[i] = p;
39513
+ levels.r1[i] = 2 * p - prev2.low;
39514
+ levels.s1[i] = 2 * p - prev2.high;
39515
+ levels.r2[i] = p + (prev2.high - prev2.low);
39516
+ levels.s2[i] = p - (prev2.high - prev2.low);
39517
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
39518
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
39519
+ }
39520
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
39521
+ return {
39522
+ plots: [
39523
+ plot("p", "P", WARNING),
39524
+ plot("r1", "R1", BEARISH),
39525
+ plot("s1", "S1", BULLISH),
39526
+ plot("r2", "R2", BEARISH),
39527
+ plot("s2", "S2", BULLISH),
39528
+ plot("r3", "R3", BEARISH),
39529
+ plot("s3", "S3", BULLISH)
39530
+ ]
39531
+ };
39532
+ }
39533
+ };
39534
+ var fiftyTwoWeek = {
39535
+ type: "52-week-high-low",
39536
+ title: "52 Week High/Low",
39537
+ shortTitle: "52W H/L",
39538
+ overlay: true,
39539
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
39540
+ compute: (bars, inputs) => {
39541
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
39542
+ const n = bars.length;
39543
+ const hi = new Array(n).fill(Number.NaN);
39544
+ const lo = new Array(n).fill(Number.NaN);
39545
+ const maxIdx = [];
39546
+ const minIdx = [];
39547
+ let from = 0;
39548
+ for (let i = 0; i < n; i++) {
39549
+ const b = bars[i];
39550
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
39551
+ maxIdx.push(i);
39552
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
39553
+ minIdx.push(i);
39554
+ while (bars[from].time < b.time - span) from += 1;
39555
+ while (maxIdx[0] < from) maxIdx.shift();
39556
+ while (minIdx[0] < from) minIdx.shift();
39557
+ hi[i] = bars[maxIdx[0]].high;
39558
+ lo[i] = bars[minIdx[0]].low;
39559
+ }
39560
+ return {
39561
+ plots: [
39562
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
39563
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
39564
+ ]
39565
+ };
39566
+ }
39567
+ };
39568
+ var zigzag = {
39569
+ type: "zigzag",
39570
+ title: "ZigZag",
39571
+ overlay: true,
39572
+ inputs: [
39573
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
39574
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
39575
+ colorInput()
39576
+ ],
39577
+ compute: (bars, inputs) => {
39578
+ const dev = num3(inputs, "deviation", 5) / 100;
39579
+ const depth = num3(inputs, "depth", 10);
39580
+ const pivots = [];
39581
+ if (bars.length > 1) {
39582
+ let up = bars[1].close >= bars[0].close;
39583
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
39584
+ for (let i = 1; i < bars.length; i++) {
39585
+ const b = bars[i];
39586
+ if (up) {
39587
+ if (b.high >= ext.price) ext = { i, price: b.high };
39588
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39589
+ pivots.push(ext);
39590
+ up = false;
39591
+ ext = { i, price: b.low };
39592
+ }
39593
+ } else if (b.low <= ext.price) {
39594
+ ext = { i, price: b.low };
39595
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
39596
+ pivots.push(ext);
39597
+ up = true;
39598
+ ext = { i, price: b.high };
39599
+ }
39600
+ }
39601
+ pivots.push(ext);
39602
+ }
39603
+ return {
39604
+ plots: [],
39605
+ polylines: [
39606
+ {
39607
+ key: "zigzag",
39608
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
39609
+ curved: false,
39610
+ closed: false,
39611
+ lineColor: str3(inputs, "color", SERIES_LINE),
39612
+ lineWidth: 2,
39613
+ lineStyle: "solid",
39614
+ arrowLeft: false,
39615
+ arrowRight: false
39616
+ }
39617
+ ]
39618
+ };
39619
+ }
39620
+ };
39621
+ var williamsFractal = {
39622
+ type: "williams-fractal",
39623
+ title: "Williams Fractal",
39624
+ shortTitle: "Fractals",
39625
+ overlay: true,
39626
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
39627
+ compute: (bars, inputs) => {
39628
+ const p = num3(inputs, "periods", 2);
39629
+ const markers = [];
39630
+ const isExtreme = (i, pick, better) => {
39631
+ const v = pick(bars[i]);
39632
+ for (let k = i - p; k <= i + p; k++) {
39633
+ if (k === i) continue;
39634
+ if (!better(v, pick(bars[k]))) return false;
39635
+ }
39636
+ return true;
39637
+ };
39638
+ for (let i = p; i < bars.length - p; i++) {
39639
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
39640
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
39641
+ }
39642
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
39643
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
39644
+ }
39645
+ }
39646
+ return { plots: [], markers };
39647
+ }
39648
+ };
39649
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
39650
+
39651
+ // src/core/native-indicators/classics/index.ts
39652
+ var classicSpecs = [
39653
+ ...averageSpecs,
39654
+ ...bandSpecs,
39655
+ ...oscillatorSpecs,
39656
+ ...trendSpecs,
39657
+ ...volatilitySpecs,
39658
+ ...volumeSpecs,
39659
+ ...overlaySpecs
39660
+ ];
39661
+ function registerClassicIndicators() {
39662
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
39663
+ }
39664
+
37259
39665
  // src/Vela.ts
37260
39666
  var Vela = class {
37261
39667
  constructor(container, options = {}, deps = {}) {
37262
39668
  registerBuiltinChartTypes();
37263
39669
  registerVolume();
37264
39670
  registerVpvr();
39671
+ registerClassicIndicators();
37265
39672
  const element = resolveElement(container);
37266
39673
  const theme = resolveTheme(options.theme);
37267
- let animZoom = true;
37268
- let animPan = true;
37269
- if (typeof options.animations === "boolean") {
37270
- animZoom = options.animations;
37271
- animPan = options.animations;
37272
- } else if (options.animations) {
37273
- animZoom = options.animations.zoom ?? true;
37274
- animPan = options.animations.pan ?? true;
37275
- }
37276
39674
  const display = {
37277
39675
  currentPriceLine: options.currentPriceLine ?? true,
37278
39676
  logScale: options.logScale ?? false,
37279
39677
  nativeBackend: options.nativeBackend ?? "auto",
37280
- animZoom,
37281
- animPan,
39678
+ ...resolveAnimations(options.animations),
37282
39679
  glow: options.glow ?? 0,
37283
39680
  upColor: options.upColor ?? BULLISH,
37284
39681
  downColor: options.downColor ?? BEARISH,
@@ -37336,25 +39733,28 @@ var Vela = class {
37336
39733
  }
37337
39734
  /**
37338
39735
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
37339
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
37340
- * events) and is single-instance per type (a second call returns the existing handle). Returns
37341
- * a fail-soft handle for an unregistered type. Native renderer only.
39736
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
39737
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
39738
+ * call returns the existing handle; the classic studies allow several instances, so each call
39739
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
37342
39740
  */
37343
39741
  addNativeIndicator(type, options) {
37344
39742
  return this.orchestrator.addNativeIndicator(type, options);
37345
39743
  }
37346
39744
  /**
37347
39745
  * The catalog of built-in native indicators with their live state on this chart — each entry's
37348
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
37349
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
37350
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
37351
- * because support may probe the provider (a type may need data the symbol lacks).
39746
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
39747
+ * and whether it allows several instances (`multiInstance`; otherwise a second
39748
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
39749
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
39750
+ * provider (a type may need data the symbol lacks).
37352
39751
  */
37353
39752
  availableNativeIndicators() {
37354
39753
  return this.orchestrator.availableNativeIndicators();
37355
39754
  }
37356
39755
  /**
37357
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
39756
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
39757
+ * multi-instance type repeats) — the synchronous slice of
37358
39758
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
37359
39759
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
37360
39760
  * happened microseconds ago, which an async catalog mirror cannot guarantee.
@@ -37590,9 +39990,6 @@ function resolveElement(container) {
37590
39990
  return element;
37591
39991
  }
37592
39992
 
37593
- // src/core/options.ts
37594
- var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
37595
-
37596
39993
  // src/widget/statusline-model.ts
37597
39994
  function segmentVisibility(parts, chartHidden = false) {
37598
39995
  return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
@@ -38097,7 +40494,7 @@ var Statusline = class {
38097
40494
  };
38098
40495
 
38099
40496
  // src/widget/session-shading.ts
38100
- var DAY_MS2 = 864e5;
40497
+ var DAY_MS3 = 864e5;
38101
40498
  var MINUTE_MS = 6e4;
38102
40499
  function parseWindow(text, allowOvernight = false) {
38103
40500
  if (typeof text !== "string") return null;
@@ -38176,7 +40573,7 @@ function expandSessionZones(spec, from, to) {
38176
40573
  const dtf = civilFormatter(spec.timezone);
38177
40574
  if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
38178
40575
  const seen = /* @__PURE__ */ new Set();
38179
- for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
40576
+ for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
38180
40577
  const civil = civilParts(dtf, cursor);
38181
40578
  const key = civil.year * 1e4 + civil.month * 100 + civil.day;
38182
40579
  if (!Number.isFinite(key) || seen.has(key)) continue;
@@ -38201,7 +40598,7 @@ function expandSessionZones(spec, from, to) {
38201
40598
  }
38202
40599
  return { pre, post, extended: mergeAbutting(extended) };
38203
40600
  }
38204
- var COVER_PAD_MIN_MS = 2 * DAY_MS2;
40601
+ var COVER_PAD_MIN_MS = 2 * DAY_MS3;
38205
40602
  var SessionShadingTracker = class {
38206
40603
  constructor(onZones) {
38207
40604
  this.onZones = onZones;
@@ -38225,7 +40622,7 @@ var SessionShadingTracker = class {
38225
40622
  this.covered = null;
38226
40623
  this.session = opts.session;
38227
40624
  const tfMs = timeframeMs(opts.timeframe);
38228
- this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
40625
+ this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
38229
40626
  this.lastRange = opts.range;
38230
40627
  void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
38231
40628
  if (my !== this.epoch) return;
@@ -38899,8 +41296,8 @@ var ChartCell = class {
38899
41296
  /** The volume auto-add rides the cell's first candles (`load:end`); until then the
38900
41297
  * registry can't show it and the dehydrated ledger reports the INTENT instead. */
38901
41298
  this.volumeMayBePending = true;
38902
- /** Sync mirror of the chart's present native types — the removal handler diffs
38903
- * against it to identify (and record) whichever type was just removed. */
41299
+ /** Sync mirror of the chart's native instances (id + type) — the removal handler
41300
+ * looks the removed id up here to learn which type an undo must re-add. */
38904
41301
  this.presentNatives = [];
38905
41302
  this.rangeBars = 0;
38906
41303
  this.pendingRange = null;
@@ -39072,15 +41469,18 @@ var ChartCell = class {
39072
41469
  redo: () => this.dropInstance(snapshot)
39073
41470
  });
39074
41471
  } else {
39075
- const now = this.inner?.presentNativeIndicators() ?? [];
39076
- for (const type of this.presentNatives.filter((t) => !now.includes(t))) {
41472
+ const gone = this.presentNatives.find((n) => n.id === id2);
41473
+ if (gone) {
41474
+ const { type } = gone;
41475
+ let revived = null;
39077
41476
  this.history.push({
39078
41477
  undo: () => {
39079
- this.inner?.addNativeIndicator(type);
41478
+ revived = this.inner?.addNativeIndicator(type) ?? null;
39080
41479
  this.refreshNativeCatalog();
39081
41480
  },
39082
41481
  redo: () => {
39083
- this.inner?.addNativeIndicator(type).remove();
41482
+ revived?.remove();
41483
+ revived = null;
39084
41484
  this.refreshNativeCatalog();
39085
41485
  }
39086
41486
  });
@@ -39385,9 +41785,9 @@ var ChartCell = class {
39385
41785
  const live = this.inner?.renderer.get("priceStyle");
39386
41786
  return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
39387
41787
  }
39388
- /** Manifest instances + present natives — the topbar indicator count. */
41788
+ /** Manifest instances + native instances — the topbar indicator count. */
39389
41789
  get indicatorCount() {
39390
- return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
41790
+ return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
39391
41791
  }
39392
41792
  /** Switch this cell's market in place (the chart instance survives). The projection
39393
41793
  * is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
@@ -39519,13 +41919,15 @@ var ChartCell = class {
39519
41919
  if (!chart) return;
39520
41920
  this.volumeIntent = led.natives.includes("volume");
39521
41921
  this.history.silently(() => {
39522
- const present = chart.presentNativeIndicators();
39523
- for (const type of led.natives) {
39524
- if (!present.includes(type)) chart.addNativeIndicator(type);
39525
- }
39526
- for (const type of present) {
39527
- if (!led.natives.includes(type)) chart.addNativeIndicator(type).remove();
39528
- }
41922
+ const owed = /* @__PURE__ */ new Map();
41923
+ for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
41924
+ for (const h of this.nativeHandles()) {
41925
+ const type = h.nativeType;
41926
+ const n = owed.get(type) ?? 0;
41927
+ if (n > 0) owed.set(type, n - 1);
41928
+ else h.remove();
41929
+ }
41930
+ for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
39529
41931
  for (const it of [...this.instances]) this.dropInstance(it);
39530
41932
  if (this.manifest.length > 0) {
39531
41933
  for (const item of led.manifest) {
@@ -39543,34 +41945,42 @@ var ChartCell = class {
39543
41945
  this.syncPresentNatives();
39544
41946
  this.refreshNativeCatalog();
39545
41947
  }
39546
- /** The picker's library rows: supported natives first, then the manifest. */
41948
+ /** The supported natives in picker order: A→Z by title — registration order follows
41949
+ * the catalog's families, which is meaningless to the reader. This is the library
41950
+ * index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
41951
+ * read it — indexing the unsorted catalog on add would land on a different study. */
41952
+ supportedNatives() {
41953
+ return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
41954
+ }
41955
+ /** The picker's library rows: supported natives first (see {@link supportedNatives}),
41956
+ * then the manifest in the host's own order. */
39547
41957
  libraryRows() {
39548
41958
  return [
39549
- ...this.nativeCatalog.filter((n) => n.supported).map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
41959
+ ...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
39550
41960
  ...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
39551
41961
  ];
39552
41962
  }
39553
- /** The picker's on-chart rows: present natives first, then live instances. */
41963
+ /** The picker's on-chart rows: native instances first, then live script instances. */
39554
41964
  onChartRows() {
39555
41965
  return [
39556
- ...this.nativeCatalog.filter((n) => n.present).map((n) => ({ name: n.title, native: true, nativeType: n.type })),
41966
+ ...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
39557
41967
  ...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
39558
41968
  ];
39559
41969
  }
39560
41970
  /** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
39561
41971
  addFromLibrary(index) {
39562
- const natives = this.nativeCatalog.filter((n) => n.supported);
41972
+ const natives = this.supportedNatives();
39563
41973
  if (index < natives.length) this.addNative(natives[index].type);
39564
41974
  else {
39565
41975
  const entry = this.manifest[index - natives.length];
39566
41976
  if (entry) this.addManifestInstance(entry);
39567
41977
  }
39568
41978
  }
39569
- /** Remove by picker ON-CHART index (present natives precede instances). */
41979
+ /** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
39570
41980
  removeFromChart(index) {
39571
- const present = this.nativeCatalog.filter((n) => n.present);
39572
- if (index < present.length) this.removeNative(present[index].type);
39573
- else this.removeInstance(index - present.length);
41981
+ const natives = this.nativeHandles();
41982
+ if (index < natives.length) this.removeNative(natives[index]);
41983
+ else this.removeInstance(index - natives.length);
39574
41984
  }
39575
41985
  /**
39576
41986
  * Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
@@ -39630,30 +42040,42 @@ var ChartCell = class {
39630
42040
  it.handle = null;
39631
42041
  this.deps.onIndicatorsChanged(this.id);
39632
42042
  }
39633
- /** Add a native indicator (single-instance per type — the core dedupes). */
42043
+ /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
42044
+ * single-instance type already on the chart hands back its existing one — nothing
42045
+ * changed, so nothing enters the undo timeline. */
39634
42046
  addNative(type) {
39635
- this.inner?.addNativeIndicator(type);
42047
+ const chart = this.inner;
42048
+ if (!chart) return;
42049
+ const before = new Set(chart.indicators().map((h) => h.id));
42050
+ let added = chart.addNativeIndicator(type);
39636
42051
  this.syncPresentNatives();
39637
42052
  this.refreshNativeCatalog();
42053
+ if (before.has(added.id)) return;
39638
42054
  this.history.push({
39639
42055
  undo: () => {
39640
- this.inner?.addNativeIndicator(type).remove();
42056
+ added?.remove();
42057
+ added = null;
39641
42058
  this.refreshNativeCatalog();
39642
42059
  },
39643
42060
  redo: () => {
39644
- this.inner?.addNativeIndicator(type);
42061
+ added = this.inner?.addNativeIndicator(type) ?? null;
39645
42062
  this.refreshNativeCatalog();
39646
42063
  }
39647
42064
  });
39648
42065
  }
39649
- removeNative(type) {
39650
- this.inner?.addNativeIndicator(type).remove();
42066
+ removeNative(handle) {
42067
+ handle.remove();
39651
42068
  this.refreshNativeCatalog();
39652
42069
  }
39653
- /** Sync mirror of the chart's present native types — the removal handler diffs
39654
- * against it to identify (and record) whichever type was just removed. */
42070
+ /** The chart's native instances, insertion order — the picker's on-chart rows and
42071
+ * the removal index space (script instances follow them). */
42072
+ nativeHandles() {
42073
+ return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
42074
+ }
42075
+ /** Sync mirror of the chart's native instances — the removal handler looks the
42076
+ * removed id up here (the registry has already forgotten it) to record its type. */
39655
42077
  syncPresentNatives() {
39656
- this.presentNatives = this.inner?.presentNativeIndicators() ?? [];
42078
+ this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
39657
42079
  }
39658
42080
  /** Refresh the native catalog (supported/present flags) for this cell's market. */
39659
42081
  refreshNativeCatalog() {