@luxalgo/vela 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
- package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
- package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
- package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
- package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
- package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
- package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
- package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
- package/dist/index.cjs +2746 -355
- package/dist/index.d.cts +15 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -5
- package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
- package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
- package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
- package/dist/ui.cjs +23 -15
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2600 -209
- package/dist/vela.global.min.js +52 -55
- package/dist/widget.cjs +2663 -241
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +2663 -241
- package/dist/workspace.d.cts +25 -14
- package/dist/workspace.d.ts +25 -14
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- 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-
|
|
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-
|
|
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, svg24, svg24Solid } from './chunk-
|
|
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-IFJJPXSV.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-T5Z5YUCF.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-CAFCLMPF.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -1136,7 +1136,7 @@ function attachForming(closed, forming) {
|
|
|
1136
1136
|
|
|
1137
1137
|
// src/data/MultiProviderFeed.ts
|
|
1138
1138
|
function intervalMs(timeframe) {
|
|
1139
|
-
const
|
|
1139
|
+
const map2 = {
|
|
1140
1140
|
"1m": 6e4,
|
|
1141
1141
|
"3m": 18e4,
|
|
1142
1142
|
"5m": 3e5,
|
|
@@ -1158,7 +1158,7 @@ function intervalMs(timeframe) {
|
|
|
1158
1158
|
W: 6048e5,
|
|
1159
1159
|
M: 2592e6
|
|
1160
1160
|
};
|
|
1161
|
-
return
|
|
1161
|
+
return map2[timeframe] ?? 36e5;
|
|
1162
1162
|
}
|
|
1163
1163
|
var MultiProviderFeed = class {
|
|
1164
1164
|
constructor(store = sharedBarStore) {
|
|
@@ -1545,8 +1545,8 @@ var DrawingsControl = class {
|
|
|
1545
1545
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
1546
1546
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
1547
1547
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
1548
|
-
setToolShortcuts(
|
|
1549
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
1548
|
+
setToolShortcuts(map2) {
|
|
1549
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
1550
1550
|
return this;
|
|
1551
1551
|
}
|
|
1552
1552
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -1987,8 +1987,8 @@ var DrawingController = class {
|
|
|
1987
1987
|
this.port?.setExternalGhost?.(doc);
|
|
1988
1988
|
}
|
|
1989
1989
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
1990
|
-
setToolShortcuts(
|
|
1991
|
-
this.port?.setToolShortcuts?.(
|
|
1990
|
+
setToolShortcuts(map2) {
|
|
1991
|
+
this.port?.setToolShortcuts?.(map2);
|
|
1992
1992
|
}
|
|
1993
1993
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
1994
1994
|
add(type, init = {}) {
|
|
@@ -2368,6 +2368,23 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
2368
2368
|
clear();
|
|
2369
2369
|
};
|
|
2370
2370
|
}
|
|
2371
|
+
function chromeHint(text, opts) {
|
|
2372
|
+
const px = opts.size ?? 16;
|
|
2373
|
+
const el = document.createElement("span");
|
|
2374
|
+
el.className = "vela-hint";
|
|
2375
|
+
el.setAttribute("role", "img");
|
|
2376
|
+
el.setAttribute("aria-label", text);
|
|
2377
|
+
el.innerHTML = iconAt("info", px);
|
|
2378
|
+
el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
|
|
2379
|
+
const hover = (on) => {
|
|
2380
|
+
el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
|
|
2381
|
+
el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
|
|
2382
|
+
};
|
|
2383
|
+
el.addEventListener("pointerenter", () => hover(true));
|
|
2384
|
+
el.addEventListener("pointerleave", () => hover(false));
|
|
2385
|
+
const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
|
|
2386
|
+
return { el, dispose };
|
|
2387
|
+
}
|
|
2371
2388
|
|
|
2372
2389
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
2373
2390
|
var TOOLBAR_WIDTH = 44;
|
|
@@ -2475,8 +2492,8 @@ var DrawingToolbar = class {
|
|
|
2475
2492
|
}
|
|
2476
2493
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
2477
2494
|
* right edge of the flyout rows, beside the favorite star. */
|
|
2478
|
-
setShortcuts(
|
|
2479
|
-
this.shortcuts = new Map(Object.entries(
|
|
2495
|
+
setShortcuts(map2) {
|
|
2496
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
2480
2497
|
}
|
|
2481
2498
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
2482
2499
|
paintStar(el, type) {
|
|
@@ -3001,6 +3018,24 @@ function arrowHitHtml(svg) {
|
|
|
3001
3018
|
return `<span class="vela-dtb-hit vela-dtb-hit--arrow">${iconSpan(svg, 11)}</span>`;
|
|
3002
3019
|
}
|
|
3003
3020
|
|
|
3021
|
+
// src/core/options.ts
|
|
3022
|
+
var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
|
|
3023
|
+
var LIVE_BAR_EASE_DEFAULT_MS = 90;
|
|
3024
|
+
var LIVE_BAR_EASE_MAX_MS = 1e3;
|
|
3025
|
+
function resolveLiveBarEaseMs(value) {
|
|
3026
|
+
if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
|
|
3027
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
3028
|
+
return Math.min(value, LIVE_BAR_EASE_MAX_MS);
|
|
3029
|
+
}
|
|
3030
|
+
function resolveAnimations(animations) {
|
|
3031
|
+
if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
|
|
3032
|
+
return {
|
|
3033
|
+
animZoom: animations?.zoom ?? true,
|
|
3034
|
+
animPan: animations?.pan ?? true,
|
|
3035
|
+
animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3004
3039
|
// src/core/model/series.ts
|
|
3005
3040
|
function isLineLikeSeries(spec) {
|
|
3006
3041
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
@@ -3373,14 +3408,11 @@ var IndicatorInputsDialog = class {
|
|
|
3373
3408
|
buildTextField(id, current, emit) {
|
|
3374
3409
|
return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
|
|
3375
3410
|
}
|
|
3376
|
-
/**
|
|
3411
|
+
/** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
|
|
3377
3412
|
infoButton(tooltip) {
|
|
3378
|
-
const
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
b.className = "vela-ind-hint";
|
|
3382
|
-
b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
|
|
3383
|
-
return b;
|
|
3413
|
+
const { el, dispose } = chromeHint(tooltip, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
|
|
3414
|
+
this.dialogTips.push(dispose);
|
|
3415
|
+
return el;
|
|
3384
3416
|
}
|
|
3385
3417
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
3386
3418
|
select(id, options, current, emit) {
|
|
@@ -3817,8 +3849,6 @@ function ensureDialogStyles() {
|
|
|
3817
3849
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
3818
3850
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
3819
3851
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
3820
|
-
.vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
3821
|
-
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
3822
3852
|
.vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
|
|
3823
3853
|
.vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
|
|
3824
3854
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
@@ -4327,8 +4357,8 @@ var InputsUI = class {
|
|
|
4327
4357
|
lg.style.top = `${top}px`;
|
|
4328
4358
|
}
|
|
4329
4359
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
4330
|
-
setCollapsedPanes(
|
|
4331
|
-
this.paneCollapse =
|
|
4360
|
+
setCollapsedPanes(map2) {
|
|
4361
|
+
this.paneCollapse = map2;
|
|
4332
4362
|
}
|
|
4333
4363
|
/**
|
|
4334
4364
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -5291,9 +5321,9 @@ function factoryResetConfig(factory) {
|
|
|
5291
5321
|
if (inst.enableKey) defaults[inst.enableKey] = false;
|
|
5292
5322
|
addRows(inst.rows);
|
|
5293
5323
|
}
|
|
5294
|
-
for (const
|
|
5295
|
-
if (
|
|
5296
|
-
addRows(
|
|
5324
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5325
|
+
if (sub2.enableKey) defaults[sub2.enableKey] = false;
|
|
5326
|
+
addRows(sub2.rows);
|
|
5297
5327
|
}
|
|
5298
5328
|
if (!section.instances) addRows(section.rows);
|
|
5299
5329
|
bag[t.id] = defaults;
|
|
@@ -5362,7 +5392,8 @@ function mergeConfig(base, patch) {
|
|
|
5362
5392
|
labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
|
|
5363
5393
|
currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
|
|
5364
5394
|
priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
|
|
5365
|
-
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
|
|
5395
|
+
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
|
|
5396
|
+
animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
|
|
5366
5397
|
},
|
|
5367
5398
|
panes: {
|
|
5368
5399
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
@@ -6622,11 +6653,11 @@ function mergeBarColors(indicators) {
|
|
|
6622
6653
|
}
|
|
6623
6654
|
}
|
|
6624
6655
|
if (!any) return EMPTY_BARCOLORS;
|
|
6625
|
-
const
|
|
6656
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
6626
6657
|
for (const m of indicators.values()) {
|
|
6627
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
6658
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
6628
6659
|
}
|
|
6629
|
-
return
|
|
6660
|
+
return map2;
|
|
6630
6661
|
}
|
|
6631
6662
|
|
|
6632
6663
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -6923,9 +6954,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
6923
6954
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
6924
6955
|
return rightOffset + deltaX / barSpacing;
|
|
6925
6956
|
}
|
|
6926
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
6957
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
6927
6958
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
6928
|
-
if (
|
|
6959
|
+
if (shift2) return deltaY;
|
|
6929
6960
|
return null;
|
|
6930
6961
|
}
|
|
6931
6962
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -8547,11 +8578,11 @@ function mergeBarColors2(indicators) {
|
|
|
8547
8578
|
}
|
|
8548
8579
|
}
|
|
8549
8580
|
if (!any) return EMPTY_BARCOLORS2;
|
|
8550
|
-
const
|
|
8581
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
8551
8582
|
for (const m of indicators.values()) {
|
|
8552
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
8583
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
8553
8584
|
}
|
|
8554
|
-
return
|
|
8585
|
+
return map2;
|
|
8555
8586
|
}
|
|
8556
8587
|
|
|
8557
8588
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -10149,6 +10180,8 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
10149
10180
|
"symbol.style.baseline.fill-bottom",
|
|
10150
10181
|
"symbol.style.baseline.base-level",
|
|
10151
10182
|
"symbol.style.baseline.width",
|
|
10183
|
+
"symbol.animation",
|
|
10184
|
+
"symbol.animation.price-changes",
|
|
10152
10185
|
"symbol.timezone",
|
|
10153
10186
|
"scales",
|
|
10154
10187
|
"scales.price-scale",
|
|
@@ -10190,9 +10223,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
10190
10223
|
};
|
|
10191
10224
|
if (section.rows) addRows(section.rows);
|
|
10192
10225
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
10193
|
-
for (const
|
|
10194
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
10195
|
-
addRows(
|
|
10226
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10227
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
10228
|
+
addRows(sub2.rows);
|
|
10196
10229
|
}
|
|
10197
10230
|
}
|
|
10198
10231
|
for (const hs of hostSections) {
|
|
@@ -10291,8 +10324,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
10291
10324
|
.vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
|
|
10292
10325
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
10293
10326
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
10294
|
-
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
|
|
10295
|
-
`;
|
|
10327
|
+
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
|
|
10296
10328
|
if (!existing) document.head.appendChild(st);
|
|
10297
10329
|
}
|
|
10298
10330
|
var SettingsDialog = class {
|
|
@@ -10313,6 +10345,8 @@ var SettingsDialog = class {
|
|
|
10313
10345
|
this.themeControl = null;
|
|
10314
10346
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
10315
10347
|
this.tabs = [];
|
|
10348
|
+
this.hintTips = [];
|
|
10349
|
+
// ⓘ tooltip disposers of the open build (see hint)
|
|
10316
10350
|
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
10317
10351
|
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
10318
10352
|
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
@@ -10501,6 +10535,13 @@ var SettingsDialog = class {
|
|
|
10501
10535
|
body.append(g);
|
|
10502
10536
|
}
|
|
10503
10537
|
showActive(config.series.style);
|
|
10538
|
+
body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
|
|
10539
|
+
body.append(sid(this.boolRow(
|
|
10540
|
+
"Animate price changes",
|
|
10541
|
+
config.priceScale.animateLastPrice,
|
|
10542
|
+
(v) => this.emit({ priceScale: { animateLastPrice: v } }),
|
|
10543
|
+
this.hint("Glide the live bar to each new price instead of snapping.")
|
|
10544
|
+
), "symbol.animation.price-changes"));
|
|
10504
10545
|
body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
|
|
10505
10546
|
body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
|
|
10506
10547
|
const renderHostSections = (placement) => {
|
|
@@ -10708,6 +10749,8 @@ var SettingsDialog = class {
|
|
|
10708
10749
|
this.ui = null;
|
|
10709
10750
|
this.root = null;
|
|
10710
10751
|
this.tabs = [];
|
|
10752
|
+
for (const dispose of this.hintTips) dispose();
|
|
10753
|
+
this.hintTips = [];
|
|
10711
10754
|
ui?.destroy();
|
|
10712
10755
|
}
|
|
10713
10756
|
destroy() {
|
|
@@ -10751,14 +10794,14 @@ var SettingsDialog = class {
|
|
|
10751
10794
|
};
|
|
10752
10795
|
if (section.rows) seed(section.rows);
|
|
10753
10796
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
10754
|
-
for (const
|
|
10797
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
10755
10798
|
for (const inst of section.instances ?? []) {
|
|
10756
10799
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
10757
10800
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
10758
10801
|
}
|
|
10759
|
-
for (const
|
|
10760
|
-
if (!
|
|
10761
|
-
bag[
|
|
10802
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10803
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
10804
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
10762
10805
|
}
|
|
10763
10806
|
const refreshers = [];
|
|
10764
10807
|
const put = (key, v) => {
|
|
@@ -10775,16 +10818,16 @@ var SettingsDialog = class {
|
|
|
10775
10818
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
10776
10819
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
10777
10820
|
}
|
|
10778
|
-
for (const
|
|
10779
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
10780
|
-
const rows = filterHiddenRows(
|
|
10821
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10822
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
10823
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
10781
10824
|
if (rows.length === 0) continue;
|
|
10782
|
-
const subMarker = this.section(
|
|
10825
|
+
const subMarker = this.section(sub2.title);
|
|
10783
10826
|
subMarker.dataset.sdStyle = typeId;
|
|
10784
10827
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
10785
10828
|
subMarker.dataset.sdSub = "1";
|
|
10786
10829
|
body.append(subMarker);
|
|
10787
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
10830
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
10788
10831
|
}
|
|
10789
10832
|
for (const r of refreshers) r();
|
|
10790
10833
|
}
|
|
@@ -11121,23 +11164,31 @@ var SettingsDialog = class {
|
|
|
11121
11164
|
}).el
|
|
11122
11165
|
});
|
|
11123
11166
|
}
|
|
11124
|
-
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
11125
|
-
|
|
11126
|
-
|
|
11167
|
+
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
11168
|
+
* `info` (see {@link hint}) rides after the label. */
|
|
11169
|
+
boolRow(label, value, onChange, info) {
|
|
11170
|
+
return this.toggleRow(label, value, onChange, [], void 0, info);
|
|
11127
11171
|
}
|
|
11128
11172
|
/** An enable row: checkbox + label in the label column, dependent controls in the
|
|
11129
11173
|
* shared control column; the control group dims and ignores input while the toggle
|
|
11130
11174
|
* is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
|
|
11131
|
-
toggleRow(label, value, onToggle, controls, get) {
|
|
11175
|
+
toggleRow(label, value, onToggle, controls, get, info) {
|
|
11132
11176
|
return fieldRow({
|
|
11133
11177
|
label,
|
|
11134
11178
|
labelSize: "sm",
|
|
11135
11179
|
bool: controls.length === 0,
|
|
11136
11180
|
toggle: { checked: value, onChange: onToggle, get },
|
|
11137
11181
|
control: controls,
|
|
11182
|
+
info,
|
|
11138
11183
|
className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
|
|
11139
11184
|
});
|
|
11140
11185
|
}
|
|
11186
|
+
/** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
|
|
11187
|
+
hint(text) {
|
|
11188
|
+
const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
|
|
11189
|
+
this.hintTips.push(dispose);
|
|
11190
|
+
return el;
|
|
11191
|
+
}
|
|
11141
11192
|
numberRow(label, value, min, max, step, onChange) {
|
|
11142
11193
|
return fieldRow({
|
|
11143
11194
|
label,
|
|
@@ -12891,11 +12942,11 @@ var DrawingInteraction = class {
|
|
|
12891
12942
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
12892
12943
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
12893
12944
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
12894
|
-
down(x, y, mode = "off",
|
|
12945
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
12895
12946
|
const proj = this.deps.projector();
|
|
12896
12947
|
if (this.state.kind === "placing") {
|
|
12897
12948
|
if (this.state.draft.placementMode() !== "click") return;
|
|
12898
|
-
const pivot =
|
|
12949
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12899
12950
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12900
12951
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
12901
12952
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -12914,7 +12965,7 @@ var DrawingInteraction = class {
|
|
|
12914
12965
|
}
|
|
12915
12966
|
const hit = this.hitAt(x, y);
|
|
12916
12967
|
if (hit) {
|
|
12917
|
-
if (
|
|
12968
|
+
if (shift2) {
|
|
12918
12969
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
12919
12970
|
return;
|
|
12920
12971
|
}
|
|
@@ -12924,7 +12975,7 @@ var DrawingInteraction = class {
|
|
|
12924
12975
|
}
|
|
12925
12976
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
12926
12977
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
12927
|
-
move(x, y, mode = "off",
|
|
12978
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
12928
12979
|
if (this.state.kind === "idle") {
|
|
12929
12980
|
const tool = this.deps.activeTool();
|
|
12930
12981
|
const had = this.snapAt != null;
|
|
@@ -12938,7 +12989,7 @@ var DrawingInteraction = class {
|
|
|
12938
12989
|
this.captureFreehand(x, y);
|
|
12939
12990
|
return;
|
|
12940
12991
|
}
|
|
12941
|
-
const pivot =
|
|
12992
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12942
12993
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12943
12994
|
this.deps.changed();
|
|
12944
12995
|
return;
|
|
@@ -12951,7 +13002,7 @@ var DrawingInteraction = class {
|
|
|
12951
13002
|
if (this.state.handle >= 0) {
|
|
12952
13003
|
const a = d.anchors[this.state.handle];
|
|
12953
13004
|
if (a) {
|
|
12954
|
-
const pivot =
|
|
13005
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
12955
13006
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
12956
13007
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
12957
13008
|
}
|
|
@@ -13649,21 +13700,21 @@ var DrawingSettingsPopup = class {
|
|
|
13649
13700
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
13650
13701
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
13651
13702
|
}
|
|
13652
|
-
const
|
|
13653
|
-
if (paths.has("vwap.midColor") &&
|
|
13703
|
+
const vwap2 = drawing.vwap;
|
|
13704
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
13654
13705
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
13655
13706
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
13656
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
13657
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
13707
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
13708
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
13658
13709
|
bar.appendChild(
|
|
13659
|
-
this.dropdown("Band multiplier", MULTS,
|
|
13710
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
13660
13711
|
label: (v) => `${v}\u03C3`,
|
|
13661
13712
|
labelInTrigger: true
|
|
13662
13713
|
})
|
|
13663
13714
|
);
|
|
13664
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
13665
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
13666
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
13715
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
13716
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
13717
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
13667
13718
|
}
|
|
13668
13719
|
if (paths.has("maxCurvature")) {
|
|
13669
13720
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -14680,8 +14731,8 @@ var UserDrawingController = class {
|
|
|
14680
14731
|
this.toolbar.setFavorites(types);
|
|
14681
14732
|
}
|
|
14682
14733
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
14683
|
-
setToolShortcuts(
|
|
14684
|
-
this.toolbar.setShortcuts(
|
|
14734
|
+
setToolShortcuts(map2) {
|
|
14735
|
+
this.toolbar.setShortcuts(map2);
|
|
14685
14736
|
}
|
|
14686
14737
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
14687
14738
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -14799,7 +14850,7 @@ var UserDrawingController = class {
|
|
|
14799
14850
|
this.render();
|
|
14800
14851
|
}
|
|
14801
14852
|
}
|
|
14802
|
-
pointerDown(x, y, snap = "off",
|
|
14853
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
14803
14854
|
if (this.eraserMode) {
|
|
14804
14855
|
this.erasing = true;
|
|
14805
14856
|
this.deleteAt(x, y);
|
|
@@ -14819,9 +14870,9 @@ var UserDrawingController = class {
|
|
|
14819
14870
|
return;
|
|
14820
14871
|
}
|
|
14821
14872
|
}
|
|
14822
|
-
this.interaction.down(x, y, snap,
|
|
14873
|
+
this.interaction.down(x, y, snap, shift2);
|
|
14823
14874
|
}
|
|
14824
|
-
pointerMove(x, y, snap = "off",
|
|
14875
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
14825
14876
|
if (this.eraserMode) {
|
|
14826
14877
|
if (this.erasing) this.deleteAt(x, y);
|
|
14827
14878
|
return;
|
|
@@ -14832,7 +14883,7 @@ var UserDrawingController = class {
|
|
|
14832
14883
|
this.render();
|
|
14833
14884
|
return;
|
|
14834
14885
|
}
|
|
14835
|
-
this.interaction.move(x, y, snap,
|
|
14886
|
+
this.interaction.move(x, y, snap, shift2);
|
|
14836
14887
|
this.updateHover(x, y);
|
|
14837
14888
|
}
|
|
14838
14889
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -15509,14 +15560,14 @@ function layoutTable(ctx, t, args) {
|
|
|
15509
15560
|
}
|
|
15510
15561
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
15511
15562
|
if (box.cs > 1) {
|
|
15512
|
-
let
|
|
15513
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
15514
|
-
if (w2 >
|
|
15563
|
+
let sum2 = 0;
|
|
15564
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
15565
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
15515
15566
|
}
|
|
15516
15567
|
if (box.rs > 1) {
|
|
15517
|
-
let
|
|
15518
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
15519
|
-
if (h2 >
|
|
15568
|
+
let sum2 = 0;
|
|
15569
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
15570
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
15520
15571
|
}
|
|
15521
15572
|
}
|
|
15522
15573
|
let w = 0;
|
|
@@ -16213,24 +16264,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
16213
16264
|
drawSvgShapes(el, alpha);
|
|
16214
16265
|
continue;
|
|
16215
16266
|
}
|
|
16216
|
-
const
|
|
16267
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
16217
16268
|
let path = null;
|
|
16218
16269
|
if (tag === "path") {
|
|
16219
16270
|
const d = el.getAttribute("d");
|
|
16220
16271
|
if (d) path = new Path2D(d);
|
|
16221
16272
|
} else if (tag === "circle") {
|
|
16222
16273
|
path = new Path2D();
|
|
16223
|
-
path.arc(
|
|
16274
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
16224
16275
|
} else if (tag === "ellipse") {
|
|
16225
16276
|
path = new Path2D();
|
|
16226
|
-
path.ellipse(
|
|
16277
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
16227
16278
|
} else if (tag === "rect") {
|
|
16228
16279
|
path = new Path2D();
|
|
16229
|
-
path.rect(
|
|
16280
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
16230
16281
|
} else if (tag === "line") {
|
|
16231
16282
|
path = new Path2D();
|
|
16232
|
-
path.moveTo(
|
|
16233
|
-
path.lineTo(
|
|
16283
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
16284
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
16234
16285
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
16235
16286
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
16236
16287
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -16491,7 +16542,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
|
|
|
16491
16542
|
var ZOOM_TAU_MS = 70;
|
|
16492
16543
|
var SCALE_TAU_MS = 80;
|
|
16493
16544
|
var FLING_TAU_MS = 110;
|
|
16494
|
-
var LIVE_BAR_TAU_MS = 90;
|
|
16495
16545
|
var SCROLL_TO_TAU_MS = 130;
|
|
16496
16546
|
var FLING_STOP_PX = 0.02;
|
|
16497
16547
|
var PRICE_SCALE_K = 4e-3;
|
|
@@ -16562,9 +16612,13 @@ var NativeRenderer = class {
|
|
|
16562
16612
|
/** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
|
|
16563
16613
|
this.historyChordsEnabled = true;
|
|
16564
16614
|
this.liveRegion = null;
|
|
16565
|
-
// ── animation state (eased zoom + inertial pan) ──
|
|
16615
|
+
// ── animation state (eased zoom + inertial pan + live-bar glide) ──
|
|
16566
16616
|
this.animZoom = true;
|
|
16567
16617
|
this.animPan = true;
|
|
16618
|
+
this.animLiveBarMs = 0;
|
|
16619
|
+
// forming-bar OHLC glide time-constant; 0 = each tick snaps
|
|
16620
|
+
this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
|
|
16621
|
+
// the duration the settings-dialog on/off toggle restores (last non-zero value configured)
|
|
16568
16622
|
// Brand default candles.
|
|
16569
16623
|
this.candleUp = BULLISH;
|
|
16570
16624
|
this.candleDown = BEARISH;
|
|
@@ -16689,7 +16743,7 @@ var NativeRenderer = class {
|
|
|
16689
16743
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
16690
16744
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
16691
16745
|
this.name = "native";
|
|
16692
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
16746
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
16693
16747
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
16694
16748
|
* so moving onto the button doesn't count as leaving). */
|
|
16695
16749
|
this.onScrollProximityMove = (e) => {
|
|
@@ -16723,6 +16777,7 @@ var NativeRenderer = class {
|
|
|
16723
16777
|
this.backendMode = opts.nativeBackend;
|
|
16724
16778
|
this.animZoom = opts.animZoom;
|
|
16725
16779
|
this.animPan = opts.animPan;
|
|
16780
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
|
|
16726
16781
|
this.glowAmount = opts.glow;
|
|
16727
16782
|
this.candleUp = opts.upColor;
|
|
16728
16783
|
this.candleDown = opts.downColor;
|
|
@@ -16772,6 +16827,10 @@ var NativeRenderer = class {
|
|
|
16772
16827
|
case "animPan":
|
|
16773
16828
|
this.animPan = Boolean(value);
|
|
16774
16829
|
return;
|
|
16830
|
+
case "animLiveBar":
|
|
16831
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(value));
|
|
16832
|
+
return;
|
|
16833
|
+
// affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
|
|
16775
16834
|
case "intro": {
|
|
16776
16835
|
const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
|
|
16777
16836
|
this.introStyle = s;
|
|
@@ -16908,6 +16967,8 @@ var NativeRenderer = class {
|
|
|
16908
16967
|
return this.animZoom;
|
|
16909
16968
|
case "animPan":
|
|
16910
16969
|
return this.animPan;
|
|
16970
|
+
case "animLiveBar":
|
|
16971
|
+
return this.animLiveBarMs;
|
|
16911
16972
|
case "intro":
|
|
16912
16973
|
return this.introStyle;
|
|
16913
16974
|
case "zoomAnchor":
|
|
@@ -17076,7 +17137,8 @@ var NativeRenderer = class {
|
|
|
17076
17137
|
labelsVisible: this.scene.showAxisLabels,
|
|
17077
17138
|
currentPriceLine: this.scene.showPriceLine,
|
|
17078
17139
|
priceLabel: this.scene.showPriceLabel,
|
|
17079
|
-
countdown: this.scene.showCountdown
|
|
17140
|
+
countdown: this.scene.showCountdown,
|
|
17141
|
+
animateLastPrice: this.animLiveBarMs > 0
|
|
17080
17142
|
},
|
|
17081
17143
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
17082
17144
|
trades: {
|
|
@@ -17183,6 +17245,7 @@ var NativeRenderer = class {
|
|
|
17183
17245
|
this.scene.showPriceLabel = next.priceScale.priceLabel;
|
|
17184
17246
|
this.scene.showCountdown = next.priceScale.countdown;
|
|
17185
17247
|
this.syncCountdownTimer();
|
|
17248
|
+
this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
|
|
17186
17249
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
17187
17250
|
this.scene.tradeMarkers = {
|
|
17188
17251
|
visible: next.trades.visible,
|
|
@@ -17647,8 +17710,8 @@ var NativeRenderer = class {
|
|
|
17647
17710
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
17648
17711
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
17649
17712
|
drawingsSnapMode: () => this.snapMode,
|
|
17650
|
-
drawingsPointerDown: (x, y, snap,
|
|
17651
|
-
drawingsPointerMove: (x, y, snap,
|
|
17713
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
17714
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
17652
17715
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
17653
17716
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
17654
17717
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -17965,18 +18028,18 @@ var NativeRenderer = class {
|
|
|
17965
18028
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
17966
18029
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
17967
18030
|
const newHeadTime = this.bars[0].time;
|
|
17968
|
-
let
|
|
18031
|
+
let shift2 = 0;
|
|
17969
18032
|
if (newHeadTime !== prevHeadTime) {
|
|
17970
18033
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
17971
|
-
if (prepended > 0)
|
|
18034
|
+
if (prepended > 0) shift2 = prepended;
|
|
17972
18035
|
else {
|
|
17973
18036
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
17974
|
-
if (trimmed > 0)
|
|
18037
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
17975
18038
|
}
|
|
17976
18039
|
}
|
|
17977
|
-
if (
|
|
17978
|
-
this.zoomAnchorLogical +=
|
|
17979
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
18040
|
+
if (shift2 !== 0) {
|
|
18041
|
+
this.zoomAnchorLogical += shift2;
|
|
18042
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
17980
18043
|
}
|
|
17981
18044
|
}
|
|
17982
18045
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -17999,8 +18062,11 @@ var NativeRenderer = class {
|
|
|
17999
18062
|
const last = this.bars[n - 1];
|
|
18000
18063
|
if (last && bar.time === last.time) {
|
|
18001
18064
|
this.bars[n - 1] = bar;
|
|
18002
|
-
if (this.liveEaseTime !== bar.time)
|
|
18003
|
-
|
|
18065
|
+
if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
|
|
18066
|
+
this.syncLiveEase(bar);
|
|
18067
|
+
} else {
|
|
18068
|
+
this.animator.start();
|
|
18069
|
+
}
|
|
18004
18070
|
} else if (!last || bar.time > last.time) {
|
|
18005
18071
|
this.bars.push(bar);
|
|
18006
18072
|
this.syncLiveEase(bar);
|
|
@@ -18012,6 +18078,12 @@ var NativeRenderer = class {
|
|
|
18012
18078
|
this.scene.bars = this.bars;
|
|
18013
18079
|
this.scheduler.invalidate(4 /* Full */);
|
|
18014
18080
|
}
|
|
18081
|
+
/** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
|
|
18082
|
+
* what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
|
|
18083
|
+
setLiveBarEase(ms) {
|
|
18084
|
+
this.animLiveBarMs = ms;
|
|
18085
|
+
if (ms > 0) this.animLiveBarOnMs = ms;
|
|
18086
|
+
}
|
|
18015
18087
|
/** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
|
|
18016
18088
|
syncLiveEase(bar) {
|
|
18017
18089
|
this.liveEaseTime = bar.time;
|
|
@@ -18024,9 +18096,10 @@ var NativeRenderer = class {
|
|
|
18024
18096
|
const target = this.bars[this.bars.length - 1];
|
|
18025
18097
|
if (!target || this.liveEaseTime !== target.time) return false;
|
|
18026
18098
|
const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
|
|
18027
|
-
const
|
|
18028
|
-
const
|
|
18029
|
-
const
|
|
18099
|
+
const tau = this.animLiveBarMs;
|
|
18100
|
+
const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
|
|
18101
|
+
const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
|
|
18102
|
+
const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
|
|
18030
18103
|
const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
|
|
18031
18104
|
this.liveEaseHigh = active ? nh : target.high;
|
|
18032
18105
|
this.liveEaseLow = active ? nl : target.low;
|
|
@@ -19582,15 +19655,15 @@ var NativeRenderer = class {
|
|
|
19582
19655
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
19583
19656
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
19584
19657
|
collapsedMasterMap() {
|
|
19585
|
-
const
|
|
19658
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
19586
19659
|
for (const pane of this.scene.panes.values()) {
|
|
19587
19660
|
if (!pane.collapsed) continue;
|
|
19588
19661
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
19589
19662
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
19590
19663
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
19591
|
-
|
|
19664
|
+
map2.set(pane.id, master?.id ?? null);
|
|
19592
19665
|
}
|
|
19593
|
-
return
|
|
19666
|
+
return map2;
|
|
19594
19667
|
}
|
|
19595
19668
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
19596
19669
|
setToolbarGutter(px) {
|
|
@@ -19862,7 +19935,7 @@ var LiveSession = class {
|
|
|
19862
19935
|
|
|
19863
19936
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
19864
19937
|
var IndicatorHandleImpl = class {
|
|
19865
|
-
constructor(id, title, controller, source) {
|
|
19938
|
+
constructor(id, title, controller, source, nativeType) {
|
|
19866
19939
|
this.controller = controller;
|
|
19867
19940
|
this.schema = [];
|
|
19868
19941
|
this.propsSchema = [];
|
|
@@ -19871,6 +19944,7 @@ var IndicatorHandleImpl = class {
|
|
|
19871
19944
|
this.id = id;
|
|
19872
19945
|
this.title = title;
|
|
19873
19946
|
if (source !== void 0) this.source = source;
|
|
19947
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
19874
19948
|
}
|
|
19875
19949
|
get inputs() {
|
|
19876
19950
|
return this.schema;
|
|
@@ -19961,24 +20035,24 @@ function summarizeModel(model) {
|
|
|
19961
20035
|
}
|
|
19962
20036
|
function inspectModels(models) {
|
|
19963
20037
|
const indicators = models.map(summarizeModel);
|
|
19964
|
-
const
|
|
20038
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
19965
20039
|
return {
|
|
19966
20040
|
indicators,
|
|
19967
20041
|
totals: {
|
|
19968
20042
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
19969
|
-
series:
|
|
19970
|
-
fills:
|
|
19971
|
-
backgrounds:
|
|
19972
|
-
priceLines:
|
|
19973
|
-
lines:
|
|
19974
|
-
boxes:
|
|
19975
|
-
labels:
|
|
19976
|
-
polylines:
|
|
19977
|
-
linefills:
|
|
19978
|
-
tables:
|
|
19979
|
-
barColors:
|
|
19980
|
-
trades:
|
|
19981
|
-
forcedOverlay:
|
|
20043
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
20044
|
+
fills: sum2((s) => s.fills),
|
|
20045
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
20046
|
+
priceLines: sum2((s) => s.priceLines),
|
|
20047
|
+
lines: sum2((s) => s.lines),
|
|
20048
|
+
boxes: sum2((s) => s.boxes),
|
|
20049
|
+
labels: sum2((s) => s.labels),
|
|
20050
|
+
polylines: sum2((s) => s.polylines),
|
|
20051
|
+
linefills: sum2((s) => s.linefills),
|
|
20052
|
+
tables: sum2((s) => s.tables),
|
|
20053
|
+
barColors: sum2((s) => s.barColors),
|
|
20054
|
+
trades: sum2((s) => s.trades),
|
|
20055
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
19982
20056
|
}
|
|
19983
20057
|
};
|
|
19984
20058
|
}
|
|
@@ -21035,16 +21109,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21035
21109
|
/**
|
|
21036
21110
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
21037
21111
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
21038
|
-
* INSTANCE per type
|
|
21039
|
-
*
|
|
21112
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
21113
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
21114
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
21115
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
21040
21116
|
*/
|
|
21041
21117
|
addNativeIndicator(type, options = {}) {
|
|
21042
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
21043
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
21044
21118
|
const descriptor = getNativeIndicator(type);
|
|
21119
|
+
if (!descriptor?.multiInstance) {
|
|
21120
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
21121
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
21122
|
+
}
|
|
21045
21123
|
const id = this.registry.nextId("native");
|
|
21046
21124
|
const title = descriptor?.title ?? type;
|
|
21047
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
21125
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
21048
21126
|
this.handles.set(id, handle);
|
|
21049
21127
|
if (!descriptor) {
|
|
21050
21128
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -21056,16 +21134,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21056
21134
|
void this.startNativeIndicator(id, handle);
|
|
21057
21135
|
return handle;
|
|
21058
21136
|
}
|
|
21059
|
-
/**
|
|
21060
|
-
*
|
|
21061
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
21062
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
21063
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
21064
|
-
*/
|
|
21065
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
21137
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
21138
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
21066
21139
|
presentNativeIndicators() {
|
|
21067
21140
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
21068
21141
|
}
|
|
21142
|
+
/**
|
|
21143
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
21144
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
21145
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
21146
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
21147
|
+
* probe the provider for a required capability.
|
|
21148
|
+
*/
|
|
21069
21149
|
async availableNativeIndicators() {
|
|
21070
21150
|
const symbol = this.qualifiedSymbol();
|
|
21071
21151
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -21075,7 +21155,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21075
21155
|
title: d.title,
|
|
21076
21156
|
supported: await this.isNativeSupported(d, symbol),
|
|
21077
21157
|
present: present.has(d.type),
|
|
21078
|
-
beta: d.beta
|
|
21158
|
+
beta: d.beta,
|
|
21159
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
21079
21160
|
}))
|
|
21080
21161
|
);
|
|
21081
21162
|
}
|
|
@@ -22015,29 +22096,2329 @@ function registerVpvr() {
|
|
|
22015
22096
|
registerNativeIndicator(vpvrDescriptor);
|
|
22016
22097
|
}
|
|
22017
22098
|
|
|
22018
|
-
// src/
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22099
|
+
// src/core/native-indicators/classics/define.ts
|
|
22100
|
+
function num3(inputs, key, fallback) {
|
|
22101
|
+
const v = inputs[key];
|
|
22102
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
22103
|
+
}
|
|
22104
|
+
function str3(inputs, key, fallback) {
|
|
22105
|
+
const v = inputs[key];
|
|
22106
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
22107
|
+
}
|
|
22108
|
+
function toPoints(bars, values, colors) {
|
|
22109
|
+
const pts = new Array(bars.length);
|
|
22110
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22111
|
+
const v = values[i];
|
|
22112
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
22113
|
+
const c = colors?.[i];
|
|
22114
|
+
if (c != null) p.color = c;
|
|
22115
|
+
pts[i] = p;
|
|
22116
|
+
}
|
|
22117
|
+
return pts;
|
|
22118
|
+
}
|
|
22119
|
+
var ClassicIndicator = class {
|
|
22120
|
+
constructor(spec) {
|
|
22121
|
+
this.spec = spec;
|
|
22122
|
+
this.inputs = {};
|
|
22123
|
+
}
|
|
22124
|
+
start(ctx, inputs) {
|
|
22125
|
+
this.ctx = ctx;
|
|
22126
|
+
this.inputs = inputs;
|
|
22127
|
+
this.recompute();
|
|
22128
|
+
}
|
|
22129
|
+
onBars() {
|
|
22130
|
+
this.recompute();
|
|
22131
|
+
}
|
|
22132
|
+
onViewport() {
|
|
22133
|
+
}
|
|
22134
|
+
setInputs(inputs) {
|
|
22135
|
+
this.inputs = inputs;
|
|
22136
|
+
this.recompute();
|
|
22137
|
+
}
|
|
22138
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
22139
|
+
suspend() {
|
|
22140
|
+
}
|
|
22141
|
+
resume() {
|
|
22142
|
+
this.recompute();
|
|
22143
|
+
}
|
|
22144
|
+
stop() {
|
|
22145
|
+
}
|
|
22146
|
+
recompute() {
|
|
22147
|
+
const bars = this.ctx.bars();
|
|
22148
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
22149
|
+
const instanceId = this.spec.type;
|
|
22150
|
+
const series = [];
|
|
22151
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
22152
|
+
out.plots.forEach((plot, ordinal) => {
|
|
22153
|
+
const kind = plot.kind ?? "line";
|
|
22154
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
22155
|
+
idsByKey.set(plot.key, id);
|
|
22156
|
+
series.push({
|
|
22157
|
+
id,
|
|
22158
|
+
title: plot.title,
|
|
22159
|
+
paneId: "",
|
|
22160
|
+
// stamped by the orchestrator
|
|
22161
|
+
kind,
|
|
22162
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
22163
|
+
style: {
|
|
22164
|
+
color: plot.color,
|
|
22165
|
+
width: plot.width ?? 1,
|
|
22166
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
22167
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
22168
|
+
},
|
|
22169
|
+
...plot.overlay ? { overlay: true } : {}
|
|
22170
|
+
});
|
|
22171
|
+
});
|
|
22172
|
+
if (out.markers && out.markers.length > 0) {
|
|
22173
|
+
series.push({
|
|
22174
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
22175
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
22176
|
+
paneId: "",
|
|
22177
|
+
kind: "markers",
|
|
22178
|
+
markers: out.markers
|
|
22179
|
+
});
|
|
22180
|
+
}
|
|
22181
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
22182
|
+
const from = idsByKey.get(band.from);
|
|
22183
|
+
const to = idsByKey.get(band.to);
|
|
22184
|
+
if (!from || !to) return [];
|
|
22185
|
+
return [
|
|
22186
|
+
{
|
|
22187
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
22188
|
+
paneId: "",
|
|
22189
|
+
fromSeriesId: from,
|
|
22190
|
+
toSeriesId: to,
|
|
22191
|
+
color: band.color
|
|
22192
|
+
}
|
|
22193
|
+
];
|
|
22194
|
+
});
|
|
22195
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
22196
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
22197
|
+
paneId: "",
|
|
22198
|
+
price: level.price,
|
|
22199
|
+
...level.color != null ? { color: level.color } : {},
|
|
22200
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
22201
|
+
width: 1,
|
|
22202
|
+
...level.title != null ? { title: level.title } : {}
|
|
22203
|
+
}));
|
|
22204
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
22205
|
+
const { key, ...rest } = poly;
|
|
22206
|
+
return {
|
|
22207
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
22208
|
+
paneId: "",
|
|
22209
|
+
...rest
|
|
22210
|
+
};
|
|
22211
|
+
});
|
|
22212
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
22213
|
+
this.ctx.setStatus("idle");
|
|
22214
|
+
}
|
|
22215
|
+
};
|
|
22216
|
+
function classicDescriptor(spec) {
|
|
22217
|
+
return {
|
|
22218
|
+
type: spec.type,
|
|
22219
|
+
title: spec.title,
|
|
22220
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
22221
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
22222
|
+
overlay: spec.overlay,
|
|
22223
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
22224
|
+
multiInstance: true,
|
|
22225
|
+
inputsSchema: () => spec.inputs,
|
|
22226
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
22227
|
+
create: () => new ClassicIndicator(spec)
|
|
22228
|
+
};
|
|
22229
|
+
}
|
|
22230
|
+
|
|
22231
|
+
// src/core/native-indicators/classics/math.ts
|
|
22232
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
22233
|
+
function sourceValues(bars, source) {
|
|
22234
|
+
const out = new Array(bars.length);
|
|
22235
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22236
|
+
const b = bars[i];
|
|
22237
|
+
switch (source) {
|
|
22238
|
+
case "Open":
|
|
22239
|
+
out[i] = b.open;
|
|
22240
|
+
break;
|
|
22241
|
+
case "High":
|
|
22242
|
+
out[i] = b.high;
|
|
22243
|
+
break;
|
|
22244
|
+
case "Low":
|
|
22245
|
+
out[i] = b.low;
|
|
22246
|
+
break;
|
|
22247
|
+
case "HL2":
|
|
22248
|
+
out[i] = (b.high + b.low) / 2;
|
|
22249
|
+
break;
|
|
22250
|
+
case "HLC3":
|
|
22251
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
22252
|
+
break;
|
|
22253
|
+
case "OHLC4":
|
|
22254
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
22255
|
+
break;
|
|
22256
|
+
case "HLCC4":
|
|
22257
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
22258
|
+
break;
|
|
22259
|
+
default:
|
|
22260
|
+
out[i] = b.close;
|
|
22261
|
+
}
|
|
22262
|
+
}
|
|
22263
|
+
return out;
|
|
22264
|
+
}
|
|
22265
|
+
function volumes(bars) {
|
|
22266
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
22267
|
+
}
|
|
22268
|
+
function highs(bars) {
|
|
22269
|
+
return bars.map((b) => b.high);
|
|
22270
|
+
}
|
|
22271
|
+
function lows(bars) {
|
|
22272
|
+
return bars.map((b) => b.low);
|
|
22273
|
+
}
|
|
22274
|
+
function closes(bars) {
|
|
22275
|
+
return bars.map((b) => b.close);
|
|
22276
|
+
}
|
|
22277
|
+
function opens(bars) {
|
|
22278
|
+
return bars.map((b) => b.open);
|
|
22279
|
+
}
|
|
22280
|
+
function zip(a, b, f) {
|
|
22281
|
+
const out = new Array(a.length);
|
|
22282
|
+
for (let i = 0; i < a.length; i++) {
|
|
22283
|
+
const x = a[i];
|
|
22284
|
+
const y = b[i];
|
|
22285
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
22286
|
+
}
|
|
22287
|
+
return out;
|
|
22288
|
+
}
|
|
22289
|
+
function map(a, f) {
|
|
22290
|
+
const out = new Array(a.length);
|
|
22291
|
+
for (let i = 0; i < a.length; i++) {
|
|
22292
|
+
const x = a[i];
|
|
22293
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
22294
|
+
}
|
|
22295
|
+
return out;
|
|
22296
|
+
}
|
|
22297
|
+
function sub(a, b) {
|
|
22298
|
+
return zip(a, b, (x, y) => x - y);
|
|
22299
|
+
}
|
|
22300
|
+
function windowed(v, len, fold) {
|
|
22301
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22302
|
+
if (len <= 0) return out;
|
|
22303
|
+
let run = 0;
|
|
22304
|
+
for (let i = 0; i < v.length; i++) {
|
|
22305
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
22306
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
22307
|
+
}
|
|
22308
|
+
return out;
|
|
22309
|
+
}
|
|
22310
|
+
function sum(v, len) {
|
|
22311
|
+
return windowed(v, len, (w, from, to) => {
|
|
22312
|
+
let s = 0;
|
|
22313
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22314
|
+
return s;
|
|
22315
|
+
});
|
|
22316
|
+
}
|
|
22317
|
+
function sma(v, len) {
|
|
22318
|
+
return windowed(v, len, (w, from, to) => {
|
|
22319
|
+
let s = 0;
|
|
22320
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22321
|
+
return s / len;
|
|
22322
|
+
});
|
|
22323
|
+
}
|
|
22324
|
+
function wma(v, len) {
|
|
22325
|
+
const norm = len * (len + 1) / 2;
|
|
22326
|
+
return windowed(v, len, (w, from, to) => {
|
|
22327
|
+
let s = 0;
|
|
22328
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
22329
|
+
return s / norm;
|
|
22330
|
+
});
|
|
22331
|
+
}
|
|
22332
|
+
function vwma(src, vol, len) {
|
|
22333
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
22334
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
22335
|
+
}
|
|
22336
|
+
function stdev(v, len) {
|
|
22337
|
+
return windowed(v, len, (w, from, to) => {
|
|
22338
|
+
let s = 0;
|
|
22339
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22340
|
+
const mean = s / len;
|
|
22341
|
+
let sq = 0;
|
|
22342
|
+
for (let k = from; k <= to; k++) {
|
|
22343
|
+
const d = w[k] - mean;
|
|
22344
|
+
sq += d * d;
|
|
22345
|
+
}
|
|
22346
|
+
return Math.sqrt(sq / len);
|
|
22347
|
+
});
|
|
22348
|
+
}
|
|
22349
|
+
function highest(v, len) {
|
|
22350
|
+
return windowed(v, len, (w, from, to) => {
|
|
22351
|
+
let m = -Infinity;
|
|
22352
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
22353
|
+
return m;
|
|
22354
|
+
});
|
|
22355
|
+
}
|
|
22356
|
+
function lowest(v, len) {
|
|
22357
|
+
return windowed(v, len, (w, from, to) => {
|
|
22358
|
+
let m = Infinity;
|
|
22359
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
22360
|
+
return m;
|
|
22361
|
+
});
|
|
22362
|
+
}
|
|
22363
|
+
function barsSinceHighest(v, len) {
|
|
22364
|
+
return windowed(v, len, (w, from, to) => {
|
|
22365
|
+
let m = -Infinity;
|
|
22366
|
+
let at = to;
|
|
22367
|
+
for (let k = from; k <= to; k++) {
|
|
22368
|
+
if (w[k] >= m) {
|
|
22369
|
+
m = w[k];
|
|
22370
|
+
at = k;
|
|
22371
|
+
}
|
|
22372
|
+
}
|
|
22373
|
+
return to - at;
|
|
22374
|
+
});
|
|
22375
|
+
}
|
|
22376
|
+
function barsSinceLowest(v, len) {
|
|
22377
|
+
return windowed(v, len, (w, from, to) => {
|
|
22378
|
+
let m = Infinity;
|
|
22379
|
+
let at = to;
|
|
22380
|
+
for (let k = from; k <= to; k++) {
|
|
22381
|
+
if (w[k] <= m) {
|
|
22382
|
+
m = w[k];
|
|
22383
|
+
at = k;
|
|
22384
|
+
}
|
|
22385
|
+
}
|
|
22386
|
+
return to - at;
|
|
22387
|
+
});
|
|
22388
|
+
}
|
|
22389
|
+
function recursive(v, len, alpha) {
|
|
22390
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22391
|
+
let run = 0;
|
|
22392
|
+
let prev2 = Number.NaN;
|
|
22393
|
+
for (let i = 0; i < v.length; i++) {
|
|
22394
|
+
const x = v[i];
|
|
22395
|
+
if (!Number.isFinite(x)) {
|
|
22396
|
+
run = 0;
|
|
22397
|
+
prev2 = Number.NaN;
|
|
22398
|
+
continue;
|
|
22399
|
+
}
|
|
22400
|
+
run += 1;
|
|
22401
|
+
if (Number.isFinite(prev2)) {
|
|
22402
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
22403
|
+
out[i] = prev2;
|
|
22404
|
+
} else if (run >= len) {
|
|
22405
|
+
let s = 0;
|
|
22406
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
22407
|
+
prev2 = s / len;
|
|
22408
|
+
out[i] = prev2;
|
|
22409
|
+
}
|
|
22410
|
+
}
|
|
22411
|
+
return out;
|
|
22412
|
+
}
|
|
22413
|
+
function ema(v, len) {
|
|
22414
|
+
return recursive(v, len, 2 / (len + 1));
|
|
22415
|
+
}
|
|
22416
|
+
function rma(v, len) {
|
|
22417
|
+
return recursive(v, len, 1 / len);
|
|
22418
|
+
}
|
|
22419
|
+
function change(v, n = 1) {
|
|
22420
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22421
|
+
for (let i = n; i < v.length; i++) {
|
|
22422
|
+
const a = v[i];
|
|
22423
|
+
const b = v[i - n];
|
|
22424
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
22425
|
+
}
|
|
22426
|
+
return out;
|
|
22427
|
+
}
|
|
22428
|
+
function roc(v, len) {
|
|
22429
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22430
|
+
for (let i = len; i < v.length; i++) {
|
|
22431
|
+
const a = v[i];
|
|
22432
|
+
const b = v[i - len];
|
|
22433
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
22434
|
+
}
|
|
22435
|
+
return out;
|
|
22436
|
+
}
|
|
22437
|
+
function shift(v, n) {
|
|
22438
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22439
|
+
for (let i = 0; i < v.length; i++) {
|
|
22440
|
+
const j = i - n;
|
|
22441
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
22442
|
+
}
|
|
22443
|
+
return out;
|
|
22444
|
+
}
|
|
22445
|
+
function trueRange(bars) {
|
|
22446
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
22447
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22448
|
+
const b = bars[i];
|
|
22449
|
+
if (i === 0) {
|
|
22450
|
+
out[i] = b.high - b.low;
|
|
22451
|
+
continue;
|
|
22452
|
+
}
|
|
22453
|
+
const pc = bars[i - 1].close;
|
|
22454
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
22455
|
+
}
|
|
22456
|
+
return out;
|
|
22457
|
+
}
|
|
22458
|
+
function atr(bars, len) {
|
|
22459
|
+
return rma(trueRange(bars), len);
|
|
22460
|
+
}
|
|
22461
|
+
function rsi(v, len) {
|
|
22462
|
+
const d = change(v);
|
|
22463
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
22464
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
22465
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
22466
|
+
}
|
|
22467
|
+
function stoch(v, hi, lo, len) {
|
|
22468
|
+
const hh = highest(hi, len);
|
|
22469
|
+
const ll = lowest(lo, len);
|
|
22470
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22471
|
+
for (let i = 0; i < v.length; i++) {
|
|
22472
|
+
const x = v[i];
|
|
22473
|
+
const h = hh[i];
|
|
22474
|
+
const l = ll[i];
|
|
22475
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
22476
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
22477
|
+
}
|
|
22478
|
+
return out;
|
|
22479
|
+
}
|
|
22480
|
+
function linreg(v, len) {
|
|
22481
|
+
const sx = (len - 1) * len / 2;
|
|
22482
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
22483
|
+
const denom = len * sxx - sx * sx;
|
|
22484
|
+
return windowed(v, len, (w, from, to) => {
|
|
22485
|
+
let sy = 0;
|
|
22486
|
+
let sxy = 0;
|
|
22487
|
+
for (let k = from; k <= to; k++) {
|
|
22488
|
+
const x = k - from;
|
|
22489
|
+
sy += w[k];
|
|
22490
|
+
sxy += x * w[k];
|
|
22491
|
+
}
|
|
22492
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
22493
|
+
const intercept = (sy - slope * sx) / len;
|
|
22494
|
+
return intercept + slope * (len - 1);
|
|
22495
|
+
});
|
|
22496
|
+
}
|
|
22497
|
+
function swma(v) {
|
|
22498
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22499
|
+
for (let i = 3; i < v.length; i++) {
|
|
22500
|
+
const a = v[i - 3];
|
|
22501
|
+
const b = v[i - 2];
|
|
22502
|
+
const c = v[i - 1];
|
|
22503
|
+
const d = v[i];
|
|
22504
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
22505
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
22506
|
+
}
|
|
22507
|
+
}
|
|
22508
|
+
return out;
|
|
22509
|
+
}
|
|
22510
|
+
function percentRank(v, len) {
|
|
22511
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22512
|
+
for (let i = len; i < v.length; i++) {
|
|
22513
|
+
const x = v[i];
|
|
22514
|
+
if (!Number.isFinite(x)) continue;
|
|
22515
|
+
let below = 0;
|
|
22516
|
+
let count = 0;
|
|
22517
|
+
for (let k = i - len; k < i; k++) {
|
|
22518
|
+
const y = v[k];
|
|
22519
|
+
if (!Number.isFinite(y)) {
|
|
22520
|
+
count = -1;
|
|
22521
|
+
break;
|
|
22522
|
+
}
|
|
22523
|
+
count += 1;
|
|
22524
|
+
if (y <= x) below += 1;
|
|
22525
|
+
}
|
|
22526
|
+
if (count === len) out[i] = 100 * below / len;
|
|
22527
|
+
}
|
|
22528
|
+
return out;
|
|
22529
|
+
}
|
|
22530
|
+
function cumSum(v) {
|
|
22531
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22532
|
+
let acc = 0;
|
|
22533
|
+
let seen = false;
|
|
22534
|
+
for (let i = 0; i < v.length; i++) {
|
|
22535
|
+
const x = v[i];
|
|
22536
|
+
if (Number.isFinite(x)) {
|
|
22537
|
+
acc += x;
|
|
22538
|
+
seen = true;
|
|
22539
|
+
}
|
|
22540
|
+
if (seen) out[i] = acc;
|
|
22541
|
+
}
|
|
22542
|
+
return out;
|
|
22543
|
+
}
|
|
22544
|
+
|
|
22545
|
+
// src/core/native-indicators/classics/shared.ts
|
|
22546
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
22547
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
22548
|
+
}
|
|
22549
|
+
function sourceInput(defval = "Close") {
|
|
22550
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
22551
|
+
}
|
|
22552
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
22553
|
+
return { key, title, type: "color", defval };
|
|
22554
|
+
}
|
|
22555
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
22556
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
22557
|
+
if (!m) return hex;
|
|
22558
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
22559
|
+
return `#${m[1]}${a}`;
|
|
22560
|
+
}
|
|
22561
|
+
|
|
22562
|
+
// src/core/native-indicators/classics/averages.ts
|
|
22563
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
22564
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
22565
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
22566
|
+
return {
|
|
22567
|
+
type,
|
|
22568
|
+
title,
|
|
22569
|
+
shortTitle,
|
|
22570
|
+
overlay: true,
|
|
22571
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
22572
|
+
compute: (bars, inputs) => {
|
|
22573
|
+
const len = num3(inputs, "length", 20);
|
|
22574
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22575
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22576
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22577
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22578
|
+
}
|
|
22579
|
+
};
|
|
22580
|
+
}
|
|
22581
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
22582
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
22583
|
+
var movingAverage = {
|
|
22584
|
+
type: "moving-average",
|
|
22585
|
+
title: "Moving Average",
|
|
22586
|
+
shortTitle: "MA",
|
|
22587
|
+
overlay: true,
|
|
22588
|
+
inputs: [
|
|
22589
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
22590
|
+
lengthInput(20),
|
|
22591
|
+
sourceInput(),
|
|
22592
|
+
offsetInput,
|
|
22593
|
+
colorInput()
|
|
22594
|
+
],
|
|
22595
|
+
compute: (bars, inputs) => {
|
|
22596
|
+
const len = num3(inputs, "length", 20);
|
|
22597
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22598
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
22599
|
+
let values;
|
|
22600
|
+
switch (kind) {
|
|
22601
|
+
case "EMA":
|
|
22602
|
+
values = ema(src, len);
|
|
22603
|
+
break;
|
|
22604
|
+
case "WMA":
|
|
22605
|
+
values = wma(src, len);
|
|
22606
|
+
break;
|
|
22607
|
+
case "RMA":
|
|
22608
|
+
values = rma(src, len);
|
|
22609
|
+
break;
|
|
22610
|
+
case "VWMA":
|
|
22611
|
+
values = vwma(src, volumes(bars), len);
|
|
22612
|
+
break;
|
|
22613
|
+
default:
|
|
22614
|
+
values = sma(src, len);
|
|
22615
|
+
}
|
|
22616
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22617
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22618
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22619
|
+
}
|
|
22620
|
+
};
|
|
22621
|
+
var smoothedMa = {
|
|
22622
|
+
type: "rma",
|
|
22623
|
+
title: "Smoothed Moving Average",
|
|
22624
|
+
shortTitle: "RMA",
|
|
22625
|
+
overlay: true,
|
|
22626
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22627
|
+
compute: (bars, inputs) => ({
|
|
22628
|
+
plots: [
|
|
22629
|
+
{
|
|
22630
|
+
key: "rma",
|
|
22631
|
+
title: "RMA",
|
|
22632
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22633
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22634
|
+
width: 2
|
|
22635
|
+
}
|
|
22636
|
+
]
|
|
22637
|
+
})
|
|
22638
|
+
};
|
|
22639
|
+
var zlema = {
|
|
22640
|
+
type: "zlema",
|
|
22641
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
22642
|
+
shortTitle: "ZLEMA",
|
|
22643
|
+
overlay: true,
|
|
22644
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22645
|
+
compute: (bars, inputs) => {
|
|
22646
|
+
const len = num3(inputs, "length", 14);
|
|
22647
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22648
|
+
const lag = Math.floor((len - 1) / 2);
|
|
22649
|
+
const lagged = shift(src, lag);
|
|
22650
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
22651
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22652
|
+
}
|
|
22653
|
+
};
|
|
22654
|
+
var vidya = {
|
|
22655
|
+
type: "vidya",
|
|
22656
|
+
title: "Variable Index Dynamic Average",
|
|
22657
|
+
shortTitle: "VIDYA",
|
|
22658
|
+
overlay: true,
|
|
22659
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
22660
|
+
compute: (bars, inputs) => {
|
|
22661
|
+
const len = num3(inputs, "length", 14);
|
|
22662
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
22663
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22664
|
+
const d = change(src);
|
|
22665
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
22666
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
22667
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
22668
|
+
let u = 0;
|
|
22669
|
+
let w = 0;
|
|
22670
|
+
let ok = true;
|
|
22671
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
22672
|
+
const x = d[k];
|
|
22673
|
+
if (!Number.isFinite(x)) {
|
|
22674
|
+
ok = false;
|
|
22675
|
+
break;
|
|
22676
|
+
}
|
|
22677
|
+
if (x > 0) u += x;
|
|
22678
|
+
else w -= x;
|
|
22679
|
+
}
|
|
22680
|
+
if (ok) {
|
|
22681
|
+
su[i] = u;
|
|
22682
|
+
sd[i] = w;
|
|
22683
|
+
}
|
|
22034
22684
|
}
|
|
22685
|
+
const alpha = 2 / (len + 1);
|
|
22686
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
22687
|
+
let prev2 = Number.NaN;
|
|
22688
|
+
for (let i = 0; i < src.length; i++) {
|
|
22689
|
+
const x = src[i];
|
|
22690
|
+
const u = su[i];
|
|
22691
|
+
const w = sd[i];
|
|
22692
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
22693
|
+
const total = u + w;
|
|
22694
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
22695
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
22696
|
+
out[i] = prev2;
|
|
22697
|
+
}
|
|
22698
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22699
|
+
}
|
|
22700
|
+
};
|
|
22701
|
+
var maEnvelope = {
|
|
22702
|
+
type: "ma-envelope",
|
|
22703
|
+
title: "Moving Average Envelope",
|
|
22704
|
+
shortTitle: "MA Env",
|
|
22705
|
+
overlay: true,
|
|
22706
|
+
inputs: [
|
|
22707
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
22708
|
+
lengthInput(20),
|
|
22709
|
+
sourceInput(),
|
|
22710
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
22711
|
+
colorInput()
|
|
22712
|
+
],
|
|
22713
|
+
compute: (bars, inputs) => {
|
|
22714
|
+
const len = num3(inputs, "length", 20);
|
|
22715
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22716
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
22717
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
22718
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22719
|
+
return {
|
|
22720
|
+
plots: [
|
|
22721
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
22722
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
22723
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
22724
|
+
],
|
|
22725
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22726
|
+
};
|
|
22727
|
+
}
|
|
22728
|
+
};
|
|
22729
|
+
var linearRegression = {
|
|
22730
|
+
type: "linear-regression",
|
|
22731
|
+
title: "Linear Regression Curve",
|
|
22732
|
+
shortTitle: "LinReg",
|
|
22733
|
+
overlay: true,
|
|
22734
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22735
|
+
compute: (bars, inputs) => ({
|
|
22736
|
+
plots: [
|
|
22737
|
+
{
|
|
22738
|
+
key: "linreg",
|
|
22739
|
+
title: "LinReg",
|
|
22740
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22741
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22742
|
+
width: 2
|
|
22743
|
+
}
|
|
22744
|
+
]
|
|
22745
|
+
})
|
|
22746
|
+
};
|
|
22747
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
22748
|
+
|
|
22749
|
+
// src/core/native-indicators/classics/bands.ts
|
|
22750
|
+
function bollinger(bars, inputs) {
|
|
22751
|
+
const len = num3(inputs, "length", 20);
|
|
22752
|
+
const mult = num3(inputs, "mult", 2);
|
|
22753
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22754
|
+
const basis = sma(src, len);
|
|
22755
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
22756
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
22757
|
+
}
|
|
22758
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
22759
|
+
var bollingerBands = {
|
|
22760
|
+
type: "bollinger-bands",
|
|
22761
|
+
title: "Bollinger Bands",
|
|
22762
|
+
shortTitle: "BB",
|
|
22763
|
+
overlay: true,
|
|
22764
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22765
|
+
compute: (bars, inputs) => {
|
|
22766
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22767
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22768
|
+
return {
|
|
22769
|
+
plots: [
|
|
22770
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22771
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22772
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22773
|
+
],
|
|
22774
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22775
|
+
};
|
|
22776
|
+
}
|
|
22777
|
+
};
|
|
22778
|
+
var percentB = {
|
|
22779
|
+
type: "percent-b",
|
|
22780
|
+
title: "Bollinger Bands %B",
|
|
22781
|
+
shortTitle: "%B",
|
|
22782
|
+
overlay: false,
|
|
22783
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22784
|
+
compute: (bars, inputs) => {
|
|
22785
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
22786
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22787
|
+
const values = src.map((x, i) => {
|
|
22788
|
+
const u = upper[i];
|
|
22789
|
+
const l = lower[i];
|
|
22790
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
22791
|
+
});
|
|
22792
|
+
return {
|
|
22793
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
22794
|
+
levels: [
|
|
22795
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
22796
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
22797
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
22798
|
+
]
|
|
22799
|
+
};
|
|
22800
|
+
}
|
|
22801
|
+
};
|
|
22802
|
+
var bandwidth = {
|
|
22803
|
+
type: "bandwidth",
|
|
22804
|
+
title: "Bollinger Bands Width",
|
|
22805
|
+
shortTitle: "BandWidth",
|
|
22806
|
+
overlay: false,
|
|
22807
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22808
|
+
compute: (bars, inputs) => {
|
|
22809
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22810
|
+
const values = basis.map((b, i) => {
|
|
22811
|
+
const u = upper[i];
|
|
22812
|
+
const l = lower[i];
|
|
22813
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
22814
|
+
});
|
|
22815
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22816
|
+
}
|
|
22817
|
+
};
|
|
22818
|
+
var keltner = {
|
|
22819
|
+
type: "keltner-channels",
|
|
22820
|
+
title: "Keltner Channels",
|
|
22821
|
+
shortTitle: "KC",
|
|
22822
|
+
overlay: true,
|
|
22823
|
+
inputs: [
|
|
22824
|
+
lengthInput(20),
|
|
22825
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
22826
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22827
|
+
sourceInput(),
|
|
22828
|
+
colorInput()
|
|
22829
|
+
],
|
|
22830
|
+
compute: (bars, inputs) => {
|
|
22831
|
+
const len = num3(inputs, "length", 20);
|
|
22832
|
+
const mult = num3(inputs, "mult", 2);
|
|
22833
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22834
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
22835
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22836
|
+
return {
|
|
22837
|
+
plots: [
|
|
22838
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22839
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
22840
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
22841
|
+
],
|
|
22842
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22843
|
+
};
|
|
22844
|
+
}
|
|
22845
|
+
};
|
|
22846
|
+
var donchian = {
|
|
22847
|
+
type: "donchian-channels",
|
|
22848
|
+
title: "Donchian Channels",
|
|
22849
|
+
shortTitle: "DC",
|
|
22850
|
+
overlay: true,
|
|
22851
|
+
inputs: [lengthInput(20), colorInput()],
|
|
22852
|
+
compute: (bars, inputs) => {
|
|
22853
|
+
const len = num3(inputs, "length", 20);
|
|
22854
|
+
const upper = highest(highs(bars), len);
|
|
22855
|
+
const lower = lowest(lows(bars), len);
|
|
22856
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22857
|
+
return {
|
|
22858
|
+
plots: [
|
|
22859
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
22860
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22861
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22862
|
+
],
|
|
22863
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22864
|
+
};
|
|
22865
|
+
}
|
|
22866
|
+
};
|
|
22867
|
+
var chandelier = {
|
|
22868
|
+
type: "chandelier-exit",
|
|
22869
|
+
title: "Chandelier Exit",
|
|
22870
|
+
shortTitle: "CE",
|
|
22871
|
+
overlay: true,
|
|
22872
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
22873
|
+
compute: (bars, inputs) => {
|
|
22874
|
+
const len = num3(inputs, "length", 22);
|
|
22875
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
22876
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
22877
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
22878
|
+
return {
|
|
22879
|
+
plots: [
|
|
22880
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
22881
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
22882
|
+
]
|
|
22883
|
+
};
|
|
22884
|
+
}
|
|
22885
|
+
};
|
|
22886
|
+
var chandeKroll = {
|
|
22887
|
+
type: "chande-kroll-stop",
|
|
22888
|
+
title: "Chande Kroll Stop",
|
|
22889
|
+
shortTitle: "CKS",
|
|
22890
|
+
overlay: true,
|
|
22891
|
+
inputs: [
|
|
22892
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22893
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
22894
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
22895
|
+
],
|
|
22896
|
+
compute: (bars, inputs) => {
|
|
22897
|
+
const p = num3(inputs, "p", 10);
|
|
22898
|
+
const x = num3(inputs, "x", 1);
|
|
22899
|
+
const q = num3(inputs, "q", 9);
|
|
22900
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
22901
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
22902
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
22903
|
+
return {
|
|
22904
|
+
plots: [
|
|
22905
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
22906
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
22907
|
+
]
|
|
22908
|
+
};
|
|
22909
|
+
}
|
|
22910
|
+
};
|
|
22911
|
+
var supertrend = {
|
|
22912
|
+
type: "supertrend",
|
|
22913
|
+
title: "SuperTrend",
|
|
22914
|
+
shortTitle: "SuperTrend",
|
|
22915
|
+
overlay: true,
|
|
22916
|
+
inputs: [
|
|
22917
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22918
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
22919
|
+
],
|
|
22920
|
+
compute: (bars, inputs) => {
|
|
22921
|
+
const len = num3(inputs, "atrLength", 10);
|
|
22922
|
+
const mult = num3(inputs, "mult", 3);
|
|
22923
|
+
const range = atr(bars, len);
|
|
22924
|
+
const n = bars.length;
|
|
22925
|
+
const st = new Array(n).fill(Number.NaN);
|
|
22926
|
+
const dir = new Array(n).fill(1);
|
|
22927
|
+
let up = Number.NaN;
|
|
22928
|
+
let dn = Number.NaN;
|
|
22929
|
+
let trend = 1;
|
|
22930
|
+
for (let i = 0; i < n; i++) {
|
|
22931
|
+
const r = range[i];
|
|
22932
|
+
if (!Number.isFinite(r)) continue;
|
|
22933
|
+
const b = bars[i];
|
|
22934
|
+
const mid = (b.high + b.low) / 2;
|
|
22935
|
+
const basicUp = mid - mult * r;
|
|
22936
|
+
const basicDn = mid + mult * r;
|
|
22937
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
22938
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
22939
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
22940
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
22941
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
22942
|
+
dir[i] = trend;
|
|
22943
|
+
st[i] = trend === 1 ? up : dn;
|
|
22944
|
+
}
|
|
22945
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
22946
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
22947
|
+
}
|
|
22948
|
+
};
|
|
22949
|
+
var alligator = {
|
|
22950
|
+
type: "williams-alligator",
|
|
22951
|
+
title: "Williams Alligator",
|
|
22952
|
+
shortTitle: "Alligator",
|
|
22953
|
+
overlay: true,
|
|
22954
|
+
inputs: [
|
|
22955
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
22956
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
22957
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
22958
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
22959
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
22960
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
22961
|
+
],
|
|
22962
|
+
compute: (bars, inputs) => {
|
|
22963
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22964
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
22965
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
22966
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
22967
|
+
return {
|
|
22968
|
+
plots: [
|
|
22969
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
22970
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
22971
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
22972
|
+
]
|
|
22973
|
+
};
|
|
22974
|
+
}
|
|
22975
|
+
};
|
|
22976
|
+
var gator = {
|
|
22977
|
+
type: "gator-oscillator",
|
|
22978
|
+
title: "Gator Oscillator",
|
|
22979
|
+
shortTitle: "Gator Oscillator",
|
|
22980
|
+
overlay: false,
|
|
22981
|
+
inputs: [],
|
|
22982
|
+
compute: (bars) => {
|
|
22983
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22984
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
22985
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
22986
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
22987
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
22988
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
22989
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
22990
|
+
if (!Number.isFinite(x)) return null;
|
|
22991
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
22992
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
22993
|
+
});
|
|
22994
|
+
return {
|
|
22995
|
+
plots: [
|
|
22996
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
22997
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
22998
|
+
]
|
|
22999
|
+
};
|
|
23000
|
+
}
|
|
23001
|
+
};
|
|
23002
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
23003
|
+
|
|
23004
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
23005
|
+
function guideLevels(low, high, mid) {
|
|
23006
|
+
const levels = [
|
|
23007
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
23008
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
23009
|
+
];
|
|
23010
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
23011
|
+
return levels;
|
|
23012
|
+
}
|
|
23013
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
23014
|
+
function histColors(values) {
|
|
23015
|
+
return values.map((x, i) => {
|
|
23016
|
+
if (!Number.isFinite(x)) return null;
|
|
23017
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
23018
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
23019
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
23020
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
23021
|
+
});
|
|
23022
|
+
}
|
|
23023
|
+
var rsiSpec = {
|
|
23024
|
+
type: "rsi",
|
|
23025
|
+
title: "Relative Strength Index",
|
|
23026
|
+
shortTitle: "RSI",
|
|
23027
|
+
overlay: false,
|
|
23028
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
23029
|
+
compute: (bars, inputs) => ({
|
|
23030
|
+
plots: [
|
|
23031
|
+
{
|
|
23032
|
+
key: "rsi",
|
|
23033
|
+
title: "RSI",
|
|
23034
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
23035
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23036
|
+
width: 2
|
|
23037
|
+
}
|
|
23038
|
+
],
|
|
23039
|
+
levels: guideLevels(30, 70, 50)
|
|
23040
|
+
})
|
|
23041
|
+
};
|
|
23042
|
+
var stochastic = {
|
|
23043
|
+
type: "stochastic",
|
|
23044
|
+
title: "Stochastic",
|
|
23045
|
+
shortTitle: "Stoch",
|
|
23046
|
+
overlay: false,
|
|
23047
|
+
inputs: [
|
|
23048
|
+
lengthInput(14, "kLength", "%K length"),
|
|
23049
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
23050
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
23051
|
+
],
|
|
23052
|
+
compute: (bars, inputs) => {
|
|
23053
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
23054
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
23055
|
+
return {
|
|
23056
|
+
plots: [
|
|
23057
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
23058
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
23059
|
+
],
|
|
23060
|
+
levels: guideLevels(20, 80)
|
|
23061
|
+
};
|
|
23062
|
+
}
|
|
23063
|
+
};
|
|
23064
|
+
var stochasticRsi = {
|
|
23065
|
+
type: "stochastic-rsi",
|
|
23066
|
+
title: "Stochastic RSI",
|
|
23067
|
+
shortTitle: "Stoch RSI",
|
|
23068
|
+
overlay: false,
|
|
23069
|
+
inputs: [
|
|
23070
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
23071
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
23072
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
23073
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
23074
|
+
sourceInput()
|
|
23075
|
+
],
|
|
23076
|
+
compute: (bars, inputs) => {
|
|
23077
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
23078
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
23079
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
23080
|
+
return {
|
|
23081
|
+
plots: [
|
|
23082
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
23083
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
23084
|
+
],
|
|
23085
|
+
levels: guideLevels(20, 80)
|
|
23086
|
+
};
|
|
23087
|
+
}
|
|
23088
|
+
};
|
|
23089
|
+
var macd = {
|
|
23090
|
+
type: "macd",
|
|
23091
|
+
title: "MACD",
|
|
23092
|
+
overlay: false,
|
|
23093
|
+
inputs: [
|
|
23094
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23095
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23096
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23097
|
+
sourceInput()
|
|
23098
|
+
],
|
|
23099
|
+
compute: (bars, inputs) => {
|
|
23100
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23101
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
23102
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23103
|
+
const hist = sub(line, signal);
|
|
23104
|
+
return {
|
|
23105
|
+
plots: [
|
|
23106
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23107
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
23108
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23109
|
+
],
|
|
23110
|
+
levels: ZERO_LEVEL
|
|
23111
|
+
};
|
|
23112
|
+
}
|
|
23113
|
+
};
|
|
23114
|
+
var ppo = {
|
|
23115
|
+
type: "ppo",
|
|
23116
|
+
title: "Percentage Price Oscillator",
|
|
23117
|
+
shortTitle: "PPO",
|
|
23118
|
+
overlay: false,
|
|
23119
|
+
inputs: [
|
|
23120
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23121
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23122
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23123
|
+
sourceInput()
|
|
23124
|
+
],
|
|
23125
|
+
compute: (bars, inputs) => {
|
|
23126
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23127
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
23128
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
23129
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
23130
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23131
|
+
const hist = sub(line, signal);
|
|
23132
|
+
return {
|
|
23133
|
+
plots: [
|
|
23134
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23135
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
23136
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23137
|
+
],
|
|
23138
|
+
levels: ZERO_LEVEL
|
|
23139
|
+
};
|
|
23140
|
+
}
|
|
23141
|
+
};
|
|
23142
|
+
var awesome = {
|
|
23143
|
+
type: "awesome-oscillator",
|
|
23144
|
+
title: "Awesome Oscillator",
|
|
23145
|
+
shortTitle: "AO",
|
|
23146
|
+
overlay: false,
|
|
23147
|
+
inputs: [],
|
|
23148
|
+
compute: (bars) => {
|
|
23149
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23150
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
23151
|
+
const colors = ao.map((x, i) => {
|
|
23152
|
+
if (!Number.isFinite(x)) return null;
|
|
23153
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
23154
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
23155
|
+
});
|
|
23156
|
+
return {
|
|
23157
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
23158
|
+
levels: ZERO_LEVEL
|
|
23159
|
+
};
|
|
23160
|
+
}
|
|
23161
|
+
};
|
|
23162
|
+
var cci = {
|
|
23163
|
+
type: "commodity-channel-index",
|
|
23164
|
+
title: "Commodity Channel Index",
|
|
23165
|
+
shortTitle: "CCI",
|
|
23166
|
+
overlay: false,
|
|
23167
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
23168
|
+
compute: (bars, inputs) => {
|
|
23169
|
+
const len = num3(inputs, "length", 20);
|
|
23170
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
23171
|
+
const basis = sma(src, len);
|
|
23172
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
23173
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
23174
|
+
const mean = basis[i];
|
|
23175
|
+
if (!Number.isFinite(mean)) continue;
|
|
23176
|
+
let d = 0;
|
|
23177
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
23178
|
+
dev[i] = d / len;
|
|
23179
|
+
}
|
|
23180
|
+
const values = src.map((x, i) => {
|
|
23181
|
+
const b = basis[i];
|
|
23182
|
+
const d = dev[i];
|
|
23183
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
23184
|
+
});
|
|
23185
|
+
return {
|
|
23186
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23187
|
+
levels: guideLevels(-100, 100, 0)
|
|
23188
|
+
};
|
|
23189
|
+
}
|
|
23190
|
+
};
|
|
23191
|
+
var williamsR = {
|
|
23192
|
+
type: "williams-percent-r",
|
|
23193
|
+
title: "Williams %R",
|
|
23194
|
+
shortTitle: "%R",
|
|
23195
|
+
overlay: false,
|
|
23196
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23197
|
+
compute: (bars, inputs) => {
|
|
23198
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
23199
|
+
return {
|
|
23200
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23201
|
+
levels: guideLevels(-80, -20)
|
|
23202
|
+
};
|
|
23203
|
+
}
|
|
23204
|
+
};
|
|
23205
|
+
var cmo = {
|
|
23206
|
+
type: "chande-momentum-oscillator",
|
|
23207
|
+
title: "Chande Momentum Oscillator",
|
|
23208
|
+
shortTitle: "CMO",
|
|
23209
|
+
overlay: false,
|
|
23210
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23211
|
+
compute: (bars, inputs) => {
|
|
23212
|
+
const len = num3(inputs, "length", 9);
|
|
23213
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
23214
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
23215
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
23216
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
23217
|
+
return {
|
|
23218
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23219
|
+
levels: guideLevels(-50, 50, 0)
|
|
23220
|
+
};
|
|
23221
|
+
}
|
|
23222
|
+
};
|
|
23223
|
+
var connorsRsi = {
|
|
23224
|
+
type: "connors-rsi",
|
|
23225
|
+
title: "Connors RSI",
|
|
23226
|
+
shortTitle: "CRSI",
|
|
23227
|
+
overlay: false,
|
|
23228
|
+
inputs: [
|
|
23229
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
23230
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
23231
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
23232
|
+
colorInput()
|
|
23233
|
+
],
|
|
23234
|
+
compute: (bars, inputs) => {
|
|
23235
|
+
const c = closes(bars);
|
|
23236
|
+
const streak = new Array(c.length).fill(0);
|
|
23237
|
+
for (let i = 1; i < c.length; i++) {
|
|
23238
|
+
const d = c[i] - c[i - 1];
|
|
23239
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
23240
|
+
}
|
|
23241
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
23242
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
23243
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
23244
|
+
const values = c.map((_, i) => {
|
|
23245
|
+
const x = a[i];
|
|
23246
|
+
const y = b[i];
|
|
23247
|
+
const z = r[i];
|
|
23248
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
23249
|
+
});
|
|
23250
|
+
return {
|
|
23251
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23252
|
+
levels: guideLevels(20, 80, 50)
|
|
23253
|
+
};
|
|
23254
|
+
}
|
|
23255
|
+
};
|
|
23256
|
+
var fisher = {
|
|
23257
|
+
type: "fisher-transform",
|
|
23258
|
+
title: "Fisher Transform",
|
|
23259
|
+
shortTitle: "Fisher",
|
|
23260
|
+
overlay: false,
|
|
23261
|
+
inputs: [lengthInput(9)],
|
|
23262
|
+
compute: (bars, inputs) => {
|
|
23263
|
+
const len = num3(inputs, "length", 9);
|
|
23264
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23265
|
+
const hi = highest(hl2, len);
|
|
23266
|
+
const lo = lowest(hl2, len);
|
|
23267
|
+
const n = bars.length;
|
|
23268
|
+
const out = new Array(n).fill(Number.NaN);
|
|
23269
|
+
let value = 0;
|
|
23270
|
+
let fish = 0;
|
|
23271
|
+
for (let i = 0; i < n; i++) {
|
|
23272
|
+
const h = hi[i];
|
|
23273
|
+
const l = lo[i];
|
|
23274
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
23275
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
23276
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
23277
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
23278
|
+
out[i] = fish;
|
|
23279
|
+
}
|
|
23280
|
+
return {
|
|
23281
|
+
plots: [
|
|
23282
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
23283
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
23284
|
+
],
|
|
23285
|
+
levels: ZERO_LEVEL
|
|
23286
|
+
};
|
|
23287
|
+
}
|
|
23288
|
+
};
|
|
23289
|
+
var trix = {
|
|
23290
|
+
type: "trix",
|
|
23291
|
+
title: "TRIX",
|
|
23292
|
+
overlay: false,
|
|
23293
|
+
inputs: [lengthInput(18), colorInput()],
|
|
23294
|
+
compute: (bars, inputs) => {
|
|
23295
|
+
const len = num3(inputs, "length", 18);
|
|
23296
|
+
const logClose = map(closes(bars), Math.log);
|
|
23297
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
23298
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
23299
|
+
return {
|
|
23300
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23301
|
+
levels: ZERO_LEVEL
|
|
23302
|
+
};
|
|
23303
|
+
}
|
|
23304
|
+
};
|
|
23305
|
+
function tsiLine(src, long, short) {
|
|
23306
|
+
const mom = change(src);
|
|
23307
|
+
const numer = ema(ema(mom, long), short);
|
|
23308
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
23309
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
23310
|
+
}
|
|
23311
|
+
var tsi = {
|
|
23312
|
+
type: "true-strength-index",
|
|
23313
|
+
title: "True Strength Index",
|
|
23314
|
+
shortTitle: "TSI",
|
|
23315
|
+
overlay: false,
|
|
23316
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23317
|
+
compute: (bars, inputs) => {
|
|
23318
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
23319
|
+
return {
|
|
23320
|
+
plots: [
|
|
23321
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
23322
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23323
|
+
],
|
|
23324
|
+
levels: ZERO_LEVEL
|
|
23325
|
+
};
|
|
23326
|
+
}
|
|
23327
|
+
};
|
|
23328
|
+
var smiErgodic = {
|
|
23329
|
+
type: "smi-ergodic",
|
|
23330
|
+
title: "SMI Ergodic Oscillator",
|
|
23331
|
+
shortTitle: "SMIE",
|
|
23332
|
+
overlay: false,
|
|
23333
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
23334
|
+
compute: (bars, inputs) => {
|
|
23335
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
23336
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
23337
|
+
const osc = sub(line, signal);
|
|
23338
|
+
return {
|
|
23339
|
+
plots: [
|
|
23340
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
23341
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
23342
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23343
|
+
],
|
|
23344
|
+
levels: ZERO_LEVEL
|
|
23345
|
+
};
|
|
23346
|
+
}
|
|
23347
|
+
};
|
|
23348
|
+
var stc = {
|
|
23349
|
+
type: "schaff-trend-cycle",
|
|
23350
|
+
title: "Schaff Trend Cycle",
|
|
23351
|
+
shortTitle: "Schaff Trend Cycle",
|
|
23352
|
+
overlay: false,
|
|
23353
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
23354
|
+
compute: (bars, inputs) => {
|
|
23355
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
23356
|
+
const src = closes(bars);
|
|
23357
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
23358
|
+
const stochOf = (v) => {
|
|
23359
|
+
const lo = lowest(v, len);
|
|
23360
|
+
const hi = highest(v, len);
|
|
23361
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
23362
|
+
let prevRaw = Number.NaN;
|
|
23363
|
+
let smoothed = Number.NaN;
|
|
23364
|
+
for (let i = 0; i < v.length; i++) {
|
|
23365
|
+
const x = v[i];
|
|
23366
|
+
const l = lo[i];
|
|
23367
|
+
const h = hi[i];
|
|
23368
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
23369
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
23370
|
+
prevRaw = raw;
|
|
23371
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
23372
|
+
out[i] = smoothed;
|
|
23373
|
+
}
|
|
23374
|
+
return out;
|
|
23375
|
+
};
|
|
23376
|
+
const values = stochOf(stochOf(macdLine));
|
|
23377
|
+
return {
|
|
23378
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
23379
|
+
levels: guideLevels(25, 75)
|
|
23380
|
+
};
|
|
23381
|
+
}
|
|
23382
|
+
};
|
|
23383
|
+
var kst = {
|
|
23384
|
+
type: "know-sure-thing",
|
|
23385
|
+
title: "Know Sure Thing",
|
|
23386
|
+
shortTitle: "KST",
|
|
23387
|
+
overlay: false,
|
|
23388
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
23389
|
+
compute: (bars, inputs) => {
|
|
23390
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23391
|
+
const line = [
|
|
23392
|
+
sma(roc(src, 10), 10),
|
|
23393
|
+
sma(roc(src, 15), 10),
|
|
23394
|
+
sma(roc(src, 20), 10),
|
|
23395
|
+
sma(roc(src, 30), 15)
|
|
23396
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
23397
|
+
return {
|
|
23398
|
+
plots: [
|
|
23399
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
23400
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
23401
|
+
],
|
|
23402
|
+
levels: ZERO_LEVEL
|
|
23403
|
+
};
|
|
23404
|
+
}
|
|
23405
|
+
};
|
|
23406
|
+
var coppock = {
|
|
23407
|
+
type: "coppock-curve",
|
|
23408
|
+
title: "Coppock Curve",
|
|
23409
|
+
shortTitle: "Coppock",
|
|
23410
|
+
overlay: false,
|
|
23411
|
+
inputs: [
|
|
23412
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
23413
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
23414
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
23415
|
+
sourceInput(),
|
|
23416
|
+
colorInput()
|
|
23417
|
+
],
|
|
23418
|
+
compute: (bars, inputs) => {
|
|
23419
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23420
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
23421
|
+
return {
|
|
23422
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23423
|
+
levels: ZERO_LEVEL
|
|
23424
|
+
};
|
|
23425
|
+
}
|
|
23426
|
+
};
|
|
23427
|
+
var dpo = {
|
|
23428
|
+
type: "detrended-price-oscillator",
|
|
23429
|
+
title: "Detrended Price Oscillator",
|
|
23430
|
+
shortTitle: "DPO",
|
|
23431
|
+
overlay: false,
|
|
23432
|
+
inputs: [lengthInput(21), colorInput()],
|
|
23433
|
+
compute: (bars, inputs) => {
|
|
23434
|
+
const len = num3(inputs, "length", 21);
|
|
23435
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
23436
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
23437
|
+
return {
|
|
23438
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23439
|
+
levels: ZERO_LEVEL
|
|
23440
|
+
};
|
|
23441
|
+
}
|
|
23442
|
+
};
|
|
23443
|
+
var rocSpec = {
|
|
23444
|
+
type: "rate-of-change",
|
|
23445
|
+
title: "Rate of Change",
|
|
23446
|
+
shortTitle: "ROC",
|
|
23447
|
+
overlay: false,
|
|
23448
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23449
|
+
compute: (bars, inputs) => ({
|
|
23450
|
+
plots: [
|
|
23451
|
+
{
|
|
23452
|
+
key: "roc",
|
|
23453
|
+
title: "RoC",
|
|
23454
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
23455
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23456
|
+
width: 2
|
|
23457
|
+
}
|
|
23458
|
+
],
|
|
23459
|
+
levels: ZERO_LEVEL
|
|
23460
|
+
})
|
|
23461
|
+
};
|
|
23462
|
+
var ultimate = {
|
|
23463
|
+
type: "ultimate-oscillator",
|
|
23464
|
+
title: "Ultimate Oscillator",
|
|
23465
|
+
shortTitle: "UO",
|
|
23466
|
+
overlay: false,
|
|
23467
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
23468
|
+
compute: (bars, inputs) => {
|
|
23469
|
+
const n = bars.length;
|
|
23470
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
23471
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
23472
|
+
for (let i = 0; i < n; i++) {
|
|
23473
|
+
const b = bars[i];
|
|
23474
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
23475
|
+
const lo = Math.min(b.low, pc);
|
|
23476
|
+
const hi = Math.max(b.high, pc);
|
|
23477
|
+
bp[i] = b.close - lo;
|
|
23478
|
+
tr[i] = hi - lo;
|
|
23479
|
+
}
|
|
23480
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23481
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
23482
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
23483
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
23484
|
+
const values = a7.map((x, i) => {
|
|
23485
|
+
const y = a14[i];
|
|
23486
|
+
const z = a28[i];
|
|
23487
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
23488
|
+
});
|
|
23489
|
+
return {
|
|
23490
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23491
|
+
levels: guideLevels(30, 70, 50)
|
|
23492
|
+
};
|
|
23493
|
+
}
|
|
23494
|
+
};
|
|
23495
|
+
var rvgi = {
|
|
23496
|
+
type: "relative-vigor-index",
|
|
23497
|
+
title: "Relative Vigor Index",
|
|
23498
|
+
shortTitle: "RVGI",
|
|
23499
|
+
overlay: false,
|
|
23500
|
+
inputs: [lengthInput(10)],
|
|
23501
|
+
compute: (bars, inputs) => {
|
|
23502
|
+
const len = num3(inputs, "length", 10);
|
|
23503
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
23504
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
23505
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
23506
|
+
return {
|
|
23507
|
+
plots: [
|
|
23508
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
23509
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
23510
|
+
],
|
|
23511
|
+
levels: ZERO_LEVEL
|
|
23512
|
+
};
|
|
23513
|
+
}
|
|
23514
|
+
};
|
|
23515
|
+
var relVolatility = {
|
|
23516
|
+
type: "relative-volatility-index",
|
|
23517
|
+
title: "Relative Volatility Index",
|
|
23518
|
+
shortTitle: "RVI",
|
|
23519
|
+
overlay: false,
|
|
23520
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
23521
|
+
compute: (bars, inputs) => {
|
|
23522
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
23523
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23524
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
23525
|
+
const d = change(src);
|
|
23526
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
23527
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
23528
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
23529
|
+
return {
|
|
23530
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23531
|
+
levels: guideLevels(20, 80, 50)
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
};
|
|
23535
|
+
var bop = {
|
|
23536
|
+
type: "balance-of-power",
|
|
23537
|
+
title: "Balance of Power",
|
|
23538
|
+
shortTitle: "BOP",
|
|
23539
|
+
overlay: false,
|
|
23540
|
+
inputs: [colorInput()],
|
|
23541
|
+
compute: (bars, inputs) => {
|
|
23542
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
23543
|
+
return {
|
|
23544
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23545
|
+
levels: ZERO_LEVEL
|
|
23546
|
+
};
|
|
23547
|
+
}
|
|
23548
|
+
};
|
|
23549
|
+
var elderRay = {
|
|
23550
|
+
type: "elder-ray",
|
|
23551
|
+
title: "Elder Ray",
|
|
23552
|
+
overlay: false,
|
|
23553
|
+
inputs: [lengthInput(13)],
|
|
23554
|
+
compute: (bars, inputs) => {
|
|
23555
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
23556
|
+
return {
|
|
23557
|
+
plots: [
|
|
23558
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
23559
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
23560
|
+
],
|
|
23561
|
+
levels: ZERO_LEVEL
|
|
23562
|
+
};
|
|
23563
|
+
}
|
|
23564
|
+
};
|
|
23565
|
+
var ttmSqueeze = {
|
|
23566
|
+
type: "ttm-squeeze",
|
|
23567
|
+
title: "TTM Squeeze",
|
|
23568
|
+
overlay: false,
|
|
23569
|
+
inputs: [
|
|
23570
|
+
lengthInput(20),
|
|
23571
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
23572
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
23573
|
+
],
|
|
23574
|
+
compute: (bars, inputs) => {
|
|
23575
|
+
const len = num3(inputs, "length", 20);
|
|
23576
|
+
const c = closes(bars);
|
|
23577
|
+
const basis = sma(c, len);
|
|
23578
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
23579
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
23580
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
23581
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
23582
|
+
const mom = linreg(sub(c, anchor), len);
|
|
23583
|
+
const squeezed = c.map((_, i) => {
|
|
23584
|
+
const d = dev[i];
|
|
23585
|
+
const r = kcRange[i];
|
|
23586
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
23587
|
+
});
|
|
23588
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
23589
|
+
return {
|
|
23590
|
+
plots: [
|
|
23591
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
23592
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
23593
|
+
]
|
|
23594
|
+
};
|
|
23595
|
+
}
|
|
23596
|
+
};
|
|
23597
|
+
var oscillatorSpecs = [
|
|
23598
|
+
rsiSpec,
|
|
23599
|
+
stochastic,
|
|
23600
|
+
stochasticRsi,
|
|
23601
|
+
macd,
|
|
23602
|
+
ppo,
|
|
23603
|
+
awesome,
|
|
23604
|
+
cci,
|
|
23605
|
+
williamsR,
|
|
23606
|
+
cmo,
|
|
23607
|
+
connorsRsi,
|
|
23608
|
+
fisher,
|
|
23609
|
+
trix,
|
|
23610
|
+
tsi,
|
|
23611
|
+
smiErgodic,
|
|
23612
|
+
stc,
|
|
23613
|
+
kst,
|
|
23614
|
+
coppock,
|
|
23615
|
+
dpo,
|
|
23616
|
+
rocSpec,
|
|
23617
|
+
ultimate,
|
|
23618
|
+
rvgi,
|
|
23619
|
+
relVolatility,
|
|
23620
|
+
bop,
|
|
23621
|
+
elderRay,
|
|
23622
|
+
ttmSqueeze
|
|
23623
|
+
];
|
|
23624
|
+
|
|
23625
|
+
// src/core/native-indicators/classics/trend.ts
|
|
23626
|
+
var aroon = {
|
|
23627
|
+
type: "aroon",
|
|
23628
|
+
title: "Aroon",
|
|
23629
|
+
overlay: false,
|
|
23630
|
+
inputs: [lengthInput(14)],
|
|
23631
|
+
compute: (bars, inputs) => {
|
|
23632
|
+
const len = num3(inputs, "length", 14);
|
|
23633
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23634
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23635
|
+
return {
|
|
23636
|
+
plots: [
|
|
23637
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
23638
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
23639
|
+
],
|
|
23640
|
+
levels: [
|
|
23641
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
23642
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
23643
|
+
]
|
|
23644
|
+
};
|
|
23645
|
+
}
|
|
23646
|
+
};
|
|
23647
|
+
var adx = {
|
|
23648
|
+
type: "average-directional-index",
|
|
23649
|
+
title: "Average Directional Index",
|
|
23650
|
+
shortTitle: "ADX",
|
|
23651
|
+
overlay: false,
|
|
23652
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
23653
|
+
compute: (bars, inputs) => {
|
|
23654
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
23655
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
23656
|
+
const n = bars.length;
|
|
23657
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
23658
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
23659
|
+
for (let i = 1; i < n; i++) {
|
|
23660
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
23661
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
23662
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
23663
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
23664
|
+
}
|
|
23665
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
23666
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23667
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23668
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
23669
|
+
return {
|
|
23670
|
+
plots: [
|
|
23671
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
23672
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
23673
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
23674
|
+
],
|
|
23675
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23676
|
+
};
|
|
23677
|
+
}
|
|
23678
|
+
};
|
|
23679
|
+
var vortex = {
|
|
23680
|
+
type: "vortex-indicator",
|
|
23681
|
+
title: "Vortex Indicator",
|
|
23682
|
+
shortTitle: "VI",
|
|
23683
|
+
overlay: false,
|
|
23684
|
+
inputs: [lengthInput(14)],
|
|
23685
|
+
compute: (bars, inputs) => {
|
|
23686
|
+
const len = num3(inputs, "length", 14);
|
|
23687
|
+
const n = bars.length;
|
|
23688
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
23689
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
23690
|
+
for (let i = 1; i < n; i++) {
|
|
23691
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
23692
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
23693
|
+
}
|
|
23694
|
+
const trSum = sum(trueRange(bars), len);
|
|
23695
|
+
return {
|
|
23696
|
+
plots: [
|
|
23697
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
23698
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
23699
|
+
],
|
|
23700
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23701
|
+
};
|
|
23702
|
+
}
|
|
23703
|
+
};
|
|
23704
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
23705
|
+
|
|
23706
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
23707
|
+
var atrSpec = {
|
|
23708
|
+
type: "average-true-range",
|
|
23709
|
+
title: "Average True Range",
|
|
23710
|
+
shortTitle: "ATR",
|
|
23711
|
+
overlay: false,
|
|
23712
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23713
|
+
compute: (bars, inputs) => ({
|
|
23714
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23715
|
+
})
|
|
23716
|
+
};
|
|
23717
|
+
var historicalVolatility = {
|
|
23718
|
+
type: "historical-volatility",
|
|
23719
|
+
title: "Historical Volatility",
|
|
23720
|
+
shortTitle: "HV",
|
|
23721
|
+
overlay: false,
|
|
23722
|
+
inputs: [
|
|
23723
|
+
lengthInput(10),
|
|
23724
|
+
{ 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)" },
|
|
23725
|
+
colorInput()
|
|
23726
|
+
],
|
|
23727
|
+
compute: (bars, inputs) => {
|
|
23728
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
23729
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
23730
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23731
|
+
}
|
|
23732
|
+
};
|
|
23733
|
+
var chaikinVolatility = {
|
|
23734
|
+
type: "chaikin-volatility",
|
|
23735
|
+
title: "Chaikin Volatility",
|
|
23736
|
+
shortTitle: "CHV",
|
|
23737
|
+
overlay: false,
|
|
23738
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
23739
|
+
compute: (bars, inputs) => {
|
|
23740
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
23741
|
+
return {
|
|
23742
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23743
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23744
|
+
};
|
|
23745
|
+
}
|
|
23746
|
+
};
|
|
23747
|
+
var massIndex = {
|
|
23748
|
+
type: "mass-index",
|
|
23749
|
+
title: "Mass Index",
|
|
23750
|
+
shortTitle: "MI",
|
|
23751
|
+
overlay: false,
|
|
23752
|
+
inputs: [lengthInput(25), colorInput()],
|
|
23753
|
+
compute: (bars, inputs) => {
|
|
23754
|
+
const range = sub(highs(bars), lows(bars));
|
|
23755
|
+
const single = ema(range, 9);
|
|
23756
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23757
|
+
return {
|
|
23758
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23759
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23760
|
+
};
|
|
23761
|
+
}
|
|
23762
|
+
};
|
|
23763
|
+
var standardDeviation = {
|
|
23764
|
+
type: "standard-deviation",
|
|
23765
|
+
title: "Standard Deviation",
|
|
23766
|
+
shortTitle: "StdDev",
|
|
23767
|
+
overlay: false,
|
|
23768
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
23769
|
+
compute: (bars, inputs) => ({
|
|
23770
|
+
plots: [
|
|
23771
|
+
{
|
|
23772
|
+
key: "stdev",
|
|
23773
|
+
title: "StdDev",
|
|
23774
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
23775
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23776
|
+
width: 2
|
|
23777
|
+
}
|
|
23778
|
+
]
|
|
23779
|
+
})
|
|
23780
|
+
};
|
|
23781
|
+
var ulcerIndex = {
|
|
23782
|
+
type: "ulcer-index",
|
|
23783
|
+
title: "Ulcer Index",
|
|
23784
|
+
overlay: false,
|
|
23785
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23786
|
+
compute: (bars, inputs) => {
|
|
23787
|
+
const len = num3(inputs, "length", 14);
|
|
23788
|
+
const c = closes(bars);
|
|
23789
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
23790
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
23791
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23792
|
+
}
|
|
23793
|
+
};
|
|
23794
|
+
var choppiness = {
|
|
23795
|
+
type: "choppiness-index",
|
|
23796
|
+
title: "Choppiness Index",
|
|
23797
|
+
shortTitle: "CHOP",
|
|
23798
|
+
overlay: false,
|
|
23799
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23800
|
+
compute: (bars, inputs) => {
|
|
23801
|
+
const len = num3(inputs, "length", 14);
|
|
23802
|
+
const trSum = sum(trueRange(bars), len);
|
|
23803
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
23804
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
23805
|
+
return {
|
|
23806
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23807
|
+
levels: [
|
|
23808
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
23809
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
23810
|
+
]
|
|
23811
|
+
};
|
|
23812
|
+
}
|
|
23813
|
+
};
|
|
23814
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
23815
|
+
|
|
23816
|
+
// src/core/native-indicators/classics/volume.ts
|
|
23817
|
+
function moneyFlowVolume(bars) {
|
|
23818
|
+
return bars.map((b) => {
|
|
23819
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
23820
|
+
if (b.high === b.low) return 0;
|
|
23821
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
23822
|
+
});
|
|
23823
|
+
}
|
|
23824
|
+
var obv = {
|
|
23825
|
+
type: "on-balance-volume",
|
|
23826
|
+
title: "On Balance Volume",
|
|
23827
|
+
shortTitle: "OBV",
|
|
23828
|
+
overlay: false,
|
|
23829
|
+
inputs: [colorInput()],
|
|
23830
|
+
compute: (bars, inputs) => {
|
|
23831
|
+
const vol = volumes(bars);
|
|
23832
|
+
const d = change(closes(bars));
|
|
23833
|
+
const signed2 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
23834
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed2), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23835
|
+
}
|
|
23836
|
+
};
|
|
23837
|
+
var accDist = {
|
|
23838
|
+
type: "accumulation-distribution",
|
|
23839
|
+
title: "Accumulation / Distribution",
|
|
23840
|
+
shortTitle: "A/D",
|
|
23841
|
+
overlay: false,
|
|
23842
|
+
inputs: [colorInput()],
|
|
23843
|
+
compute: (bars, inputs) => ({
|
|
23844
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23845
|
+
})
|
|
23846
|
+
};
|
|
23847
|
+
var cmf = {
|
|
23848
|
+
type: "chaikin-money-flow",
|
|
23849
|
+
title: "Chaikin Money Flow",
|
|
23850
|
+
shortTitle: "CMF",
|
|
23851
|
+
overlay: false,
|
|
23852
|
+
inputs: [lengthInput(20), colorInput()],
|
|
23853
|
+
compute: (bars, inputs) => {
|
|
23854
|
+
const len = num3(inputs, "length", 20);
|
|
23855
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
23856
|
+
return {
|
|
23857
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23858
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23859
|
+
};
|
|
23860
|
+
}
|
|
23861
|
+
};
|
|
23862
|
+
var chaikinOscillator = {
|
|
23863
|
+
type: "chaikin-oscillator",
|
|
23864
|
+
title: "Chaikin Oscillator",
|
|
23865
|
+
shortTitle: "Chaikin Osc",
|
|
23866
|
+
overlay: false,
|
|
23867
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
23868
|
+
compute: (bars, inputs) => {
|
|
23869
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
23870
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
23871
|
+
return {
|
|
23872
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23873
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23874
|
+
};
|
|
23875
|
+
}
|
|
23876
|
+
};
|
|
23877
|
+
var eom = {
|
|
23878
|
+
type: "ease-of-movement",
|
|
23879
|
+
title: "Ease of Movement",
|
|
23880
|
+
shortTitle: "EOM",
|
|
23881
|
+
overlay: false,
|
|
23882
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
23883
|
+
compute: (bars, inputs) => {
|
|
23884
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
23885
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23886
|
+
const move = change(hl2);
|
|
23887
|
+
const raw = bars.map((b, i) => {
|
|
23888
|
+
const m = move[i];
|
|
23889
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
23890
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
23891
|
+
});
|
|
23892
|
+
return {
|
|
23893
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23894
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23895
|
+
};
|
|
23896
|
+
}
|
|
23897
|
+
};
|
|
23898
|
+
var forceIndex = {
|
|
23899
|
+
type: "force-index",
|
|
23900
|
+
title: "Force Index",
|
|
23901
|
+
shortTitle: "FI",
|
|
23902
|
+
overlay: false,
|
|
23903
|
+
inputs: [lengthInput(13), colorInput()],
|
|
23904
|
+
compute: (bars, inputs) => {
|
|
23905
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
23906
|
+
return {
|
|
23907
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23908
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23909
|
+
};
|
|
23910
|
+
}
|
|
23911
|
+
};
|
|
23912
|
+
var klinger = {
|
|
23913
|
+
type: "klinger-oscillator",
|
|
23914
|
+
title: "Klinger Oscillator",
|
|
23915
|
+
shortTitle: "KVO",
|
|
23916
|
+
overlay: false,
|
|
23917
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23918
|
+
compute: (bars, inputs) => {
|
|
23919
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
23920
|
+
const trend = change(hlc3);
|
|
23921
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
23922
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
23923
|
+
return {
|
|
23924
|
+
plots: [
|
|
23925
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
23926
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23927
|
+
],
|
|
23928
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23929
|
+
};
|
|
23930
|
+
}
|
|
23931
|
+
};
|
|
23932
|
+
var mfi = {
|
|
23933
|
+
type: "money-flow-index",
|
|
23934
|
+
title: "Money Flow Index",
|
|
23935
|
+
shortTitle: "MFI",
|
|
23936
|
+
overlay: false,
|
|
23937
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23938
|
+
compute: (bars, inputs) => {
|
|
23939
|
+
const len = num3(inputs, "length", 14);
|
|
23940
|
+
const tp = sourceValues(bars, "HLC3");
|
|
23941
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
23942
|
+
const d = change(tp);
|
|
23943
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
23944
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
23945
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
23946
|
+
return {
|
|
23947
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23948
|
+
levels: [
|
|
23949
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
23950
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
23951
|
+
]
|
|
23952
|
+
};
|
|
23953
|
+
}
|
|
23954
|
+
};
|
|
23955
|
+
function volumeIndex(bars, when) {
|
|
23956
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
23957
|
+
let index = 1e3;
|
|
23958
|
+
let started = false;
|
|
23959
|
+
for (let i = 1; i < bars.length; i++) {
|
|
23960
|
+
const v = bars[i].volume;
|
|
23961
|
+
const pv = bars[i - 1].volume;
|
|
23962
|
+
const pc = bars[i - 1].close;
|
|
23963
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
23964
|
+
if (started) out[i] = index;
|
|
23965
|
+
continue;
|
|
23966
|
+
}
|
|
23967
|
+
started = true;
|
|
23968
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
23969
|
+
out[i] = index;
|
|
23970
|
+
}
|
|
23971
|
+
return out;
|
|
23972
|
+
}
|
|
23973
|
+
var nvi = {
|
|
23974
|
+
type: "negative-volume-index",
|
|
23975
|
+
title: "Negative Volume Index",
|
|
23976
|
+
shortTitle: "Negative Volume Index",
|
|
23977
|
+
overlay: false,
|
|
23978
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23979
|
+
compute: (bars, inputs) => {
|
|
23980
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
23981
|
+
return {
|
|
23982
|
+
plots: [
|
|
23983
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
23984
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
23985
|
+
]
|
|
23986
|
+
};
|
|
23987
|
+
}
|
|
23988
|
+
};
|
|
23989
|
+
var pvi = {
|
|
23990
|
+
type: "positive-volume-index",
|
|
23991
|
+
title: "Positive Volume Index",
|
|
23992
|
+
shortTitle: "Positive Volume Index",
|
|
23993
|
+
overlay: false,
|
|
23994
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23995
|
+
compute: (bars, inputs) => {
|
|
23996
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
23997
|
+
return {
|
|
23998
|
+
plots: [
|
|
23999
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
24000
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
24001
|
+
]
|
|
24002
|
+
};
|
|
24003
|
+
}
|
|
24004
|
+
};
|
|
24005
|
+
var pvt = {
|
|
24006
|
+
type: "price-volume-trend",
|
|
24007
|
+
title: "Price Volume Trend",
|
|
24008
|
+
shortTitle: "PVT",
|
|
24009
|
+
overlay: false,
|
|
24010
|
+
inputs: [colorInput()],
|
|
24011
|
+
compute: (bars, inputs) => {
|
|
24012
|
+
const c = closes(bars);
|
|
24013
|
+
const raw = bars.map((b, i) => {
|
|
24014
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
24015
|
+
const pc = c[i - 1];
|
|
24016
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
24017
|
+
});
|
|
24018
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
24019
|
+
}
|
|
24020
|
+
};
|
|
24021
|
+
var volumeOscillator = {
|
|
24022
|
+
type: "volume-oscillator",
|
|
24023
|
+
title: "Volume Oscillator",
|
|
24024
|
+
shortTitle: "Vol Osc",
|
|
24025
|
+
overlay: false,
|
|
24026
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
24027
|
+
compute: (bars, inputs) => {
|
|
24028
|
+
const vol = volumes(bars);
|
|
24029
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
24030
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
24031
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
24032
|
+
return {
|
|
24033
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
24034
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24035
|
+
};
|
|
24036
|
+
}
|
|
24037
|
+
};
|
|
24038
|
+
var pvo = {
|
|
24039
|
+
type: "pvo",
|
|
24040
|
+
title: "Percentage Volume Oscillator",
|
|
24041
|
+
shortTitle: "PVO",
|
|
24042
|
+
overlay: false,
|
|
24043
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
24044
|
+
compute: (bars, inputs) => {
|
|
24045
|
+
const vol = volumes(bars);
|
|
24046
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
24047
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
24048
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
24049
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
24050
|
+
const hist = sub(line, signal);
|
|
24051
|
+
return {
|
|
24052
|
+
plots: [
|
|
24053
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
24054
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
24055
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
24056
|
+
],
|
|
24057
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24058
|
+
};
|
|
24059
|
+
}
|
|
24060
|
+
};
|
|
24061
|
+
var vfi = {
|
|
24062
|
+
type: "volume-flow-indicator",
|
|
24063
|
+
title: "Volume Flow Indicator",
|
|
24064
|
+
shortTitle: "Volume Flow Indicator",
|
|
24065
|
+
overlay: false,
|
|
24066
|
+
inputs: [
|
|
24067
|
+
lengthInput(130),
|
|
24068
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
24069
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
24070
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
24071
|
+
],
|
|
24072
|
+
compute: (bars, inputs) => {
|
|
24073
|
+
const len = num3(inputs, "length", 130);
|
|
24074
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
24075
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
24076
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
24077
|
+
const inter = change(typical);
|
|
24078
|
+
const vInter = stdev(inter, 30);
|
|
24079
|
+
const vol = volumes(bars);
|
|
24080
|
+
const vAve = sma(vol, len);
|
|
24081
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
24082
|
+
const raw = bars.map((b, i) => {
|
|
24083
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
24084
|
+
const v = vol[i];
|
|
24085
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24086
|
+
const m = mf[i];
|
|
24087
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
24088
|
+
const capped = Math.min(v, ave * volCoef);
|
|
24089
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
24090
|
+
});
|
|
24091
|
+
const values = bars.map((_, i) => {
|
|
24092
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24093
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
24094
|
+
});
|
|
24095
|
+
const flowSum = sum(raw, len);
|
|
24096
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
24097
|
+
return {
|
|
24098
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
24099
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24100
|
+
};
|
|
24101
|
+
}
|
|
24102
|
+
};
|
|
24103
|
+
var intradayIntensity = {
|
|
24104
|
+
type: "intraday-intensity",
|
|
24105
|
+
title: "Intraday Intensity",
|
|
24106
|
+
shortTitle: "Intraday Intensity",
|
|
24107
|
+
overlay: false,
|
|
24108
|
+
inputs: [lengthInput(21), colorInput()],
|
|
24109
|
+
compute: (bars, inputs) => {
|
|
24110
|
+
const len = num3(inputs, "length", 21);
|
|
24111
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
24112
|
+
return {
|
|
24113
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
24114
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24115
|
+
};
|
|
24116
|
+
}
|
|
24117
|
+
};
|
|
24118
|
+
var volumeSpecs = [
|
|
24119
|
+
obv,
|
|
24120
|
+
accDist,
|
|
24121
|
+
cmf,
|
|
24122
|
+
chaikinOscillator,
|
|
24123
|
+
eom,
|
|
24124
|
+
forceIndex,
|
|
24125
|
+
klinger,
|
|
24126
|
+
mfi,
|
|
24127
|
+
nvi,
|
|
24128
|
+
pvi,
|
|
24129
|
+
pvt,
|
|
24130
|
+
volumeOscillator,
|
|
24131
|
+
pvo,
|
|
24132
|
+
vfi,
|
|
24133
|
+
intradayIntensity
|
|
24134
|
+
];
|
|
24135
|
+
|
|
24136
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
24137
|
+
var DAY_MS2 = 864e5;
|
|
24138
|
+
function periodKey(anchor, time) {
|
|
24139
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
24140
|
+
if (anchor === "Month") {
|
|
24141
|
+
const d = new Date(time);
|
|
24142
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
24143
|
+
}
|
|
24144
|
+
return Math.floor(time / DAY_MS2);
|
|
24145
|
+
}
|
|
24146
|
+
var parabolicSar = {
|
|
24147
|
+
type: "parabolic-sar",
|
|
24148
|
+
title: "Parabolic SAR",
|
|
24149
|
+
shortTitle: "SAR",
|
|
24150
|
+
overlay: true,
|
|
24151
|
+
inputs: [
|
|
24152
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24153
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24154
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
24155
|
+
],
|
|
24156
|
+
compute: (bars, inputs) => {
|
|
24157
|
+
const start = num3(inputs, "start", 0.02);
|
|
24158
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
24159
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
24160
|
+
const n = bars.length;
|
|
24161
|
+
const values = new Array(n).fill(Number.NaN);
|
|
24162
|
+
const colors = new Array(n).fill(null);
|
|
24163
|
+
if (n >= 2) {
|
|
24164
|
+
let long = bars[1].close >= bars[0].close;
|
|
24165
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
24166
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
24167
|
+
let af = start;
|
|
24168
|
+
for (let i = 1; i < n; i++) {
|
|
24169
|
+
const b = bars[i];
|
|
24170
|
+
sar += af * (ep - sar);
|
|
24171
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
24172
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
24173
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
24174
|
+
if (reversed) {
|
|
24175
|
+
long = !long;
|
|
24176
|
+
sar = ep;
|
|
24177
|
+
ep = long ? b.high : b.low;
|
|
24178
|
+
af = start;
|
|
24179
|
+
} else if (long && b.high > ep) {
|
|
24180
|
+
ep = b.high;
|
|
24181
|
+
af = Math.min(max, af + inc);
|
|
24182
|
+
} else if (!long && b.low < ep) {
|
|
24183
|
+
ep = b.low;
|
|
24184
|
+
af = Math.min(max, af + inc);
|
|
24185
|
+
}
|
|
24186
|
+
values[i] = sar;
|
|
24187
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
24188
|
+
}
|
|
24189
|
+
}
|
|
24190
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
24191
|
+
}
|
|
24192
|
+
};
|
|
24193
|
+
var vwap = {
|
|
24194
|
+
type: "vwap",
|
|
24195
|
+
title: "Volume Weighted Average Price",
|
|
24196
|
+
shortTitle: "VWAP",
|
|
24197
|
+
overlay: true,
|
|
24198
|
+
inputs: [
|
|
24199
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
24200
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
24201
|
+
colorInput(INFO)
|
|
24202
|
+
],
|
|
24203
|
+
compute: (bars, inputs) => {
|
|
24204
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24205
|
+
const source = str3(inputs, "source", "HLC3");
|
|
24206
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
24207
|
+
let period = Number.NaN;
|
|
24208
|
+
let cumPV = 0;
|
|
24209
|
+
let cumV = 0;
|
|
24210
|
+
for (let i = 0; i < bars.length; i++) {
|
|
24211
|
+
const b = bars[i];
|
|
24212
|
+
const key = periodKey(anchor, b.time);
|
|
24213
|
+
if (key !== period) {
|
|
24214
|
+
period = key;
|
|
24215
|
+
cumPV = 0;
|
|
24216
|
+
cumV = 0;
|
|
24217
|
+
}
|
|
24218
|
+
const v = b.volume;
|
|
24219
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
24220
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
24221
|
+
cumPV += tp * v;
|
|
24222
|
+
cumV += v;
|
|
24223
|
+
}
|
|
24224
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
24225
|
+
}
|
|
24226
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
24227
|
+
}
|
|
24228
|
+
};
|
|
24229
|
+
var pivotPoints = {
|
|
24230
|
+
type: "pivot-points",
|
|
24231
|
+
title: "Pivot Points",
|
|
24232
|
+
shortTitle: "Pivots",
|
|
24233
|
+
overlay: true,
|
|
24234
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
24235
|
+
compute: (bars, inputs) => {
|
|
24236
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24237
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
24238
|
+
for (const b of bars) {
|
|
24239
|
+
const key = periodKey(anchor, b.time);
|
|
24240
|
+
const a = aggs.get(key);
|
|
24241
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
24242
|
+
else {
|
|
24243
|
+
a.high = Math.max(a.high, b.high);
|
|
24244
|
+
a.low = Math.min(a.low, b.low);
|
|
24245
|
+
a.close = b.close;
|
|
24246
|
+
}
|
|
24247
|
+
}
|
|
24248
|
+
const n = bars.length;
|
|
24249
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
24250
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
24251
|
+
for (let i = 0; i < n; i++) {
|
|
24252
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
24253
|
+
if (!prev2) continue;
|
|
24254
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
24255
|
+
levels.p[i] = p;
|
|
24256
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
24257
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
24258
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
24259
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
24260
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
24261
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
24262
|
+
}
|
|
24263
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
24264
|
+
return {
|
|
24265
|
+
plots: [
|
|
24266
|
+
plot("p", "P", WARNING),
|
|
24267
|
+
plot("r1", "R1", BEARISH),
|
|
24268
|
+
plot("s1", "S1", BULLISH),
|
|
24269
|
+
plot("r2", "R2", BEARISH),
|
|
24270
|
+
plot("s2", "S2", BULLISH),
|
|
24271
|
+
plot("r3", "R3", BEARISH),
|
|
24272
|
+
plot("s3", "S3", BULLISH)
|
|
24273
|
+
]
|
|
24274
|
+
};
|
|
24275
|
+
}
|
|
24276
|
+
};
|
|
24277
|
+
var fiftyTwoWeek = {
|
|
24278
|
+
type: "52-week-high-low",
|
|
24279
|
+
title: "52 Week High/Low",
|
|
24280
|
+
shortTitle: "52W H/L",
|
|
24281
|
+
overlay: true,
|
|
24282
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
24283
|
+
compute: (bars, inputs) => {
|
|
24284
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
24285
|
+
const n = bars.length;
|
|
24286
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
24287
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
24288
|
+
const maxIdx = [];
|
|
24289
|
+
const minIdx = [];
|
|
24290
|
+
let from = 0;
|
|
24291
|
+
for (let i = 0; i < n; i++) {
|
|
24292
|
+
const b = bars[i];
|
|
24293
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
24294
|
+
maxIdx.push(i);
|
|
24295
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
24296
|
+
minIdx.push(i);
|
|
24297
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
24298
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
24299
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
24300
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
24301
|
+
lo[i] = bars[minIdx[0]].low;
|
|
24302
|
+
}
|
|
24303
|
+
return {
|
|
24304
|
+
plots: [
|
|
24305
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
24306
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
24307
|
+
]
|
|
24308
|
+
};
|
|
24309
|
+
}
|
|
24310
|
+
};
|
|
24311
|
+
var zigzag = {
|
|
24312
|
+
type: "zigzag",
|
|
24313
|
+
title: "ZigZag",
|
|
24314
|
+
overlay: true,
|
|
24315
|
+
inputs: [
|
|
24316
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
24317
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
24318
|
+
colorInput()
|
|
24319
|
+
],
|
|
24320
|
+
compute: (bars, inputs) => {
|
|
24321
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
24322
|
+
const depth = num3(inputs, "depth", 10);
|
|
24323
|
+
const pivots = [];
|
|
24324
|
+
if (bars.length > 1) {
|
|
24325
|
+
let up = bars[1].close >= bars[0].close;
|
|
24326
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
24327
|
+
for (let i = 1; i < bars.length; i++) {
|
|
24328
|
+
const b = bars[i];
|
|
24329
|
+
if (up) {
|
|
24330
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
24331
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24332
|
+
pivots.push(ext);
|
|
24333
|
+
up = false;
|
|
24334
|
+
ext = { i, price: b.low };
|
|
24335
|
+
}
|
|
24336
|
+
} else if (b.low <= ext.price) {
|
|
24337
|
+
ext = { i, price: b.low };
|
|
24338
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24339
|
+
pivots.push(ext);
|
|
24340
|
+
up = true;
|
|
24341
|
+
ext = { i, price: b.high };
|
|
24342
|
+
}
|
|
24343
|
+
}
|
|
24344
|
+
pivots.push(ext);
|
|
24345
|
+
}
|
|
24346
|
+
return {
|
|
24347
|
+
plots: [],
|
|
24348
|
+
polylines: [
|
|
24349
|
+
{
|
|
24350
|
+
key: "zigzag",
|
|
24351
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
24352
|
+
curved: false,
|
|
24353
|
+
closed: false,
|
|
24354
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
24355
|
+
lineWidth: 2,
|
|
24356
|
+
lineStyle: "solid",
|
|
24357
|
+
arrowLeft: false,
|
|
24358
|
+
arrowRight: false
|
|
24359
|
+
}
|
|
24360
|
+
]
|
|
24361
|
+
};
|
|
24362
|
+
}
|
|
24363
|
+
};
|
|
24364
|
+
var williamsFractal = {
|
|
24365
|
+
type: "williams-fractal",
|
|
24366
|
+
title: "Williams Fractal",
|
|
24367
|
+
shortTitle: "Fractals",
|
|
24368
|
+
overlay: true,
|
|
24369
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
24370
|
+
compute: (bars, inputs) => {
|
|
24371
|
+
const p = num3(inputs, "periods", 2);
|
|
24372
|
+
const markers = [];
|
|
24373
|
+
const isExtreme = (i, pick, better) => {
|
|
24374
|
+
const v = pick(bars[i]);
|
|
24375
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
24376
|
+
if (k === i) continue;
|
|
24377
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
24378
|
+
}
|
|
24379
|
+
return true;
|
|
24380
|
+
};
|
|
24381
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
24382
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
24383
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
24384
|
+
}
|
|
24385
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
24386
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
24387
|
+
}
|
|
24388
|
+
}
|
|
24389
|
+
return { plots: [], markers };
|
|
24390
|
+
}
|
|
24391
|
+
};
|
|
24392
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
24393
|
+
|
|
24394
|
+
// src/core/native-indicators/classics/index.ts
|
|
24395
|
+
var classicSpecs = [
|
|
24396
|
+
...averageSpecs,
|
|
24397
|
+
...bandSpecs,
|
|
24398
|
+
...oscillatorSpecs,
|
|
24399
|
+
...trendSpecs,
|
|
24400
|
+
...volatilitySpecs,
|
|
24401
|
+
...volumeSpecs,
|
|
24402
|
+
...overlaySpecs
|
|
24403
|
+
];
|
|
24404
|
+
function registerClassicIndicators() {
|
|
24405
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
24406
|
+
}
|
|
24407
|
+
|
|
24408
|
+
// src/Vela.ts
|
|
24409
|
+
var Vela = class {
|
|
24410
|
+
constructor(container, options = {}, deps = {}) {
|
|
24411
|
+
registerBuiltinChartTypes();
|
|
24412
|
+
registerVolume();
|
|
24413
|
+
registerVpvr();
|
|
24414
|
+
registerClassicIndicators();
|
|
24415
|
+
const element = resolveElement(container);
|
|
24416
|
+
const theme = resolveTheme(options.theme);
|
|
22035
24417
|
const display = {
|
|
22036
24418
|
currentPriceLine: options.currentPriceLine ?? true,
|
|
22037
24419
|
logScale: options.logScale ?? false,
|
|
22038
24420
|
nativeBackend: options.nativeBackend ?? "auto",
|
|
22039
|
-
|
|
22040
|
-
animPan,
|
|
24421
|
+
...resolveAnimations(options.animations),
|
|
22041
24422
|
glow: options.glow ?? 0,
|
|
22042
24423
|
upColor: options.upColor ?? BULLISH,
|
|
22043
24424
|
downColor: options.downColor ?? BEARISH,
|
|
@@ -22095,25 +24476,28 @@ var Vela = class {
|
|
|
22095
24476
|
}
|
|
22096
24477
|
/**
|
|
22097
24478
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
22098
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
22099
|
-
* events)
|
|
22100
|
-
*
|
|
24479
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
24480
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
24481
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
24482
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
22101
24483
|
*/
|
|
22102
24484
|
addNativeIndicator(type, options) {
|
|
22103
24485
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
22104
24486
|
}
|
|
22105
24487
|
/**
|
|
22106
24488
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
22107
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
22108
|
-
*
|
|
22109
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
22110
|
-
*
|
|
24489
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
24490
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
24491
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
24492
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
24493
|
+
* provider (a type may need data the symbol lacks).
|
|
22111
24494
|
*/
|
|
22112
24495
|
availableNativeIndicators() {
|
|
22113
24496
|
return this.orchestrator.availableNativeIndicators();
|
|
22114
24497
|
}
|
|
22115
24498
|
/**
|
|
22116
|
-
* The native-indicator types PRESENT on the chart right now
|
|
24499
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
24500
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
22117
24501
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
22118
24502
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
22119
24503
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -22349,4 +24733,4 @@ function resolveElement(container) {
|
|
|
22349
24733
|
return element;
|
|
22350
24734
|
}
|
|
22351
24735
|
|
|
22352
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|
|
24736
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|