@luxalgo/vela 0.6.15 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-J5QIR2Z3.js → chunk-GIJYO4LO.js} +2436 -115
- package/dist/{chunk-32A7KHFM.js → chunk-I77F2SVE.js} +7 -1
- package/dist/{chunk-MWDNPGU3.js → chunk-MGU3USTP.js} +22 -15
- package/dist/{chunk-MWS4CUJL.js → chunk-Q3TFJNJW.js} +1 -1
- package/dist/{chunk-7C7NWEXH.js → chunk-SEOO6YG7.js} +82 -55
- package/dist/{contributions-x7iamlnd.d.cts → contributions-BEkxwA5F.d.cts} +27 -11
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-CoAX7_SF.d.ts} +27 -11
- package/dist/index.cjs +2665 -337
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/{plugin-BLaNPh55.d.ts → plugin-CNUgbRNq.d.ts} +1 -1
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-Dxyq35Zf.d.cts} +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-CbPza-Wc.d.cts} +1 -1
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-D7cWkw8A.d.ts} +1 -1
- package/dist/ui.cjs +22 -15
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +2501 -173
- package/dist/vela.global.min.js +52 -52
- package/dist/widget.cjs +2563 -202
- package/dist/widget.d.cts +4 -4
- package/dist/widget.d.ts +4 -4
- package/dist/widget.js +6 -6
- package/dist/workspace.cjs +2563 -202
- package/dist/workspace.d.cts +23 -12
- package/dist/workspace.d.ts +23 -12
- package/dist/workspace.js +5 -5
- 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-6DPAYEGO.js';
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-I77F2SVE.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MGU3USTP.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, INFO, svg24, svg24Solid } from './chunk-6DPAYEGO.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -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 = {}) {
|
|
@@ -2475,8 +2475,8 @@ var DrawingToolbar = class {
|
|
|
2475
2475
|
}
|
|
2476
2476
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
2477
2477
|
* right edge of the flyout rows, beside the favorite star. */
|
|
2478
|
-
setShortcuts(
|
|
2479
|
-
this.shortcuts = new Map(Object.entries(
|
|
2478
|
+
setShortcuts(map2) {
|
|
2479
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
2480
2480
|
}
|
|
2481
2481
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
2482
2482
|
paintStar(el, type) {
|
|
@@ -4327,8 +4327,8 @@ var InputsUI = class {
|
|
|
4327
4327
|
lg.style.top = `${top}px`;
|
|
4328
4328
|
}
|
|
4329
4329
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
4330
|
-
setCollapsedPanes(
|
|
4331
|
-
this.paneCollapse =
|
|
4330
|
+
setCollapsedPanes(map2) {
|
|
4331
|
+
this.paneCollapse = map2;
|
|
4332
4332
|
}
|
|
4333
4333
|
/**
|
|
4334
4334
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -5291,9 +5291,9 @@ function factoryResetConfig(factory) {
|
|
|
5291
5291
|
if (inst.enableKey) defaults[inst.enableKey] = false;
|
|
5292
5292
|
addRows(inst.rows);
|
|
5293
5293
|
}
|
|
5294
|
-
for (const
|
|
5295
|
-
if (
|
|
5296
|
-
addRows(
|
|
5294
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5295
|
+
if (sub2.enableKey) defaults[sub2.enableKey] = false;
|
|
5296
|
+
addRows(sub2.rows);
|
|
5297
5297
|
}
|
|
5298
5298
|
if (!section.instances) addRows(section.rows);
|
|
5299
5299
|
bag[t.id] = defaults;
|
|
@@ -6622,11 +6622,11 @@ function mergeBarColors(indicators) {
|
|
|
6622
6622
|
}
|
|
6623
6623
|
}
|
|
6624
6624
|
if (!any) return EMPTY_BARCOLORS;
|
|
6625
|
-
const
|
|
6625
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
6626
6626
|
for (const m of indicators.values()) {
|
|
6627
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
6627
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
6628
6628
|
}
|
|
6629
|
-
return
|
|
6629
|
+
return map2;
|
|
6630
6630
|
}
|
|
6631
6631
|
|
|
6632
6632
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -6923,9 +6923,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
6923
6923
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
6924
6924
|
return rightOffset + deltaX / barSpacing;
|
|
6925
6925
|
}
|
|
6926
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
6926
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
6927
6927
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
6928
|
-
if (
|
|
6928
|
+
if (shift2) return deltaY;
|
|
6929
6929
|
return null;
|
|
6930
6930
|
}
|
|
6931
6931
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -8547,11 +8547,11 @@ function mergeBarColors2(indicators) {
|
|
|
8547
8547
|
}
|
|
8548
8548
|
}
|
|
8549
8549
|
if (!any) return EMPTY_BARCOLORS2;
|
|
8550
|
-
const
|
|
8550
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
8551
8551
|
for (const m of indicators.values()) {
|
|
8552
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
8552
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
8553
8553
|
}
|
|
8554
|
-
return
|
|
8554
|
+
return map2;
|
|
8555
8555
|
}
|
|
8556
8556
|
|
|
8557
8557
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -10190,9 +10190,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
10190
10190
|
};
|
|
10191
10191
|
if (section.rows) addRows(section.rows);
|
|
10192
10192
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
10193
|
-
for (const
|
|
10194
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
10195
|
-
addRows(
|
|
10193
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10194
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
10195
|
+
addRows(sub2.rows);
|
|
10196
10196
|
}
|
|
10197
10197
|
}
|
|
10198
10198
|
for (const hs of hostSections) {
|
|
@@ -10751,14 +10751,14 @@ var SettingsDialog = class {
|
|
|
10751
10751
|
};
|
|
10752
10752
|
if (section.rows) seed(section.rows);
|
|
10753
10753
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
10754
|
-
for (const
|
|
10754
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
10755
10755
|
for (const inst of section.instances ?? []) {
|
|
10756
10756
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
10757
10757
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
10758
10758
|
}
|
|
10759
|
-
for (const
|
|
10760
|
-
if (!
|
|
10761
|
-
bag[
|
|
10759
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10760
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
10761
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
10762
10762
|
}
|
|
10763
10763
|
const refreshers = [];
|
|
10764
10764
|
const put = (key, v) => {
|
|
@@ -10775,16 +10775,16 @@ var SettingsDialog = class {
|
|
|
10775
10775
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
10776
10776
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
10777
10777
|
}
|
|
10778
|
-
for (const
|
|
10779
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
10780
|
-
const rows = filterHiddenRows(
|
|
10778
|
+
for (const sub2 of section.subsections ?? []) {
|
|
10779
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
10780
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
10781
10781
|
if (rows.length === 0) continue;
|
|
10782
|
-
const subMarker = this.section(
|
|
10782
|
+
const subMarker = this.section(sub2.title);
|
|
10783
10783
|
subMarker.dataset.sdStyle = typeId;
|
|
10784
10784
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
10785
10785
|
subMarker.dataset.sdSub = "1";
|
|
10786
10786
|
body.append(subMarker);
|
|
10787
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
10787
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
10788
10788
|
}
|
|
10789
10789
|
for (const r of refreshers) r();
|
|
10790
10790
|
}
|
|
@@ -12891,11 +12891,11 @@ var DrawingInteraction = class {
|
|
|
12891
12891
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
12892
12892
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
12893
12893
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
12894
|
-
down(x, y, mode = "off",
|
|
12894
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
12895
12895
|
const proj = this.deps.projector();
|
|
12896
12896
|
if (this.state.kind === "placing") {
|
|
12897
12897
|
if (this.state.draft.placementMode() !== "click") return;
|
|
12898
|
-
const pivot =
|
|
12898
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12899
12899
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12900
12900
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
12901
12901
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -12914,7 +12914,7 @@ var DrawingInteraction = class {
|
|
|
12914
12914
|
}
|
|
12915
12915
|
const hit = this.hitAt(x, y);
|
|
12916
12916
|
if (hit) {
|
|
12917
|
-
if (
|
|
12917
|
+
if (shift2) {
|
|
12918
12918
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
12919
12919
|
return;
|
|
12920
12920
|
}
|
|
@@ -12924,7 +12924,7 @@ var DrawingInteraction = class {
|
|
|
12924
12924
|
}
|
|
12925
12925
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
12926
12926
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
12927
|
-
move(x, y, mode = "off",
|
|
12927
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
12928
12928
|
if (this.state.kind === "idle") {
|
|
12929
12929
|
const tool = this.deps.activeTool();
|
|
12930
12930
|
const had = this.snapAt != null;
|
|
@@ -12938,7 +12938,7 @@ var DrawingInteraction = class {
|
|
|
12938
12938
|
this.captureFreehand(x, y);
|
|
12939
12939
|
return;
|
|
12940
12940
|
}
|
|
12941
|
-
const pivot =
|
|
12941
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
12942
12942
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
12943
12943
|
this.deps.changed();
|
|
12944
12944
|
return;
|
|
@@ -12951,7 +12951,7 @@ var DrawingInteraction = class {
|
|
|
12951
12951
|
if (this.state.handle >= 0) {
|
|
12952
12952
|
const a = d.anchors[this.state.handle];
|
|
12953
12953
|
if (a) {
|
|
12954
|
-
const pivot =
|
|
12954
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
12955
12955
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
12956
12956
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
12957
12957
|
}
|
|
@@ -13649,21 +13649,21 @@ var DrawingSettingsPopup = class {
|
|
|
13649
13649
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
13650
13650
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
13651
13651
|
}
|
|
13652
|
-
const
|
|
13653
|
-
if (paths.has("vwap.midColor") &&
|
|
13652
|
+
const vwap2 = drawing.vwap;
|
|
13653
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
13654
13654
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
13655
13655
|
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,
|
|
13656
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
13657
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
13658
13658
|
bar.appendChild(
|
|
13659
|
-
this.dropdown("Band multiplier", MULTS,
|
|
13659
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
13660
13660
|
label: (v) => `${v}\u03C3`,
|
|
13661
13661
|
labelInTrigger: true
|
|
13662
13662
|
})
|
|
13663
13663
|
);
|
|
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,
|
|
13664
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
13665
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
13666
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
13667
13667
|
}
|
|
13668
13668
|
if (paths.has("maxCurvature")) {
|
|
13669
13669
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -14680,8 +14680,8 @@ var UserDrawingController = class {
|
|
|
14680
14680
|
this.toolbar.setFavorites(types);
|
|
14681
14681
|
}
|
|
14682
14682
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
14683
|
-
setToolShortcuts(
|
|
14684
|
-
this.toolbar.setShortcuts(
|
|
14683
|
+
setToolShortcuts(map2) {
|
|
14684
|
+
this.toolbar.setShortcuts(map2);
|
|
14685
14685
|
}
|
|
14686
14686
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
14687
14687
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -14799,7 +14799,7 @@ var UserDrawingController = class {
|
|
|
14799
14799
|
this.render();
|
|
14800
14800
|
}
|
|
14801
14801
|
}
|
|
14802
|
-
pointerDown(x, y, snap = "off",
|
|
14802
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
14803
14803
|
if (this.eraserMode) {
|
|
14804
14804
|
this.erasing = true;
|
|
14805
14805
|
this.deleteAt(x, y);
|
|
@@ -14819,9 +14819,9 @@ var UserDrawingController = class {
|
|
|
14819
14819
|
return;
|
|
14820
14820
|
}
|
|
14821
14821
|
}
|
|
14822
|
-
this.interaction.down(x, y, snap,
|
|
14822
|
+
this.interaction.down(x, y, snap, shift2);
|
|
14823
14823
|
}
|
|
14824
|
-
pointerMove(x, y, snap = "off",
|
|
14824
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
14825
14825
|
if (this.eraserMode) {
|
|
14826
14826
|
if (this.erasing) this.deleteAt(x, y);
|
|
14827
14827
|
return;
|
|
@@ -14832,7 +14832,7 @@ var UserDrawingController = class {
|
|
|
14832
14832
|
this.render();
|
|
14833
14833
|
return;
|
|
14834
14834
|
}
|
|
14835
|
-
this.interaction.move(x, y, snap,
|
|
14835
|
+
this.interaction.move(x, y, snap, shift2);
|
|
14836
14836
|
this.updateHover(x, y);
|
|
14837
14837
|
}
|
|
14838
14838
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -15509,14 +15509,14 @@ function layoutTable(ctx, t, args) {
|
|
|
15509
15509
|
}
|
|
15510
15510
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
15511
15511
|
if (box.cs > 1) {
|
|
15512
|
-
let
|
|
15513
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
15514
|
-
if (w2 >
|
|
15512
|
+
let sum2 = 0;
|
|
15513
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
15514
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
15515
15515
|
}
|
|
15516
15516
|
if (box.rs > 1) {
|
|
15517
|
-
let
|
|
15518
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
15519
|
-
if (h2 >
|
|
15517
|
+
let sum2 = 0;
|
|
15518
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
15519
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
15520
15520
|
}
|
|
15521
15521
|
}
|
|
15522
15522
|
let w = 0;
|
|
@@ -16213,24 +16213,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
16213
16213
|
drawSvgShapes(el, alpha);
|
|
16214
16214
|
continue;
|
|
16215
16215
|
}
|
|
16216
|
-
const
|
|
16216
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
16217
16217
|
let path = null;
|
|
16218
16218
|
if (tag === "path") {
|
|
16219
16219
|
const d = el.getAttribute("d");
|
|
16220
16220
|
if (d) path = new Path2D(d);
|
|
16221
16221
|
} else if (tag === "circle") {
|
|
16222
16222
|
path = new Path2D();
|
|
16223
|
-
path.arc(
|
|
16223
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
16224
16224
|
} else if (tag === "ellipse") {
|
|
16225
16225
|
path = new Path2D();
|
|
16226
|
-
path.ellipse(
|
|
16226
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
16227
16227
|
} else if (tag === "rect") {
|
|
16228
16228
|
path = new Path2D();
|
|
16229
|
-
path.rect(
|
|
16229
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
16230
16230
|
} else if (tag === "line") {
|
|
16231
16231
|
path = new Path2D();
|
|
16232
|
-
path.moveTo(
|
|
16233
|
-
path.lineTo(
|
|
16232
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
16233
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
16234
16234
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
16235
16235
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
16236
16236
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -17647,8 +17647,8 @@ var NativeRenderer = class {
|
|
|
17647
17647
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
17648
17648
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
17649
17649
|
drawingsSnapMode: () => this.snapMode,
|
|
17650
|
-
drawingsPointerDown: (x, y, snap,
|
|
17651
|
-
drawingsPointerMove: (x, y, snap,
|
|
17650
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
17651
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
17652
17652
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
17653
17653
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
17654
17654
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -17965,18 +17965,18 @@ var NativeRenderer = class {
|
|
|
17965
17965
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
17966
17966
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
17967
17967
|
const newHeadTime = this.bars[0].time;
|
|
17968
|
-
let
|
|
17968
|
+
let shift2 = 0;
|
|
17969
17969
|
if (newHeadTime !== prevHeadTime) {
|
|
17970
17970
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
17971
|
-
if (prepended > 0)
|
|
17971
|
+
if (prepended > 0) shift2 = prepended;
|
|
17972
17972
|
else {
|
|
17973
17973
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
17974
|
-
if (trimmed > 0)
|
|
17974
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
17975
17975
|
}
|
|
17976
17976
|
}
|
|
17977
|
-
if (
|
|
17978
|
-
this.zoomAnchorLogical +=
|
|
17979
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
17977
|
+
if (shift2 !== 0) {
|
|
17978
|
+
this.zoomAnchorLogical += shift2;
|
|
17979
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
17980
17980
|
}
|
|
17981
17981
|
}
|
|
17982
17982
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -19582,15 +19582,15 @@ var NativeRenderer = class {
|
|
|
19582
19582
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
19583
19583
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
19584
19584
|
collapsedMasterMap() {
|
|
19585
|
-
const
|
|
19585
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
19586
19586
|
for (const pane of this.scene.panes.values()) {
|
|
19587
19587
|
if (!pane.collapsed) continue;
|
|
19588
19588
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
19589
19589
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
19590
19590
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
19591
|
-
|
|
19591
|
+
map2.set(pane.id, master?.id ?? null);
|
|
19592
19592
|
}
|
|
19593
|
-
return
|
|
19593
|
+
return map2;
|
|
19594
19594
|
}
|
|
19595
19595
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
19596
19596
|
setToolbarGutter(px) {
|
|
@@ -19862,7 +19862,7 @@ var LiveSession = class {
|
|
|
19862
19862
|
|
|
19863
19863
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
19864
19864
|
var IndicatorHandleImpl = class {
|
|
19865
|
-
constructor(id, title, controller, source) {
|
|
19865
|
+
constructor(id, title, controller, source, nativeType) {
|
|
19866
19866
|
this.controller = controller;
|
|
19867
19867
|
this.schema = [];
|
|
19868
19868
|
this.propsSchema = [];
|
|
@@ -19871,6 +19871,7 @@ var IndicatorHandleImpl = class {
|
|
|
19871
19871
|
this.id = id;
|
|
19872
19872
|
this.title = title;
|
|
19873
19873
|
if (source !== void 0) this.source = source;
|
|
19874
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
19874
19875
|
}
|
|
19875
19876
|
get inputs() {
|
|
19876
19877
|
return this.schema;
|
|
@@ -19961,24 +19962,24 @@ function summarizeModel(model) {
|
|
|
19961
19962
|
}
|
|
19962
19963
|
function inspectModels(models) {
|
|
19963
19964
|
const indicators = models.map(summarizeModel);
|
|
19964
|
-
const
|
|
19965
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
19965
19966
|
return {
|
|
19966
19967
|
indicators,
|
|
19967
19968
|
totals: {
|
|
19968
19969
|
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:
|
|
19970
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
19971
|
+
fills: sum2((s) => s.fills),
|
|
19972
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
19973
|
+
priceLines: sum2((s) => s.priceLines),
|
|
19974
|
+
lines: sum2((s) => s.lines),
|
|
19975
|
+
boxes: sum2((s) => s.boxes),
|
|
19976
|
+
labels: sum2((s) => s.labels),
|
|
19977
|
+
polylines: sum2((s) => s.polylines),
|
|
19978
|
+
linefills: sum2((s) => s.linefills),
|
|
19979
|
+
tables: sum2((s) => s.tables),
|
|
19980
|
+
barColors: sum2((s) => s.barColors),
|
|
19981
|
+
trades: sum2((s) => s.trades),
|
|
19982
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
19982
19983
|
}
|
|
19983
19984
|
};
|
|
19984
19985
|
}
|
|
@@ -21035,16 +21036,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21035
21036
|
/**
|
|
21036
21037
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
21037
21038
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
21038
|
-
* INSTANCE per type
|
|
21039
|
-
*
|
|
21039
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
21040
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
21041
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
21042
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
21040
21043
|
*/
|
|
21041
21044
|
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
21045
|
const descriptor = getNativeIndicator(type);
|
|
21046
|
+
if (!descriptor?.multiInstance) {
|
|
21047
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
21048
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
21049
|
+
}
|
|
21045
21050
|
const id = this.registry.nextId("native");
|
|
21046
21051
|
const title = descriptor?.title ?? type;
|
|
21047
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
21052
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
21048
21053
|
this.handles.set(id, handle);
|
|
21049
21054
|
if (!descriptor) {
|
|
21050
21055
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -21056,16 +21061,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21056
21061
|
void this.startNativeIndicator(id, handle);
|
|
21057
21062
|
return handle;
|
|
21058
21063
|
}
|
|
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). */
|
|
21064
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
21065
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
21066
21066
|
presentNativeIndicators() {
|
|
21067
21067
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
21068
21068
|
}
|
|
21069
|
+
/**
|
|
21070
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
21071
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
21072
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
21073
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
21074
|
+
* probe the provider for a required capability.
|
|
21075
|
+
*/
|
|
21069
21076
|
async availableNativeIndicators() {
|
|
21070
21077
|
const symbol = this.qualifiedSymbol();
|
|
21071
21078
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -21075,7 +21082,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21075
21082
|
title: d.title,
|
|
21076
21083
|
supported: await this.isNativeSupported(d, symbol),
|
|
21077
21084
|
present: present.has(d.type),
|
|
21078
|
-
beta: d.beta
|
|
21085
|
+
beta: d.beta,
|
|
21086
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
21079
21087
|
}))
|
|
21080
21088
|
);
|
|
21081
21089
|
}
|
|
@@ -22015,12 +22023,2322 @@ function registerVpvr() {
|
|
|
22015
22023
|
registerNativeIndicator(vpvrDescriptor);
|
|
22016
22024
|
}
|
|
22017
22025
|
|
|
22026
|
+
// src/core/native-indicators/classics/define.ts
|
|
22027
|
+
function num3(inputs, key, fallback) {
|
|
22028
|
+
const v = inputs[key];
|
|
22029
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
22030
|
+
}
|
|
22031
|
+
function str3(inputs, key, fallback) {
|
|
22032
|
+
const v = inputs[key];
|
|
22033
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
22034
|
+
}
|
|
22035
|
+
function toPoints(bars, values, colors) {
|
|
22036
|
+
const pts = new Array(bars.length);
|
|
22037
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22038
|
+
const v = values[i];
|
|
22039
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
22040
|
+
const c = colors?.[i];
|
|
22041
|
+
if (c != null) p.color = c;
|
|
22042
|
+
pts[i] = p;
|
|
22043
|
+
}
|
|
22044
|
+
return pts;
|
|
22045
|
+
}
|
|
22046
|
+
var ClassicIndicator = class {
|
|
22047
|
+
constructor(spec) {
|
|
22048
|
+
this.spec = spec;
|
|
22049
|
+
this.inputs = {};
|
|
22050
|
+
}
|
|
22051
|
+
start(ctx, inputs) {
|
|
22052
|
+
this.ctx = ctx;
|
|
22053
|
+
this.inputs = inputs;
|
|
22054
|
+
this.recompute();
|
|
22055
|
+
}
|
|
22056
|
+
onBars() {
|
|
22057
|
+
this.recompute();
|
|
22058
|
+
}
|
|
22059
|
+
onViewport() {
|
|
22060
|
+
}
|
|
22061
|
+
setInputs(inputs) {
|
|
22062
|
+
this.inputs = inputs;
|
|
22063
|
+
this.recompute();
|
|
22064
|
+
}
|
|
22065
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
22066
|
+
suspend() {
|
|
22067
|
+
}
|
|
22068
|
+
resume() {
|
|
22069
|
+
this.recompute();
|
|
22070
|
+
}
|
|
22071
|
+
stop() {
|
|
22072
|
+
}
|
|
22073
|
+
recompute() {
|
|
22074
|
+
const bars = this.ctx.bars();
|
|
22075
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
22076
|
+
const instanceId = this.spec.type;
|
|
22077
|
+
const series = [];
|
|
22078
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
22079
|
+
out.plots.forEach((plot, ordinal) => {
|
|
22080
|
+
const kind = plot.kind ?? "line";
|
|
22081
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
22082
|
+
idsByKey.set(plot.key, id);
|
|
22083
|
+
series.push({
|
|
22084
|
+
id,
|
|
22085
|
+
title: plot.title,
|
|
22086
|
+
paneId: "",
|
|
22087
|
+
// stamped by the orchestrator
|
|
22088
|
+
kind,
|
|
22089
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
22090
|
+
style: {
|
|
22091
|
+
color: plot.color,
|
|
22092
|
+
width: plot.width ?? 1,
|
|
22093
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
22094
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
22095
|
+
},
|
|
22096
|
+
...plot.overlay ? { overlay: true } : {}
|
|
22097
|
+
});
|
|
22098
|
+
});
|
|
22099
|
+
if (out.markers && out.markers.length > 0) {
|
|
22100
|
+
series.push({
|
|
22101
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
22102
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
22103
|
+
paneId: "",
|
|
22104
|
+
kind: "markers",
|
|
22105
|
+
markers: out.markers
|
|
22106
|
+
});
|
|
22107
|
+
}
|
|
22108
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
22109
|
+
const from = idsByKey.get(band.from);
|
|
22110
|
+
const to = idsByKey.get(band.to);
|
|
22111
|
+
if (!from || !to) return [];
|
|
22112
|
+
return [
|
|
22113
|
+
{
|
|
22114
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
22115
|
+
paneId: "",
|
|
22116
|
+
fromSeriesId: from,
|
|
22117
|
+
toSeriesId: to,
|
|
22118
|
+
color: band.color
|
|
22119
|
+
}
|
|
22120
|
+
];
|
|
22121
|
+
});
|
|
22122
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
22123
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
22124
|
+
paneId: "",
|
|
22125
|
+
price: level.price,
|
|
22126
|
+
...level.color != null ? { color: level.color } : {},
|
|
22127
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
22128
|
+
width: 1,
|
|
22129
|
+
...level.title != null ? { title: level.title } : {}
|
|
22130
|
+
}));
|
|
22131
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
22132
|
+
const { key, ...rest } = poly;
|
|
22133
|
+
return {
|
|
22134
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
22135
|
+
paneId: "",
|
|
22136
|
+
...rest
|
|
22137
|
+
};
|
|
22138
|
+
});
|
|
22139
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
22140
|
+
this.ctx.setStatus("idle");
|
|
22141
|
+
}
|
|
22142
|
+
};
|
|
22143
|
+
function classicDescriptor(spec) {
|
|
22144
|
+
return {
|
|
22145
|
+
type: spec.type,
|
|
22146
|
+
title: spec.title,
|
|
22147
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
22148
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
22149
|
+
overlay: spec.overlay,
|
|
22150
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
22151
|
+
multiInstance: true,
|
|
22152
|
+
inputsSchema: () => spec.inputs,
|
|
22153
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
22154
|
+
create: () => new ClassicIndicator(spec)
|
|
22155
|
+
};
|
|
22156
|
+
}
|
|
22157
|
+
|
|
22158
|
+
// src/core/native-indicators/classics/math.ts
|
|
22159
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
22160
|
+
function sourceValues(bars, source) {
|
|
22161
|
+
const out = new Array(bars.length);
|
|
22162
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22163
|
+
const b = bars[i];
|
|
22164
|
+
switch (source) {
|
|
22165
|
+
case "Open":
|
|
22166
|
+
out[i] = b.open;
|
|
22167
|
+
break;
|
|
22168
|
+
case "High":
|
|
22169
|
+
out[i] = b.high;
|
|
22170
|
+
break;
|
|
22171
|
+
case "Low":
|
|
22172
|
+
out[i] = b.low;
|
|
22173
|
+
break;
|
|
22174
|
+
case "HL2":
|
|
22175
|
+
out[i] = (b.high + b.low) / 2;
|
|
22176
|
+
break;
|
|
22177
|
+
case "HLC3":
|
|
22178
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
22179
|
+
break;
|
|
22180
|
+
case "OHLC4":
|
|
22181
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
22182
|
+
break;
|
|
22183
|
+
case "HLCC4":
|
|
22184
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
22185
|
+
break;
|
|
22186
|
+
default:
|
|
22187
|
+
out[i] = b.close;
|
|
22188
|
+
}
|
|
22189
|
+
}
|
|
22190
|
+
return out;
|
|
22191
|
+
}
|
|
22192
|
+
function volumes(bars) {
|
|
22193
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
22194
|
+
}
|
|
22195
|
+
function highs(bars) {
|
|
22196
|
+
return bars.map((b) => b.high);
|
|
22197
|
+
}
|
|
22198
|
+
function lows(bars) {
|
|
22199
|
+
return bars.map((b) => b.low);
|
|
22200
|
+
}
|
|
22201
|
+
function closes(bars) {
|
|
22202
|
+
return bars.map((b) => b.close);
|
|
22203
|
+
}
|
|
22204
|
+
function opens(bars) {
|
|
22205
|
+
return bars.map((b) => b.open);
|
|
22206
|
+
}
|
|
22207
|
+
function zip(a, b, f) {
|
|
22208
|
+
const out = new Array(a.length);
|
|
22209
|
+
for (let i = 0; i < a.length; i++) {
|
|
22210
|
+
const x = a[i];
|
|
22211
|
+
const y = b[i];
|
|
22212
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
22213
|
+
}
|
|
22214
|
+
return out;
|
|
22215
|
+
}
|
|
22216
|
+
function map(a, f) {
|
|
22217
|
+
const out = new Array(a.length);
|
|
22218
|
+
for (let i = 0; i < a.length; i++) {
|
|
22219
|
+
const x = a[i];
|
|
22220
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
22221
|
+
}
|
|
22222
|
+
return out;
|
|
22223
|
+
}
|
|
22224
|
+
function sub(a, b) {
|
|
22225
|
+
return zip(a, b, (x, y) => x - y);
|
|
22226
|
+
}
|
|
22227
|
+
function windowed(v, len, fold) {
|
|
22228
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22229
|
+
if (len <= 0) return out;
|
|
22230
|
+
let run = 0;
|
|
22231
|
+
for (let i = 0; i < v.length; i++) {
|
|
22232
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
22233
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
22234
|
+
}
|
|
22235
|
+
return out;
|
|
22236
|
+
}
|
|
22237
|
+
function sum(v, len) {
|
|
22238
|
+
return windowed(v, len, (w, from, to) => {
|
|
22239
|
+
let s = 0;
|
|
22240
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22241
|
+
return s;
|
|
22242
|
+
});
|
|
22243
|
+
}
|
|
22244
|
+
function sma(v, len) {
|
|
22245
|
+
return windowed(v, len, (w, from, to) => {
|
|
22246
|
+
let s = 0;
|
|
22247
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22248
|
+
return s / len;
|
|
22249
|
+
});
|
|
22250
|
+
}
|
|
22251
|
+
function wma(v, len) {
|
|
22252
|
+
const norm = len * (len + 1) / 2;
|
|
22253
|
+
return windowed(v, len, (w, from, to) => {
|
|
22254
|
+
let s = 0;
|
|
22255
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
22256
|
+
return s / norm;
|
|
22257
|
+
});
|
|
22258
|
+
}
|
|
22259
|
+
function vwma(src, vol, len) {
|
|
22260
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
22261
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
22262
|
+
}
|
|
22263
|
+
function stdev(v, len) {
|
|
22264
|
+
return windowed(v, len, (w, from, to) => {
|
|
22265
|
+
let s = 0;
|
|
22266
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
22267
|
+
const mean = s / len;
|
|
22268
|
+
let sq = 0;
|
|
22269
|
+
for (let k = from; k <= to; k++) {
|
|
22270
|
+
const d = w[k] - mean;
|
|
22271
|
+
sq += d * d;
|
|
22272
|
+
}
|
|
22273
|
+
return Math.sqrt(sq / len);
|
|
22274
|
+
});
|
|
22275
|
+
}
|
|
22276
|
+
function highest(v, len) {
|
|
22277
|
+
return windowed(v, len, (w, from, to) => {
|
|
22278
|
+
let m = -Infinity;
|
|
22279
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
22280
|
+
return m;
|
|
22281
|
+
});
|
|
22282
|
+
}
|
|
22283
|
+
function lowest(v, len) {
|
|
22284
|
+
return windowed(v, len, (w, from, to) => {
|
|
22285
|
+
let m = Infinity;
|
|
22286
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
22287
|
+
return m;
|
|
22288
|
+
});
|
|
22289
|
+
}
|
|
22290
|
+
function barsSinceHighest(v, len) {
|
|
22291
|
+
return windowed(v, len, (w, from, to) => {
|
|
22292
|
+
let m = -Infinity;
|
|
22293
|
+
let at = to;
|
|
22294
|
+
for (let k = from; k <= to; k++) {
|
|
22295
|
+
if (w[k] >= m) {
|
|
22296
|
+
m = w[k];
|
|
22297
|
+
at = k;
|
|
22298
|
+
}
|
|
22299
|
+
}
|
|
22300
|
+
return to - at;
|
|
22301
|
+
});
|
|
22302
|
+
}
|
|
22303
|
+
function barsSinceLowest(v, len) {
|
|
22304
|
+
return windowed(v, len, (w, from, to) => {
|
|
22305
|
+
let m = Infinity;
|
|
22306
|
+
let at = to;
|
|
22307
|
+
for (let k = from; k <= to; k++) {
|
|
22308
|
+
if (w[k] <= m) {
|
|
22309
|
+
m = w[k];
|
|
22310
|
+
at = k;
|
|
22311
|
+
}
|
|
22312
|
+
}
|
|
22313
|
+
return to - at;
|
|
22314
|
+
});
|
|
22315
|
+
}
|
|
22316
|
+
function recursive(v, len, alpha) {
|
|
22317
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22318
|
+
let run = 0;
|
|
22319
|
+
let prev2 = Number.NaN;
|
|
22320
|
+
for (let i = 0; i < v.length; i++) {
|
|
22321
|
+
const x = v[i];
|
|
22322
|
+
if (!Number.isFinite(x)) {
|
|
22323
|
+
run = 0;
|
|
22324
|
+
prev2 = Number.NaN;
|
|
22325
|
+
continue;
|
|
22326
|
+
}
|
|
22327
|
+
run += 1;
|
|
22328
|
+
if (Number.isFinite(prev2)) {
|
|
22329
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
22330
|
+
out[i] = prev2;
|
|
22331
|
+
} else if (run >= len) {
|
|
22332
|
+
let s = 0;
|
|
22333
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
22334
|
+
prev2 = s / len;
|
|
22335
|
+
out[i] = prev2;
|
|
22336
|
+
}
|
|
22337
|
+
}
|
|
22338
|
+
return out;
|
|
22339
|
+
}
|
|
22340
|
+
function ema(v, len) {
|
|
22341
|
+
return recursive(v, len, 2 / (len + 1));
|
|
22342
|
+
}
|
|
22343
|
+
function rma(v, len) {
|
|
22344
|
+
return recursive(v, len, 1 / len);
|
|
22345
|
+
}
|
|
22346
|
+
function change(v, n = 1) {
|
|
22347
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22348
|
+
for (let i = n; i < v.length; i++) {
|
|
22349
|
+
const a = v[i];
|
|
22350
|
+
const b = v[i - n];
|
|
22351
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
22352
|
+
}
|
|
22353
|
+
return out;
|
|
22354
|
+
}
|
|
22355
|
+
function roc(v, len) {
|
|
22356
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22357
|
+
for (let i = len; i < v.length; i++) {
|
|
22358
|
+
const a = v[i];
|
|
22359
|
+
const b = v[i - len];
|
|
22360
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
22361
|
+
}
|
|
22362
|
+
return out;
|
|
22363
|
+
}
|
|
22364
|
+
function shift(v, n) {
|
|
22365
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22366
|
+
for (let i = 0; i < v.length; i++) {
|
|
22367
|
+
const j = i - n;
|
|
22368
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
22369
|
+
}
|
|
22370
|
+
return out;
|
|
22371
|
+
}
|
|
22372
|
+
function trueRange(bars) {
|
|
22373
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
22374
|
+
for (let i = 0; i < bars.length; i++) {
|
|
22375
|
+
const b = bars[i];
|
|
22376
|
+
if (i === 0) {
|
|
22377
|
+
out[i] = b.high - b.low;
|
|
22378
|
+
continue;
|
|
22379
|
+
}
|
|
22380
|
+
const pc = bars[i - 1].close;
|
|
22381
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
22382
|
+
}
|
|
22383
|
+
return out;
|
|
22384
|
+
}
|
|
22385
|
+
function atr(bars, len) {
|
|
22386
|
+
return rma(trueRange(bars), len);
|
|
22387
|
+
}
|
|
22388
|
+
function rsi(v, len) {
|
|
22389
|
+
const d = change(v);
|
|
22390
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
22391
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
22392
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
22393
|
+
}
|
|
22394
|
+
function stoch(v, hi, lo, len) {
|
|
22395
|
+
const hh = highest(hi, len);
|
|
22396
|
+
const ll = lowest(lo, len);
|
|
22397
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22398
|
+
for (let i = 0; i < v.length; i++) {
|
|
22399
|
+
const x = v[i];
|
|
22400
|
+
const h = hh[i];
|
|
22401
|
+
const l = ll[i];
|
|
22402
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
22403
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
22404
|
+
}
|
|
22405
|
+
return out;
|
|
22406
|
+
}
|
|
22407
|
+
function linreg(v, len) {
|
|
22408
|
+
const sx = (len - 1) * len / 2;
|
|
22409
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
22410
|
+
const denom = len * sxx - sx * sx;
|
|
22411
|
+
return windowed(v, len, (w, from, to) => {
|
|
22412
|
+
let sy = 0;
|
|
22413
|
+
let sxy = 0;
|
|
22414
|
+
for (let k = from; k <= to; k++) {
|
|
22415
|
+
const x = k - from;
|
|
22416
|
+
sy += w[k];
|
|
22417
|
+
sxy += x * w[k];
|
|
22418
|
+
}
|
|
22419
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
22420
|
+
const intercept = (sy - slope * sx) / len;
|
|
22421
|
+
return intercept + slope * (len - 1);
|
|
22422
|
+
});
|
|
22423
|
+
}
|
|
22424
|
+
function swma(v) {
|
|
22425
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22426
|
+
for (let i = 3; i < v.length; i++) {
|
|
22427
|
+
const a = v[i - 3];
|
|
22428
|
+
const b = v[i - 2];
|
|
22429
|
+
const c = v[i - 1];
|
|
22430
|
+
const d = v[i];
|
|
22431
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
22432
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
22433
|
+
}
|
|
22434
|
+
}
|
|
22435
|
+
return out;
|
|
22436
|
+
}
|
|
22437
|
+
function percentRank(v, len) {
|
|
22438
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22439
|
+
for (let i = len; i < v.length; i++) {
|
|
22440
|
+
const x = v[i];
|
|
22441
|
+
if (!Number.isFinite(x)) continue;
|
|
22442
|
+
let below = 0;
|
|
22443
|
+
let count = 0;
|
|
22444
|
+
for (let k = i - len; k < i; k++) {
|
|
22445
|
+
const y = v[k];
|
|
22446
|
+
if (!Number.isFinite(y)) {
|
|
22447
|
+
count = -1;
|
|
22448
|
+
break;
|
|
22449
|
+
}
|
|
22450
|
+
count += 1;
|
|
22451
|
+
if (y <= x) below += 1;
|
|
22452
|
+
}
|
|
22453
|
+
if (count === len) out[i] = 100 * below / len;
|
|
22454
|
+
}
|
|
22455
|
+
return out;
|
|
22456
|
+
}
|
|
22457
|
+
function cumSum(v) {
|
|
22458
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
22459
|
+
let acc = 0;
|
|
22460
|
+
let seen = false;
|
|
22461
|
+
for (let i = 0; i < v.length; i++) {
|
|
22462
|
+
const x = v[i];
|
|
22463
|
+
if (Number.isFinite(x)) {
|
|
22464
|
+
acc += x;
|
|
22465
|
+
seen = true;
|
|
22466
|
+
}
|
|
22467
|
+
if (seen) out[i] = acc;
|
|
22468
|
+
}
|
|
22469
|
+
return out;
|
|
22470
|
+
}
|
|
22471
|
+
|
|
22472
|
+
// src/core/native-indicators/classics/shared.ts
|
|
22473
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
22474
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
22475
|
+
}
|
|
22476
|
+
function sourceInput(defval = "Close") {
|
|
22477
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
22478
|
+
}
|
|
22479
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
22480
|
+
return { key, title, type: "color", defval };
|
|
22481
|
+
}
|
|
22482
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
22483
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
22484
|
+
if (!m) return hex;
|
|
22485
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
22486
|
+
return `#${m[1]}${a}`;
|
|
22487
|
+
}
|
|
22488
|
+
|
|
22489
|
+
// src/core/native-indicators/classics/averages.ts
|
|
22490
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
22491
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
22492
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
22493
|
+
return {
|
|
22494
|
+
type,
|
|
22495
|
+
title,
|
|
22496
|
+
shortTitle,
|
|
22497
|
+
overlay: true,
|
|
22498
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
22499
|
+
compute: (bars, inputs) => {
|
|
22500
|
+
const len = num3(inputs, "length", 20);
|
|
22501
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22502
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22503
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22504
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22505
|
+
}
|
|
22506
|
+
};
|
|
22507
|
+
}
|
|
22508
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
22509
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
22510
|
+
var movingAverage = {
|
|
22511
|
+
type: "moving-average",
|
|
22512
|
+
title: "Moving Average",
|
|
22513
|
+
shortTitle: "MA",
|
|
22514
|
+
overlay: true,
|
|
22515
|
+
inputs: [
|
|
22516
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
22517
|
+
lengthInput(20),
|
|
22518
|
+
sourceInput(),
|
|
22519
|
+
offsetInput,
|
|
22520
|
+
colorInput()
|
|
22521
|
+
],
|
|
22522
|
+
compute: (bars, inputs) => {
|
|
22523
|
+
const len = num3(inputs, "length", 20);
|
|
22524
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22525
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
22526
|
+
let values;
|
|
22527
|
+
switch (kind) {
|
|
22528
|
+
case "EMA":
|
|
22529
|
+
values = ema(src, len);
|
|
22530
|
+
break;
|
|
22531
|
+
case "WMA":
|
|
22532
|
+
values = wma(src, len);
|
|
22533
|
+
break;
|
|
22534
|
+
case "RMA":
|
|
22535
|
+
values = rma(src, len);
|
|
22536
|
+
break;
|
|
22537
|
+
case "VWMA":
|
|
22538
|
+
values = vwma(src, volumes(bars), len);
|
|
22539
|
+
break;
|
|
22540
|
+
default:
|
|
22541
|
+
values = sma(src, len);
|
|
22542
|
+
}
|
|
22543
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
22544
|
+
if (offset !== 0) values = shift(values, offset);
|
|
22545
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22546
|
+
}
|
|
22547
|
+
};
|
|
22548
|
+
var smoothedMa = {
|
|
22549
|
+
type: "rma",
|
|
22550
|
+
title: "Smoothed Moving Average",
|
|
22551
|
+
shortTitle: "RMA",
|
|
22552
|
+
overlay: true,
|
|
22553
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22554
|
+
compute: (bars, inputs) => ({
|
|
22555
|
+
plots: [
|
|
22556
|
+
{
|
|
22557
|
+
key: "rma",
|
|
22558
|
+
title: "RMA",
|
|
22559
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22560
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22561
|
+
width: 2
|
|
22562
|
+
}
|
|
22563
|
+
]
|
|
22564
|
+
})
|
|
22565
|
+
};
|
|
22566
|
+
var zlema = {
|
|
22567
|
+
type: "zlema",
|
|
22568
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
22569
|
+
shortTitle: "ZLEMA",
|
|
22570
|
+
overlay: true,
|
|
22571
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22572
|
+
compute: (bars, inputs) => {
|
|
22573
|
+
const len = num3(inputs, "length", 14);
|
|
22574
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22575
|
+
const lag = Math.floor((len - 1) / 2);
|
|
22576
|
+
const lagged = shift(src, lag);
|
|
22577
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
22578
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22579
|
+
}
|
|
22580
|
+
};
|
|
22581
|
+
var vidya = {
|
|
22582
|
+
type: "vidya",
|
|
22583
|
+
title: "Variable Index Dynamic Average",
|
|
22584
|
+
shortTitle: "VIDYA",
|
|
22585
|
+
overlay: true,
|
|
22586
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
22587
|
+
compute: (bars, inputs) => {
|
|
22588
|
+
const len = num3(inputs, "length", 14);
|
|
22589
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
22590
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22591
|
+
const d = change(src);
|
|
22592
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
22593
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
22594
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
22595
|
+
let u = 0;
|
|
22596
|
+
let w = 0;
|
|
22597
|
+
let ok = true;
|
|
22598
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
22599
|
+
const x = d[k];
|
|
22600
|
+
if (!Number.isFinite(x)) {
|
|
22601
|
+
ok = false;
|
|
22602
|
+
break;
|
|
22603
|
+
}
|
|
22604
|
+
if (x > 0) u += x;
|
|
22605
|
+
else w -= x;
|
|
22606
|
+
}
|
|
22607
|
+
if (ok) {
|
|
22608
|
+
su[i] = u;
|
|
22609
|
+
sd[i] = w;
|
|
22610
|
+
}
|
|
22611
|
+
}
|
|
22612
|
+
const alpha = 2 / (len + 1);
|
|
22613
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
22614
|
+
let prev2 = Number.NaN;
|
|
22615
|
+
for (let i = 0; i < src.length; i++) {
|
|
22616
|
+
const x = src[i];
|
|
22617
|
+
const u = su[i];
|
|
22618
|
+
const w = sd[i];
|
|
22619
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
22620
|
+
const total = u + w;
|
|
22621
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
22622
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
22623
|
+
out[i] = prev2;
|
|
22624
|
+
}
|
|
22625
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22626
|
+
}
|
|
22627
|
+
};
|
|
22628
|
+
var maEnvelope = {
|
|
22629
|
+
type: "ma-envelope",
|
|
22630
|
+
title: "Moving Average Envelope",
|
|
22631
|
+
shortTitle: "MA Env",
|
|
22632
|
+
overlay: true,
|
|
22633
|
+
inputs: [
|
|
22634
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
22635
|
+
lengthInput(20),
|
|
22636
|
+
sourceInput(),
|
|
22637
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
22638
|
+
colorInput()
|
|
22639
|
+
],
|
|
22640
|
+
compute: (bars, inputs) => {
|
|
22641
|
+
const len = num3(inputs, "length", 20);
|
|
22642
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22643
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
22644
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
22645
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22646
|
+
return {
|
|
22647
|
+
plots: [
|
|
22648
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
22649
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
22650
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
22651
|
+
],
|
|
22652
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22653
|
+
};
|
|
22654
|
+
}
|
|
22655
|
+
};
|
|
22656
|
+
var linearRegression = {
|
|
22657
|
+
type: "linear-regression",
|
|
22658
|
+
title: "Linear Regression Curve",
|
|
22659
|
+
shortTitle: "LinReg",
|
|
22660
|
+
overlay: true,
|
|
22661
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22662
|
+
compute: (bars, inputs) => ({
|
|
22663
|
+
plots: [
|
|
22664
|
+
{
|
|
22665
|
+
key: "linreg",
|
|
22666
|
+
title: "LinReg",
|
|
22667
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22668
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22669
|
+
width: 2
|
|
22670
|
+
}
|
|
22671
|
+
]
|
|
22672
|
+
})
|
|
22673
|
+
};
|
|
22674
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
22675
|
+
|
|
22676
|
+
// src/core/native-indicators/classics/bands.ts
|
|
22677
|
+
function bollinger(bars, inputs) {
|
|
22678
|
+
const len = num3(inputs, "length", 20);
|
|
22679
|
+
const mult = num3(inputs, "mult", 2);
|
|
22680
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22681
|
+
const basis = sma(src, len);
|
|
22682
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
22683
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
22684
|
+
}
|
|
22685
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
22686
|
+
var bollingerBands = {
|
|
22687
|
+
type: "bollinger-bands",
|
|
22688
|
+
title: "Bollinger Bands",
|
|
22689
|
+
shortTitle: "BB",
|
|
22690
|
+
overlay: true,
|
|
22691
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22692
|
+
compute: (bars, inputs) => {
|
|
22693
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22694
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22695
|
+
return {
|
|
22696
|
+
plots: [
|
|
22697
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22698
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22699
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22700
|
+
],
|
|
22701
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22702
|
+
};
|
|
22703
|
+
}
|
|
22704
|
+
};
|
|
22705
|
+
var percentB = {
|
|
22706
|
+
type: "percent-b",
|
|
22707
|
+
title: "Bollinger Bands %B",
|
|
22708
|
+
shortTitle: "%B",
|
|
22709
|
+
overlay: false,
|
|
22710
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22711
|
+
compute: (bars, inputs) => {
|
|
22712
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
22713
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
22714
|
+
const values = src.map((x, i) => {
|
|
22715
|
+
const u = upper[i];
|
|
22716
|
+
const l = lower[i];
|
|
22717
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
22718
|
+
});
|
|
22719
|
+
return {
|
|
22720
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
22721
|
+
levels: [
|
|
22722
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
22723
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
22724
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
22725
|
+
]
|
|
22726
|
+
};
|
|
22727
|
+
}
|
|
22728
|
+
};
|
|
22729
|
+
var bandwidth = {
|
|
22730
|
+
type: "bandwidth",
|
|
22731
|
+
title: "Bollinger Bands Width",
|
|
22732
|
+
shortTitle: "BandWidth",
|
|
22733
|
+
overlay: false,
|
|
22734
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
22735
|
+
compute: (bars, inputs) => {
|
|
22736
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
22737
|
+
const values = basis.map((b, i) => {
|
|
22738
|
+
const u = upper[i];
|
|
22739
|
+
const l = lower[i];
|
|
22740
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
22741
|
+
});
|
|
22742
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
22743
|
+
}
|
|
22744
|
+
};
|
|
22745
|
+
var keltner = {
|
|
22746
|
+
type: "keltner-channels",
|
|
22747
|
+
title: "Keltner Channels",
|
|
22748
|
+
shortTitle: "KC",
|
|
22749
|
+
overlay: true,
|
|
22750
|
+
inputs: [
|
|
22751
|
+
lengthInput(20),
|
|
22752
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
22753
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22754
|
+
sourceInput(),
|
|
22755
|
+
colorInput()
|
|
22756
|
+
],
|
|
22757
|
+
compute: (bars, inputs) => {
|
|
22758
|
+
const len = num3(inputs, "length", 20);
|
|
22759
|
+
const mult = num3(inputs, "mult", 2);
|
|
22760
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
22761
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
22762
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22763
|
+
return {
|
|
22764
|
+
plots: [
|
|
22765
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
22766
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
22767
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
22768
|
+
],
|
|
22769
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22770
|
+
};
|
|
22771
|
+
}
|
|
22772
|
+
};
|
|
22773
|
+
var donchian = {
|
|
22774
|
+
type: "donchian-channels",
|
|
22775
|
+
title: "Donchian Channels",
|
|
22776
|
+
shortTitle: "DC",
|
|
22777
|
+
overlay: true,
|
|
22778
|
+
inputs: [lengthInput(20), colorInput()],
|
|
22779
|
+
compute: (bars, inputs) => {
|
|
22780
|
+
const len = num3(inputs, "length", 20);
|
|
22781
|
+
const upper = highest(highs(bars), len);
|
|
22782
|
+
const lower = lowest(lows(bars), len);
|
|
22783
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
22784
|
+
return {
|
|
22785
|
+
plots: [
|
|
22786
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
22787
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
22788
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
22789
|
+
],
|
|
22790
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22793
|
+
};
|
|
22794
|
+
var chandelier = {
|
|
22795
|
+
type: "chandelier-exit",
|
|
22796
|
+
title: "Chandelier Exit",
|
|
22797
|
+
shortTitle: "CE",
|
|
22798
|
+
overlay: true,
|
|
22799
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
22800
|
+
compute: (bars, inputs) => {
|
|
22801
|
+
const len = num3(inputs, "length", 22);
|
|
22802
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
22803
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
22804
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
22805
|
+
return {
|
|
22806
|
+
plots: [
|
|
22807
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
22808
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
22809
|
+
]
|
|
22810
|
+
};
|
|
22811
|
+
}
|
|
22812
|
+
};
|
|
22813
|
+
var chandeKroll = {
|
|
22814
|
+
type: "chande-kroll-stop",
|
|
22815
|
+
title: "Chande Kroll Stop",
|
|
22816
|
+
shortTitle: "CKS",
|
|
22817
|
+
overlay: true,
|
|
22818
|
+
inputs: [
|
|
22819
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22820
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
22821
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
22822
|
+
],
|
|
22823
|
+
compute: (bars, inputs) => {
|
|
22824
|
+
const p = num3(inputs, "p", 10);
|
|
22825
|
+
const x = num3(inputs, "x", 1);
|
|
22826
|
+
const q = num3(inputs, "q", 9);
|
|
22827
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
22828
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
22829
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
22830
|
+
return {
|
|
22831
|
+
plots: [
|
|
22832
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
22833
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
22834
|
+
]
|
|
22835
|
+
};
|
|
22836
|
+
}
|
|
22837
|
+
};
|
|
22838
|
+
var supertrend = {
|
|
22839
|
+
type: "supertrend",
|
|
22840
|
+
title: "SuperTrend",
|
|
22841
|
+
shortTitle: "SuperTrend",
|
|
22842
|
+
overlay: true,
|
|
22843
|
+
inputs: [
|
|
22844
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
22845
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
22846
|
+
],
|
|
22847
|
+
compute: (bars, inputs) => {
|
|
22848
|
+
const len = num3(inputs, "atrLength", 10);
|
|
22849
|
+
const mult = num3(inputs, "mult", 3);
|
|
22850
|
+
const range = atr(bars, len);
|
|
22851
|
+
const n = bars.length;
|
|
22852
|
+
const st = new Array(n).fill(Number.NaN);
|
|
22853
|
+
const dir = new Array(n).fill(1);
|
|
22854
|
+
let up = Number.NaN;
|
|
22855
|
+
let dn = Number.NaN;
|
|
22856
|
+
let trend = 1;
|
|
22857
|
+
for (let i = 0; i < n; i++) {
|
|
22858
|
+
const r = range[i];
|
|
22859
|
+
if (!Number.isFinite(r)) continue;
|
|
22860
|
+
const b = bars[i];
|
|
22861
|
+
const mid = (b.high + b.low) / 2;
|
|
22862
|
+
const basicUp = mid - mult * r;
|
|
22863
|
+
const basicDn = mid + mult * r;
|
|
22864
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
22865
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
22866
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
22867
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
22868
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
22869
|
+
dir[i] = trend;
|
|
22870
|
+
st[i] = trend === 1 ? up : dn;
|
|
22871
|
+
}
|
|
22872
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
22873
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
22874
|
+
}
|
|
22875
|
+
};
|
|
22876
|
+
var alligator = {
|
|
22877
|
+
type: "williams-alligator",
|
|
22878
|
+
title: "Williams Alligator",
|
|
22879
|
+
shortTitle: "Alligator",
|
|
22880
|
+
overlay: true,
|
|
22881
|
+
inputs: [
|
|
22882
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
22883
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
22884
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
22885
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
22886
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
22887
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
22888
|
+
],
|
|
22889
|
+
compute: (bars, inputs) => {
|
|
22890
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22891
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
22892
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
22893
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
22894
|
+
return {
|
|
22895
|
+
plots: [
|
|
22896
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
22897
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
22898
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
22899
|
+
]
|
|
22900
|
+
};
|
|
22901
|
+
}
|
|
22902
|
+
};
|
|
22903
|
+
var gator = {
|
|
22904
|
+
type: "gator-oscillator",
|
|
22905
|
+
title: "Gator Oscillator",
|
|
22906
|
+
shortTitle: "Gator Oscillator",
|
|
22907
|
+
overlay: false,
|
|
22908
|
+
inputs: [],
|
|
22909
|
+
compute: (bars) => {
|
|
22910
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
22911
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
22912
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
22913
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
22914
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
22915
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
22916
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
22917
|
+
if (!Number.isFinite(x)) return null;
|
|
22918
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
22919
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
22920
|
+
});
|
|
22921
|
+
return {
|
|
22922
|
+
plots: [
|
|
22923
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
22924
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
22925
|
+
]
|
|
22926
|
+
};
|
|
22927
|
+
}
|
|
22928
|
+
};
|
|
22929
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
22930
|
+
|
|
22931
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
22932
|
+
function guideLevels(low, high, mid) {
|
|
22933
|
+
const levels = [
|
|
22934
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
22935
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
22936
|
+
];
|
|
22937
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
22938
|
+
return levels;
|
|
22939
|
+
}
|
|
22940
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
22941
|
+
function histColors(values) {
|
|
22942
|
+
return values.map((x, i) => {
|
|
22943
|
+
if (!Number.isFinite(x)) return null;
|
|
22944
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
22945
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
22946
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
22947
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
22948
|
+
});
|
|
22949
|
+
}
|
|
22950
|
+
var rsiSpec = {
|
|
22951
|
+
type: "rsi",
|
|
22952
|
+
title: "Relative Strength Index",
|
|
22953
|
+
shortTitle: "RSI",
|
|
22954
|
+
overlay: false,
|
|
22955
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
22956
|
+
compute: (bars, inputs) => ({
|
|
22957
|
+
plots: [
|
|
22958
|
+
{
|
|
22959
|
+
key: "rsi",
|
|
22960
|
+
title: "RSI",
|
|
22961
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
22962
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
22963
|
+
width: 2
|
|
22964
|
+
}
|
|
22965
|
+
],
|
|
22966
|
+
levels: guideLevels(30, 70, 50)
|
|
22967
|
+
})
|
|
22968
|
+
};
|
|
22969
|
+
var stochastic = {
|
|
22970
|
+
type: "stochastic",
|
|
22971
|
+
title: "Stochastic",
|
|
22972
|
+
shortTitle: "Stoch",
|
|
22973
|
+
overlay: false,
|
|
22974
|
+
inputs: [
|
|
22975
|
+
lengthInput(14, "kLength", "%K length"),
|
|
22976
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
22977
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
22978
|
+
],
|
|
22979
|
+
compute: (bars, inputs) => {
|
|
22980
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
22981
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
22982
|
+
return {
|
|
22983
|
+
plots: [
|
|
22984
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
22985
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
22986
|
+
],
|
|
22987
|
+
levels: guideLevels(20, 80)
|
|
22988
|
+
};
|
|
22989
|
+
}
|
|
22990
|
+
};
|
|
22991
|
+
var stochasticRsi = {
|
|
22992
|
+
type: "stochastic-rsi",
|
|
22993
|
+
title: "Stochastic RSI",
|
|
22994
|
+
shortTitle: "Stoch RSI",
|
|
22995
|
+
overlay: false,
|
|
22996
|
+
inputs: [
|
|
22997
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
22998
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
22999
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
23000
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
23001
|
+
sourceInput()
|
|
23002
|
+
],
|
|
23003
|
+
compute: (bars, inputs) => {
|
|
23004
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
23005
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
23006
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
23007
|
+
return {
|
|
23008
|
+
plots: [
|
|
23009
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
23010
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
23011
|
+
],
|
|
23012
|
+
levels: guideLevels(20, 80)
|
|
23013
|
+
};
|
|
23014
|
+
}
|
|
23015
|
+
};
|
|
23016
|
+
var macd = {
|
|
23017
|
+
type: "macd",
|
|
23018
|
+
title: "MACD",
|
|
23019
|
+
overlay: false,
|
|
23020
|
+
inputs: [
|
|
23021
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23022
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23023
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23024
|
+
sourceInput()
|
|
23025
|
+
],
|
|
23026
|
+
compute: (bars, inputs) => {
|
|
23027
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23028
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
23029
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23030
|
+
const hist = sub(line, signal);
|
|
23031
|
+
return {
|
|
23032
|
+
plots: [
|
|
23033
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23034
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
23035
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23036
|
+
],
|
|
23037
|
+
levels: ZERO_LEVEL
|
|
23038
|
+
};
|
|
23039
|
+
}
|
|
23040
|
+
};
|
|
23041
|
+
var ppo = {
|
|
23042
|
+
type: "ppo",
|
|
23043
|
+
title: "Percentage Price Oscillator",
|
|
23044
|
+
shortTitle: "PPO",
|
|
23045
|
+
overlay: false,
|
|
23046
|
+
inputs: [
|
|
23047
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
23048
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
23049
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
23050
|
+
sourceInput()
|
|
23051
|
+
],
|
|
23052
|
+
compute: (bars, inputs) => {
|
|
23053
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23054
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
23055
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
23056
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
23057
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23058
|
+
const hist = sub(line, signal);
|
|
23059
|
+
return {
|
|
23060
|
+
plots: [
|
|
23061
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
23062
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
23063
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23064
|
+
],
|
|
23065
|
+
levels: ZERO_LEVEL
|
|
23066
|
+
};
|
|
23067
|
+
}
|
|
23068
|
+
};
|
|
23069
|
+
var awesome = {
|
|
23070
|
+
type: "awesome-oscillator",
|
|
23071
|
+
title: "Awesome Oscillator",
|
|
23072
|
+
shortTitle: "AO",
|
|
23073
|
+
overlay: false,
|
|
23074
|
+
inputs: [],
|
|
23075
|
+
compute: (bars) => {
|
|
23076
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23077
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
23078
|
+
const colors = ao.map((x, i) => {
|
|
23079
|
+
if (!Number.isFinite(x)) return null;
|
|
23080
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
23081
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
23082
|
+
});
|
|
23083
|
+
return {
|
|
23084
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
23085
|
+
levels: ZERO_LEVEL
|
|
23086
|
+
};
|
|
23087
|
+
}
|
|
23088
|
+
};
|
|
23089
|
+
var cci = {
|
|
23090
|
+
type: "commodity-channel-index",
|
|
23091
|
+
title: "Commodity Channel Index",
|
|
23092
|
+
shortTitle: "CCI",
|
|
23093
|
+
overlay: false,
|
|
23094
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
23095
|
+
compute: (bars, inputs) => {
|
|
23096
|
+
const len = num3(inputs, "length", 20);
|
|
23097
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
23098
|
+
const basis = sma(src, len);
|
|
23099
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
23100
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
23101
|
+
const mean = basis[i];
|
|
23102
|
+
if (!Number.isFinite(mean)) continue;
|
|
23103
|
+
let d = 0;
|
|
23104
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
23105
|
+
dev[i] = d / len;
|
|
23106
|
+
}
|
|
23107
|
+
const values = src.map((x, i) => {
|
|
23108
|
+
const b = basis[i];
|
|
23109
|
+
const d = dev[i];
|
|
23110
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
23111
|
+
});
|
|
23112
|
+
return {
|
|
23113
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23114
|
+
levels: guideLevels(-100, 100, 0)
|
|
23115
|
+
};
|
|
23116
|
+
}
|
|
23117
|
+
};
|
|
23118
|
+
var williamsR = {
|
|
23119
|
+
type: "williams-percent-r",
|
|
23120
|
+
title: "Williams %R",
|
|
23121
|
+
shortTitle: "%R",
|
|
23122
|
+
overlay: false,
|
|
23123
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23124
|
+
compute: (bars, inputs) => {
|
|
23125
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
23126
|
+
return {
|
|
23127
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23128
|
+
levels: guideLevels(-80, -20)
|
|
23129
|
+
};
|
|
23130
|
+
}
|
|
23131
|
+
};
|
|
23132
|
+
var cmo = {
|
|
23133
|
+
type: "chande-momentum-oscillator",
|
|
23134
|
+
title: "Chande Momentum Oscillator",
|
|
23135
|
+
shortTitle: "CMO",
|
|
23136
|
+
overlay: false,
|
|
23137
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23138
|
+
compute: (bars, inputs) => {
|
|
23139
|
+
const len = num3(inputs, "length", 9);
|
|
23140
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
23141
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
23142
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
23143
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
23144
|
+
return {
|
|
23145
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23146
|
+
levels: guideLevels(-50, 50, 0)
|
|
23147
|
+
};
|
|
23148
|
+
}
|
|
23149
|
+
};
|
|
23150
|
+
var connorsRsi = {
|
|
23151
|
+
type: "connors-rsi",
|
|
23152
|
+
title: "Connors RSI",
|
|
23153
|
+
shortTitle: "CRSI",
|
|
23154
|
+
overlay: false,
|
|
23155
|
+
inputs: [
|
|
23156
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
23157
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
23158
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
23159
|
+
colorInput()
|
|
23160
|
+
],
|
|
23161
|
+
compute: (bars, inputs) => {
|
|
23162
|
+
const c = closes(bars);
|
|
23163
|
+
const streak = new Array(c.length).fill(0);
|
|
23164
|
+
for (let i = 1; i < c.length; i++) {
|
|
23165
|
+
const d = c[i] - c[i - 1];
|
|
23166
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
23167
|
+
}
|
|
23168
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
23169
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
23170
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
23171
|
+
const values = c.map((_, i) => {
|
|
23172
|
+
const x = a[i];
|
|
23173
|
+
const y = b[i];
|
|
23174
|
+
const z = r[i];
|
|
23175
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
23176
|
+
});
|
|
23177
|
+
return {
|
|
23178
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23179
|
+
levels: guideLevels(20, 80, 50)
|
|
23180
|
+
};
|
|
23181
|
+
}
|
|
23182
|
+
};
|
|
23183
|
+
var fisher = {
|
|
23184
|
+
type: "fisher-transform",
|
|
23185
|
+
title: "Fisher Transform",
|
|
23186
|
+
shortTitle: "Fisher",
|
|
23187
|
+
overlay: false,
|
|
23188
|
+
inputs: [lengthInput(9)],
|
|
23189
|
+
compute: (bars, inputs) => {
|
|
23190
|
+
const len = num3(inputs, "length", 9);
|
|
23191
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23192
|
+
const hi = highest(hl2, len);
|
|
23193
|
+
const lo = lowest(hl2, len);
|
|
23194
|
+
const n = bars.length;
|
|
23195
|
+
const out = new Array(n).fill(Number.NaN);
|
|
23196
|
+
let value = 0;
|
|
23197
|
+
let fish = 0;
|
|
23198
|
+
for (let i = 0; i < n; i++) {
|
|
23199
|
+
const h = hi[i];
|
|
23200
|
+
const l = lo[i];
|
|
23201
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
23202
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
23203
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
23204
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
23205
|
+
out[i] = fish;
|
|
23206
|
+
}
|
|
23207
|
+
return {
|
|
23208
|
+
plots: [
|
|
23209
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
23210
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
23211
|
+
],
|
|
23212
|
+
levels: ZERO_LEVEL
|
|
23213
|
+
};
|
|
23214
|
+
}
|
|
23215
|
+
};
|
|
23216
|
+
var trix = {
|
|
23217
|
+
type: "trix",
|
|
23218
|
+
title: "TRIX",
|
|
23219
|
+
overlay: false,
|
|
23220
|
+
inputs: [lengthInput(18), colorInput()],
|
|
23221
|
+
compute: (bars, inputs) => {
|
|
23222
|
+
const len = num3(inputs, "length", 18);
|
|
23223
|
+
const logClose = map(closes(bars), Math.log);
|
|
23224
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
23225
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
23226
|
+
return {
|
|
23227
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23228
|
+
levels: ZERO_LEVEL
|
|
23229
|
+
};
|
|
23230
|
+
}
|
|
23231
|
+
};
|
|
23232
|
+
function tsiLine(src, long, short) {
|
|
23233
|
+
const mom = change(src);
|
|
23234
|
+
const numer = ema(ema(mom, long), short);
|
|
23235
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
23236
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
23237
|
+
}
|
|
23238
|
+
var tsi = {
|
|
23239
|
+
type: "true-strength-index",
|
|
23240
|
+
title: "True Strength Index",
|
|
23241
|
+
shortTitle: "TSI",
|
|
23242
|
+
overlay: false,
|
|
23243
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23244
|
+
compute: (bars, inputs) => {
|
|
23245
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
23246
|
+
return {
|
|
23247
|
+
plots: [
|
|
23248
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
23249
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23250
|
+
],
|
|
23251
|
+
levels: ZERO_LEVEL
|
|
23252
|
+
};
|
|
23253
|
+
}
|
|
23254
|
+
};
|
|
23255
|
+
var smiErgodic = {
|
|
23256
|
+
type: "smi-ergodic",
|
|
23257
|
+
title: "SMI Ergodic Oscillator",
|
|
23258
|
+
shortTitle: "SMIE",
|
|
23259
|
+
overlay: false,
|
|
23260
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
23261
|
+
compute: (bars, inputs) => {
|
|
23262
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
23263
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
23264
|
+
const osc = sub(line, signal);
|
|
23265
|
+
return {
|
|
23266
|
+
plots: [
|
|
23267
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
23268
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
23269
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23270
|
+
],
|
|
23271
|
+
levels: ZERO_LEVEL
|
|
23272
|
+
};
|
|
23273
|
+
}
|
|
23274
|
+
};
|
|
23275
|
+
var stc = {
|
|
23276
|
+
type: "schaff-trend-cycle",
|
|
23277
|
+
title: "Schaff Trend Cycle",
|
|
23278
|
+
shortTitle: "Schaff Trend Cycle",
|
|
23279
|
+
overlay: false,
|
|
23280
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
23281
|
+
compute: (bars, inputs) => {
|
|
23282
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
23283
|
+
const src = closes(bars);
|
|
23284
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
23285
|
+
const stochOf = (v) => {
|
|
23286
|
+
const lo = lowest(v, len);
|
|
23287
|
+
const hi = highest(v, len);
|
|
23288
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
23289
|
+
let prevRaw = Number.NaN;
|
|
23290
|
+
let smoothed = Number.NaN;
|
|
23291
|
+
for (let i = 0; i < v.length; i++) {
|
|
23292
|
+
const x = v[i];
|
|
23293
|
+
const l = lo[i];
|
|
23294
|
+
const h = hi[i];
|
|
23295
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
23296
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
23297
|
+
prevRaw = raw;
|
|
23298
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
23299
|
+
out[i] = smoothed;
|
|
23300
|
+
}
|
|
23301
|
+
return out;
|
|
23302
|
+
};
|
|
23303
|
+
const values = stochOf(stochOf(macdLine));
|
|
23304
|
+
return {
|
|
23305
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
23306
|
+
levels: guideLevels(25, 75)
|
|
23307
|
+
};
|
|
23308
|
+
}
|
|
23309
|
+
};
|
|
23310
|
+
var kst = {
|
|
23311
|
+
type: "know-sure-thing",
|
|
23312
|
+
title: "Know Sure Thing",
|
|
23313
|
+
shortTitle: "KST",
|
|
23314
|
+
overlay: false,
|
|
23315
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
23316
|
+
compute: (bars, inputs) => {
|
|
23317
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23318
|
+
const line = [
|
|
23319
|
+
sma(roc(src, 10), 10),
|
|
23320
|
+
sma(roc(src, 15), 10),
|
|
23321
|
+
sma(roc(src, 20), 10),
|
|
23322
|
+
sma(roc(src, 30), 15)
|
|
23323
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
23324
|
+
return {
|
|
23325
|
+
plots: [
|
|
23326
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
23327
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
23328
|
+
],
|
|
23329
|
+
levels: ZERO_LEVEL
|
|
23330
|
+
};
|
|
23331
|
+
}
|
|
23332
|
+
};
|
|
23333
|
+
var coppock = {
|
|
23334
|
+
type: "coppock-curve",
|
|
23335
|
+
title: "Coppock Curve",
|
|
23336
|
+
shortTitle: "Coppock",
|
|
23337
|
+
overlay: false,
|
|
23338
|
+
inputs: [
|
|
23339
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
23340
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
23341
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
23342
|
+
sourceInput(),
|
|
23343
|
+
colorInput()
|
|
23344
|
+
],
|
|
23345
|
+
compute: (bars, inputs) => {
|
|
23346
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23347
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
23348
|
+
return {
|
|
23349
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23350
|
+
levels: ZERO_LEVEL
|
|
23351
|
+
};
|
|
23352
|
+
}
|
|
23353
|
+
};
|
|
23354
|
+
var dpo = {
|
|
23355
|
+
type: "detrended-price-oscillator",
|
|
23356
|
+
title: "Detrended Price Oscillator",
|
|
23357
|
+
shortTitle: "DPO",
|
|
23358
|
+
overlay: false,
|
|
23359
|
+
inputs: [lengthInput(21), colorInput()],
|
|
23360
|
+
compute: (bars, inputs) => {
|
|
23361
|
+
const len = num3(inputs, "length", 21);
|
|
23362
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
23363
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
23364
|
+
return {
|
|
23365
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23366
|
+
levels: ZERO_LEVEL
|
|
23367
|
+
};
|
|
23368
|
+
}
|
|
23369
|
+
};
|
|
23370
|
+
var rocSpec = {
|
|
23371
|
+
type: "rate-of-change",
|
|
23372
|
+
title: "Rate of Change",
|
|
23373
|
+
shortTitle: "ROC",
|
|
23374
|
+
overlay: false,
|
|
23375
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
23376
|
+
compute: (bars, inputs) => ({
|
|
23377
|
+
plots: [
|
|
23378
|
+
{
|
|
23379
|
+
key: "roc",
|
|
23380
|
+
title: "RoC",
|
|
23381
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
23382
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23383
|
+
width: 2
|
|
23384
|
+
}
|
|
23385
|
+
],
|
|
23386
|
+
levels: ZERO_LEVEL
|
|
23387
|
+
})
|
|
23388
|
+
};
|
|
23389
|
+
var ultimate = {
|
|
23390
|
+
type: "ultimate-oscillator",
|
|
23391
|
+
title: "Ultimate Oscillator",
|
|
23392
|
+
shortTitle: "UO",
|
|
23393
|
+
overlay: false,
|
|
23394
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
23395
|
+
compute: (bars, inputs) => {
|
|
23396
|
+
const n = bars.length;
|
|
23397
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
23398
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
23399
|
+
for (let i = 0; i < n; i++) {
|
|
23400
|
+
const b = bars[i];
|
|
23401
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
23402
|
+
const lo = Math.min(b.low, pc);
|
|
23403
|
+
const hi = Math.max(b.high, pc);
|
|
23404
|
+
bp[i] = b.close - lo;
|
|
23405
|
+
tr[i] = hi - lo;
|
|
23406
|
+
}
|
|
23407
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23408
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
23409
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
23410
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
23411
|
+
const values = a7.map((x, i) => {
|
|
23412
|
+
const y = a14[i];
|
|
23413
|
+
const z = a28[i];
|
|
23414
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
23415
|
+
});
|
|
23416
|
+
return {
|
|
23417
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23418
|
+
levels: guideLevels(30, 70, 50)
|
|
23419
|
+
};
|
|
23420
|
+
}
|
|
23421
|
+
};
|
|
23422
|
+
var rvgi = {
|
|
23423
|
+
type: "relative-vigor-index",
|
|
23424
|
+
title: "Relative Vigor Index",
|
|
23425
|
+
shortTitle: "RVGI",
|
|
23426
|
+
overlay: false,
|
|
23427
|
+
inputs: [lengthInput(10)],
|
|
23428
|
+
compute: (bars, inputs) => {
|
|
23429
|
+
const len = num3(inputs, "length", 10);
|
|
23430
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
23431
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
23432
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
23433
|
+
return {
|
|
23434
|
+
plots: [
|
|
23435
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
23436
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
23437
|
+
],
|
|
23438
|
+
levels: ZERO_LEVEL
|
|
23439
|
+
};
|
|
23440
|
+
}
|
|
23441
|
+
};
|
|
23442
|
+
var relVolatility = {
|
|
23443
|
+
type: "relative-volatility-index",
|
|
23444
|
+
title: "Relative Volatility Index",
|
|
23445
|
+
shortTitle: "RVI",
|
|
23446
|
+
overlay: false,
|
|
23447
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
23448
|
+
compute: (bars, inputs) => {
|
|
23449
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
23450
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
23451
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
23452
|
+
const d = change(src);
|
|
23453
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
23454
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
23455
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
23456
|
+
return {
|
|
23457
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23458
|
+
levels: guideLevels(20, 80, 50)
|
|
23459
|
+
};
|
|
23460
|
+
}
|
|
23461
|
+
};
|
|
23462
|
+
var bop = {
|
|
23463
|
+
type: "balance-of-power",
|
|
23464
|
+
title: "Balance of Power",
|
|
23465
|
+
shortTitle: "BOP",
|
|
23466
|
+
overlay: false,
|
|
23467
|
+
inputs: [colorInput()],
|
|
23468
|
+
compute: (bars, inputs) => {
|
|
23469
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
23470
|
+
return {
|
|
23471
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23472
|
+
levels: ZERO_LEVEL
|
|
23473
|
+
};
|
|
23474
|
+
}
|
|
23475
|
+
};
|
|
23476
|
+
var elderRay = {
|
|
23477
|
+
type: "elder-ray",
|
|
23478
|
+
title: "Elder Ray",
|
|
23479
|
+
overlay: false,
|
|
23480
|
+
inputs: [lengthInput(13)],
|
|
23481
|
+
compute: (bars, inputs) => {
|
|
23482
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
23483
|
+
return {
|
|
23484
|
+
plots: [
|
|
23485
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
23486
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
23487
|
+
],
|
|
23488
|
+
levels: ZERO_LEVEL
|
|
23489
|
+
};
|
|
23490
|
+
}
|
|
23491
|
+
};
|
|
23492
|
+
var ttmSqueeze = {
|
|
23493
|
+
type: "ttm-squeeze",
|
|
23494
|
+
title: "TTM Squeeze",
|
|
23495
|
+
overlay: false,
|
|
23496
|
+
inputs: [
|
|
23497
|
+
lengthInput(20),
|
|
23498
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
23499
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
23500
|
+
],
|
|
23501
|
+
compute: (bars, inputs) => {
|
|
23502
|
+
const len = num3(inputs, "length", 20);
|
|
23503
|
+
const c = closes(bars);
|
|
23504
|
+
const basis = sma(c, len);
|
|
23505
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
23506
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
23507
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
23508
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
23509
|
+
const mom = linreg(sub(c, anchor), len);
|
|
23510
|
+
const squeezed = c.map((_, i) => {
|
|
23511
|
+
const d = dev[i];
|
|
23512
|
+
const r = kcRange[i];
|
|
23513
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
23514
|
+
});
|
|
23515
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
23516
|
+
return {
|
|
23517
|
+
plots: [
|
|
23518
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
23519
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
23520
|
+
]
|
|
23521
|
+
};
|
|
23522
|
+
}
|
|
23523
|
+
};
|
|
23524
|
+
var oscillatorSpecs = [
|
|
23525
|
+
rsiSpec,
|
|
23526
|
+
stochastic,
|
|
23527
|
+
stochasticRsi,
|
|
23528
|
+
macd,
|
|
23529
|
+
ppo,
|
|
23530
|
+
awesome,
|
|
23531
|
+
cci,
|
|
23532
|
+
williamsR,
|
|
23533
|
+
cmo,
|
|
23534
|
+
connorsRsi,
|
|
23535
|
+
fisher,
|
|
23536
|
+
trix,
|
|
23537
|
+
tsi,
|
|
23538
|
+
smiErgodic,
|
|
23539
|
+
stc,
|
|
23540
|
+
kst,
|
|
23541
|
+
coppock,
|
|
23542
|
+
dpo,
|
|
23543
|
+
rocSpec,
|
|
23544
|
+
ultimate,
|
|
23545
|
+
rvgi,
|
|
23546
|
+
relVolatility,
|
|
23547
|
+
bop,
|
|
23548
|
+
elderRay,
|
|
23549
|
+
ttmSqueeze
|
|
23550
|
+
];
|
|
23551
|
+
|
|
23552
|
+
// src/core/native-indicators/classics/trend.ts
|
|
23553
|
+
var aroon = {
|
|
23554
|
+
type: "aroon",
|
|
23555
|
+
title: "Aroon",
|
|
23556
|
+
overlay: false,
|
|
23557
|
+
inputs: [lengthInput(14)],
|
|
23558
|
+
compute: (bars, inputs) => {
|
|
23559
|
+
const len = num3(inputs, "length", 14);
|
|
23560
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23561
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
23562
|
+
return {
|
|
23563
|
+
plots: [
|
|
23564
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
23565
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
23566
|
+
],
|
|
23567
|
+
levels: [
|
|
23568
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
23569
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
23570
|
+
]
|
|
23571
|
+
};
|
|
23572
|
+
}
|
|
23573
|
+
};
|
|
23574
|
+
var adx = {
|
|
23575
|
+
type: "average-directional-index",
|
|
23576
|
+
title: "Average Directional Index",
|
|
23577
|
+
shortTitle: "ADX",
|
|
23578
|
+
overlay: false,
|
|
23579
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
23580
|
+
compute: (bars, inputs) => {
|
|
23581
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
23582
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
23583
|
+
const n = bars.length;
|
|
23584
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
23585
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
23586
|
+
for (let i = 1; i < n; i++) {
|
|
23587
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
23588
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
23589
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
23590
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
23591
|
+
}
|
|
23592
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
23593
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23594
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
23595
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
23596
|
+
return {
|
|
23597
|
+
plots: [
|
|
23598
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
23599
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
23600
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
23601
|
+
],
|
|
23602
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23603
|
+
};
|
|
23604
|
+
}
|
|
23605
|
+
};
|
|
23606
|
+
var vortex = {
|
|
23607
|
+
type: "vortex-indicator",
|
|
23608
|
+
title: "Vortex Indicator",
|
|
23609
|
+
shortTitle: "VI",
|
|
23610
|
+
overlay: false,
|
|
23611
|
+
inputs: [lengthInput(14)],
|
|
23612
|
+
compute: (bars, inputs) => {
|
|
23613
|
+
const len = num3(inputs, "length", 14);
|
|
23614
|
+
const n = bars.length;
|
|
23615
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
23616
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
23617
|
+
for (let i = 1; i < n; i++) {
|
|
23618
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
23619
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
23620
|
+
}
|
|
23621
|
+
const trSum = sum(trueRange(bars), len);
|
|
23622
|
+
return {
|
|
23623
|
+
plots: [
|
|
23624
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
23625
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
23626
|
+
],
|
|
23627
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23628
|
+
};
|
|
23629
|
+
}
|
|
23630
|
+
};
|
|
23631
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
23632
|
+
|
|
23633
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
23634
|
+
var atrSpec = {
|
|
23635
|
+
type: "average-true-range",
|
|
23636
|
+
title: "Average True Range",
|
|
23637
|
+
shortTitle: "ATR",
|
|
23638
|
+
overlay: false,
|
|
23639
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23640
|
+
compute: (bars, inputs) => ({
|
|
23641
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23642
|
+
})
|
|
23643
|
+
};
|
|
23644
|
+
var historicalVolatility = {
|
|
23645
|
+
type: "historical-volatility",
|
|
23646
|
+
title: "Historical Volatility",
|
|
23647
|
+
shortTitle: "HV",
|
|
23648
|
+
overlay: false,
|
|
23649
|
+
inputs: [
|
|
23650
|
+
lengthInput(10),
|
|
23651
|
+
{ key: "annual", title: "Periods per year", type: "int", defval: 365, min: 1, max: 1e5, step: 1, tooltip: "Annualization factor (365 for daily crypto bars, 252 for stock sessions)" },
|
|
23652
|
+
colorInput()
|
|
23653
|
+
],
|
|
23654
|
+
compute: (bars, inputs) => {
|
|
23655
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
23656
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
23657
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23658
|
+
}
|
|
23659
|
+
};
|
|
23660
|
+
var chaikinVolatility = {
|
|
23661
|
+
type: "chaikin-volatility",
|
|
23662
|
+
title: "Chaikin Volatility",
|
|
23663
|
+
shortTitle: "CHV",
|
|
23664
|
+
overlay: false,
|
|
23665
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
23666
|
+
compute: (bars, inputs) => {
|
|
23667
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
23668
|
+
return {
|
|
23669
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23670
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23671
|
+
};
|
|
23672
|
+
}
|
|
23673
|
+
};
|
|
23674
|
+
var massIndex = {
|
|
23675
|
+
type: "mass-index",
|
|
23676
|
+
title: "Mass Index",
|
|
23677
|
+
shortTitle: "MI",
|
|
23678
|
+
overlay: false,
|
|
23679
|
+
inputs: [lengthInput(25), colorInput()],
|
|
23680
|
+
compute: (bars, inputs) => {
|
|
23681
|
+
const range = sub(highs(bars), lows(bars));
|
|
23682
|
+
const single = ema(range, 9);
|
|
23683
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
23684
|
+
return {
|
|
23685
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23686
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
23687
|
+
};
|
|
23688
|
+
}
|
|
23689
|
+
};
|
|
23690
|
+
var standardDeviation = {
|
|
23691
|
+
type: "standard-deviation",
|
|
23692
|
+
title: "Standard Deviation",
|
|
23693
|
+
shortTitle: "StdDev",
|
|
23694
|
+
overlay: false,
|
|
23695
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
23696
|
+
compute: (bars, inputs) => ({
|
|
23697
|
+
plots: [
|
|
23698
|
+
{
|
|
23699
|
+
key: "stdev",
|
|
23700
|
+
title: "StdDev",
|
|
23701
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
23702
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
23703
|
+
width: 2
|
|
23704
|
+
}
|
|
23705
|
+
]
|
|
23706
|
+
})
|
|
23707
|
+
};
|
|
23708
|
+
var ulcerIndex = {
|
|
23709
|
+
type: "ulcer-index",
|
|
23710
|
+
title: "Ulcer Index",
|
|
23711
|
+
overlay: false,
|
|
23712
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23713
|
+
compute: (bars, inputs) => {
|
|
23714
|
+
const len = num3(inputs, "length", 14);
|
|
23715
|
+
const c = closes(bars);
|
|
23716
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
23717
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
23718
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23719
|
+
}
|
|
23720
|
+
};
|
|
23721
|
+
var choppiness = {
|
|
23722
|
+
type: "choppiness-index",
|
|
23723
|
+
title: "Choppiness Index",
|
|
23724
|
+
shortTitle: "CHOP",
|
|
23725
|
+
overlay: false,
|
|
23726
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23727
|
+
compute: (bars, inputs) => {
|
|
23728
|
+
const len = num3(inputs, "length", 14);
|
|
23729
|
+
const trSum = sum(trueRange(bars), len);
|
|
23730
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
23731
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
23732
|
+
return {
|
|
23733
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23734
|
+
levels: [
|
|
23735
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
23736
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
23737
|
+
]
|
|
23738
|
+
};
|
|
23739
|
+
}
|
|
23740
|
+
};
|
|
23741
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
23742
|
+
|
|
23743
|
+
// src/core/native-indicators/classics/volume.ts
|
|
23744
|
+
function moneyFlowVolume(bars) {
|
|
23745
|
+
return bars.map((b) => {
|
|
23746
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
23747
|
+
if (b.high === b.low) return 0;
|
|
23748
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
23749
|
+
});
|
|
23750
|
+
}
|
|
23751
|
+
var obv = {
|
|
23752
|
+
type: "on-balance-volume",
|
|
23753
|
+
title: "On Balance Volume",
|
|
23754
|
+
shortTitle: "OBV",
|
|
23755
|
+
overlay: false,
|
|
23756
|
+
inputs: [colorInput()],
|
|
23757
|
+
compute: (bars, inputs) => {
|
|
23758
|
+
const vol = volumes(bars);
|
|
23759
|
+
const d = change(closes(bars));
|
|
23760
|
+
const signed2 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
23761
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed2), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23762
|
+
}
|
|
23763
|
+
};
|
|
23764
|
+
var accDist = {
|
|
23765
|
+
type: "accumulation-distribution",
|
|
23766
|
+
title: "Accumulation / Distribution",
|
|
23767
|
+
shortTitle: "A/D",
|
|
23768
|
+
overlay: false,
|
|
23769
|
+
inputs: [colorInput()],
|
|
23770
|
+
compute: (bars, inputs) => ({
|
|
23771
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
23772
|
+
})
|
|
23773
|
+
};
|
|
23774
|
+
var cmf = {
|
|
23775
|
+
type: "chaikin-money-flow",
|
|
23776
|
+
title: "Chaikin Money Flow",
|
|
23777
|
+
shortTitle: "CMF",
|
|
23778
|
+
overlay: false,
|
|
23779
|
+
inputs: [lengthInput(20), colorInput()],
|
|
23780
|
+
compute: (bars, inputs) => {
|
|
23781
|
+
const len = num3(inputs, "length", 20);
|
|
23782
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
23783
|
+
return {
|
|
23784
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23785
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23786
|
+
};
|
|
23787
|
+
}
|
|
23788
|
+
};
|
|
23789
|
+
var chaikinOscillator = {
|
|
23790
|
+
type: "chaikin-oscillator",
|
|
23791
|
+
title: "Chaikin Oscillator",
|
|
23792
|
+
shortTitle: "Chaikin Osc",
|
|
23793
|
+
overlay: false,
|
|
23794
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
23795
|
+
compute: (bars, inputs) => {
|
|
23796
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
23797
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
23798
|
+
return {
|
|
23799
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23800
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23801
|
+
};
|
|
23802
|
+
}
|
|
23803
|
+
};
|
|
23804
|
+
var eom = {
|
|
23805
|
+
type: "ease-of-movement",
|
|
23806
|
+
title: "Ease of Movement",
|
|
23807
|
+
shortTitle: "EOM",
|
|
23808
|
+
overlay: false,
|
|
23809
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
23810
|
+
compute: (bars, inputs) => {
|
|
23811
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
23812
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
23813
|
+
const move = change(hl2);
|
|
23814
|
+
const raw = bars.map((b, i) => {
|
|
23815
|
+
const m = move[i];
|
|
23816
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
23817
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
23818
|
+
});
|
|
23819
|
+
return {
|
|
23820
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23821
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23822
|
+
};
|
|
23823
|
+
}
|
|
23824
|
+
};
|
|
23825
|
+
var forceIndex = {
|
|
23826
|
+
type: "force-index",
|
|
23827
|
+
title: "Force Index",
|
|
23828
|
+
shortTitle: "FI",
|
|
23829
|
+
overlay: false,
|
|
23830
|
+
inputs: [lengthInput(13), colorInput()],
|
|
23831
|
+
compute: (bars, inputs) => {
|
|
23832
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
23833
|
+
return {
|
|
23834
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23835
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23836
|
+
};
|
|
23837
|
+
}
|
|
23838
|
+
};
|
|
23839
|
+
var klinger = {
|
|
23840
|
+
type: "klinger-oscillator",
|
|
23841
|
+
title: "Klinger Oscillator",
|
|
23842
|
+
shortTitle: "KVO",
|
|
23843
|
+
overlay: false,
|
|
23844
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
23845
|
+
compute: (bars, inputs) => {
|
|
23846
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
23847
|
+
const trend = change(hlc3);
|
|
23848
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
23849
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
23850
|
+
return {
|
|
23851
|
+
plots: [
|
|
23852
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
23853
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
23854
|
+
],
|
|
23855
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23856
|
+
};
|
|
23857
|
+
}
|
|
23858
|
+
};
|
|
23859
|
+
var mfi = {
|
|
23860
|
+
type: "money-flow-index",
|
|
23861
|
+
title: "Money Flow Index",
|
|
23862
|
+
shortTitle: "MFI",
|
|
23863
|
+
overlay: false,
|
|
23864
|
+
inputs: [lengthInput(14), colorInput()],
|
|
23865
|
+
compute: (bars, inputs) => {
|
|
23866
|
+
const len = num3(inputs, "length", 14);
|
|
23867
|
+
const tp = sourceValues(bars, "HLC3");
|
|
23868
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
23869
|
+
const d = change(tp);
|
|
23870
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
23871
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
23872
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
23873
|
+
return {
|
|
23874
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23875
|
+
levels: [
|
|
23876
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
23877
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
23878
|
+
]
|
|
23879
|
+
};
|
|
23880
|
+
}
|
|
23881
|
+
};
|
|
23882
|
+
function volumeIndex(bars, when) {
|
|
23883
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
23884
|
+
let index = 1e3;
|
|
23885
|
+
let started = false;
|
|
23886
|
+
for (let i = 1; i < bars.length; i++) {
|
|
23887
|
+
const v = bars[i].volume;
|
|
23888
|
+
const pv = bars[i - 1].volume;
|
|
23889
|
+
const pc = bars[i - 1].close;
|
|
23890
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
23891
|
+
if (started) out[i] = index;
|
|
23892
|
+
continue;
|
|
23893
|
+
}
|
|
23894
|
+
started = true;
|
|
23895
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
23896
|
+
out[i] = index;
|
|
23897
|
+
}
|
|
23898
|
+
return out;
|
|
23899
|
+
}
|
|
23900
|
+
var nvi = {
|
|
23901
|
+
type: "negative-volume-index",
|
|
23902
|
+
title: "Negative Volume Index",
|
|
23903
|
+
shortTitle: "Negative Volume Index",
|
|
23904
|
+
overlay: false,
|
|
23905
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23906
|
+
compute: (bars, inputs) => {
|
|
23907
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
23908
|
+
return {
|
|
23909
|
+
plots: [
|
|
23910
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
23911
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
23912
|
+
]
|
|
23913
|
+
};
|
|
23914
|
+
}
|
|
23915
|
+
};
|
|
23916
|
+
var pvi = {
|
|
23917
|
+
type: "positive-volume-index",
|
|
23918
|
+
title: "Positive Volume Index",
|
|
23919
|
+
shortTitle: "Positive Volume Index",
|
|
23920
|
+
overlay: false,
|
|
23921
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
23922
|
+
compute: (bars, inputs) => {
|
|
23923
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
23924
|
+
return {
|
|
23925
|
+
plots: [
|
|
23926
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
23927
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
23928
|
+
]
|
|
23929
|
+
};
|
|
23930
|
+
}
|
|
23931
|
+
};
|
|
23932
|
+
var pvt = {
|
|
23933
|
+
type: "price-volume-trend",
|
|
23934
|
+
title: "Price Volume Trend",
|
|
23935
|
+
shortTitle: "PVT",
|
|
23936
|
+
overlay: false,
|
|
23937
|
+
inputs: [colorInput()],
|
|
23938
|
+
compute: (bars, inputs) => {
|
|
23939
|
+
const c = closes(bars);
|
|
23940
|
+
const raw = bars.map((b, i) => {
|
|
23941
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
23942
|
+
const pc = c[i - 1];
|
|
23943
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
23944
|
+
});
|
|
23945
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
23946
|
+
}
|
|
23947
|
+
};
|
|
23948
|
+
var volumeOscillator = {
|
|
23949
|
+
type: "volume-oscillator",
|
|
23950
|
+
title: "Volume Oscillator",
|
|
23951
|
+
shortTitle: "Vol Osc",
|
|
23952
|
+
overlay: false,
|
|
23953
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
23954
|
+
compute: (bars, inputs) => {
|
|
23955
|
+
const vol = volumes(bars);
|
|
23956
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
23957
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
23958
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
23959
|
+
return {
|
|
23960
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
23961
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23962
|
+
};
|
|
23963
|
+
}
|
|
23964
|
+
};
|
|
23965
|
+
var pvo = {
|
|
23966
|
+
type: "pvo",
|
|
23967
|
+
title: "Percentage Volume Oscillator",
|
|
23968
|
+
shortTitle: "PVO",
|
|
23969
|
+
overlay: false,
|
|
23970
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
23971
|
+
compute: (bars, inputs) => {
|
|
23972
|
+
const vol = volumes(bars);
|
|
23973
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
23974
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
23975
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
23976
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
23977
|
+
const hist = sub(line, signal);
|
|
23978
|
+
return {
|
|
23979
|
+
plots: [
|
|
23980
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
23981
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
23982
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
23983
|
+
],
|
|
23984
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
23985
|
+
};
|
|
23986
|
+
}
|
|
23987
|
+
};
|
|
23988
|
+
var vfi = {
|
|
23989
|
+
type: "volume-flow-indicator",
|
|
23990
|
+
title: "Volume Flow Indicator",
|
|
23991
|
+
shortTitle: "Volume Flow Indicator",
|
|
23992
|
+
overlay: false,
|
|
23993
|
+
inputs: [
|
|
23994
|
+
lengthInput(130),
|
|
23995
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
23996
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
23997
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
23998
|
+
],
|
|
23999
|
+
compute: (bars, inputs) => {
|
|
24000
|
+
const len = num3(inputs, "length", 130);
|
|
24001
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
24002
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
24003
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
24004
|
+
const inter = change(typical);
|
|
24005
|
+
const vInter = stdev(inter, 30);
|
|
24006
|
+
const vol = volumes(bars);
|
|
24007
|
+
const vAve = sma(vol, len);
|
|
24008
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
24009
|
+
const raw = bars.map((b, i) => {
|
|
24010
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
24011
|
+
const v = vol[i];
|
|
24012
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24013
|
+
const m = mf[i];
|
|
24014
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
24015
|
+
const capped = Math.min(v, ave * volCoef);
|
|
24016
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
24017
|
+
});
|
|
24018
|
+
const values = bars.map((_, i) => {
|
|
24019
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
24020
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
24021
|
+
});
|
|
24022
|
+
const flowSum = sum(raw, len);
|
|
24023
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
24024
|
+
return {
|
|
24025
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
24026
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24027
|
+
};
|
|
24028
|
+
}
|
|
24029
|
+
};
|
|
24030
|
+
var intradayIntensity = {
|
|
24031
|
+
type: "intraday-intensity",
|
|
24032
|
+
title: "Intraday Intensity",
|
|
24033
|
+
shortTitle: "Intraday Intensity",
|
|
24034
|
+
overlay: false,
|
|
24035
|
+
inputs: [lengthInput(21), colorInput()],
|
|
24036
|
+
compute: (bars, inputs) => {
|
|
24037
|
+
const len = num3(inputs, "length", 21);
|
|
24038
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
24039
|
+
return {
|
|
24040
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
24041
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
24042
|
+
};
|
|
24043
|
+
}
|
|
24044
|
+
};
|
|
24045
|
+
var volumeSpecs = [
|
|
24046
|
+
obv,
|
|
24047
|
+
accDist,
|
|
24048
|
+
cmf,
|
|
24049
|
+
chaikinOscillator,
|
|
24050
|
+
eom,
|
|
24051
|
+
forceIndex,
|
|
24052
|
+
klinger,
|
|
24053
|
+
mfi,
|
|
24054
|
+
nvi,
|
|
24055
|
+
pvi,
|
|
24056
|
+
pvt,
|
|
24057
|
+
volumeOscillator,
|
|
24058
|
+
pvo,
|
|
24059
|
+
vfi,
|
|
24060
|
+
intradayIntensity
|
|
24061
|
+
];
|
|
24062
|
+
|
|
24063
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
24064
|
+
var DAY_MS2 = 864e5;
|
|
24065
|
+
function periodKey(anchor, time) {
|
|
24066
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
24067
|
+
if (anchor === "Month") {
|
|
24068
|
+
const d = new Date(time);
|
|
24069
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
24070
|
+
}
|
|
24071
|
+
return Math.floor(time / DAY_MS2);
|
|
24072
|
+
}
|
|
24073
|
+
var parabolicSar = {
|
|
24074
|
+
type: "parabolic-sar",
|
|
24075
|
+
title: "Parabolic SAR",
|
|
24076
|
+
shortTitle: "SAR",
|
|
24077
|
+
overlay: true,
|
|
24078
|
+
inputs: [
|
|
24079
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24080
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
24081
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
24082
|
+
],
|
|
24083
|
+
compute: (bars, inputs) => {
|
|
24084
|
+
const start = num3(inputs, "start", 0.02);
|
|
24085
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
24086
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
24087
|
+
const n = bars.length;
|
|
24088
|
+
const values = new Array(n).fill(Number.NaN);
|
|
24089
|
+
const colors = new Array(n).fill(null);
|
|
24090
|
+
if (n >= 2) {
|
|
24091
|
+
let long = bars[1].close >= bars[0].close;
|
|
24092
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
24093
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
24094
|
+
let af = start;
|
|
24095
|
+
for (let i = 1; i < n; i++) {
|
|
24096
|
+
const b = bars[i];
|
|
24097
|
+
sar += af * (ep - sar);
|
|
24098
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
24099
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
24100
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
24101
|
+
if (reversed) {
|
|
24102
|
+
long = !long;
|
|
24103
|
+
sar = ep;
|
|
24104
|
+
ep = long ? b.high : b.low;
|
|
24105
|
+
af = start;
|
|
24106
|
+
} else if (long && b.high > ep) {
|
|
24107
|
+
ep = b.high;
|
|
24108
|
+
af = Math.min(max, af + inc);
|
|
24109
|
+
} else if (!long && b.low < ep) {
|
|
24110
|
+
ep = b.low;
|
|
24111
|
+
af = Math.min(max, af + inc);
|
|
24112
|
+
}
|
|
24113
|
+
values[i] = sar;
|
|
24114
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
24115
|
+
}
|
|
24116
|
+
}
|
|
24117
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
24118
|
+
}
|
|
24119
|
+
};
|
|
24120
|
+
var vwap = {
|
|
24121
|
+
type: "vwap",
|
|
24122
|
+
title: "Volume Weighted Average Price",
|
|
24123
|
+
shortTitle: "VWAP",
|
|
24124
|
+
overlay: true,
|
|
24125
|
+
inputs: [
|
|
24126
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
24127
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
24128
|
+
colorInput(INFO)
|
|
24129
|
+
],
|
|
24130
|
+
compute: (bars, inputs) => {
|
|
24131
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24132
|
+
const source = str3(inputs, "source", "HLC3");
|
|
24133
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
24134
|
+
let period = Number.NaN;
|
|
24135
|
+
let cumPV = 0;
|
|
24136
|
+
let cumV = 0;
|
|
24137
|
+
for (let i = 0; i < bars.length; i++) {
|
|
24138
|
+
const b = bars[i];
|
|
24139
|
+
const key = periodKey(anchor, b.time);
|
|
24140
|
+
if (key !== period) {
|
|
24141
|
+
period = key;
|
|
24142
|
+
cumPV = 0;
|
|
24143
|
+
cumV = 0;
|
|
24144
|
+
}
|
|
24145
|
+
const v = b.volume;
|
|
24146
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
24147
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
24148
|
+
cumPV += tp * v;
|
|
24149
|
+
cumV += v;
|
|
24150
|
+
}
|
|
24151
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
24152
|
+
}
|
|
24153
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
24154
|
+
}
|
|
24155
|
+
};
|
|
24156
|
+
var pivotPoints = {
|
|
24157
|
+
type: "pivot-points",
|
|
24158
|
+
title: "Pivot Points",
|
|
24159
|
+
shortTitle: "Pivots",
|
|
24160
|
+
overlay: true,
|
|
24161
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
24162
|
+
compute: (bars, inputs) => {
|
|
24163
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
24164
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
24165
|
+
for (const b of bars) {
|
|
24166
|
+
const key = periodKey(anchor, b.time);
|
|
24167
|
+
const a = aggs.get(key);
|
|
24168
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
24169
|
+
else {
|
|
24170
|
+
a.high = Math.max(a.high, b.high);
|
|
24171
|
+
a.low = Math.min(a.low, b.low);
|
|
24172
|
+
a.close = b.close;
|
|
24173
|
+
}
|
|
24174
|
+
}
|
|
24175
|
+
const n = bars.length;
|
|
24176
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
24177
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
24178
|
+
for (let i = 0; i < n; i++) {
|
|
24179
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
24180
|
+
if (!prev2) continue;
|
|
24181
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
24182
|
+
levels.p[i] = p;
|
|
24183
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
24184
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
24185
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
24186
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
24187
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
24188
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
24189
|
+
}
|
|
24190
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
24191
|
+
return {
|
|
24192
|
+
plots: [
|
|
24193
|
+
plot("p", "P", WARNING),
|
|
24194
|
+
plot("r1", "R1", BEARISH),
|
|
24195
|
+
plot("s1", "S1", BULLISH),
|
|
24196
|
+
plot("r2", "R2", BEARISH),
|
|
24197
|
+
plot("s2", "S2", BULLISH),
|
|
24198
|
+
plot("r3", "R3", BEARISH),
|
|
24199
|
+
plot("s3", "S3", BULLISH)
|
|
24200
|
+
]
|
|
24201
|
+
};
|
|
24202
|
+
}
|
|
24203
|
+
};
|
|
24204
|
+
var fiftyTwoWeek = {
|
|
24205
|
+
type: "52-week-high-low",
|
|
24206
|
+
title: "52 Week High/Low",
|
|
24207
|
+
shortTitle: "52W H/L",
|
|
24208
|
+
overlay: true,
|
|
24209
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
24210
|
+
compute: (bars, inputs) => {
|
|
24211
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
24212
|
+
const n = bars.length;
|
|
24213
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
24214
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
24215
|
+
const maxIdx = [];
|
|
24216
|
+
const minIdx = [];
|
|
24217
|
+
let from = 0;
|
|
24218
|
+
for (let i = 0; i < n; i++) {
|
|
24219
|
+
const b = bars[i];
|
|
24220
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
24221
|
+
maxIdx.push(i);
|
|
24222
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
24223
|
+
minIdx.push(i);
|
|
24224
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
24225
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
24226
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
24227
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
24228
|
+
lo[i] = bars[minIdx[0]].low;
|
|
24229
|
+
}
|
|
24230
|
+
return {
|
|
24231
|
+
plots: [
|
|
24232
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
24233
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
24234
|
+
]
|
|
24235
|
+
};
|
|
24236
|
+
}
|
|
24237
|
+
};
|
|
24238
|
+
var zigzag = {
|
|
24239
|
+
type: "zigzag",
|
|
24240
|
+
title: "ZigZag",
|
|
24241
|
+
overlay: true,
|
|
24242
|
+
inputs: [
|
|
24243
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
24244
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
24245
|
+
colorInput()
|
|
24246
|
+
],
|
|
24247
|
+
compute: (bars, inputs) => {
|
|
24248
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
24249
|
+
const depth = num3(inputs, "depth", 10);
|
|
24250
|
+
const pivots = [];
|
|
24251
|
+
if (bars.length > 1) {
|
|
24252
|
+
let up = bars[1].close >= bars[0].close;
|
|
24253
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
24254
|
+
for (let i = 1; i < bars.length; i++) {
|
|
24255
|
+
const b = bars[i];
|
|
24256
|
+
if (up) {
|
|
24257
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
24258
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24259
|
+
pivots.push(ext);
|
|
24260
|
+
up = false;
|
|
24261
|
+
ext = { i, price: b.low };
|
|
24262
|
+
}
|
|
24263
|
+
} else if (b.low <= ext.price) {
|
|
24264
|
+
ext = { i, price: b.low };
|
|
24265
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
24266
|
+
pivots.push(ext);
|
|
24267
|
+
up = true;
|
|
24268
|
+
ext = { i, price: b.high };
|
|
24269
|
+
}
|
|
24270
|
+
}
|
|
24271
|
+
pivots.push(ext);
|
|
24272
|
+
}
|
|
24273
|
+
return {
|
|
24274
|
+
plots: [],
|
|
24275
|
+
polylines: [
|
|
24276
|
+
{
|
|
24277
|
+
key: "zigzag",
|
|
24278
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
24279
|
+
curved: false,
|
|
24280
|
+
closed: false,
|
|
24281
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
24282
|
+
lineWidth: 2,
|
|
24283
|
+
lineStyle: "solid",
|
|
24284
|
+
arrowLeft: false,
|
|
24285
|
+
arrowRight: false
|
|
24286
|
+
}
|
|
24287
|
+
]
|
|
24288
|
+
};
|
|
24289
|
+
}
|
|
24290
|
+
};
|
|
24291
|
+
var williamsFractal = {
|
|
24292
|
+
type: "williams-fractal",
|
|
24293
|
+
title: "Williams Fractal",
|
|
24294
|
+
shortTitle: "Fractals",
|
|
24295
|
+
overlay: true,
|
|
24296
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
24297
|
+
compute: (bars, inputs) => {
|
|
24298
|
+
const p = num3(inputs, "periods", 2);
|
|
24299
|
+
const markers = [];
|
|
24300
|
+
const isExtreme = (i, pick, better) => {
|
|
24301
|
+
const v = pick(bars[i]);
|
|
24302
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
24303
|
+
if (k === i) continue;
|
|
24304
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
24305
|
+
}
|
|
24306
|
+
return true;
|
|
24307
|
+
};
|
|
24308
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
24309
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
24310
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
24311
|
+
}
|
|
24312
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
24313
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
24314
|
+
}
|
|
24315
|
+
}
|
|
24316
|
+
return { plots: [], markers };
|
|
24317
|
+
}
|
|
24318
|
+
};
|
|
24319
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
24320
|
+
|
|
24321
|
+
// src/core/native-indicators/classics/index.ts
|
|
24322
|
+
var classicSpecs = [
|
|
24323
|
+
...averageSpecs,
|
|
24324
|
+
...bandSpecs,
|
|
24325
|
+
...oscillatorSpecs,
|
|
24326
|
+
...trendSpecs,
|
|
24327
|
+
...volatilitySpecs,
|
|
24328
|
+
...volumeSpecs,
|
|
24329
|
+
...overlaySpecs
|
|
24330
|
+
];
|
|
24331
|
+
function registerClassicIndicators() {
|
|
24332
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
24333
|
+
}
|
|
24334
|
+
|
|
22018
24335
|
// src/Vela.ts
|
|
22019
24336
|
var Vela = class {
|
|
22020
24337
|
constructor(container, options = {}, deps = {}) {
|
|
22021
24338
|
registerBuiltinChartTypes();
|
|
22022
24339
|
registerVolume();
|
|
22023
24340
|
registerVpvr();
|
|
24341
|
+
registerClassicIndicators();
|
|
22024
24342
|
const element = resolveElement(container);
|
|
22025
24343
|
const theme = resolveTheme(options.theme);
|
|
22026
24344
|
let animZoom = true;
|
|
@@ -22095,25 +24413,28 @@ var Vela = class {
|
|
|
22095
24413
|
}
|
|
22096
24414
|
/**
|
|
22097
24415
|
* 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
|
-
*
|
|
24416
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
24417
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
24418
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
24419
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
22101
24420
|
*/
|
|
22102
24421
|
addNativeIndicator(type, options) {
|
|
22103
24422
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
22104
24423
|
}
|
|
22105
24424
|
/**
|
|
22106
24425
|
* 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
|
-
*
|
|
24426
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
24427
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
24428
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
24429
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
24430
|
+
* provider (a type may need data the symbol lacks).
|
|
22111
24431
|
*/
|
|
22112
24432
|
availableNativeIndicators() {
|
|
22113
24433
|
return this.orchestrator.availableNativeIndicators();
|
|
22114
24434
|
}
|
|
22115
24435
|
/**
|
|
22116
|
-
* The native-indicator types PRESENT on the chart right now
|
|
24436
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
24437
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
22117
24438
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
22118
24439
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
22119
24440
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|