@luxalgo/vela 0.6.14 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/NOTICE +28 -28
  2. package/README.md +9 -2
  3. package/dist/{DataProvider-DhzstpQb.d.ts → DataProvider-XLdJB86x.d.ts} +1 -1
  4. package/dist/{DataProvider-DlMtrwqM.d.cts → DataProvider-xH2VBcFT.d.cts} +1 -1
  5. package/dist/{chunk-KG4YT3TI.js → chunk-6DPAYEGO.js} +8 -1
  6. package/dist/chunk-FE6V2YAQ.js +1 -0
  7. package/dist/{chunk-N2Q5EEFH.js → chunk-GIJYO4LO.js} +2596 -242
  8. package/dist/{chunk-F4M24ANM.js → chunk-I77F2SVE.js} +80 -4
  9. package/dist/{chunk-MHY7MVXH.js → chunk-MGU3USTP.js} +29 -16
  10. package/dist/{chunk-G77Y7LK2.js → chunk-Q3TFJNJW.js} +2 -2
  11. package/dist/{chunk-64TF7JAJ.js → chunk-SEOO6YG7.js} +408 -184
  12. package/dist/{contributions-lPojhTxI.d.cts → contributions-BEkxwA5F.d.cts} +40 -13
  13. package/dist/{contributions-37nni40G.d.ts → contributions-CoAX7_SF.d.ts} +40 -13
  14. package/dist/index.cjs +2755 -380
  15. package/dist/index.d.cts +20 -12
  16. package/dist/index.d.ts +20 -12
  17. package/dist/index.js +5 -5
  18. package/dist/{options-CYS5Wmlx.d.ts → options-bdSAtp7y.d.cts} +5 -0
  19. package/dist/{options-CYS5Wmlx.d.cts → options-bdSAtp7y.d.ts} +5 -0
  20. package/dist/{plugin-DHyaoMjW.d.ts → plugin-CNUgbRNq.d.ts} +11 -8
  21. package/dist/{plugin-Cwikpz1m.d.cts → plugin-Dxyq35Zf.d.cts} +11 -8
  22. package/dist/plugin.cjs +8 -0
  23. package/dist/plugin.d.cts +5 -5
  24. package/dist/plugin.d.ts +5 -5
  25. package/dist/plugin.js +3 -3
  26. package/dist/providers/binance.d.cts +2 -2
  27. package/dist/providers/binance.d.ts +2 -2
  28. package/dist/providers/coinbase.d.cts +2 -2
  29. package/dist/providers/coinbase.d.ts +2 -2
  30. package/dist/providers/hyperliquid.d.cts +2 -2
  31. package/dist/providers/hyperliquid.d.ts +2 -2
  32. package/dist/{side-panel-CT9ZwIGz.d.cts → side-panel-HF0IAzwf.d.cts} +17 -0
  33. package/dist/{side-panel-CT9ZwIGz.d.ts → side-panel-HF0IAzwf.d.ts} +17 -0
  34. package/dist/{statusline-model-D-q_OOx9.d.ts → statusline-model-CbPza-Wc.d.cts} +6 -4
  35. package/dist/{statusline-model-CiJm-riV.d.cts → statusline-model-D7cWkw8A.d.ts} +6 -4
  36. package/dist/ui.cjs +34 -15
  37. package/dist/ui.d.cts +1 -1
  38. package/dist/ui.d.ts +1 -1
  39. package/dist/ui.js +3 -3
  40. package/dist/vela.global.js +2675 -300
  41. package/dist/vela.global.min.js +58 -52
  42. package/dist/widget.cjs +3296 -622
  43. package/dist/widget.d.cts +15 -11
  44. package/dist/widget.d.ts +15 -11
  45. package/dist/widget.js +7 -7
  46. package/dist/workspace.cjs +3296 -622
  47. package/dist/workspace.d.cts +49 -19
  48. package/dist/workspace.d.ts +49 -19
  49. package/dist/workspace.js +6 -6
  50. package/package.json +1 -1
  51. package/dist/chunk-H26BEHF4.js +0 -7
@@ -1,6 +1,6 @@
1
- import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-F4M24ANM.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MHY7MVXH.js';
3
- import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-KG4YT3TI.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-I77F2SVE.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MGU3USTP.js';
3
+ import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, INFO, svg24, svg24Solid } from './chunk-6DPAYEGO.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -468,6 +468,15 @@ var RendererControl = class {
468
468
  console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
469
469
  return null;
470
470
  }
471
+ /**
472
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
473
+ * or null if the renderer has no canvas export. Silent — a host compositing
474
+ * several charts skips a renderer that cannot contribute.
475
+ */
476
+ screenshotCanvas() {
477
+ if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
478
+ return null;
479
+ }
471
480
  /**
472
481
  * The active renderer's full cosmetic config as a serializable, versioned JSON
473
482
  * document — persist it (templates, saved settings) and feed it back to
@@ -1127,7 +1136,7 @@ function attachForming(closed, forming) {
1127
1136
 
1128
1137
  // src/data/MultiProviderFeed.ts
1129
1138
  function intervalMs(timeframe) {
1130
- const map = {
1139
+ const map2 = {
1131
1140
  "1m": 6e4,
1132
1141
  "3m": 18e4,
1133
1142
  "5m": 3e5,
@@ -1149,7 +1158,7 @@ function intervalMs(timeframe) {
1149
1158
  W: 6048e5,
1150
1159
  M: 2592e6
1151
1160
  };
1152
- return map[timeframe] ?? 36e5;
1161
+ return map2[timeframe] ?? 36e5;
1153
1162
  }
1154
1163
  var MultiProviderFeed = class {
1155
1164
  constructor(store = sharedBarStore) {
@@ -1536,8 +1545,8 @@ var DrawingsControl = class {
1536
1545
  /** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
1537
1546
  * display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
1538
1547
  * platform formatting, so hints always match the host's actual bindings. */
1539
- setToolShortcuts(map) {
1540
- if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
1548
+ setToolShortcuts(map2) {
1549
+ if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
1541
1550
  return this;
1542
1551
  }
1543
1552
  /** Display another chart's in-progress placement as a GHOST at reduced opacity
@@ -1978,8 +1987,8 @@ var DrawingController = class {
1978
1987
  this.port?.setExternalGhost?.(doc);
1979
1988
  }
1980
1989
  /** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
1981
- setToolShortcuts(map) {
1982
- this.port?.setToolShortcuts?.(map);
1990
+ setToolShortcuts(map2) {
1991
+ this.port?.setToolShortcuts?.(map2);
1983
1992
  }
1984
1993
  // ── programmatic CRUD (facade-facing) ── each is one undo step
1985
1994
  add(type, init = {}) {
@@ -2466,8 +2475,8 @@ var DrawingToolbar = class {
2466
2475
  }
2467
2476
  /** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
2468
2477
  * right edge of the flyout rows, beside the favorite star. */
2469
- setShortcuts(map) {
2470
- this.shortcuts = new Map(Object.entries(map));
2478
+ setShortcuts(map2) {
2479
+ this.shortcuts = new Map(Object.entries(map2));
2471
2480
  }
2472
2481
  /** Paint one star for the current favorite state (filled + gold, or outline). */
2473
2482
  paintStar(el, type) {
@@ -4318,8 +4327,8 @@ var InputsUI = class {
4318
4327
  lg.style.top = `${top}px`;
4319
4328
  }
4320
4329
  /** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
4321
- setCollapsedPanes(map) {
4322
- this.paneCollapse = map;
4330
+ setCollapsedPanes(map2) {
4331
+ this.paneCollapse = map2;
4323
4332
  }
4324
4333
  /**
4325
4334
  * Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
@@ -5080,6 +5089,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
5080
5089
  center: (wickLeftDev + wickDev / 2) / dpr
5081
5090
  };
5082
5091
  }
5092
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
5093
+ const out = [];
5094
+ let col = NaN;
5095
+ let intervals = [];
5096
+ const flush = () => {
5097
+ if (intervals.length === 0) return;
5098
+ const merged = [intervals[0]];
5099
+ for (let k = 1; k < intervals.length; k += 1) {
5100
+ const prev2 = merged[merged.length - 1];
5101
+ const next = intervals[k];
5102
+ if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
5103
+ prev2.hi = next.hi;
5104
+ if (next.headIdx < prev2.headIdx) {
5105
+ prev2.headIdx = next.headIdx;
5106
+ prev2.headTime = next.headTime;
5107
+ prev2.open = next.open;
5108
+ }
5109
+ if (next.lastIdx > prev2.lastIdx) {
5110
+ prev2.lastIdx = next.lastIdx;
5111
+ prev2.close = next.close;
5112
+ }
5113
+ } else {
5114
+ merged.push(next);
5115
+ }
5116
+ }
5117
+ for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
5118
+ intervals = [];
5119
+ };
5120
+ for (let i = i0; i <= i1; i += 1) {
5121
+ const b = bars[i];
5122
+ if (!b || b.high <= b.low) continue;
5123
+ const x = Math.round(xOf(i));
5124
+ if (x !== col) {
5125
+ flush();
5126
+ col = x;
5127
+ }
5128
+ let lo = b.low;
5129
+ let hi = b.high;
5130
+ let headIdx = i;
5131
+ let headTime = b.time;
5132
+ let open = b.open;
5133
+ for (let k = intervals.length - 1; k >= 0; k -= 1) {
5134
+ const iv = intervals[k];
5135
+ if (iv.lo > hi || iv.hi < lo) continue;
5136
+ if (iv.lo < lo) lo = iv.lo;
5137
+ if (iv.hi > hi) hi = iv.hi;
5138
+ if (iv.headIdx < headIdx) {
5139
+ headIdx = iv.headIdx;
5140
+ headTime = iv.headTime;
5141
+ open = iv.open;
5142
+ }
5143
+ intervals.splice(k, 1);
5144
+ }
5145
+ let insertAt = 0;
5146
+ while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
5147
+ intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open, lastIdx: i, close: b.close });
5148
+ }
5149
+ flush();
5150
+ return out;
5151
+ }
5083
5152
 
5084
5153
  // src/renderers/native/core/chartConfig.ts
5085
5154
  var CHART_CONFIG_VERSION = 1;
@@ -5090,6 +5159,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
5090
5159
  var BASELINE_LEVEL_DEFAULT = 50;
5091
5160
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
5092
5161
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
5162
+ var EXTENDED_SHADE = POSTMARKET_SHADE;
5093
5163
  function defaultChartStyle() {
5094
5164
  return {
5095
5165
  chartTypes: {},
@@ -5098,7 +5168,7 @@ function defaultChartStyle() {
5098
5168
  gridHorz: { visible: true, color: null },
5099
5169
  borderColor: null,
5100
5170
  separatorColor: null,
5101
- crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: SLATE },
5171
+ crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
5102
5172
  candle: {
5103
5173
  bodyVisible: true,
5104
5174
  borderVisible: false,
@@ -5121,7 +5191,7 @@ function defaultChartStyle() {
5121
5191
  width: 2,
5122
5192
  baselineLevel: BASELINE_LEVEL_DEFAULT
5123
5193
  },
5124
- sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
5194
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
5125
5195
  };
5126
5196
  }
5127
5197
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -5221,9 +5291,9 @@ function factoryResetConfig(factory) {
5221
5291
  if (inst.enableKey) defaults[inst.enableKey] = false;
5222
5292
  addRows(inst.rows);
5223
5293
  }
5224
- for (const sub of section.subsections ?? []) {
5225
- if (sub.enableKey) defaults[sub.enableKey] = false;
5226
- addRows(sub.rows);
5294
+ for (const sub2 of section.subsections ?? []) {
5295
+ if (sub2.enableKey) defaults[sub2.enableKey] = false;
5296
+ addRows(sub2.rows);
5227
5297
  }
5228
5298
  if (!section.instances) addRows(section.rows);
5229
5299
  bag[t.id] = defaults;
@@ -5350,7 +5420,8 @@ function mergeConfig(base, patch) {
5350
5420
  },
5351
5421
  sessions: {
5352
5422
  premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
5353
- postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
5423
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
5424
+ extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
5354
5425
  },
5355
5426
  stacking: {
5356
5427
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
@@ -6335,44 +6406,19 @@ var WebGL2Backend = class {
6335
6406
  }
6336
6407
  }
6337
6408
  /**
6338
- * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bucket bars
6339
- * sharing a rounded pixel column into one high-low stick → draw cost bounded by
6340
- * screen width, not bar count. Bucket color follows first-open→last-close.
6409
+ * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
6410
+ * rounded pixel column collapse into high-low sticks — one per contiguous
6411
+ * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
6412
+ * column stays a void. Draw cost stays bounded by screen width, not bar count;
6413
+ * stick color follows its own first-open→last-close.
6341
6414
  */
6342
6415
  emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
6343
- let px = NaN;
6344
- let lo = Infinity;
6345
- let hi = -Infinity;
6346
- let openV = 0;
6347
- let closeV = 0;
6348
- let headTime = 0;
6349
- let active = false;
6350
- const flush = () => {
6351
- if (!active) return;
6352
- const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
6353
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
6354
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
6355
- b.rect(px - 0.5, hY, 1, lY - hY, c);
6356
- };
6357
- for (let i = i0; i <= i1; i += 1) {
6358
- const bar = bars[i];
6359
- if (!bar || bar.high <= bar.low) continue;
6360
- const x = Math.round(coords.logicalToX(i));
6361
- if (!active || x !== px) {
6362
- flush();
6363
- px = x;
6364
- lo = bar.low;
6365
- hi = bar.high;
6366
- openV = bar.open;
6367
- headTime = bar.time;
6368
- active = true;
6369
- } else {
6370
- if (bar.low < lo) lo = bar.low;
6371
- if (bar.high > hi) hi = bar.high;
6372
- }
6373
- closeV = bar.close;
6416
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
6417
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
6418
+ const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
6419
+ const hY = yOf(s.hi);
6420
+ b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
6374
6421
  }
6375
- flush();
6376
6422
  }
6377
6423
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
6378
6424
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -6576,11 +6622,11 @@ function mergeBarColors(indicators) {
6576
6622
  }
6577
6623
  }
6578
6624
  if (!any) return EMPTY_BARCOLORS;
6579
- const map = /* @__PURE__ */ new Map();
6625
+ const map2 = /* @__PURE__ */ new Map();
6580
6626
  for (const m of indicators.values()) {
6581
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
6627
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
6582
6628
  }
6583
- return map;
6629
+ return map2;
6584
6630
  }
6585
6631
 
6586
6632
  // src/renderers/native/core/ViewportState.ts
@@ -6877,9 +6923,9 @@ function isHorizontalWheel(deltaX, deltaY) {
6877
6923
  function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
6878
6924
  return rightOffset + deltaX / barSpacing;
6879
6925
  }
6880
- function wheelPanDelta(deltaX, deltaY, shift) {
6926
+ function wheelPanDelta(deltaX, deltaY, shift2) {
6881
6927
  if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
6882
- if (shift) return deltaY;
6928
+ if (shift2) return deltaY;
6883
6929
  return null;
6884
6930
  }
6885
6931
  function effectiveSnapMode(momentaryOverride, sticky) {
@@ -7670,13 +7716,15 @@ var SceneGraph = class {
7670
7716
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
7671
7717
  return this.orderedCache;
7672
7718
  }
7673
- /** The session zones resolved into colored bands (pre/post-market washes from the
7674
- * config's session colors) — consumed by the same painting path as {@link highlights}. */
7719
+ /** The session zones resolved into colored bands (pre/post-market or extended-hours
7720
+ * washes from the config's session colors) — consumed by the same painting path as
7721
+ * {@link highlights}. */
7675
7722
  sessionHighlightBands() {
7676
7723
  if (!this.sessionZones) return [];
7677
7724
  const out = [];
7678
7725
  for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
7679
7726
  for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
7727
+ for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
7680
7728
  return out;
7681
7729
  }
7682
7730
  indicatorsForPane(paneId) {
@@ -8215,50 +8263,23 @@ var Canvas2dBackend = class {
8215
8263
  }
8216
8264
  }
8217
8265
  /**
8218
- * Sub-pixel LOD: bucket every bar sharing a rounded pixel column into ONE
8219
- * high-low stick, so draw cost is bounded by screen width (not bar count) when
8220
- * zoomed far out. The bucket's color follows its first-open→last-close
8221
- * direction (a barcolor() on the head bar still wins).
8266
+ * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
8267
+ * sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
8268
+ * so draw cost is bounded by screen width (not bar count) when zoomed far out
8269
+ * and a price gap inside the column stays a void. Each stick's color follows
8270
+ * its first-open→last-close direction (a barcolor() on the head bar still wins).
8222
8271
  */
8223
8272
  drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
8224
- let px = NaN;
8225
- let lo = Infinity;
8226
- let hi = -Infinity;
8227
- let openV = 0;
8228
- let closeV = 0;
8229
- let headTime = 0;
8230
- let active = false;
8231
- const flush = () => {
8232
- if (!active) return;
8233
- const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
8234
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
8235
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
8236
- ctx.strokeStyle = color;
8237
- ctx.lineWidth = 1;
8273
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
8274
+ ctx.lineWidth = 1;
8275
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
8276
+ const x = s.x + 0.5;
8277
+ ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
8238
8278
  ctx.beginPath();
8239
- ctx.moveTo(px, hY);
8240
- ctx.lineTo(px, lY);
8279
+ ctx.moveTo(x, yOf(s.hi));
8280
+ ctx.lineTo(x, yOf(s.lo));
8241
8281
  ctx.stroke();
8242
- };
8243
- for (let i = i0; i <= i1; i += 1) {
8244
- const b = bars[i];
8245
- if (!b || b.high <= b.low) continue;
8246
- const x = Math.round(coords.logicalToX(i)) + 0.5;
8247
- if (!active || x !== px) {
8248
- flush();
8249
- px = x;
8250
- lo = b.low;
8251
- hi = b.high;
8252
- openV = b.open;
8253
- headTime = b.time;
8254
- active = true;
8255
- } else {
8256
- if (b.low < lo) lo = b.low;
8257
- if (b.high > hi) hi = b.high;
8258
- }
8259
- closeV = b.close;
8260
8282
  }
8261
- flush();
8262
8283
  }
8263
8284
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
8264
8285
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -8526,11 +8547,11 @@ function mergeBarColors2(indicators) {
8526
8547
  }
8527
8548
  }
8528
8549
  if (!any) return EMPTY_BARCOLORS2;
8529
- const map = /* @__PURE__ */ new Map();
8550
+ const map2 = /* @__PURE__ */ new Map();
8530
8551
  for (const m of indicators.values()) {
8531
- if (m.barColors) for (const bc of m.barColors) map.set(bc.time, bc.color);
8552
+ if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
8532
8553
  }
8533
- return map;
8554
+ return map2;
8534
8555
  }
8535
8556
 
8536
8557
  // src/renderers/shared/drawing-geometry.ts
@@ -9282,6 +9303,32 @@ var DrawingSceneRenderer = class {
9282
9303
  }
9283
9304
  };
9284
9305
 
9306
+ // src/renderers/native/chrome/tz.ts
9307
+ function tzOffsetMs(ms, timeZone) {
9308
+ if (!timeZone || timeZone === "UTC") return 0;
9309
+ try {
9310
+ const dtf = new Intl.DateTimeFormat("en-US", {
9311
+ timeZone,
9312
+ hourCycle: "h23",
9313
+ year: "numeric",
9314
+ month: "2-digit",
9315
+ day: "2-digit",
9316
+ hour: "2-digit",
9317
+ minute: "2-digit",
9318
+ second: "2-digit"
9319
+ });
9320
+ const parts = dtf.formatToParts(new Date(ms));
9321
+ const get = (t) => Number(parts.find((p) => p.type === t)?.value);
9322
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
9323
+ return asUTC - ms;
9324
+ } catch {
9325
+ return 0;
9326
+ }
9327
+ }
9328
+ function zonedDate(ms, timeZone) {
9329
+ return new Date(ms + tzOffsetMs(ms, timeZone));
9330
+ }
9331
+
9285
9332
  // src/renderers/native/chrome/ticks.ts
9286
9333
  function priceTicks(min, max, target = 6) {
9287
9334
  if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
@@ -9429,7 +9476,14 @@ var STEP_LADDER = [
9429
9476
  YEAR
9430
9477
  ];
9431
9478
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
9479
+ var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
9432
9480
  var pad2 = (n) => n < 10 ? `0${n}` : String(n);
9481
+ function formatTimeStamp(ms, timeZone, barIntervalMs) {
9482
+ const d = zonedDate(ms, timeZone);
9483
+ const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
9484
+ if (barIntervalMs >= DAY) return date;
9485
+ return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
9486
+ }
9433
9487
  function pickStep(targetMs) {
9434
9488
  for (const step of STEP_LADDER) if (step >= targetMs) return step;
9435
9489
  return STEP_LADDER[STEP_LADDER.length - 1];
@@ -9471,32 +9525,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
9471
9525
  return out;
9472
9526
  }
9473
9527
 
9474
- // src/renderers/native/chrome/tz.ts
9475
- function tzOffsetMs(ms, timeZone) {
9476
- if (!timeZone || timeZone === "UTC") return 0;
9477
- try {
9478
- const dtf = new Intl.DateTimeFormat("en-US", {
9479
- timeZone,
9480
- hourCycle: "h23",
9481
- year: "numeric",
9482
- month: "2-digit",
9483
- day: "2-digit",
9484
- hour: "2-digit",
9485
- minute: "2-digit",
9486
- second: "2-digit"
9487
- });
9488
- const parts = dtf.formatToParts(new Date(ms));
9489
- const get = (t) => Number(parts.find((p) => p.type === t)?.value);
9490
- const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
9491
- return asUTC - ms;
9492
- } catch {
9493
- return 0;
9494
- }
9495
- }
9496
- function zonedDate(ms, timeZone) {
9497
- return new Date(ms + tzOffsetMs(ms, timeZone));
9498
- }
9499
-
9500
9528
  // src/renderers/native/chrome/ChromeRenderer.ts
9501
9529
  var ChromeRenderer = class {
9502
9530
  constructor() {
@@ -9943,7 +9971,7 @@ var CrosshairRenderer = class {
9943
9971
  const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
9944
9972
  this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
9945
9973
  }
9946
- this.chip(ctx, x, dataH + 1, formatStamp(coords.logicalToTime(logical), scene.timezone), chipBg, "center", true, theme.background);
9974
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
9947
9975
  }
9948
9976
  destroy() {
9949
9977
  this.canvas = null;
@@ -9991,7 +10019,7 @@ var CrosshairRenderer = class {
9991
10019
  this.chip(ctx, dataW + 1, ext.y, formatAxisValue(pane.scale, pane.bounds.height, ext.price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
9992
10020
  }
9993
10021
  }
9994
- this.chip(ctx, x, dataH + 1, formatStamp(ext.time, scene.timezone), chipBg, "center", true, theme.background);
10022
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
9995
10023
  ctx.globalAlpha = 1;
9996
10024
  }
9997
10025
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
@@ -10024,11 +10052,6 @@ function setDash3(ctx, style) {
10024
10052
  else if (style === "dotted") ctx.setLineDash([2, 3]);
10025
10053
  else ctx.setLineDash([]);
10026
10054
  }
10027
- function formatStamp(ms, timeZone) {
10028
- const d = zonedDate(ms, timeZone);
10029
- const pad = (n) => n < 10 ? `0${n}` : String(n);
10030
- return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
10031
- }
10032
10055
 
10033
10056
  // src/renderers/native/chrome/settings-visibility.ts
10034
10057
  function settingsIdSlug(label) {
@@ -10167,9 +10190,9 @@ function settingsIdCatalog(hostSections) {
10167
10190
  };
10168
10191
  if (section.rows) addRows(section.rows);
10169
10192
  for (const inst of section.instances ?? []) addRows(inst.rows);
10170
- for (const sub of section.subsections ?? []) {
10171
- ids.add(`${scope}.${settingsIdSlug(sub.title)}`);
10172
- addRows(sub.rows);
10193
+ for (const sub2 of section.subsections ?? []) {
10194
+ ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
10195
+ addRows(sub2.rows);
10173
10196
  }
10174
10197
  }
10175
10198
  for (const hs of hostSections) {
@@ -10728,14 +10751,14 @@ var SettingsDialog = class {
10728
10751
  };
10729
10752
  if (section.rows) seed(section.rows);
10730
10753
  for (const inst of section.instances ?? []) seed(inst.rows);
10731
- for (const sub of section.subsections ?? []) seed(sub.rows);
10754
+ for (const sub2 of section.subsections ?? []) seed(sub2.rows);
10732
10755
  for (const inst of section.instances ?? []) {
10733
10756
  if (!inst.enableKey || inst.enableKey in bag) continue;
10734
10757
  bag[inst.enableKey] = values[inst.enableKey] === true;
10735
10758
  }
10736
- for (const sub of section.subsections ?? []) {
10737
- if (!sub.enableKey || sub.enableKey in bag) continue;
10738
- bag[sub.enableKey] = values[sub.enableKey] === true;
10759
+ for (const sub2 of section.subsections ?? []) {
10760
+ if (!sub2.enableKey || sub2.enableKey in bag) continue;
10761
+ bag[sub2.enableKey] = values[sub2.enableKey] === true;
10739
10762
  }
10740
10763
  const refreshers = [];
10741
10764
  const put = (key, v) => {
@@ -10752,16 +10775,16 @@ var SettingsDialog = class {
10752
10775
  if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
10753
10776
  else this.flatTypeRows(rows, bag, put, refreshers, body);
10754
10777
  }
10755
- for (const sub of section.subsections ?? []) {
10756
- if (settingsIdHidden(`${scope}.${settingsIdSlug(sub.title)}`, this.hiddenSettings)) continue;
10757
- const rows = filterHiddenRows(sub.rows, scope, this.hiddenSettings);
10778
+ for (const sub2 of section.subsections ?? []) {
10779
+ if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
10780
+ const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
10758
10781
  if (rows.length === 0) continue;
10759
- const subMarker = this.section(sub.title);
10782
+ const subMarker = this.section(sub2.title);
10760
10783
  subMarker.dataset.sdStyle = typeId;
10761
10784
  subMarker.dataset.sdVisibility = section.visibility ?? "active";
10762
10785
  subMarker.dataset.sdSub = "1";
10763
10786
  body.append(subMarker);
10764
- body.append(this.groupedRows(`${typeId}/${sub.title}`, rows, bag, put, refreshers, sub.enableKey));
10787
+ body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
10765
10788
  }
10766
10789
  for (const r of refreshers) r();
10767
10790
  }
@@ -12868,11 +12891,11 @@ var DrawingInteraction = class {
12868
12891
  /** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
12869
12892
  * `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
12870
12893
  * while placing a line tool it locks the segment angle to 45° steps. */
12871
- down(x, y, mode = "off", shift = false) {
12894
+ down(x, y, mode = "off", shift2 = false) {
12872
12895
  const proj = this.deps.projector();
12873
12896
  if (this.state.kind === "placing") {
12874
12897
  if (this.state.draft.placementMode() !== "click") return;
12875
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
12898
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
12876
12899
  const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
12877
12900
  this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
12878
12901
  if (this.state.draft.anchors.length >= this.state.need) this.finalize();
@@ -12891,7 +12914,7 @@ var DrawingInteraction = class {
12891
12914
  }
12892
12915
  const hit = this.hitAt(x, y);
12893
12916
  if (hit) {
12894
- if (shift) {
12917
+ if (shift2) {
12895
12918
  this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
12896
12919
  return;
12897
12920
  }
@@ -12901,7 +12924,7 @@ var DrawingInteraction = class {
12901
12924
  }
12902
12925
  /** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
12903
12926
  * `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
12904
- move(x, y, mode = "off", shift = false) {
12927
+ move(x, y, mode = "off", shift2 = false) {
12905
12928
  if (this.state.kind === "idle") {
12906
12929
  const tool = this.deps.activeTool();
12907
12930
  const had = this.snapAt != null;
@@ -12915,7 +12938,7 @@ var DrawingInteraction = class {
12915
12938
  this.captureFreehand(x, y);
12916
12939
  return;
12917
12940
  }
12918
- const pivot = shift ? this.placingPivot(this.state.draft) : null;
12941
+ const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
12919
12942
  this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
12920
12943
  this.deps.changed();
12921
12944
  return;
@@ -12928,7 +12951,7 @@ var DrawingInteraction = class {
12928
12951
  if (this.state.handle >= 0) {
12929
12952
  const a = d.anchors[this.state.handle];
12930
12953
  if (a) {
12931
- const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
12954
+ const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
12932
12955
  const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
12933
12956
  applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
12934
12957
  }
@@ -13626,21 +13649,21 @@ var DrawingSettingsPopup = class {
13626
13649
  bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
13627
13650
  bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
13628
13651
  }
13629
- const vwap = drawing.vwap;
13630
- if (paths.has("vwap.midColor") && vwap) {
13652
+ const vwap2 = drawing.vwap;
13653
+ if (paths.has("vwap.midColor") && vwap2) {
13631
13654
  const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
13632
13655
  const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
13633
- bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap.midColor, (v) => actions.patch({ "vwap.midColor": v })));
13634
- bar.appendChild(this.dropdown("VWAP style", styles, vwap.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
13656
+ bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
13657
+ bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
13635
13658
  bar.appendChild(
13636
- this.dropdown("Band multiplier", MULTS, vwap.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
13659
+ this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
13637
13660
  label: (v) => `${v}\u03C3`,
13638
13661
  labelInTrigger: true
13639
13662
  })
13640
13663
  );
13641
- bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
13642
- bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
13643
- bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
13664
+ bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
13665
+ bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
13666
+ bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
13644
13667
  }
13645
13668
  if (paths.has("maxCurvature")) {
13646
13669
  const cur = drawing.maxCurvature ?? 24;
@@ -14657,8 +14680,8 @@ var UserDrawingController = class {
14657
14680
  this.toolbar.setFavorites(types);
14658
14681
  }
14659
14682
  /** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
14660
- setToolShortcuts(map) {
14661
- this.toolbar.setShortcuts(map);
14683
+ setToolShortcuts(map2) {
14684
+ this.toolbar.setShortcuts(map2);
14662
14685
  }
14663
14686
  /** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
14664
14687
  * in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
@@ -14776,7 +14799,7 @@ var UserDrawingController = class {
14776
14799
  this.render();
14777
14800
  }
14778
14801
  }
14779
- pointerDown(x, y, snap = "off", shift = false) {
14802
+ pointerDown(x, y, snap = "off", shift2 = false) {
14780
14803
  if (this.eraserMode) {
14781
14804
  this.erasing = true;
14782
14805
  this.deleteAt(x, y);
@@ -14796,9 +14819,9 @@ var UserDrawingController = class {
14796
14819
  return;
14797
14820
  }
14798
14821
  }
14799
- this.interaction.down(x, y, snap, shift);
14822
+ this.interaction.down(x, y, snap, shift2);
14800
14823
  }
14801
- pointerMove(x, y, snap = "off", shift = false) {
14824
+ pointerMove(x, y, snap = "off", shift2 = false) {
14802
14825
  if (this.eraserMode) {
14803
14826
  if (this.erasing) this.deleteAt(x, y);
14804
14827
  return;
@@ -14809,7 +14832,7 @@ var UserDrawingController = class {
14809
14832
  this.render();
14810
14833
  return;
14811
14834
  }
14812
- this.interaction.move(x, y, snap, shift);
14835
+ this.interaction.move(x, y, snap, shift2);
14813
14836
  this.updateHover(x, y);
14814
14837
  }
14815
14838
  /** Track which drawing is hovered so its handles appear on hover (and only then). */
@@ -15486,14 +15509,14 @@ function layoutTable(ctx, t, args) {
15486
15509
  }
15487
15510
  for (const { box, w: w2, h: h2 } of spanning) {
15488
15511
  if (box.cs > 1) {
15489
- let sum = 0;
15490
- for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
15491
- if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
15512
+ let sum2 = 0;
15513
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
15514
+ if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
15492
15515
  }
15493
15516
  if (box.rs > 1) {
15494
- let sum = 0;
15495
- for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
15496
- if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
15517
+ let sum2 = 0;
15518
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
15519
+ if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
15497
15520
  }
15498
15521
  }
15499
15522
  let w = 0;
@@ -15807,6 +15830,9 @@ function clipHighlightRect(x1, x2, left, right) {
15807
15830
  const end = Math.min(right, x2);
15808
15831
  return end > x ? { x, width: end - x } : null;
15809
15832
  }
15833
+ function bandEdgeSlot(logical) {
15834
+ return Math.ceil(logical) - 0.5;
15835
+ }
15810
15836
  var BackdropRenderer = class {
15811
15837
  constructor() {
15812
15838
  this.canvas = null;
@@ -15837,21 +15863,22 @@ var BackdropRenderer = class {
15837
15863
  this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
15838
15864
  }
15839
15865
  /** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
15840
- * Session-zone washes (pre/post-market) paint first, host highlights on top. */
15866
+ * Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
15867
+ * never cuts a candle in two), host highlights on top (continuous time mapping —
15868
+ * hosts may paint sub-bar spans). */
15841
15869
  drawHighlights(ctx, scene, coords) {
15842
15870
  const sessions = scene.sessionHighlightBands();
15843
15871
  if (sessions.length > 0) {
15844
15872
  const left = Math.max(0, coords.logicalToX(-0.5));
15845
15873
  const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
15846
- this.drawHighlightSet(ctx, sessions, coords, left, right);
15874
+ this.drawHighlightSet(ctx, sessions, coords, left, right, true);
15847
15875
  }
15848
- this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
15876
+ this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
15849
15877
  }
15850
- drawHighlightSet(ctx, bands, coords, left, right) {
15878
+ drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
15879
+ const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
15851
15880
  for (const band of bands) {
15852
- const x1 = coords.timeToX(band.from);
15853
- const x2 = coords.timeToX(band.to);
15854
- const rect = clipHighlightRect(x1, x2, left, right);
15881
+ const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
15855
15882
  if (!rect) continue;
15856
15883
  ctx.fillStyle = band.color;
15857
15884
  ctx.fillRect(rect.x, 0, rect.width, coords.height);
@@ -16084,6 +16111,7 @@ function createCustomMark(doc, html, background) {
16084
16111
  });
16085
16112
  el.style.color = attributionMarkColor(background);
16086
16113
  el.innerHTML = html;
16114
+ el.dataset.velaScreenshot = "1";
16087
16115
  return el;
16088
16116
  }
16089
16117
  function createAttributionMark(doc, background) {
@@ -16102,6 +16130,7 @@ function createAttributionMark(doc, background) {
16102
16130
  cursor: "pointer"
16103
16131
  });
16104
16132
  applyAttributionMarkTheme(a, background);
16133
+ a.dataset.velaScreenshot = "1";
16105
16134
  const symbol = doc.createElement("span");
16106
16135
  symbol.className = "vela-attr-symbol";
16107
16136
  symbol.setAttribute("aria-hidden", "true");
@@ -16184,24 +16213,24 @@ function rasterizeOverlay(ctx, root, frame) {
16184
16213
  drawSvgShapes(el, alpha);
16185
16214
  continue;
16186
16215
  }
16187
- const num3 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
16216
+ const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
16188
16217
  let path = null;
16189
16218
  if (tag === "path") {
16190
16219
  const d = el.getAttribute("d");
16191
16220
  if (d) path = new Path2D(d);
16192
16221
  } else if (tag === "circle") {
16193
16222
  path = new Path2D();
16194
- path.arc(num3("cx"), num3("cy"), num3("r"), 0, Math.PI * 2);
16223
+ path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
16195
16224
  } else if (tag === "ellipse") {
16196
16225
  path = new Path2D();
16197
- path.ellipse(num3("cx"), num3("cy"), num3("rx"), num3("ry"), 0, 0, Math.PI * 2);
16226
+ path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
16198
16227
  } else if (tag === "rect") {
16199
16228
  path = new Path2D();
16200
- path.rect(num3("x"), num3("y"), num3("width"), num3("height"));
16229
+ path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
16201
16230
  } else if (tag === "line") {
16202
16231
  path = new Path2D();
16203
- path.moveTo(num3("x1"), num3("y1"));
16204
- path.lineTo(num3("x2"), num3("y2"));
16232
+ path.moveTo(num4("x1"), num4("y1"));
16233
+ path.lineTo(num4("x2"), num4("y2"));
16205
16234
  } else if (tag === "polyline" || tag === "polygon") {
16206
16235
  const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
16207
16236
  if (pts.length < 4 || pts.some(Number.isNaN)) continue;
@@ -17106,7 +17135,7 @@ var NativeRenderer = class {
17106
17135
  };
17107
17136
  })(),
17108
17137
  series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
17109
- sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
17138
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
17110
17139
  };
17111
17140
  }
17112
17141
  /**
@@ -17188,7 +17217,7 @@ var NativeRenderer = class {
17188
17217
  width: next.baseline.width,
17189
17218
  baselineLevel: next.baseline.baselineLevel
17190
17219
  };
17191
- s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
17220
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
17192
17221
  this.setPriceStyle(next.series.style);
17193
17222
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
17194
17223
  this.scene.baselineValue = next.series.baseline;
@@ -17410,9 +17439,10 @@ var NativeRenderer = class {
17410
17439
  * opts in with a `data-vela-screenshot` attribute on the mount container's
17411
17440
  * subtree (the widget marks its status line, and its symbol watermark with
17412
17441
  * `"under"` — drawn beneath the canvases, where it sits on screen). Only the
17413
- * crosshair (L2) is intentionally excluded.
17442
+ * crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
17443
+ * as a PNG data URL.
17414
17444
  */
17415
- screenshot() {
17445
+ screenshotCanvas() {
17416
17446
  if (!this.dataCanvas) return null;
17417
17447
  this.computeScales();
17418
17448
  this.paintData();
@@ -17440,7 +17470,10 @@ var NativeRenderer = class {
17440
17470
  if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
17441
17471
  }
17442
17472
  }
17443
- return out.toDataURL("image/png");
17473
+ return out;
17474
+ }
17475
+ screenshot() {
17476
+ return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
17444
17477
  }
17445
17478
  /**
17446
17479
  * The candles-first-appear reveal — animates the **candles themselves** (no extra
@@ -17614,8 +17647,8 @@ var NativeRenderer = class {
17614
17647
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
17615
17648
  drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
17616
17649
  drawingsSnapMode: () => this.snapMode,
17617
- drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
17618
- drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
17650
+ drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
17651
+ drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
17619
17652
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
17620
17653
  drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
17621
17654
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
@@ -17932,18 +17965,18 @@ var NativeRenderer = class {
17932
17965
  for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
17933
17966
  if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
17934
17967
  const newHeadTime = this.bars[0].time;
17935
- let shift = 0;
17968
+ let shift2 = 0;
17936
17969
  if (newHeadTime !== prevHeadTime) {
17937
17970
  const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
17938
- if (prepended > 0) shift = prepended;
17971
+ if (prepended > 0) shift2 = prepended;
17939
17972
  else {
17940
17973
  const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
17941
- if (trimmed > 0) shift = -trimmed;
17974
+ if (trimmed > 0) shift2 = -trimmed;
17942
17975
  }
17943
17976
  }
17944
- if (shift !== 0) {
17945
- this.zoomAnchorLogical += shift;
17946
- if (this.hoverLogical != null) this.hoverLogical += shift;
17977
+ if (shift2 !== 0) {
17978
+ this.zoomAnchorLogical += shift2;
17979
+ if (this.hoverLogical != null) this.hoverLogical += shift2;
17947
17980
  }
17948
17981
  }
17949
17982
  const skipFit = opts?.preserveView === true && this.didInitialFit;
@@ -18835,7 +18868,7 @@ var NativeRenderer = class {
18835
18868
  const pricePane = this.dataWindowPricePane();
18836
18869
  const ohlc = this.dataWindowOHLC(bar, pricePane);
18837
18870
  const parts = [];
18838
- if (bar) parts.push(formatStamp2(bar.time, this.scene.timezone));
18871
+ if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
18839
18872
  if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
18840
18873
  for (const group of this.dataWindowGroups(idx, pricePane)) {
18841
18874
  for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
@@ -19549,15 +19582,15 @@ var NativeRenderer = class {
19549
19582
  /** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
19550
19583
  * strip — merged own-scale indicators are hidden while collapsed. */
19551
19584
  collapsedMasterMap() {
19552
- const map = /* @__PURE__ */ new Map();
19585
+ const map2 = /* @__PURE__ */ new Map();
19553
19586
  for (const pane of this.scene.panes.values()) {
19554
19587
  if (!pane.collapsed) continue;
19555
19588
  const models = this.scene.orderedIndicatorsForPane(pane.id);
19556
19589
  const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
19557
19590
  const master = models.find((m) => !merged.has(m.id)) ?? models[0];
19558
- map.set(pane.id, master?.id ?? null);
19591
+ map2.set(pane.id, master?.id ?? null);
19559
19592
  }
19560
- return map;
19593
+ return map2;
19561
19594
  }
19562
19595
  /** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
19563
19596
  setToolbarGutter(px) {
@@ -19722,7 +19755,7 @@ function formatStampParts(ms, timeZone) {
19722
19755
  time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
19723
19756
  };
19724
19757
  }
19725
- function formatStamp2(ms, timeZone) {
19758
+ function formatStamp(ms, timeZone) {
19726
19759
  const { date, time } = formatStampParts(ms, timeZone);
19727
19760
  return `${date} ${time}`;
19728
19761
  }
@@ -19753,7 +19786,7 @@ function sanitizeSessionZones(value) {
19753
19786
  }
19754
19787
  return out.sort((a, b) => a[0] - b[0]);
19755
19788
  };
19756
- return { pre: windows(v.pre), post: windows(v.post) };
19789
+ return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
19757
19790
  }
19758
19791
  function clamp012(v) {
19759
19792
  return Math.max(0, Math.min(1, v));
@@ -19829,7 +19862,7 @@ var LiveSession = class {
19829
19862
 
19830
19863
  // src/core/engine/IndicatorHandleImpl.ts
19831
19864
  var IndicatorHandleImpl = class {
19832
- constructor(id, title, controller, source) {
19865
+ constructor(id, title, controller, source, nativeType) {
19833
19866
  this.controller = controller;
19834
19867
  this.schema = [];
19835
19868
  this.propsSchema = [];
@@ -19838,6 +19871,7 @@ var IndicatorHandleImpl = class {
19838
19871
  this.id = id;
19839
19872
  this.title = title;
19840
19873
  if (source !== void 0) this.source = source;
19874
+ if (nativeType !== void 0) this.nativeType = nativeType;
19841
19875
  }
19842
19876
  get inputs() {
19843
19877
  return this.schema;
@@ -19928,24 +19962,24 @@ function summarizeModel(model) {
19928
19962
  }
19929
19963
  function inspectModels(models) {
19930
19964
  const indicators = models.map(summarizeModel);
19931
- const sum = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
19965
+ const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
19932
19966
  return {
19933
19967
  indicators,
19934
19968
  totals: {
19935
19969
  panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
19936
- series: sum((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
19937
- fills: sum((s) => s.fills),
19938
- backgrounds: sum((s) => s.backgrounds),
19939
- priceLines: sum((s) => s.priceLines),
19940
- lines: sum((s) => s.lines),
19941
- boxes: sum((s) => s.boxes),
19942
- labels: sum((s) => s.labels),
19943
- polylines: sum((s) => s.polylines),
19944
- linefills: sum((s) => s.linefills),
19945
- tables: sum((s) => s.tables),
19946
- barColors: sum((s) => s.barColors),
19947
- trades: sum((s) => s.trades),
19948
- forcedOverlay: sum((s) => s.forcedOverlay)
19970
+ series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
19971
+ fills: sum2((s) => s.fills),
19972
+ backgrounds: sum2((s) => s.backgrounds),
19973
+ priceLines: sum2((s) => s.priceLines),
19974
+ lines: sum2((s) => s.lines),
19975
+ boxes: sum2((s) => s.boxes),
19976
+ labels: sum2((s) => s.labels),
19977
+ polylines: sum2((s) => s.polylines),
19978
+ linefills: sum2((s) => s.linefills),
19979
+ tables: sum2((s) => s.tables),
19980
+ barColors: sum2((s) => s.barColors),
19981
+ trades: sum2((s) => s.trades),
19982
+ forcedOverlay: sum2((s) => s.forcedOverlay)
19949
19983
  }
19950
19984
  };
19951
19985
  }
@@ -21002,16 +21036,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
21002
21036
  /**
21003
21037
  * Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
21004
21038
  * indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
21005
- * INSTANCE per type: a second add returns the existing handle. Unknown type ⇒ a fail-soft handle
21006
- * that never mounts (mirrors addIndicator). Built-in types: `'volume'`, `'vpvr'`.
21039
+ * INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
21040
+ * single-instance type returns the existing handle; a multi-instance type gets a fresh
21041
+ * instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
21042
+ * addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
21007
21043
  */
21008
21044
  addNativeIndicator(type, options = {}) {
21009
- const existing = this.registry.all().find((r) => r.native?.type === type);
21010
- if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
21011
21045
  const descriptor = getNativeIndicator(type);
21046
+ if (!descriptor?.multiInstance) {
21047
+ const existing = this.registry.all().find((r) => r.native?.type === type);
21048
+ if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
21049
+ }
21012
21050
  const id = this.registry.nextId("native");
21013
21051
  const title = descriptor?.title ?? type;
21014
- const handle = new IndicatorHandleImpl(id, title, this);
21052
+ const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
21015
21053
  this.handles.set(id, handle);
21016
21054
  if (!descriptor) {
21017
21055
  console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
@@ -21023,16 +21061,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
21023
21061
  void this.startNativeIndicator(id, handle);
21024
21062
  return handle;
21025
21063
  }
21026
- /**
21027
- * The catalog of registered native-indicator types with their live state on THIS chart: each
21028
- * entry's `supported` (applies to the current symbol) and `present` (already added — a second
21029
- * add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
21030
- * because a descriptor's `isSupported` may probe the provider for a required capability.
21031
- */
21032
- /** The native types present on the chart — SYNC (registry state; no support probe). */
21064
+ /** The native types present on the chart, one entry per INSTANCE in registry order (a
21065
+ * multi-instance type repeats) — SYNC (registry state; no support probe). */
21033
21066
  presentNativeIndicators() {
21034
21067
  return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
21035
21068
  }
21069
+ /**
21070
+ * The catalog of registered native-indicator types with their live state on THIS chart: each
21071
+ * entry's `supported` (applies to the current symbol) and `present` (at least one instance
21072
+ * added — a second add of a single-instance type is a no-op). Powers a host "add native
21073
+ * indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
21074
+ * probe the provider for a required capability.
21075
+ */
21036
21076
  async availableNativeIndicators() {
21037
21077
  const symbol = this.qualifiedSymbol();
21038
21078
  const present = new Set(this.presentNativeIndicators());
@@ -21042,7 +21082,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
21042
21082
  title: d.title,
21043
21083
  supported: await this.isNativeSupported(d, symbol),
21044
21084
  present: present.has(d.type),
21045
- beta: d.beta
21085
+ beta: d.beta,
21086
+ ...d.multiInstance ? { multiInstance: true } : {}
21046
21087
  }))
21047
21088
  );
21048
21089
  }
@@ -21982,12 +22023,2322 @@ function registerVpvr() {
21982
22023
  registerNativeIndicator(vpvrDescriptor);
21983
22024
  }
21984
22025
 
22026
+ // src/core/native-indicators/classics/define.ts
22027
+ function num3(inputs, key, fallback) {
22028
+ const v = inputs[key];
22029
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
22030
+ }
22031
+ function str3(inputs, key, fallback) {
22032
+ const v = inputs[key];
22033
+ return typeof v === "string" && v !== "" ? v : fallback;
22034
+ }
22035
+ function toPoints(bars, values, colors) {
22036
+ const pts = new Array(bars.length);
22037
+ for (let i = 0; i < bars.length; i++) {
22038
+ const v = values[i];
22039
+ const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
22040
+ const c = colors?.[i];
22041
+ if (c != null) p.color = c;
22042
+ pts[i] = p;
22043
+ }
22044
+ return pts;
22045
+ }
22046
+ var ClassicIndicator = class {
22047
+ constructor(spec) {
22048
+ this.spec = spec;
22049
+ this.inputs = {};
22050
+ }
22051
+ start(ctx, inputs) {
22052
+ this.ctx = ctx;
22053
+ this.inputs = inputs;
22054
+ this.recompute();
22055
+ }
22056
+ onBars() {
22057
+ this.recompute();
22058
+ }
22059
+ onViewport() {
22060
+ }
22061
+ setInputs(inputs) {
22062
+ this.inputs = inputs;
22063
+ this.recompute();
22064
+ }
22065
+ /** Nothing to free — the compute reads the live bar accessor on demand. */
22066
+ suspend() {
22067
+ }
22068
+ resume() {
22069
+ this.recompute();
22070
+ }
22071
+ stop() {
22072
+ }
22073
+ recompute() {
22074
+ const bars = this.ctx.bars();
22075
+ const out = this.spec.compute(bars, this.inputs);
22076
+ const instanceId = this.spec.type;
22077
+ const series = [];
22078
+ const idsByKey = /* @__PURE__ */ new Map();
22079
+ out.plots.forEach((plot, ordinal) => {
22080
+ const kind = plot.kind ?? "line";
22081
+ const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
22082
+ idsByKey.set(plot.key, id);
22083
+ series.push({
22084
+ id,
22085
+ title: plot.title,
22086
+ paneId: "",
22087
+ // stamped by the orchestrator
22088
+ kind,
22089
+ points: toPoints(bars, plot.values, plot.colors),
22090
+ style: {
22091
+ color: plot.color,
22092
+ width: plot.width ?? 1,
22093
+ lineStyle: plot.lineStyle ?? "solid",
22094
+ ...plot.base != null ? { base: plot.base } : {}
22095
+ },
22096
+ ...plot.overlay ? { overlay: true } : {}
22097
+ });
22098
+ });
22099
+ if (out.markers && out.markers.length > 0) {
22100
+ series.push({
22101
+ id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
22102
+ title: this.spec.shortTitle ?? this.spec.title,
22103
+ paneId: "",
22104
+ kind: "markers",
22105
+ markers: out.markers
22106
+ });
22107
+ }
22108
+ const fills = (out.bands ?? []).flatMap((band, ordinal) => {
22109
+ const from = idsByKey.get(band.from);
22110
+ const to = idsByKey.get(band.to);
22111
+ if (!from || !to) return [];
22112
+ return [
22113
+ {
22114
+ id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
22115
+ paneId: "",
22116
+ fromSeriesId: from,
22117
+ toSeriesId: to,
22118
+ color: band.color
22119
+ }
22120
+ ];
22121
+ });
22122
+ const priceLines = (out.levels ?? []).map((level, ordinal) => ({
22123
+ id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
22124
+ paneId: "",
22125
+ price: level.price,
22126
+ ...level.color != null ? { color: level.color } : {},
22127
+ lineStyle: level.lineStyle ?? "dashed",
22128
+ width: 1,
22129
+ ...level.title != null ? { title: level.title } : {}
22130
+ }));
22131
+ const polylines = (out.polylines ?? []).map((poly, ordinal) => {
22132
+ const { key, ...rest } = poly;
22133
+ return {
22134
+ id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
22135
+ paneId: "",
22136
+ ...rest
22137
+ };
22138
+ });
22139
+ this.ctx.emit({ series, fills, priceLines, polylines });
22140
+ this.ctx.setStatus("idle");
22141
+ }
22142
+ };
22143
+ function classicDescriptor(spec) {
22144
+ return {
22145
+ type: spec.type,
22146
+ title: spec.title,
22147
+ ...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
22148
+ paneHint: spec.overlay ? "price" : "new",
22149
+ overlay: spec.overlay,
22150
+ // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
22151
+ multiInstance: true,
22152
+ inputsSchema: () => spec.inputs,
22153
+ defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
22154
+ create: () => new ClassicIndicator(spec)
22155
+ };
22156
+ }
22157
+
22158
+ // src/core/native-indicators/classics/math.ts
22159
+ var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
22160
+ function sourceValues(bars, source) {
22161
+ const out = new Array(bars.length);
22162
+ for (let i = 0; i < bars.length; i++) {
22163
+ const b = bars[i];
22164
+ switch (source) {
22165
+ case "Open":
22166
+ out[i] = b.open;
22167
+ break;
22168
+ case "High":
22169
+ out[i] = b.high;
22170
+ break;
22171
+ case "Low":
22172
+ out[i] = b.low;
22173
+ break;
22174
+ case "HL2":
22175
+ out[i] = (b.high + b.low) / 2;
22176
+ break;
22177
+ case "HLC3":
22178
+ out[i] = (b.high + b.low + b.close) / 3;
22179
+ break;
22180
+ case "OHLC4":
22181
+ out[i] = (b.open + b.high + b.low + b.close) / 4;
22182
+ break;
22183
+ case "HLCC4":
22184
+ out[i] = (b.high + b.low + b.close + b.close) / 4;
22185
+ break;
22186
+ default:
22187
+ out[i] = b.close;
22188
+ }
22189
+ }
22190
+ return out;
22191
+ }
22192
+ function volumes(bars) {
22193
+ return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
22194
+ }
22195
+ function highs(bars) {
22196
+ return bars.map((b) => b.high);
22197
+ }
22198
+ function lows(bars) {
22199
+ return bars.map((b) => b.low);
22200
+ }
22201
+ function closes(bars) {
22202
+ return bars.map((b) => b.close);
22203
+ }
22204
+ function opens(bars) {
22205
+ return bars.map((b) => b.open);
22206
+ }
22207
+ function zip(a, b, f) {
22208
+ const out = new Array(a.length);
22209
+ for (let i = 0; i < a.length; i++) {
22210
+ const x = a[i];
22211
+ const y = b[i];
22212
+ out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
22213
+ }
22214
+ return out;
22215
+ }
22216
+ function map(a, f) {
22217
+ const out = new Array(a.length);
22218
+ for (let i = 0; i < a.length; i++) {
22219
+ const x = a[i];
22220
+ out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
22221
+ }
22222
+ return out;
22223
+ }
22224
+ function sub(a, b) {
22225
+ return zip(a, b, (x, y) => x - y);
22226
+ }
22227
+ function windowed(v, len, fold) {
22228
+ const out = new Array(v.length).fill(Number.NaN);
22229
+ if (len <= 0) return out;
22230
+ let run = 0;
22231
+ for (let i = 0; i < v.length; i++) {
22232
+ run = Number.isFinite(v[i]) ? run + 1 : 0;
22233
+ if (run >= len) out[i] = fold(v, i - len + 1, i);
22234
+ }
22235
+ return out;
22236
+ }
22237
+ function sum(v, len) {
22238
+ return windowed(v, len, (w, from, to) => {
22239
+ let s = 0;
22240
+ for (let k = from; k <= to; k++) s += w[k];
22241
+ return s;
22242
+ });
22243
+ }
22244
+ function sma(v, len) {
22245
+ return windowed(v, len, (w, from, to) => {
22246
+ let s = 0;
22247
+ for (let k = from; k <= to; k++) s += w[k];
22248
+ return s / len;
22249
+ });
22250
+ }
22251
+ function wma(v, len) {
22252
+ const norm = len * (len + 1) / 2;
22253
+ return windowed(v, len, (w, from, to) => {
22254
+ let s = 0;
22255
+ for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
22256
+ return s / norm;
22257
+ });
22258
+ }
22259
+ function vwma(src, vol, len) {
22260
+ const pv = zip(src, vol, (p, q) => p * q);
22261
+ return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
22262
+ }
22263
+ function stdev(v, len) {
22264
+ return windowed(v, len, (w, from, to) => {
22265
+ let s = 0;
22266
+ for (let k = from; k <= to; k++) s += w[k];
22267
+ const mean = s / len;
22268
+ let sq = 0;
22269
+ for (let k = from; k <= to; k++) {
22270
+ const d = w[k] - mean;
22271
+ sq += d * d;
22272
+ }
22273
+ return Math.sqrt(sq / len);
22274
+ });
22275
+ }
22276
+ function highest(v, len) {
22277
+ return windowed(v, len, (w, from, to) => {
22278
+ let m = -Infinity;
22279
+ for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
22280
+ return m;
22281
+ });
22282
+ }
22283
+ function lowest(v, len) {
22284
+ return windowed(v, len, (w, from, to) => {
22285
+ let m = Infinity;
22286
+ for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
22287
+ return m;
22288
+ });
22289
+ }
22290
+ function barsSinceHighest(v, len) {
22291
+ return windowed(v, len, (w, from, to) => {
22292
+ let m = -Infinity;
22293
+ let at = to;
22294
+ for (let k = from; k <= to; k++) {
22295
+ if (w[k] >= m) {
22296
+ m = w[k];
22297
+ at = k;
22298
+ }
22299
+ }
22300
+ return to - at;
22301
+ });
22302
+ }
22303
+ function barsSinceLowest(v, len) {
22304
+ return windowed(v, len, (w, from, to) => {
22305
+ let m = Infinity;
22306
+ let at = to;
22307
+ for (let k = from; k <= to; k++) {
22308
+ if (w[k] <= m) {
22309
+ m = w[k];
22310
+ at = k;
22311
+ }
22312
+ }
22313
+ return to - at;
22314
+ });
22315
+ }
22316
+ function recursive(v, len, alpha) {
22317
+ const out = new Array(v.length).fill(Number.NaN);
22318
+ let run = 0;
22319
+ let prev2 = Number.NaN;
22320
+ for (let i = 0; i < v.length; i++) {
22321
+ const x = v[i];
22322
+ if (!Number.isFinite(x)) {
22323
+ run = 0;
22324
+ prev2 = Number.NaN;
22325
+ continue;
22326
+ }
22327
+ run += 1;
22328
+ if (Number.isFinite(prev2)) {
22329
+ prev2 = alpha * x + (1 - alpha) * prev2;
22330
+ out[i] = prev2;
22331
+ } else if (run >= len) {
22332
+ let s = 0;
22333
+ for (let k = i - len + 1; k <= i; k++) s += v[k];
22334
+ prev2 = s / len;
22335
+ out[i] = prev2;
22336
+ }
22337
+ }
22338
+ return out;
22339
+ }
22340
+ function ema(v, len) {
22341
+ return recursive(v, len, 2 / (len + 1));
22342
+ }
22343
+ function rma(v, len) {
22344
+ return recursive(v, len, 1 / len);
22345
+ }
22346
+ function change(v, n = 1) {
22347
+ const out = new Array(v.length).fill(Number.NaN);
22348
+ for (let i = n; i < v.length; i++) {
22349
+ const a = v[i];
22350
+ const b = v[i - n];
22351
+ if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
22352
+ }
22353
+ return out;
22354
+ }
22355
+ function roc(v, len) {
22356
+ const out = new Array(v.length).fill(Number.NaN);
22357
+ for (let i = len; i < v.length; i++) {
22358
+ const a = v[i];
22359
+ const b = v[i - len];
22360
+ if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
22361
+ }
22362
+ return out;
22363
+ }
22364
+ function shift(v, n) {
22365
+ const out = new Array(v.length).fill(Number.NaN);
22366
+ for (let i = 0; i < v.length; i++) {
22367
+ const j = i - n;
22368
+ if (j >= 0 && j < v.length) out[i] = v[j];
22369
+ }
22370
+ return out;
22371
+ }
22372
+ function trueRange(bars) {
22373
+ const out = new Array(bars.length).fill(Number.NaN);
22374
+ for (let i = 0; i < bars.length; i++) {
22375
+ const b = bars[i];
22376
+ if (i === 0) {
22377
+ out[i] = b.high - b.low;
22378
+ continue;
22379
+ }
22380
+ const pc = bars[i - 1].close;
22381
+ out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
22382
+ }
22383
+ return out;
22384
+ }
22385
+ function atr(bars, len) {
22386
+ return rma(trueRange(bars), len);
22387
+ }
22388
+ function rsi(v, len) {
22389
+ const d = change(v);
22390
+ const up = map(d, (x) => Math.max(x, 0));
22391
+ const dn = map(d, (x) => Math.max(-x, 0));
22392
+ return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
22393
+ }
22394
+ function stoch(v, hi, lo, len) {
22395
+ const hh = highest(hi, len);
22396
+ const ll = lowest(lo, len);
22397
+ const out = new Array(v.length).fill(Number.NaN);
22398
+ for (let i = 0; i < v.length; i++) {
22399
+ const x = v[i];
22400
+ const h = hh[i];
22401
+ const l = ll[i];
22402
+ if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
22403
+ out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
22404
+ }
22405
+ return out;
22406
+ }
22407
+ function linreg(v, len) {
22408
+ const sx = (len - 1) * len / 2;
22409
+ const sxx = (len - 1) * len * (2 * len - 1) / 6;
22410
+ const denom = len * sxx - sx * sx;
22411
+ return windowed(v, len, (w, from, to) => {
22412
+ let sy = 0;
22413
+ let sxy = 0;
22414
+ for (let k = from; k <= to; k++) {
22415
+ const x = k - from;
22416
+ sy += w[k];
22417
+ sxy += x * w[k];
22418
+ }
22419
+ const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
22420
+ const intercept = (sy - slope * sx) / len;
22421
+ return intercept + slope * (len - 1);
22422
+ });
22423
+ }
22424
+ function swma(v) {
22425
+ const out = new Array(v.length).fill(Number.NaN);
22426
+ for (let i = 3; i < v.length; i++) {
22427
+ const a = v[i - 3];
22428
+ const b = v[i - 2];
22429
+ const c = v[i - 1];
22430
+ const d = v[i];
22431
+ if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
22432
+ out[i] = (a + 2 * b + 2 * c + d) / 6;
22433
+ }
22434
+ }
22435
+ return out;
22436
+ }
22437
+ function percentRank(v, len) {
22438
+ const out = new Array(v.length).fill(Number.NaN);
22439
+ for (let i = len; i < v.length; i++) {
22440
+ const x = v[i];
22441
+ if (!Number.isFinite(x)) continue;
22442
+ let below = 0;
22443
+ let count = 0;
22444
+ for (let k = i - len; k < i; k++) {
22445
+ const y = v[k];
22446
+ if (!Number.isFinite(y)) {
22447
+ count = -1;
22448
+ break;
22449
+ }
22450
+ count += 1;
22451
+ if (y <= x) below += 1;
22452
+ }
22453
+ if (count === len) out[i] = 100 * below / len;
22454
+ }
22455
+ return out;
22456
+ }
22457
+ function cumSum(v) {
22458
+ const out = new Array(v.length).fill(Number.NaN);
22459
+ let acc = 0;
22460
+ let seen = false;
22461
+ for (let i = 0; i < v.length; i++) {
22462
+ const x = v[i];
22463
+ if (Number.isFinite(x)) {
22464
+ acc += x;
22465
+ seen = true;
22466
+ }
22467
+ if (seen) out[i] = acc;
22468
+ }
22469
+ return out;
22470
+ }
22471
+
22472
+ // src/core/native-indicators/classics/shared.ts
22473
+ function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
22474
+ return { key, title, type: "int", defval, min: 1, max, step: 1 };
22475
+ }
22476
+ function sourceInput(defval = "Close") {
22477
+ return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
22478
+ }
22479
+ function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
22480
+ return { key, title, type: "color", defval };
22481
+ }
22482
+ function withAlpha2(hex, alpha = 0.08) {
22483
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
22484
+ if (!m) return hex;
22485
+ const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
22486
+ return `#${m[1]}${a}`;
22487
+ }
22488
+
22489
+ // src/core/native-indicators/classics/averages.ts
22490
+ var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
22491
+ var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
22492
+ function fixedAverage(type, title, shortTitle, smooth) {
22493
+ return {
22494
+ type,
22495
+ title,
22496
+ shortTitle,
22497
+ overlay: true,
22498
+ inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
22499
+ compute: (bars, inputs) => {
22500
+ const len = num3(inputs, "length", 20);
22501
+ let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
22502
+ const offset = Math.trunc(num3(inputs, "offset", 0));
22503
+ if (offset !== 0) values = shift(values, offset);
22504
+ return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
22505
+ }
22506
+ };
22507
+ }
22508
+ var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
22509
+ var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
22510
+ var movingAverage = {
22511
+ type: "moving-average",
22512
+ title: "Moving Average",
22513
+ shortTitle: "MA",
22514
+ overlay: true,
22515
+ inputs: [
22516
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
22517
+ lengthInput(20),
22518
+ sourceInput(),
22519
+ offsetInput,
22520
+ colorInput()
22521
+ ],
22522
+ compute: (bars, inputs) => {
22523
+ const len = num3(inputs, "length", 20);
22524
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22525
+ const kind = str3(inputs, "maType", "SMA");
22526
+ let values;
22527
+ switch (kind) {
22528
+ case "EMA":
22529
+ values = ema(src, len);
22530
+ break;
22531
+ case "WMA":
22532
+ values = wma(src, len);
22533
+ break;
22534
+ case "RMA":
22535
+ values = rma(src, len);
22536
+ break;
22537
+ case "VWMA":
22538
+ values = vwma(src, volumes(bars), len);
22539
+ break;
22540
+ default:
22541
+ values = sma(src, len);
22542
+ }
22543
+ const offset = Math.trunc(num3(inputs, "offset", 0));
22544
+ if (offset !== 0) values = shift(values, offset);
22545
+ return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
22546
+ }
22547
+ };
22548
+ var smoothedMa = {
22549
+ type: "rma",
22550
+ title: "Smoothed Moving Average",
22551
+ shortTitle: "RMA",
22552
+ overlay: true,
22553
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
22554
+ compute: (bars, inputs) => ({
22555
+ plots: [
22556
+ {
22557
+ key: "rma",
22558
+ title: "RMA",
22559
+ values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
22560
+ color: str3(inputs, "color", SERIES_LINE),
22561
+ width: 2
22562
+ }
22563
+ ]
22564
+ })
22565
+ };
22566
+ var zlema = {
22567
+ type: "zlema",
22568
+ title: "Zero-Lag Exponential Moving Average",
22569
+ shortTitle: "ZLEMA",
22570
+ overlay: true,
22571
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
22572
+ compute: (bars, inputs) => {
22573
+ const len = num3(inputs, "length", 14);
22574
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22575
+ const lag = Math.floor((len - 1) / 2);
22576
+ const lagged = shift(src, lag);
22577
+ const delagged = zip(src, lagged, (a, b) => 2 * a - b);
22578
+ return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
22579
+ }
22580
+ };
22581
+ var vidya = {
22582
+ type: "vidya",
22583
+ title: "Variable Index Dynamic Average",
22584
+ shortTitle: "VIDYA",
22585
+ overlay: true,
22586
+ inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
22587
+ compute: (bars, inputs) => {
22588
+ const len = num3(inputs, "length", 14);
22589
+ const cmoLen = num3(inputs, "cmoLength", 9);
22590
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22591
+ const d = change(src);
22592
+ const su = new Array(src.length).fill(Number.NaN);
22593
+ const sd = new Array(src.length).fill(Number.NaN);
22594
+ for (let i = cmoLen; i < src.length; i++) {
22595
+ let u = 0;
22596
+ let w = 0;
22597
+ let ok = true;
22598
+ for (let k = i - cmoLen + 1; k <= i; k++) {
22599
+ const x = d[k];
22600
+ if (!Number.isFinite(x)) {
22601
+ ok = false;
22602
+ break;
22603
+ }
22604
+ if (x > 0) u += x;
22605
+ else w -= x;
22606
+ }
22607
+ if (ok) {
22608
+ su[i] = u;
22609
+ sd[i] = w;
22610
+ }
22611
+ }
22612
+ const alpha = 2 / (len + 1);
22613
+ const out = new Array(src.length).fill(Number.NaN);
22614
+ let prev2 = Number.NaN;
22615
+ for (let i = 0; i < src.length; i++) {
22616
+ const x = src[i];
22617
+ const u = su[i];
22618
+ const w = sd[i];
22619
+ if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
22620
+ const total = u + w;
22621
+ const k = total === 0 ? 0 : Math.abs((u - w) / total);
22622
+ prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
22623
+ out[i] = prev2;
22624
+ }
22625
+ return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
22626
+ }
22627
+ };
22628
+ var maEnvelope = {
22629
+ type: "ma-envelope",
22630
+ title: "Moving Average Envelope",
22631
+ shortTitle: "MA Env",
22632
+ overlay: true,
22633
+ inputs: [
22634
+ { key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
22635
+ lengthInput(20),
22636
+ sourceInput(),
22637
+ { key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
22638
+ colorInput()
22639
+ ],
22640
+ compute: (bars, inputs) => {
22641
+ const len = num3(inputs, "length", 20);
22642
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22643
+ const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
22644
+ const k = num3(inputs, "percent", 2.5) / 100;
22645
+ const ink = str3(inputs, "color", SERIES_LINE);
22646
+ return {
22647
+ plots: [
22648
+ { key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
22649
+ { key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
22650
+ { key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
22651
+ ],
22652
+ bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
22653
+ };
22654
+ }
22655
+ };
22656
+ var linearRegression = {
22657
+ type: "linear-regression",
22658
+ title: "Linear Regression Curve",
22659
+ shortTitle: "LinReg",
22660
+ overlay: true,
22661
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
22662
+ compute: (bars, inputs) => ({
22663
+ plots: [
22664
+ {
22665
+ key: "linreg",
22666
+ title: "LinReg",
22667
+ values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
22668
+ color: str3(inputs, "color", SERIES_LINE),
22669
+ width: 2
22670
+ }
22671
+ ]
22672
+ })
22673
+ };
22674
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
22675
+
22676
+ // src/core/native-indicators/classics/bands.ts
22677
+ function bollinger(bars, inputs) {
22678
+ const len = num3(inputs, "length", 20);
22679
+ const mult = num3(inputs, "mult", 2);
22680
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22681
+ const basis = sma(src, len);
22682
+ const dev = map(stdev(src, len), (x) => x * mult);
22683
+ return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
22684
+ }
22685
+ var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
22686
+ var bollingerBands = {
22687
+ type: "bollinger-bands",
22688
+ title: "Bollinger Bands",
22689
+ shortTitle: "BB",
22690
+ overlay: true,
22691
+ inputs: [...BB_INPUTS, colorInput()],
22692
+ compute: (bars, inputs) => {
22693
+ const { basis, upper, lower } = bollinger(bars, inputs);
22694
+ const ink = str3(inputs, "color", SERIES_LINE);
22695
+ return {
22696
+ plots: [
22697
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
22698
+ { key: "upper", title: "Upper", values: upper, color: ink },
22699
+ { key: "lower", title: "Lower", values: lower, color: ink }
22700
+ ],
22701
+ bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
22702
+ };
22703
+ }
22704
+ };
22705
+ var percentB = {
22706
+ type: "percent-b",
22707
+ title: "Bollinger Bands %B",
22708
+ shortTitle: "%B",
22709
+ overlay: false,
22710
+ inputs: [...BB_INPUTS, colorInput()],
22711
+ compute: (bars, inputs) => {
22712
+ const { upper, lower } = bollinger(bars, inputs);
22713
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
22714
+ const values = src.map((x, i) => {
22715
+ const u = upper[i];
22716
+ const l = lower[i];
22717
+ return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
22718
+ });
22719
+ return {
22720
+ plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
22721
+ levels: [
22722
+ { key: "upper", price: 1, color: NEUTRAL },
22723
+ { key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
22724
+ { key: "lower", price: 0, color: NEUTRAL }
22725
+ ]
22726
+ };
22727
+ }
22728
+ };
22729
+ var bandwidth = {
22730
+ type: "bandwidth",
22731
+ title: "Bollinger Bands Width",
22732
+ shortTitle: "BandWidth",
22733
+ overlay: false,
22734
+ inputs: [...BB_INPUTS, colorInput()],
22735
+ compute: (bars, inputs) => {
22736
+ const { basis, upper, lower } = bollinger(bars, inputs);
22737
+ const values = basis.map((b, i) => {
22738
+ const u = upper[i];
22739
+ const l = lower[i];
22740
+ return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
22741
+ });
22742
+ return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
22743
+ }
22744
+ };
22745
+ var keltner = {
22746
+ type: "keltner-channels",
22747
+ title: "Keltner Channels",
22748
+ shortTitle: "KC",
22749
+ overlay: true,
22750
+ inputs: [
22751
+ lengthInput(20),
22752
+ { key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
22753
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
22754
+ sourceInput(),
22755
+ colorInput()
22756
+ ],
22757
+ compute: (bars, inputs) => {
22758
+ const len = num3(inputs, "length", 20);
22759
+ const mult = num3(inputs, "mult", 2);
22760
+ const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
22761
+ const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
22762
+ const ink = str3(inputs, "color", SERIES_LINE);
22763
+ return {
22764
+ plots: [
22765
+ { key: "basis", title: "Basis", values: basis, color: WARNING },
22766
+ { key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
22767
+ { key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
22768
+ ],
22769
+ bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
22770
+ };
22771
+ }
22772
+ };
22773
+ var donchian = {
22774
+ type: "donchian-channels",
22775
+ title: "Donchian Channels",
22776
+ shortTitle: "DC",
22777
+ overlay: true,
22778
+ inputs: [lengthInput(20), colorInput()],
22779
+ compute: (bars, inputs) => {
22780
+ const len = num3(inputs, "length", 20);
22781
+ const upper = highest(highs(bars), len);
22782
+ const lower = lowest(lows(bars), len);
22783
+ const ink = str3(inputs, "color", SERIES_LINE);
22784
+ return {
22785
+ plots: [
22786
+ { key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
22787
+ { key: "upper", title: "Upper", values: upper, color: ink },
22788
+ { key: "lower", title: "Lower", values: lower, color: ink }
22789
+ ],
22790
+ bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
22791
+ };
22792
+ }
22793
+ };
22794
+ var chandelier = {
22795
+ type: "chandelier-exit",
22796
+ title: "Chandelier Exit",
22797
+ shortTitle: "CE",
22798
+ overlay: true,
22799
+ inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
22800
+ compute: (bars, inputs) => {
22801
+ const len = num3(inputs, "length", 22);
22802
+ const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
22803
+ const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
22804
+ const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
22805
+ return {
22806
+ plots: [
22807
+ { key: "long", title: "Long stop", values: long, color: BULLISH },
22808
+ { key: "short", title: "Short stop", values: short, color: BEARISH }
22809
+ ]
22810
+ };
22811
+ }
22812
+ };
22813
+ var chandeKroll = {
22814
+ type: "chande-kroll-stop",
22815
+ title: "Chande Kroll Stop",
22816
+ shortTitle: "CKS",
22817
+ overlay: true,
22818
+ inputs: [
22819
+ { key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
22820
+ { key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
22821
+ { key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
22822
+ ],
22823
+ compute: (bars, inputs) => {
22824
+ const p = num3(inputs, "p", 10);
22825
+ const x = num3(inputs, "x", 1);
22826
+ const q = num3(inputs, "q", 9);
22827
+ const range = map(atr(bars, p), (v) => v * x);
22828
+ const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
22829
+ const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
22830
+ return {
22831
+ plots: [
22832
+ { key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
22833
+ { key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
22834
+ ]
22835
+ };
22836
+ }
22837
+ };
22838
+ var supertrend = {
22839
+ type: "supertrend",
22840
+ title: "SuperTrend",
22841
+ shortTitle: "SuperTrend",
22842
+ overlay: true,
22843
+ inputs: [
22844
+ { key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
22845
+ { key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
22846
+ ],
22847
+ compute: (bars, inputs) => {
22848
+ const len = num3(inputs, "atrLength", 10);
22849
+ const mult = num3(inputs, "mult", 3);
22850
+ const range = atr(bars, len);
22851
+ const n = bars.length;
22852
+ const st = new Array(n).fill(Number.NaN);
22853
+ const dir = new Array(n).fill(1);
22854
+ let up = Number.NaN;
22855
+ let dn = Number.NaN;
22856
+ let trend = 1;
22857
+ for (let i = 0; i < n; i++) {
22858
+ const r = range[i];
22859
+ if (!Number.isFinite(r)) continue;
22860
+ const b = bars[i];
22861
+ const mid = (b.high + b.low) / 2;
22862
+ const basicUp = mid - mult * r;
22863
+ const basicDn = mid + mult * r;
22864
+ const prevClose = i > 0 ? bars[i - 1].close : b.close;
22865
+ up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
22866
+ dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
22867
+ if (trend === 1 && b.close < up) trend = -1;
22868
+ else if (trend === -1 && b.close > dn) trend = 1;
22869
+ dir[i] = trend;
22870
+ st[i] = trend === 1 ? up : dn;
22871
+ }
22872
+ const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
22873
+ return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
22874
+ }
22875
+ };
22876
+ var alligator = {
22877
+ type: "williams-alligator",
22878
+ title: "Williams Alligator",
22879
+ shortTitle: "Alligator",
22880
+ overlay: true,
22881
+ inputs: [
22882
+ { key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
22883
+ { key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
22884
+ { key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
22885
+ { key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
22886
+ { key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
22887
+ { key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
22888
+ ],
22889
+ compute: (bars, inputs) => {
22890
+ const hl2 = sourceValues(bars, "HL2");
22891
+ const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
22892
+ const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
22893
+ const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
22894
+ return {
22895
+ plots: [
22896
+ { key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
22897
+ { key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
22898
+ { key: "lips", title: "Lips", values: lips, color: BULLISH }
22899
+ ]
22900
+ };
22901
+ }
22902
+ };
22903
+ var gator = {
22904
+ type: "gator-oscillator",
22905
+ title: "Gator Oscillator",
22906
+ shortTitle: "Gator Oscillator",
22907
+ overlay: false,
22908
+ inputs: [],
22909
+ compute: (bars) => {
22910
+ const hl2 = sourceValues(bars, "HL2");
22911
+ const jaw = shift(rma(hl2, 13), 8);
22912
+ const teeth = shift(rma(hl2, 8), 5);
22913
+ const lips = shift(rma(hl2, 5), 3);
22914
+ const upper = map(sub(jaw, teeth), Math.abs);
22915
+ const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
22916
+ const colorByExpansion = (v) => v.map((x, i) => {
22917
+ if (!Number.isFinite(x)) return null;
22918
+ const prev2 = i > 0 ? v[i - 1] : Number.NaN;
22919
+ return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
22920
+ });
22921
+ return {
22922
+ plots: [
22923
+ { key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
22924
+ { key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
22925
+ ]
22926
+ };
22927
+ }
22928
+ };
22929
+ var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
22930
+
22931
+ // src/core/native-indicators/classics/oscillators.ts
22932
+ function guideLevels(low, high, mid) {
22933
+ const levels = [
22934
+ { key: "upper", price: high, color: NEUTRAL },
22935
+ { key: "lower", price: low, color: NEUTRAL }
22936
+ ];
22937
+ if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
22938
+ return levels;
22939
+ }
22940
+ var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
22941
+ function histColors(values) {
22942
+ return values.map((x, i) => {
22943
+ if (!Number.isFinite(x)) return null;
22944
+ const prev2 = i > 0 ? values[i - 1] : Number.NaN;
22945
+ const rising = Number.isFinite(prev2) ? x >= prev2 : true;
22946
+ if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
22947
+ return rising ? `${BEARISH}80` : BEARISH;
22948
+ });
22949
+ }
22950
+ var rsiSpec = {
22951
+ type: "rsi",
22952
+ title: "Relative Strength Index",
22953
+ shortTitle: "RSI",
22954
+ overlay: false,
22955
+ inputs: [lengthInput(14), sourceInput(), colorInput()],
22956
+ compute: (bars, inputs) => ({
22957
+ plots: [
22958
+ {
22959
+ key: "rsi",
22960
+ title: "RSI",
22961
+ values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
22962
+ color: str3(inputs, "color", SERIES_LINE),
22963
+ width: 2
22964
+ }
22965
+ ],
22966
+ levels: guideLevels(30, 70, 50)
22967
+ })
22968
+ };
22969
+ var stochastic = {
22970
+ type: "stochastic",
22971
+ title: "Stochastic",
22972
+ shortTitle: "Stoch",
22973
+ overlay: false,
22974
+ inputs: [
22975
+ lengthInput(14, "kLength", "%K length"),
22976
+ lengthInput(1, "kSmoothing", "%K smoothing", 500),
22977
+ lengthInput(3, "dLength", "%D smoothing", 500)
22978
+ ],
22979
+ compute: (bars, inputs) => {
22980
+ const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
22981
+ const d = sma(k, num3(inputs, "dLength", 3));
22982
+ return {
22983
+ plots: [
22984
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
22985
+ { key: "d", title: "%D", values: d, color: WARNING }
22986
+ ],
22987
+ levels: guideLevels(20, 80)
22988
+ };
22989
+ }
22990
+ };
22991
+ var stochasticRsi = {
22992
+ type: "stochastic-rsi",
22993
+ title: "Stochastic RSI",
22994
+ shortTitle: "Stoch RSI",
22995
+ overlay: false,
22996
+ inputs: [
22997
+ lengthInput(14, "rsiLength", "RSI length"),
22998
+ lengthInput(14, "stochLength", "Stochastic length"),
22999
+ lengthInput(3, "kSmoothing", "%K smoothing", 500),
23000
+ lengthInput(3, "dLength", "%D smoothing", 500),
23001
+ sourceInput()
23002
+ ],
23003
+ compute: (bars, inputs) => {
23004
+ const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
23005
+ const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
23006
+ const d = sma(k, num3(inputs, "dLength", 3));
23007
+ return {
23008
+ plots: [
23009
+ { key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
23010
+ { key: "d", title: "%D", values: d, color: WARNING }
23011
+ ],
23012
+ levels: guideLevels(20, 80)
23013
+ };
23014
+ }
23015
+ };
23016
+ var macd = {
23017
+ type: "macd",
23018
+ title: "MACD",
23019
+ overlay: false,
23020
+ inputs: [
23021
+ lengthInput(12, "fastLength", "Fast length"),
23022
+ lengthInput(26, "slowLength", "Slow length"),
23023
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
23024
+ sourceInput()
23025
+ ],
23026
+ compute: (bars, inputs) => {
23027
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
23028
+ const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
23029
+ const signal = ema(line, num3(inputs, "signalLength", 9));
23030
+ const hist = sub(line, signal);
23031
+ return {
23032
+ plots: [
23033
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
23034
+ { key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
23035
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
23036
+ ],
23037
+ levels: ZERO_LEVEL
23038
+ };
23039
+ }
23040
+ };
23041
+ var ppo = {
23042
+ type: "ppo",
23043
+ title: "Percentage Price Oscillator",
23044
+ shortTitle: "PPO",
23045
+ overlay: false,
23046
+ inputs: [
23047
+ lengthInput(12, "fastLength", "Fast length"),
23048
+ lengthInput(26, "slowLength", "Slow length"),
23049
+ lengthInput(9, "signalLength", "Signal smoothing", 500),
23050
+ sourceInput()
23051
+ ],
23052
+ compute: (bars, inputs) => {
23053
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
23054
+ const fast = ema(src, num3(inputs, "fastLength", 12));
23055
+ const slow = ema(src, num3(inputs, "slowLength", 26));
23056
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
23057
+ const signal = ema(line, num3(inputs, "signalLength", 9));
23058
+ const hist = sub(line, signal);
23059
+ return {
23060
+ plots: [
23061
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
23062
+ { key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
23063
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
23064
+ ],
23065
+ levels: ZERO_LEVEL
23066
+ };
23067
+ }
23068
+ };
23069
+ var awesome = {
23070
+ type: "awesome-oscillator",
23071
+ title: "Awesome Oscillator",
23072
+ shortTitle: "AO",
23073
+ overlay: false,
23074
+ inputs: [],
23075
+ compute: (bars) => {
23076
+ const hl2 = sourceValues(bars, "HL2");
23077
+ const ao = sub(sma(hl2, 5), sma(hl2, 34));
23078
+ const colors = ao.map((x, i) => {
23079
+ if (!Number.isFinite(x)) return null;
23080
+ const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
23081
+ return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
23082
+ });
23083
+ return {
23084
+ plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
23085
+ levels: ZERO_LEVEL
23086
+ };
23087
+ }
23088
+ };
23089
+ var cci = {
23090
+ type: "commodity-channel-index",
23091
+ title: "Commodity Channel Index",
23092
+ shortTitle: "CCI",
23093
+ overlay: false,
23094
+ inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
23095
+ compute: (bars, inputs) => {
23096
+ const len = num3(inputs, "length", 20);
23097
+ const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
23098
+ const basis = sma(src, len);
23099
+ const dev = new Array(src.length).fill(Number.NaN);
23100
+ for (let i = len - 1; i < src.length; i++) {
23101
+ const mean = basis[i];
23102
+ if (!Number.isFinite(mean)) continue;
23103
+ let d = 0;
23104
+ for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
23105
+ dev[i] = d / len;
23106
+ }
23107
+ const values = src.map((x, i) => {
23108
+ const b = basis[i];
23109
+ const d = dev[i];
23110
+ return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
23111
+ });
23112
+ return {
23113
+ plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23114
+ levels: guideLevels(-100, 100, 0)
23115
+ };
23116
+ }
23117
+ };
23118
+ var williamsR = {
23119
+ type: "williams-percent-r",
23120
+ title: "Williams %R",
23121
+ shortTitle: "%R",
23122
+ overlay: false,
23123
+ inputs: [lengthInput(14), colorInput()],
23124
+ compute: (bars, inputs) => {
23125
+ const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
23126
+ return {
23127
+ plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23128
+ levels: guideLevels(-80, -20)
23129
+ };
23130
+ }
23131
+ };
23132
+ var cmo = {
23133
+ type: "chande-momentum-oscillator",
23134
+ title: "Chande Momentum Oscillator",
23135
+ shortTitle: "CMO",
23136
+ overlay: false,
23137
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
23138
+ compute: (bars, inputs) => {
23139
+ const len = num3(inputs, "length", 9);
23140
+ const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
23141
+ const su = sum(map(d, (x) => Math.max(x, 0)), len);
23142
+ const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
23143
+ const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
23144
+ return {
23145
+ plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23146
+ levels: guideLevels(-50, 50, 0)
23147
+ };
23148
+ }
23149
+ };
23150
+ var connorsRsi = {
23151
+ type: "connors-rsi",
23152
+ title: "Connors RSI",
23153
+ shortTitle: "CRSI",
23154
+ overlay: false,
23155
+ inputs: [
23156
+ lengthInput(3, "rsiLength", "RSI length"),
23157
+ lengthInput(2, "streakLength", "Streak RSI length"),
23158
+ lengthInput(100, "rankLength", "Percent-rank length"),
23159
+ colorInput()
23160
+ ],
23161
+ compute: (bars, inputs) => {
23162
+ const c = closes(bars);
23163
+ const streak = new Array(c.length).fill(0);
23164
+ for (let i = 1; i < c.length; i++) {
23165
+ const d = c[i] - c[i - 1];
23166
+ streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
23167
+ }
23168
+ const a = rsi(c, num3(inputs, "rsiLength", 3));
23169
+ const b = rsi(streak, num3(inputs, "streakLength", 2));
23170
+ const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
23171
+ const values = c.map((_, i) => {
23172
+ const x = a[i];
23173
+ const y = b[i];
23174
+ const z = r[i];
23175
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
23176
+ });
23177
+ return {
23178
+ plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23179
+ levels: guideLevels(20, 80, 50)
23180
+ };
23181
+ }
23182
+ };
23183
+ var fisher = {
23184
+ type: "fisher-transform",
23185
+ title: "Fisher Transform",
23186
+ shortTitle: "Fisher",
23187
+ overlay: false,
23188
+ inputs: [lengthInput(9)],
23189
+ compute: (bars, inputs) => {
23190
+ const len = num3(inputs, "length", 9);
23191
+ const hl2 = sourceValues(bars, "HL2");
23192
+ const hi = highest(hl2, len);
23193
+ const lo = lowest(hl2, len);
23194
+ const n = bars.length;
23195
+ const out = new Array(n).fill(Number.NaN);
23196
+ let value = 0;
23197
+ let fish = 0;
23198
+ for (let i = 0; i < n; i++) {
23199
+ const h = hi[i];
23200
+ const l = lo[i];
23201
+ if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
23202
+ const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
23203
+ value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
23204
+ fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
23205
+ out[i] = fish;
23206
+ }
23207
+ return {
23208
+ plots: [
23209
+ { key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
23210
+ { key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
23211
+ ],
23212
+ levels: ZERO_LEVEL
23213
+ };
23214
+ }
23215
+ };
23216
+ var trix = {
23217
+ type: "trix",
23218
+ title: "TRIX",
23219
+ overlay: false,
23220
+ inputs: [lengthInput(18), colorInput()],
23221
+ compute: (bars, inputs) => {
23222
+ const len = num3(inputs, "length", 18);
23223
+ const logClose = map(closes(bars), Math.log);
23224
+ const smooth = ema(ema(ema(logClose, len), len), len);
23225
+ const values = map(change(smooth), (x) => x * 1e4);
23226
+ return {
23227
+ plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23228
+ levels: ZERO_LEVEL
23229
+ };
23230
+ }
23231
+ };
23232
+ function tsiLine(src, long, short) {
23233
+ const mom = change(src);
23234
+ const numer = ema(ema(mom, long), short);
23235
+ const denom = ema(ema(map(mom, Math.abs), long), short);
23236
+ return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
23237
+ }
23238
+ var tsi = {
23239
+ type: "true-strength-index",
23240
+ title: "True Strength Index",
23241
+ shortTitle: "TSI",
23242
+ overlay: false,
23243
+ inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
23244
+ compute: (bars, inputs) => {
23245
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
23246
+ return {
23247
+ plots: [
23248
+ { key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
23249
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
23250
+ ],
23251
+ levels: ZERO_LEVEL
23252
+ };
23253
+ }
23254
+ };
23255
+ var smiErgodic = {
23256
+ type: "smi-ergodic",
23257
+ title: "SMI Ergodic Oscillator",
23258
+ shortTitle: "SMIE",
23259
+ overlay: false,
23260
+ inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
23261
+ compute: (bars, inputs) => {
23262
+ const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
23263
+ const signal = ema(line, num3(inputs, "signalLength", 5));
23264
+ const osc = sub(line, signal);
23265
+ return {
23266
+ plots: [
23267
+ { key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
23268
+ { key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
23269
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
23270
+ ],
23271
+ levels: ZERO_LEVEL
23272
+ };
23273
+ }
23274
+ };
23275
+ var stc = {
23276
+ type: "schaff-trend-cycle",
23277
+ title: "Schaff Trend Cycle",
23278
+ shortTitle: "Schaff Trend Cycle",
23279
+ overlay: false,
23280
+ inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
23281
+ compute: (bars, inputs) => {
23282
+ const len = num3(inputs, "cycleLength", 10);
23283
+ const src = closes(bars);
23284
+ const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
23285
+ const stochOf = (v) => {
23286
+ const lo = lowest(v, len);
23287
+ const hi = highest(v, len);
23288
+ const out = new Array(v.length).fill(Number.NaN);
23289
+ let prevRaw = Number.NaN;
23290
+ let smoothed = Number.NaN;
23291
+ for (let i = 0; i < v.length; i++) {
23292
+ const x = v[i];
23293
+ const l = lo[i];
23294
+ const h = hi[i];
23295
+ if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
23296
+ const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
23297
+ prevRaw = raw;
23298
+ smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
23299
+ out[i] = smoothed;
23300
+ }
23301
+ return out;
23302
+ };
23303
+ const values = stochOf(stochOf(macdLine));
23304
+ return {
23305
+ plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
23306
+ levels: guideLevels(25, 75)
23307
+ };
23308
+ }
23309
+ };
23310
+ var kst = {
23311
+ type: "know-sure-thing",
23312
+ title: "Know Sure Thing",
23313
+ shortTitle: "KST",
23314
+ overlay: false,
23315
+ inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
23316
+ compute: (bars, inputs) => {
23317
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
23318
+ const line = [
23319
+ sma(roc(src, 10), 10),
23320
+ sma(roc(src, 15), 10),
23321
+ sma(roc(src, 20), 10),
23322
+ sma(roc(src, 30), 15)
23323
+ ].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
23324
+ return {
23325
+ plots: [
23326
+ { key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
23327
+ { key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
23328
+ ],
23329
+ levels: ZERO_LEVEL
23330
+ };
23331
+ }
23332
+ };
23333
+ var coppock = {
23334
+ type: "coppock-curve",
23335
+ title: "Coppock Curve",
23336
+ shortTitle: "Coppock",
23337
+ overlay: false,
23338
+ inputs: [
23339
+ lengthInput(10, "wmaLength", "WMA length"),
23340
+ lengthInput(14, "longRoc", "Long RoC length"),
23341
+ lengthInput(11, "shortRoc", "Short RoC length"),
23342
+ sourceInput(),
23343
+ colorInput()
23344
+ ],
23345
+ compute: (bars, inputs) => {
23346
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
23347
+ const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
23348
+ return {
23349
+ plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23350
+ levels: ZERO_LEVEL
23351
+ };
23352
+ }
23353
+ };
23354
+ var dpo = {
23355
+ type: "detrended-price-oscillator",
23356
+ title: "Detrended Price Oscillator",
23357
+ shortTitle: "DPO",
23358
+ overlay: false,
23359
+ inputs: [lengthInput(21), colorInput()],
23360
+ compute: (bars, inputs) => {
23361
+ const len = num3(inputs, "length", 21);
23362
+ const barsback = Math.floor(len / 2) + 1;
23363
+ const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
23364
+ return {
23365
+ plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23366
+ levels: ZERO_LEVEL
23367
+ };
23368
+ }
23369
+ };
23370
+ var rocSpec = {
23371
+ type: "rate-of-change",
23372
+ title: "Rate of Change",
23373
+ shortTitle: "ROC",
23374
+ overlay: false,
23375
+ inputs: [lengthInput(9), sourceInput(), colorInput()],
23376
+ compute: (bars, inputs) => ({
23377
+ plots: [
23378
+ {
23379
+ key: "roc",
23380
+ title: "RoC",
23381
+ values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
23382
+ color: str3(inputs, "color", SERIES_LINE),
23383
+ width: 2
23384
+ }
23385
+ ],
23386
+ levels: ZERO_LEVEL
23387
+ })
23388
+ };
23389
+ var ultimate = {
23390
+ type: "ultimate-oscillator",
23391
+ title: "Ultimate Oscillator",
23392
+ shortTitle: "UO",
23393
+ overlay: false,
23394
+ inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
23395
+ compute: (bars, inputs) => {
23396
+ const n = bars.length;
23397
+ const bp = new Array(n).fill(Number.NaN);
23398
+ const tr = new Array(n).fill(Number.NaN);
23399
+ for (let i = 0; i < n; i++) {
23400
+ const b = bars[i];
23401
+ const pc = i > 0 ? bars[i - 1].close : b.close;
23402
+ const lo = Math.min(b.low, pc);
23403
+ const hi = Math.max(b.high, pc);
23404
+ bp[i] = b.close - lo;
23405
+ tr[i] = hi - lo;
23406
+ }
23407
+ const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
23408
+ const a7 = avg(num3(inputs, "fastLength", 7));
23409
+ const a14 = avg(num3(inputs, "middleLength", 14));
23410
+ const a28 = avg(num3(inputs, "slowLength", 28));
23411
+ const values = a7.map((x, i) => {
23412
+ const y = a14[i];
23413
+ const z = a28[i];
23414
+ return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
23415
+ });
23416
+ return {
23417
+ plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23418
+ levels: guideLevels(30, 70, 50)
23419
+ };
23420
+ }
23421
+ };
23422
+ var rvgi = {
23423
+ type: "relative-vigor-index",
23424
+ title: "Relative Vigor Index",
23425
+ shortTitle: "RVGI",
23426
+ overlay: false,
23427
+ inputs: [lengthInput(10)],
23428
+ compute: (bars, inputs) => {
23429
+ const len = num3(inputs, "length", 10);
23430
+ const co = swma(sub(closes(bars), opens(bars)));
23431
+ const hl = swma(sub(highs(bars), lows(bars)));
23432
+ const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
23433
+ return {
23434
+ plots: [
23435
+ { key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
23436
+ { key: "signal", title: "Signal", values: swma(line), color: WARNING }
23437
+ ],
23438
+ levels: ZERO_LEVEL
23439
+ };
23440
+ }
23441
+ };
23442
+ var relVolatility = {
23443
+ type: "relative-volatility-index",
23444
+ title: "Relative Volatility Index",
23445
+ shortTitle: "RVI",
23446
+ overlay: false,
23447
+ inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
23448
+ compute: (bars, inputs) => {
23449
+ const smoothLen = num3(inputs, "smoothLength", 14);
23450
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
23451
+ const dev = stdev(src, num3(inputs, "stdevLength", 10));
23452
+ const d = change(src);
23453
+ const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
23454
+ const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
23455
+ const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
23456
+ return {
23457
+ plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23458
+ levels: guideLevels(20, 80, 50)
23459
+ };
23460
+ }
23461
+ };
23462
+ var bop = {
23463
+ type: "balance-of-power",
23464
+ title: "Balance of Power",
23465
+ shortTitle: "BOP",
23466
+ overlay: false,
23467
+ inputs: [colorInput()],
23468
+ compute: (bars, inputs) => {
23469
+ const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
23470
+ return {
23471
+ plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23472
+ levels: ZERO_LEVEL
23473
+ };
23474
+ }
23475
+ };
23476
+ var elderRay = {
23477
+ type: "elder-ray",
23478
+ title: "Elder Ray",
23479
+ overlay: false,
23480
+ inputs: [lengthInput(13)],
23481
+ compute: (bars, inputs) => {
23482
+ const basis = ema(closes(bars), num3(inputs, "length", 13));
23483
+ return {
23484
+ plots: [
23485
+ { key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
23486
+ { key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
23487
+ ],
23488
+ levels: ZERO_LEVEL
23489
+ };
23490
+ }
23491
+ };
23492
+ var ttmSqueeze = {
23493
+ type: "ttm-squeeze",
23494
+ title: "TTM Squeeze",
23495
+ overlay: false,
23496
+ inputs: [
23497
+ lengthInput(20),
23498
+ { key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
23499
+ { key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
23500
+ ],
23501
+ compute: (bars, inputs) => {
23502
+ const len = num3(inputs, "length", 20);
23503
+ const c = closes(bars);
23504
+ const basis = sma(c, len);
23505
+ const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
23506
+ const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
23507
+ const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
23508
+ const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
23509
+ const mom = linreg(sub(c, anchor), len);
23510
+ const squeezed = c.map((_, i) => {
23511
+ const d = dev[i];
23512
+ const r = kcRange[i];
23513
+ return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
23514
+ });
23515
+ const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
23516
+ return {
23517
+ plots: [
23518
+ { key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
23519
+ { key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
23520
+ ]
23521
+ };
23522
+ }
23523
+ };
23524
+ var oscillatorSpecs = [
23525
+ rsiSpec,
23526
+ stochastic,
23527
+ stochasticRsi,
23528
+ macd,
23529
+ ppo,
23530
+ awesome,
23531
+ cci,
23532
+ williamsR,
23533
+ cmo,
23534
+ connorsRsi,
23535
+ fisher,
23536
+ trix,
23537
+ tsi,
23538
+ smiErgodic,
23539
+ stc,
23540
+ kst,
23541
+ coppock,
23542
+ dpo,
23543
+ rocSpec,
23544
+ ultimate,
23545
+ rvgi,
23546
+ relVolatility,
23547
+ bop,
23548
+ elderRay,
23549
+ ttmSqueeze
23550
+ ];
23551
+
23552
+ // src/core/native-indicators/classics/trend.ts
23553
+ var aroon = {
23554
+ type: "aroon",
23555
+ title: "Aroon",
23556
+ overlay: false,
23557
+ inputs: [lengthInput(14)],
23558
+ compute: (bars, inputs) => {
23559
+ const len = num3(inputs, "length", 14);
23560
+ const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
23561
+ const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
23562
+ return {
23563
+ plots: [
23564
+ { key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
23565
+ { key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
23566
+ ],
23567
+ levels: [
23568
+ { key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
23569
+ { key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
23570
+ ]
23571
+ };
23572
+ }
23573
+ };
23574
+ var adx = {
23575
+ type: "average-directional-index",
23576
+ title: "Average Directional Index",
23577
+ shortTitle: "ADX",
23578
+ overlay: false,
23579
+ inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
23580
+ compute: (bars, inputs) => {
23581
+ const diLen = num3(inputs, "diLength", 14);
23582
+ const adxLen = num3(inputs, "adxLength", 14);
23583
+ const n = bars.length;
23584
+ const plusDm = new Array(n).fill(Number.NaN);
23585
+ const minusDm = new Array(n).fill(Number.NaN);
23586
+ for (let i = 1; i < n; i++) {
23587
+ const up = bars[i].high - bars[i - 1].high;
23588
+ const dn = bars[i - 1].low - bars[i].low;
23589
+ plusDm[i] = up > dn && up > 0 ? up : 0;
23590
+ minusDm[i] = dn > up && dn > 0 ? dn : 0;
23591
+ }
23592
+ const atrLine = rma(trueRange(bars), diLen);
23593
+ const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
23594
+ const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
23595
+ const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
23596
+ return {
23597
+ plots: [
23598
+ { key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
23599
+ { key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
23600
+ { key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
23601
+ ],
23602
+ levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
23603
+ };
23604
+ }
23605
+ };
23606
+ var vortex = {
23607
+ type: "vortex-indicator",
23608
+ title: "Vortex Indicator",
23609
+ shortTitle: "VI",
23610
+ overlay: false,
23611
+ inputs: [lengthInput(14)],
23612
+ compute: (bars, inputs) => {
23613
+ const len = num3(inputs, "length", 14);
23614
+ const n = bars.length;
23615
+ const vmPlus = new Array(n).fill(Number.NaN);
23616
+ const vmMinus = new Array(n).fill(Number.NaN);
23617
+ for (let i = 1; i < n; i++) {
23618
+ vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
23619
+ vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
23620
+ }
23621
+ const trSum = sum(trueRange(bars), len);
23622
+ return {
23623
+ plots: [
23624
+ { key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
23625
+ { key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
23626
+ ],
23627
+ levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
23628
+ };
23629
+ }
23630
+ };
23631
+ var trendSpecs = [aroon, adx, vortex];
23632
+
23633
+ // src/core/native-indicators/classics/volatility.ts
23634
+ var atrSpec = {
23635
+ type: "average-true-range",
23636
+ title: "Average True Range",
23637
+ shortTitle: "ATR",
23638
+ overlay: false,
23639
+ inputs: [lengthInput(14), colorInput()],
23640
+ compute: (bars, inputs) => ({
23641
+ plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
23642
+ })
23643
+ };
23644
+ var historicalVolatility = {
23645
+ type: "historical-volatility",
23646
+ title: "Historical Volatility",
23647
+ shortTitle: "HV",
23648
+ overlay: false,
23649
+ inputs: [
23650
+ lengthInput(10),
23651
+ { 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)" },
23652
+ colorInput()
23653
+ ],
23654
+ compute: (bars, inputs) => {
23655
+ const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
23656
+ const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
23657
+ return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
23658
+ }
23659
+ };
23660
+ var chaikinVolatility = {
23661
+ type: "chaikin-volatility",
23662
+ title: "Chaikin Volatility",
23663
+ shortTitle: "CHV",
23664
+ overlay: false,
23665
+ inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
23666
+ compute: (bars, inputs) => {
23667
+ const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
23668
+ return {
23669
+ plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23670
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23671
+ };
23672
+ }
23673
+ };
23674
+ var massIndex = {
23675
+ type: "mass-index",
23676
+ title: "Mass Index",
23677
+ shortTitle: "MI",
23678
+ overlay: false,
23679
+ inputs: [lengthInput(25), colorInput()],
23680
+ compute: (bars, inputs) => {
23681
+ const range = sub(highs(bars), lows(bars));
23682
+ const single = ema(range, 9);
23683
+ const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
23684
+ return {
23685
+ plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23686
+ levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
23687
+ };
23688
+ }
23689
+ };
23690
+ var standardDeviation = {
23691
+ type: "standard-deviation",
23692
+ title: "Standard Deviation",
23693
+ shortTitle: "StdDev",
23694
+ overlay: false,
23695
+ inputs: [lengthInput(20), sourceInput(), colorInput()],
23696
+ compute: (bars, inputs) => ({
23697
+ plots: [
23698
+ {
23699
+ key: "stdev",
23700
+ title: "StdDev",
23701
+ values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
23702
+ color: str3(inputs, "color", SERIES_LINE),
23703
+ width: 2
23704
+ }
23705
+ ]
23706
+ })
23707
+ };
23708
+ var ulcerIndex = {
23709
+ type: "ulcer-index",
23710
+ title: "Ulcer Index",
23711
+ overlay: false,
23712
+ inputs: [lengthInput(14), colorInput()],
23713
+ compute: (bars, inputs) => {
23714
+ const len = num3(inputs, "length", 14);
23715
+ const c = closes(bars);
23716
+ const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
23717
+ const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
23718
+ return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
23719
+ }
23720
+ };
23721
+ var choppiness = {
23722
+ type: "choppiness-index",
23723
+ title: "Choppiness Index",
23724
+ shortTitle: "CHOP",
23725
+ overlay: false,
23726
+ inputs: [lengthInput(14), colorInput()],
23727
+ compute: (bars, inputs) => {
23728
+ const len = num3(inputs, "length", 14);
23729
+ const trSum = sum(trueRange(bars), len);
23730
+ const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
23731
+ const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
23732
+ return {
23733
+ plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23734
+ levels: [
23735
+ { key: "upper", price: 61.8, color: NEUTRAL },
23736
+ { key: "lower", price: 38.2, color: NEUTRAL }
23737
+ ]
23738
+ };
23739
+ }
23740
+ };
23741
+ var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
23742
+
23743
+ // src/core/native-indicators/classics/volume.ts
23744
+ function moneyFlowVolume(bars) {
23745
+ return bars.map((b) => {
23746
+ if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
23747
+ if (b.high === b.low) return 0;
23748
+ return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
23749
+ });
23750
+ }
23751
+ var obv = {
23752
+ type: "on-balance-volume",
23753
+ title: "On Balance Volume",
23754
+ shortTitle: "OBV",
23755
+ overlay: false,
23756
+ inputs: [colorInput()],
23757
+ compute: (bars, inputs) => {
23758
+ const vol = volumes(bars);
23759
+ const d = change(closes(bars));
23760
+ const signed2 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
23761
+ return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed2), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
23762
+ }
23763
+ };
23764
+ var accDist = {
23765
+ type: "accumulation-distribution",
23766
+ title: "Accumulation / Distribution",
23767
+ shortTitle: "A/D",
23768
+ overlay: false,
23769
+ inputs: [colorInput()],
23770
+ compute: (bars, inputs) => ({
23771
+ plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
23772
+ })
23773
+ };
23774
+ var cmf = {
23775
+ type: "chaikin-money-flow",
23776
+ title: "Chaikin Money Flow",
23777
+ shortTitle: "CMF",
23778
+ overlay: false,
23779
+ inputs: [lengthInput(20), colorInput()],
23780
+ compute: (bars, inputs) => {
23781
+ const len = num3(inputs, "length", 20);
23782
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
23783
+ return {
23784
+ plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23785
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23786
+ };
23787
+ }
23788
+ };
23789
+ var chaikinOscillator = {
23790
+ type: "chaikin-oscillator",
23791
+ title: "Chaikin Oscillator",
23792
+ shortTitle: "Chaikin Osc",
23793
+ overlay: false,
23794
+ inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
23795
+ compute: (bars, inputs) => {
23796
+ const ad = cumSum(moneyFlowVolume(bars));
23797
+ const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
23798
+ return {
23799
+ plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23800
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23801
+ };
23802
+ }
23803
+ };
23804
+ var eom = {
23805
+ type: "ease-of-movement",
23806
+ title: "Ease of Movement",
23807
+ shortTitle: "EOM",
23808
+ overlay: false,
23809
+ inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
23810
+ compute: (bars, inputs) => {
23811
+ const div = num3(inputs, "divisor", 1e4);
23812
+ const hl2 = sourceValues(bars, "HL2");
23813
+ const move = change(hl2);
23814
+ const raw = bars.map((b, i) => {
23815
+ const m = move[i];
23816
+ if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
23817
+ return div * m * (b.high - b.low) / b.volume;
23818
+ });
23819
+ return {
23820
+ plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23821
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23822
+ };
23823
+ }
23824
+ };
23825
+ var forceIndex = {
23826
+ type: "force-index",
23827
+ title: "Force Index",
23828
+ shortTitle: "FI",
23829
+ overlay: false,
23830
+ inputs: [lengthInput(13), colorInput()],
23831
+ compute: (bars, inputs) => {
23832
+ const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
23833
+ return {
23834
+ plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23835
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23836
+ };
23837
+ }
23838
+ };
23839
+ var klinger = {
23840
+ type: "klinger-oscillator",
23841
+ title: "Klinger Oscillator",
23842
+ shortTitle: "KVO",
23843
+ overlay: false,
23844
+ inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
23845
+ compute: (bars, inputs) => {
23846
+ const hlc3 = sourceValues(bars, "HLC3");
23847
+ const trend = change(hlc3);
23848
+ const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
23849
+ const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
23850
+ return {
23851
+ plots: [
23852
+ { key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
23853
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
23854
+ ],
23855
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23856
+ };
23857
+ }
23858
+ };
23859
+ var mfi = {
23860
+ type: "money-flow-index",
23861
+ title: "Money Flow Index",
23862
+ shortTitle: "MFI",
23863
+ overlay: false,
23864
+ inputs: [lengthInput(14), colorInput()],
23865
+ compute: (bars, inputs) => {
23866
+ const len = num3(inputs, "length", 14);
23867
+ const tp = sourceValues(bars, "HLC3");
23868
+ const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
23869
+ const d = change(tp);
23870
+ const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
23871
+ const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
23872
+ const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
23873
+ return {
23874
+ plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23875
+ levels: [
23876
+ { key: "upper", price: 80, color: NEUTRAL },
23877
+ { key: "lower", price: 20, color: NEUTRAL }
23878
+ ]
23879
+ };
23880
+ }
23881
+ };
23882
+ function volumeIndex(bars, when) {
23883
+ const out = new Array(bars.length).fill(Number.NaN);
23884
+ let index = 1e3;
23885
+ let started = false;
23886
+ for (let i = 1; i < bars.length; i++) {
23887
+ const v = bars[i].volume;
23888
+ const pv = bars[i - 1].volume;
23889
+ const pc = bars[i - 1].close;
23890
+ if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
23891
+ if (started) out[i] = index;
23892
+ continue;
23893
+ }
23894
+ started = true;
23895
+ if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
23896
+ out[i] = index;
23897
+ }
23898
+ return out;
23899
+ }
23900
+ var nvi = {
23901
+ type: "negative-volume-index",
23902
+ title: "Negative Volume Index",
23903
+ shortTitle: "Negative Volume Index",
23904
+ overlay: false,
23905
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
23906
+ compute: (bars, inputs) => {
23907
+ const line = volumeIndex(bars, (v, pv) => v < pv);
23908
+ return {
23909
+ plots: [
23910
+ { key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
23911
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
23912
+ ]
23913
+ };
23914
+ }
23915
+ };
23916
+ var pvi = {
23917
+ type: "positive-volume-index",
23918
+ title: "Positive Volume Index",
23919
+ shortTitle: "Positive Volume Index",
23920
+ overlay: false,
23921
+ inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
23922
+ compute: (bars, inputs) => {
23923
+ const line = volumeIndex(bars, (v, pv) => v > pv);
23924
+ return {
23925
+ plots: [
23926
+ { key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
23927
+ { key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
23928
+ ]
23929
+ };
23930
+ }
23931
+ };
23932
+ var pvt = {
23933
+ type: "price-volume-trend",
23934
+ title: "Price Volume Trend",
23935
+ shortTitle: "PVT",
23936
+ overlay: false,
23937
+ inputs: [colorInput()],
23938
+ compute: (bars, inputs) => {
23939
+ const c = closes(bars);
23940
+ const raw = bars.map((b, i) => {
23941
+ if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
23942
+ const pc = c[i - 1];
23943
+ return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
23944
+ });
23945
+ return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
23946
+ }
23947
+ };
23948
+ var volumeOscillator = {
23949
+ type: "volume-oscillator",
23950
+ title: "Volume Oscillator",
23951
+ shortTitle: "Vol Osc",
23952
+ overlay: false,
23953
+ inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
23954
+ compute: (bars, inputs) => {
23955
+ const vol = volumes(bars);
23956
+ const fast = ema(vol, num3(inputs, "fastLength", 5));
23957
+ const slow = ema(vol, num3(inputs, "slowLength", 10));
23958
+ const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
23959
+ return {
23960
+ plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
23961
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23962
+ };
23963
+ }
23964
+ };
23965
+ var pvo = {
23966
+ type: "pvo",
23967
+ title: "Percentage Volume Oscillator",
23968
+ shortTitle: "PVO",
23969
+ overlay: false,
23970
+ inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
23971
+ compute: (bars, inputs) => {
23972
+ const vol = volumes(bars);
23973
+ const fast = ema(vol, num3(inputs, "fastLength", 12));
23974
+ const slow = ema(vol, num3(inputs, "slowLength", 26));
23975
+ const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
23976
+ const signal = ema(line, num3(inputs, "signalLength", 9));
23977
+ const hist = sub(line, signal);
23978
+ return {
23979
+ plots: [
23980
+ { key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
23981
+ { key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
23982
+ { key: "signal", title: "Signal", values: signal, color: WARNING }
23983
+ ],
23984
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
23985
+ };
23986
+ }
23987
+ };
23988
+ var vfi = {
23989
+ type: "volume-flow-indicator",
23990
+ title: "Volume Flow Indicator",
23991
+ shortTitle: "Volume Flow Indicator",
23992
+ overlay: false,
23993
+ inputs: [
23994
+ lengthInput(130),
23995
+ { key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
23996
+ { key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
23997
+ lengthInput(3, "smoothLength", "Smoothing length")
23998
+ ],
23999
+ compute: (bars, inputs) => {
24000
+ const len = num3(inputs, "length", 130);
24001
+ const coef = num3(inputs, "coef", 0.2);
24002
+ const volCoef = num3(inputs, "volCoef", 2.5);
24003
+ const typical = map(sourceValues(bars, "HLC3"), Math.log);
24004
+ const inter = change(typical);
24005
+ const vInter = stdev(inter, 30);
24006
+ const vol = volumes(bars);
24007
+ const vAve = sma(vol, len);
24008
+ const mf = change(sourceValues(bars, "HLC3"));
24009
+ const raw = bars.map((b, i) => {
24010
+ const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
24011
+ const v = vol[i];
24012
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
24013
+ const m = mf[i];
24014
+ if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
24015
+ const capped = Math.min(v, ave * volCoef);
24016
+ return m > cutoff ? capped : m < -cutoff ? -capped : 0;
24017
+ });
24018
+ const values = bars.map((_, i) => {
24019
+ const ave = i > 0 ? vAve[i - 1] : Number.NaN;
24020
+ return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
24021
+ });
24022
+ const flowSum = sum(raw, len);
24023
+ const line = zip(flowSum, values, (f, a) => f / a);
24024
+ return {
24025
+ plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
24026
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
24027
+ };
24028
+ }
24029
+ };
24030
+ var intradayIntensity = {
24031
+ type: "intraday-intensity",
24032
+ title: "Intraday Intensity",
24033
+ shortTitle: "Intraday Intensity",
24034
+ overlay: false,
24035
+ inputs: [lengthInput(21), colorInput()],
24036
+ compute: (bars, inputs) => {
24037
+ const len = num3(inputs, "length", 21);
24038
+ const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
24039
+ return {
24040
+ plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
24041
+ levels: [{ key: "zero", price: 0, color: NEUTRAL }]
24042
+ };
24043
+ }
24044
+ };
24045
+ var volumeSpecs = [
24046
+ obv,
24047
+ accDist,
24048
+ cmf,
24049
+ chaikinOscillator,
24050
+ eom,
24051
+ forceIndex,
24052
+ klinger,
24053
+ mfi,
24054
+ nvi,
24055
+ pvi,
24056
+ pvt,
24057
+ volumeOscillator,
24058
+ pvo,
24059
+ vfi,
24060
+ intradayIntensity
24061
+ ];
24062
+
24063
+ // src/core/native-indicators/classics/overlays.ts
24064
+ var DAY_MS2 = 864e5;
24065
+ function periodKey(anchor, time) {
24066
+ if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
24067
+ if (anchor === "Month") {
24068
+ const d = new Date(time);
24069
+ return d.getUTCFullYear() * 12 + d.getUTCMonth();
24070
+ }
24071
+ return Math.floor(time / DAY_MS2);
24072
+ }
24073
+ var parabolicSar = {
24074
+ type: "parabolic-sar",
24075
+ title: "Parabolic SAR",
24076
+ shortTitle: "SAR",
24077
+ overlay: true,
24078
+ inputs: [
24079
+ { key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
24080
+ { key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
24081
+ { key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
24082
+ ],
24083
+ compute: (bars, inputs) => {
24084
+ const start = num3(inputs, "start", 0.02);
24085
+ const inc = num3(inputs, "increment", 0.02);
24086
+ const max = num3(inputs, "maximum", 0.2);
24087
+ const n = bars.length;
24088
+ const values = new Array(n).fill(Number.NaN);
24089
+ const colors = new Array(n).fill(null);
24090
+ if (n >= 2) {
24091
+ let long = bars[1].close >= bars[0].close;
24092
+ let sar = long ? bars[0].low : bars[0].high;
24093
+ let ep = long ? bars[0].high : bars[0].low;
24094
+ let af = start;
24095
+ for (let i = 1; i < n; i++) {
24096
+ const b = bars[i];
24097
+ sar += af * (ep - sar);
24098
+ if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
24099
+ else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
24100
+ const reversed = long ? b.low < sar : b.high > sar;
24101
+ if (reversed) {
24102
+ long = !long;
24103
+ sar = ep;
24104
+ ep = long ? b.high : b.low;
24105
+ af = start;
24106
+ } else if (long && b.high > ep) {
24107
+ ep = b.high;
24108
+ af = Math.min(max, af + inc);
24109
+ } else if (!long && b.low < ep) {
24110
+ ep = b.low;
24111
+ af = Math.min(max, af + inc);
24112
+ }
24113
+ values[i] = sar;
24114
+ colors[i] = long ? BULLISH : BEARISH;
24115
+ }
24116
+ }
24117
+ return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
24118
+ }
24119
+ };
24120
+ var vwap = {
24121
+ type: "vwap",
24122
+ title: "Volume Weighted Average Price",
24123
+ shortTitle: "VWAP",
24124
+ overlay: true,
24125
+ inputs: [
24126
+ { key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
24127
+ { key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
24128
+ colorInput(INFO)
24129
+ ],
24130
+ compute: (bars, inputs) => {
24131
+ const anchor = str3(inputs, "anchor", "Day");
24132
+ const source = str3(inputs, "source", "HLC3");
24133
+ const values = new Array(bars.length).fill(Number.NaN);
24134
+ let period = Number.NaN;
24135
+ let cumPV = 0;
24136
+ let cumV = 0;
24137
+ for (let i = 0; i < bars.length; i++) {
24138
+ const b = bars[i];
24139
+ const key = periodKey(anchor, b.time);
24140
+ if (key !== period) {
24141
+ period = key;
24142
+ cumPV = 0;
24143
+ cumV = 0;
24144
+ }
24145
+ const v = b.volume;
24146
+ if (v != null && Number.isFinite(v) && v > 0) {
24147
+ const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
24148
+ cumPV += tp * v;
24149
+ cumV += v;
24150
+ }
24151
+ if (cumV > 0) values[i] = cumPV / cumV;
24152
+ }
24153
+ return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
24154
+ }
24155
+ };
24156
+ var pivotPoints = {
24157
+ type: "pivot-points",
24158
+ title: "Pivot Points",
24159
+ shortTitle: "Pivots",
24160
+ overlay: true,
24161
+ inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
24162
+ compute: (bars, inputs) => {
24163
+ const anchor = str3(inputs, "anchor", "Day");
24164
+ const aggs = /* @__PURE__ */ new Map();
24165
+ for (const b of bars) {
24166
+ const key = periodKey(anchor, b.time);
24167
+ const a = aggs.get(key);
24168
+ if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
24169
+ else {
24170
+ a.high = Math.max(a.high, b.high);
24171
+ a.low = Math.min(a.low, b.low);
24172
+ a.close = b.close;
24173
+ }
24174
+ }
24175
+ const n = bars.length;
24176
+ const mk = () => new Array(n).fill(Number.NaN);
24177
+ const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
24178
+ for (let i = 0; i < n; i++) {
24179
+ const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
24180
+ if (!prev2) continue;
24181
+ const p = (prev2.high + prev2.low + prev2.close) / 3;
24182
+ levels.p[i] = p;
24183
+ levels.r1[i] = 2 * p - prev2.low;
24184
+ levels.s1[i] = 2 * p - prev2.high;
24185
+ levels.r2[i] = p + (prev2.high - prev2.low);
24186
+ levels.s2[i] = p - (prev2.high - prev2.low);
24187
+ levels.r3[i] = prev2.high + 2 * (p - prev2.low);
24188
+ levels.s3[i] = prev2.low - 2 * (prev2.high - p);
24189
+ }
24190
+ const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
24191
+ return {
24192
+ plots: [
24193
+ plot("p", "P", WARNING),
24194
+ plot("r1", "R1", BEARISH),
24195
+ plot("s1", "S1", BULLISH),
24196
+ plot("r2", "R2", BEARISH),
24197
+ plot("s2", "S2", BULLISH),
24198
+ plot("r3", "R3", BEARISH),
24199
+ plot("s3", "S3", BULLISH)
24200
+ ]
24201
+ };
24202
+ }
24203
+ };
24204
+ var fiftyTwoWeek = {
24205
+ type: "52-week-high-low",
24206
+ title: "52 Week High/Low",
24207
+ shortTitle: "52W H/L",
24208
+ overlay: true,
24209
+ inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
24210
+ compute: (bars, inputs) => {
24211
+ const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
24212
+ const n = bars.length;
24213
+ const hi = new Array(n).fill(Number.NaN);
24214
+ const lo = new Array(n).fill(Number.NaN);
24215
+ const maxIdx = [];
24216
+ const minIdx = [];
24217
+ let from = 0;
24218
+ for (let i = 0; i < n; i++) {
24219
+ const b = bars[i];
24220
+ while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
24221
+ maxIdx.push(i);
24222
+ while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
24223
+ minIdx.push(i);
24224
+ while (bars[from].time < b.time - span) from += 1;
24225
+ while (maxIdx[0] < from) maxIdx.shift();
24226
+ while (minIdx[0] < from) minIdx.shift();
24227
+ hi[i] = bars[maxIdx[0]].high;
24228
+ lo[i] = bars[minIdx[0]].low;
24229
+ }
24230
+ return {
24231
+ plots: [
24232
+ { key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
24233
+ { key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
24234
+ ]
24235
+ };
24236
+ }
24237
+ };
24238
+ var zigzag = {
24239
+ type: "zigzag",
24240
+ title: "ZigZag",
24241
+ overlay: true,
24242
+ inputs: [
24243
+ { key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
24244
+ { key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
24245
+ colorInput()
24246
+ ],
24247
+ compute: (bars, inputs) => {
24248
+ const dev = num3(inputs, "deviation", 5) / 100;
24249
+ const depth = num3(inputs, "depth", 10);
24250
+ const pivots = [];
24251
+ if (bars.length > 1) {
24252
+ let up = bars[1].close >= bars[0].close;
24253
+ let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
24254
+ for (let i = 1; i < bars.length; i++) {
24255
+ const b = bars[i];
24256
+ if (up) {
24257
+ if (b.high >= ext.price) ext = { i, price: b.high };
24258
+ else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
24259
+ pivots.push(ext);
24260
+ up = false;
24261
+ ext = { i, price: b.low };
24262
+ }
24263
+ } else if (b.low <= ext.price) {
24264
+ ext = { i, price: b.low };
24265
+ } else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
24266
+ pivots.push(ext);
24267
+ up = true;
24268
+ ext = { i, price: b.high };
24269
+ }
24270
+ }
24271
+ pivots.push(ext);
24272
+ }
24273
+ return {
24274
+ plots: [],
24275
+ polylines: [
24276
+ {
24277
+ key: "zigzag",
24278
+ points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
24279
+ curved: false,
24280
+ closed: false,
24281
+ lineColor: str3(inputs, "color", SERIES_LINE),
24282
+ lineWidth: 2,
24283
+ lineStyle: "solid",
24284
+ arrowLeft: false,
24285
+ arrowRight: false
24286
+ }
24287
+ ]
24288
+ };
24289
+ }
24290
+ };
24291
+ var williamsFractal = {
24292
+ type: "williams-fractal",
24293
+ title: "Williams Fractal",
24294
+ shortTitle: "Fractals",
24295
+ overlay: true,
24296
+ inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
24297
+ compute: (bars, inputs) => {
24298
+ const p = num3(inputs, "periods", 2);
24299
+ const markers = [];
24300
+ const isExtreme = (i, pick, better) => {
24301
+ const v = pick(bars[i]);
24302
+ for (let k = i - p; k <= i + p; k++) {
24303
+ if (k === i) continue;
24304
+ if (!better(v, pick(bars[k]))) return false;
24305
+ }
24306
+ return true;
24307
+ };
24308
+ for (let i = p; i < bars.length - p; i++) {
24309
+ if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
24310
+ markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
24311
+ }
24312
+ if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
24313
+ markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
24314
+ }
24315
+ }
24316
+ return { plots: [], markers };
24317
+ }
24318
+ };
24319
+ var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
24320
+
24321
+ // src/core/native-indicators/classics/index.ts
24322
+ var classicSpecs = [
24323
+ ...averageSpecs,
24324
+ ...bandSpecs,
24325
+ ...oscillatorSpecs,
24326
+ ...trendSpecs,
24327
+ ...volatilitySpecs,
24328
+ ...volumeSpecs,
24329
+ ...overlaySpecs
24330
+ ];
24331
+ function registerClassicIndicators() {
24332
+ for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
24333
+ }
24334
+
21985
24335
  // src/Vela.ts
21986
24336
  var Vela = class {
21987
24337
  constructor(container, options = {}, deps = {}) {
21988
24338
  registerBuiltinChartTypes();
21989
24339
  registerVolume();
21990
24340
  registerVpvr();
24341
+ registerClassicIndicators();
21991
24342
  const element = resolveElement(container);
21992
24343
  const theme = resolveTheme(options.theme);
21993
24344
  let animZoom = true;
@@ -22062,25 +24413,28 @@ var Vela = class {
22062
24413
  }
22063
24414
  /**
22064
24415
  * Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
22065
- * e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
22066
- * events) and is single-instance per type (a second call returns the existing handle). Returns
22067
- * a fail-soft handle for an unregistered type. Native renderer only.
24416
+ * e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
24417
+ * remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
24418
+ * call returns the existing handle; the classic studies allow several instances, so each call
24419
+ * adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
22068
24420
  */
22069
24421
  addNativeIndicator(type, options) {
22070
24422
  return this.orchestrator.addNativeIndicator(type, options);
22071
24423
  }
22072
24424
  /**
22073
24425
  * The catalog of built-in native indicators with their live state on this chart — each entry's
22074
- * `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
22075
- * (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
22076
- * Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
22077
- * because support may probe the provider (a type may need data the symbol lacks).
24426
+ * `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
24427
+ * and whether it allows several instances (`multiInstance`; otherwise a second
24428
+ * `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
24429
+ * ones, and de-duplicate the single-instance ones. Async because support may probe the
24430
+ * provider (a type may need data the symbol lacks).
22078
24431
  */
22079
24432
  availableNativeIndicators() {
22080
24433
  return this.orchestrator.availableNativeIndicators();
22081
24434
  }
22082
24435
  /**
22083
- * The native-indicator types PRESENT on the chart right now — the synchronous slice of
24436
+ * The native-indicator types PRESENT on the chart right now, one entry per instance (a
24437
+ * multi-instance type repeats) — the synchronous slice of
22084
24438
  * {@link availableNativeIndicators} (only support probing is async; presence never is).
22085
24439
  * Persistence snapshots read this: an unload-time flush must see an add/remove that
22086
24440
  * happened microseconds ago, which an async catalog mirror cannot guarantee.