@luxalgo/vela 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
- package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
- package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
- package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
- package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
- package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
- package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
- package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
- package/dist/index.cjs +2746 -355
- package/dist/index.d.cts +15 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -5
- package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
- package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
- package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
- package/dist/ui.cjs +23 -15
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2600 -209
- package/dist/vela.global.min.js +52 -55
- package/dist/widget.cjs +2663 -241
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +2663 -241
- package/dist/workspace.d.cts +25 -14
- package/dist/workspace.d.ts +25 -14
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- package/dist/chunk-H26BEHF4.js +0 -7
package/dist/vela.global.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
var Vela = (function (exports) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// src/core/options.ts
|
|
5
|
+
var LIVE_BAR_EASE_DEFAULT_MS = 90;
|
|
6
|
+
var LIVE_BAR_EASE_MAX_MS = 1e3;
|
|
7
|
+
function resolveLiveBarEaseMs(value) {
|
|
8
|
+
if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
|
|
9
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
10
|
+
return Math.min(value, LIVE_BAR_EASE_MAX_MS);
|
|
11
|
+
}
|
|
12
|
+
function resolveAnimations(animations) {
|
|
13
|
+
if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
|
|
14
|
+
return {
|
|
15
|
+
animZoom: animations?.zoom ?? true,
|
|
16
|
+
animPan: animations?.pan ?? true,
|
|
17
|
+
animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
// src/core/model/series.ts
|
|
5
22
|
function isLineLikeSeries(spec) {
|
|
6
23
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
@@ -111,7 +128,7 @@ var Vela = (function (exports) {
|
|
|
111
128
|
|
|
112
129
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
113
130
|
var IndicatorHandleImpl = class {
|
|
114
|
-
constructor(id, title, controller, source) {
|
|
131
|
+
constructor(id, title, controller, source, nativeType) {
|
|
115
132
|
this.controller = controller;
|
|
116
133
|
this.schema = [];
|
|
117
134
|
this.propsSchema = [];
|
|
@@ -120,6 +137,7 @@ var Vela = (function (exports) {
|
|
|
120
137
|
this.id = id;
|
|
121
138
|
this.title = title;
|
|
122
139
|
if (source !== void 0) this.source = source;
|
|
140
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
123
141
|
}
|
|
124
142
|
get inputs() {
|
|
125
143
|
return this.schema;
|
|
@@ -210,24 +228,24 @@ var Vela = (function (exports) {
|
|
|
210
228
|
}
|
|
211
229
|
function inspectModels(models) {
|
|
212
230
|
const indicators = models.map(summarizeModel);
|
|
213
|
-
const
|
|
231
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
214
232
|
return {
|
|
215
233
|
indicators,
|
|
216
234
|
totals: {
|
|
217
235
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
218
|
-
series:
|
|
219
|
-
fills:
|
|
220
|
-
backgrounds:
|
|
221
|
-
priceLines:
|
|
222
|
-
lines:
|
|
223
|
-
boxes:
|
|
224
|
-
labels:
|
|
225
|
-
polylines:
|
|
226
|
-
linefills:
|
|
227
|
-
tables:
|
|
228
|
-
barColors:
|
|
229
|
-
trades:
|
|
230
|
-
forcedOverlay:
|
|
236
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
237
|
+
fills: sum2((s) => s.fills),
|
|
238
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
239
|
+
priceLines: sum2((s) => s.priceLines),
|
|
240
|
+
lines: sum2((s) => s.lines),
|
|
241
|
+
boxes: sum2((s) => s.boxes),
|
|
242
|
+
labels: sum2((s) => s.labels),
|
|
243
|
+
polylines: sum2((s) => s.polylines),
|
|
244
|
+
linefills: sum2((s) => s.linefills),
|
|
245
|
+
tables: sum2((s) => s.tables),
|
|
246
|
+
barColors: sum2((s) => s.barColors),
|
|
247
|
+
trades: sum2((s) => s.trades),
|
|
248
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
231
249
|
}
|
|
232
250
|
};
|
|
233
251
|
}
|
|
@@ -550,6 +568,7 @@ var Vela = (function (exports) {
|
|
|
550
568
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
551
569
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
552
570
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
571
|
+
registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
|
|
553
572
|
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
554
573
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
555
574
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
@@ -6707,8 +6726,8 @@ var Vela = (function (exports) {
|
|
|
6707
6726
|
this.port?.setExternalGhost?.(doc);
|
|
6708
6727
|
}
|
|
6709
6728
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6710
|
-
setToolShortcuts(
|
|
6711
|
-
this.port?.setToolShortcuts?.(
|
|
6729
|
+
setToolShortcuts(map2) {
|
|
6730
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6712
6731
|
}
|
|
6713
6732
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6714
6733
|
add(type, init = {}) {
|
|
@@ -8432,16 +8451,20 @@ var Vela = (function (exports) {
|
|
|
8432
8451
|
/**
|
|
8433
8452
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8434
8453
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8435
|
-
* INSTANCE per type
|
|
8436
|
-
*
|
|
8454
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8455
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8456
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8457
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8437
8458
|
*/
|
|
8438
8459
|
addNativeIndicator(type, options = {}) {
|
|
8439
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8440
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
8441
8460
|
const descriptor = getNativeIndicator(type);
|
|
8461
|
+
if (!descriptor?.multiInstance) {
|
|
8462
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8463
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8464
|
+
}
|
|
8442
8465
|
const id = this.registry.nextId("native");
|
|
8443
8466
|
const title = descriptor?.title ?? type;
|
|
8444
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8467
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8445
8468
|
this.handles.set(id, handle);
|
|
8446
8469
|
if (!descriptor) {
|
|
8447
8470
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8453,16 +8476,18 @@ var Vela = (function (exports) {
|
|
|
8453
8476
|
void this.startNativeIndicator(id, handle);
|
|
8454
8477
|
return handle;
|
|
8455
8478
|
}
|
|
8456
|
-
/**
|
|
8457
|
-
*
|
|
8458
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
8459
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
8460
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
8461
|
-
*/
|
|
8462
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
8479
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8480
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8463
8481
|
presentNativeIndicators() {
|
|
8464
8482
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8465
8483
|
}
|
|
8484
|
+
/**
|
|
8485
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8486
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8487
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8488
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8489
|
+
* probe the provider for a required capability.
|
|
8490
|
+
*/
|
|
8466
8491
|
async availableNativeIndicators() {
|
|
8467
8492
|
const symbol = this.qualifiedSymbol();
|
|
8468
8493
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8472,7 +8497,8 @@ var Vela = (function (exports) {
|
|
|
8472
8497
|
title: d.title,
|
|
8473
8498
|
supported: await this.isNativeSupported(d, symbol),
|
|
8474
8499
|
present: present.has(d.type),
|
|
8475
|
-
beta: d.beta
|
|
8500
|
+
beta: d.beta,
|
|
8501
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8476
8502
|
}))
|
|
8477
8503
|
);
|
|
8478
8504
|
}
|
|
@@ -9756,7 +9782,7 @@ var Vela = (function (exports) {
|
|
|
9756
9782
|
|
|
9757
9783
|
// src/data/MultiProviderFeed.ts
|
|
9758
9784
|
function intervalMs(timeframe) {
|
|
9759
|
-
const
|
|
9785
|
+
const map2 = {
|
|
9760
9786
|
"1m": 6e4,
|
|
9761
9787
|
"3m": 18e4,
|
|
9762
9788
|
"5m": 3e5,
|
|
@@ -9778,7 +9804,7 @@ var Vela = (function (exports) {
|
|
|
9778
9804
|
W: 6048e5,
|
|
9779
9805
|
M: 2592e6
|
|
9780
9806
|
};
|
|
9781
|
-
return
|
|
9807
|
+
return map2[timeframe] ?? 36e5;
|
|
9782
9808
|
}
|
|
9783
9809
|
var MultiProviderFeed = class {
|
|
9784
9810
|
constructor(store = sharedBarStore) {
|
|
@@ -10165,8 +10191,8 @@ var Vela = (function (exports) {
|
|
|
10165
10191
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10166
10192
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10167
10193
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10168
|
-
setToolShortcuts(
|
|
10169
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10194
|
+
setToolShortcuts(map2) {
|
|
10195
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10170
10196
|
return this;
|
|
10171
10197
|
}
|
|
10172
10198
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10493,6 +10519,23 @@ var Vela = (function (exports) {
|
|
|
10493
10519
|
clear();
|
|
10494
10520
|
};
|
|
10495
10521
|
}
|
|
10522
|
+
function chromeHint(text, opts) {
|
|
10523
|
+
const px = opts.size ?? 16;
|
|
10524
|
+
const el = document.createElement("span");
|
|
10525
|
+
el.className = "vela-hint";
|
|
10526
|
+
el.setAttribute("role", "img");
|
|
10527
|
+
el.setAttribute("aria-label", text);
|
|
10528
|
+
el.innerHTML = iconAt("info", px);
|
|
10529
|
+
el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
|
|
10530
|
+
const hover = (on) => {
|
|
10531
|
+
el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
|
|
10532
|
+
el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
|
|
10533
|
+
};
|
|
10534
|
+
el.addEventListener("pointerenter", () => hover(true));
|
|
10535
|
+
el.addEventListener("pointerleave", () => hover(false));
|
|
10536
|
+
const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
|
|
10537
|
+
return { el, dispose };
|
|
10538
|
+
}
|
|
10496
10539
|
|
|
10497
10540
|
// node_modules/@zag-js/vanilla/dist/chunk-QZ7TP4HQ.mjs
|
|
10498
10541
|
var __defProp = Object.defineProperty;
|
|
@@ -10631,7 +10674,7 @@ var Vela = (function (exports) {
|
|
|
10631
10674
|
}
|
|
10632
10675
|
|
|
10633
10676
|
// node_modules/@zag-js/core/dist/merge-props.mjs
|
|
10634
|
-
var clsx = (...args) => args.map((
|
|
10677
|
+
var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
|
|
10635
10678
|
var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
|
|
10636
10679
|
var serialize = (style) => {
|
|
10637
10680
|
const res = {};
|
|
@@ -10855,12 +10898,12 @@ var Vela = (function (exports) {
|
|
|
10855
10898
|
return {
|
|
10856
10899
|
and: (...guards) => {
|
|
10857
10900
|
return function andGuard(params) {
|
|
10858
|
-
return guards.every((
|
|
10901
|
+
return guards.every((str4) => params.guard(str4));
|
|
10859
10902
|
};
|
|
10860
10903
|
},
|
|
10861
10904
|
or: (...guards) => {
|
|
10862
10905
|
return function orGuard(params) {
|
|
10863
|
-
return guards.some((
|
|
10906
|
+
return guards.some((str4) => params.guard(str4));
|
|
10864
10907
|
};
|
|
10865
10908
|
},
|
|
10866
10909
|
not: (guard) => {
|
|
@@ -11421,9 +11464,9 @@ var Vela = (function (exports) {
|
|
|
11421
11464
|
const { attributes, callback: fn } = options;
|
|
11422
11465
|
const win = node.ownerDocument.defaultView || window;
|
|
11423
11466
|
const obs = new win.MutationObserver((changes) => {
|
|
11424
|
-
for (const
|
|
11425
|
-
if (
|
|
11426
|
-
fn(
|
|
11467
|
+
for (const change2 of changes) {
|
|
11468
|
+
if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
|
|
11469
|
+
fn(change2);
|
|
11427
11470
|
}
|
|
11428
11471
|
}
|
|
11429
11472
|
});
|
|
@@ -11497,7 +11540,7 @@ var Vela = (function (exports) {
|
|
|
11497
11540
|
}
|
|
11498
11541
|
|
|
11499
11542
|
// node_modules/@zag-js/dom-query/dist/searchable.mjs
|
|
11500
|
-
var sanitize = (
|
|
11543
|
+
var sanitize = (str4) => str4.split("").map((char) => {
|
|
11501
11544
|
const code = char.charCodeAt(0);
|
|
11502
11545
|
if (code > 0 && code < 128) return char;
|
|
11503
11546
|
if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
|
|
@@ -12197,10 +12240,10 @@ var Vela = (function (exports) {
|
|
|
12197
12240
|
fn?.(this.getParams());
|
|
12198
12241
|
}
|
|
12199
12242
|
});
|
|
12200
|
-
__publicField(this, "guard", (
|
|
12201
|
-
if (isFunction(
|
|
12202
|
-
const fn = this.machine.implementations?.guards?.[
|
|
12203
|
-
if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(
|
|
12243
|
+
__publicField(this, "guard", (str4) => {
|
|
12244
|
+
if (isFunction(str4)) return str4(this.getParams());
|
|
12245
|
+
const fn = this.machine.implementations?.guards?.[str4];
|
|
12246
|
+
if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
|
|
12204
12247
|
return fn?.(this.getParams());
|
|
12205
12248
|
});
|
|
12206
12249
|
__publicField(this, "effect", (keys) => {
|
|
@@ -16978,19 +17021,19 @@ var Vela = (function (exports) {
|
|
|
16978
17021
|
}
|
|
16979
17022
|
setItems(items) {
|
|
16980
17023
|
this.items = items;
|
|
16981
|
-
for (const
|
|
17024
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16982
17025
|
this.subs.clear();
|
|
16983
17026
|
this.build();
|
|
16984
17027
|
const api = this.api;
|
|
16985
|
-
for (const
|
|
16986
|
-
api.setChild(
|
|
16987
|
-
|
|
17028
|
+
for (const sub2 of this.subs.values()) {
|
|
17029
|
+
api.setChild(sub2.handle.service);
|
|
17030
|
+
sub2.api.setParent(this.handle.service);
|
|
16988
17031
|
}
|
|
16989
17032
|
this.handle.flush();
|
|
16990
17033
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
16991
17034
|
}
|
|
16992
17035
|
destroy() {
|
|
16993
|
-
for (const
|
|
17036
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16994
17037
|
this.subs.clear();
|
|
16995
17038
|
this.handle.stop();
|
|
16996
17039
|
this.positioner.remove();
|
|
@@ -17004,9 +17047,9 @@ var Vela = (function (exports) {
|
|
|
17004
17047
|
for (const li of this.list.children) {
|
|
17005
17048
|
const id = li.dataset.veiId;
|
|
17006
17049
|
if (!id) continue;
|
|
17007
|
-
const
|
|
17008
|
-
if (
|
|
17009
|
-
spreadProps(li, api.getTriggerItemProps(
|
|
17050
|
+
const sub2 = this.subs.get(id);
|
|
17051
|
+
if (sub2) {
|
|
17052
|
+
spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
17010
17053
|
continue;
|
|
17011
17054
|
}
|
|
17012
17055
|
const item = this.byId(id);
|
|
@@ -17083,7 +17126,7 @@ var Vela = (function (exports) {
|
|
|
17083
17126
|
const arrow3 = iconEl("chevron-right", doc);
|
|
17084
17127
|
arrow3.classList.add("vela-menu-arrow");
|
|
17085
17128
|
li.appendChild(arrow3);
|
|
17086
|
-
const
|
|
17129
|
+
const sub2 = new _Surface(doc, {
|
|
17087
17130
|
host: this.host,
|
|
17088
17131
|
placement: "right-start",
|
|
17089
17132
|
onSelect: this.onSelect,
|
|
@@ -17091,8 +17134,8 @@ var Vela = (function (exports) {
|
|
|
17091
17134
|
checkmarks: this.checkmarks,
|
|
17092
17135
|
id: `${this.mid}--${item.id}`
|
|
17093
17136
|
});
|
|
17094
|
-
|
|
17095
|
-
this.subs.set(item.id,
|
|
17137
|
+
sub2.setItems(item.submenu ?? []);
|
|
17138
|
+
this.subs.set(item.id, sub2);
|
|
17096
17139
|
}
|
|
17097
17140
|
this.list.appendChild(li);
|
|
17098
17141
|
}
|
|
@@ -20047,8 +20090,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20047
20090
|
};
|
|
20048
20091
|
const recentRow = document.createElement("div");
|
|
20049
20092
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
20093
|
+
const recentSwatches = document.createElement("div");
|
|
20094
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
20095
|
+
const add = document.createElement("label");
|
|
20096
|
+
add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
|
|
20097
|
+
add.textContent = "+";
|
|
20098
|
+
const customInput = document.createElement("input");
|
|
20099
|
+
customInput.type = "color";
|
|
20100
|
+
customInput.value = curHex;
|
|
20101
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
20102
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
20103
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
20104
|
+
add.appendChild(customInput);
|
|
20105
|
+
recentRow.append(recentSwatches, add);
|
|
20050
20106
|
const renderRecents = () => {
|
|
20051
|
-
|
|
20107
|
+
recentSwatches.replaceChildren();
|
|
20052
20108
|
for (const c of recents) {
|
|
20053
20109
|
const sw = document.createElement("button");
|
|
20054
20110
|
sw.type = "button";
|
|
@@ -20057,18 +20113,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20057
20113
|
e.stopPropagation();
|
|
20058
20114
|
pickHex(c);
|
|
20059
20115
|
});
|
|
20060
|
-
|
|
20116
|
+
recentSwatches.appendChild(sw);
|
|
20061
20117
|
}
|
|
20062
|
-
const add = document.createElement("label");
|
|
20063
|
-
add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
|
|
20064
|
-
add.textContent = "+";
|
|
20065
|
-
const input = document.createElement("input");
|
|
20066
|
-
input.type = "color";
|
|
20067
|
-
input.value = curHex;
|
|
20068
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
20069
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
20070
|
-
add.appendChild(input);
|
|
20071
|
-
recentRow.appendChild(add);
|
|
20072
20118
|
};
|
|
20073
20119
|
const opLabel = document.createElement("div");
|
|
20074
20120
|
opLabel.textContent = "Opacity";
|
|
@@ -20108,14 +20154,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20108
20154
|
function emit() {
|
|
20109
20155
|
onChange(combineColor(curHex, curAlpha));
|
|
20110
20156
|
}
|
|
20111
|
-
function
|
|
20157
|
+
function previewHex(hex) {
|
|
20112
20158
|
curHex = splitColor(hex).hex6;
|
|
20113
|
-
addRecent(curHex);
|
|
20114
20159
|
paintSelection();
|
|
20115
20160
|
paintOpacity();
|
|
20116
|
-
if (custom) renderRecents();
|
|
20117
20161
|
emit();
|
|
20118
20162
|
}
|
|
20163
|
+
function pickHex(hex, custom = false) {
|
|
20164
|
+
previewHex(hex);
|
|
20165
|
+
addRecent(curHex);
|
|
20166
|
+
customInput.value = curHex;
|
|
20167
|
+
if (custom) renderRecents();
|
|
20168
|
+
}
|
|
20119
20169
|
renderRecents();
|
|
20120
20170
|
paintSelection();
|
|
20121
20171
|
paintOpacity();
|
|
@@ -21067,14 +21117,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21067
21117
|
buildTextField(id, current, emit) {
|
|
21068
21118
|
return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
|
|
21069
21119
|
}
|
|
21070
|
-
/**
|
|
21120
|
+
/** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
|
|
21071
21121
|
infoButton(tooltip) {
|
|
21072
|
-
const
|
|
21073
|
-
|
|
21074
|
-
|
|
21075
|
-
b.className = "vela-ind-hint";
|
|
21076
|
-
b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
|
|
21077
|
-
return b;
|
|
21122
|
+
const { el, dispose } = chromeHint(tooltip, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
|
|
21123
|
+
this.dialogTips.push(dispose);
|
|
21124
|
+
return el;
|
|
21078
21125
|
}
|
|
21079
21126
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
21080
21127
|
select(id, options, current, emit) {
|
|
@@ -21511,8 +21558,6 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21511
21558
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
21512
21559
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
21513
21560
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
21514
|
-
.vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
21515
|
-
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21516
21561
|
.vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
|
|
21517
21562
|
.vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
|
|
21518
21563
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
@@ -22021,8 +22066,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22021
22066
|
lg.style.top = `${top}px`;
|
|
22022
22067
|
}
|
|
22023
22068
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22024
|
-
setCollapsedPanes(
|
|
22025
|
-
this.paneCollapse =
|
|
22069
|
+
setCollapsedPanes(map2) {
|
|
22070
|
+
this.paneCollapse = map2;
|
|
22026
22071
|
}
|
|
22027
22072
|
/**
|
|
22028
22073
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -22985,9 +23030,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22985
23030
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
22986
23031
|
addRows(inst.rows);
|
|
22987
23032
|
}
|
|
22988
|
-
for (const
|
|
22989
|
-
if (
|
|
22990
|
-
addRows(
|
|
23033
|
+
for (const sub2 of section.subsections ?? []) {
|
|
23034
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
23035
|
+
addRows(sub2.rows);
|
|
22991
23036
|
}
|
|
22992
23037
|
if (!section.instances) addRows(section.rows);
|
|
22993
23038
|
bag[t.id] = defaults2;
|
|
@@ -23056,7 +23101,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23056
23101
|
labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
|
|
23057
23102
|
currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
|
|
23058
23103
|
priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
|
|
23059
|
-
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
|
|
23104
|
+
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
|
|
23105
|
+
animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
|
|
23060
23106
|
},
|
|
23061
23107
|
panes: {
|
|
23062
23108
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
@@ -24316,11 +24362,11 @@ void main() {
|
|
|
24316
24362
|
}
|
|
24317
24363
|
}
|
|
24318
24364
|
if (!any) return EMPTY_BARCOLORS;
|
|
24319
|
-
const
|
|
24365
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24320
24366
|
for (const m of indicators.values()) {
|
|
24321
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24367
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24322
24368
|
}
|
|
24323
|
-
return
|
|
24369
|
+
return map2;
|
|
24324
24370
|
}
|
|
24325
24371
|
|
|
24326
24372
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24617,9 +24663,9 @@ void main() {
|
|
|
24617
24663
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24618
24664
|
return rightOffset + deltaX / barSpacing;
|
|
24619
24665
|
}
|
|
24620
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24666
|
+
function wheelPanDelta(deltaX, deltaY, shift4) {
|
|
24621
24667
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24622
|
-
if (
|
|
24668
|
+
if (shift4) return deltaY;
|
|
24623
24669
|
return null;
|
|
24624
24670
|
}
|
|
24625
24671
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -26241,11 +26287,11 @@ void main() {
|
|
|
26241
26287
|
}
|
|
26242
26288
|
}
|
|
26243
26289
|
if (!any) return EMPTY_BARCOLORS2;
|
|
26244
|
-
const
|
|
26290
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
26245
26291
|
for (const m of indicators.values()) {
|
|
26246
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26292
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
26247
26293
|
}
|
|
26248
|
-
return
|
|
26294
|
+
return map2;
|
|
26249
26295
|
}
|
|
26250
26296
|
|
|
26251
26297
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27920,6 +27966,8 @@ void main() {
|
|
|
27920
27966
|
"symbol.style.baseline.fill-bottom",
|
|
27921
27967
|
"symbol.style.baseline.base-level",
|
|
27922
27968
|
"symbol.style.baseline.width",
|
|
27969
|
+
"symbol.animation",
|
|
27970
|
+
"symbol.animation.price-changes",
|
|
27923
27971
|
"symbol.timezone",
|
|
27924
27972
|
"scales",
|
|
27925
27973
|
"scales.price-scale",
|
|
@@ -27961,9 +28009,9 @@ void main() {
|
|
|
27961
28009
|
};
|
|
27962
28010
|
if (section.rows) addRows(section.rows);
|
|
27963
28011
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27964
|
-
for (const
|
|
27965
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27966
|
-
addRows(
|
|
28012
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28013
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
28014
|
+
addRows(sub2.rows);
|
|
27967
28015
|
}
|
|
27968
28016
|
}
|
|
27969
28017
|
for (const hs of hostSections) {
|
|
@@ -28062,8 +28110,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28062
28110
|
.vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
|
|
28063
28111
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
28064
28112
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
28065
|
-
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
|
|
28066
|
-
`;
|
|
28113
|
+
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
|
|
28067
28114
|
if (!existing) document.head.appendChild(st);
|
|
28068
28115
|
}
|
|
28069
28116
|
var SettingsDialog = class {
|
|
@@ -28084,6 +28131,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28084
28131
|
this.themeControl = null;
|
|
28085
28132
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
28086
28133
|
this.tabs = [];
|
|
28134
|
+
this.hintTips = [];
|
|
28135
|
+
// ⓘ tooltip disposers of the open build (see hint)
|
|
28087
28136
|
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
28088
28137
|
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
28089
28138
|
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
@@ -28272,6 +28321,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28272
28321
|
body.append(g);
|
|
28273
28322
|
}
|
|
28274
28323
|
showActive(config.series.style);
|
|
28324
|
+
body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
|
|
28325
|
+
body.append(sid(this.boolRow(
|
|
28326
|
+
"Animate price changes",
|
|
28327
|
+
config.priceScale.animateLastPrice,
|
|
28328
|
+
(v) => this.emit({ priceScale: { animateLastPrice: v } }),
|
|
28329
|
+
this.hint("Glide the live bar to each new price instead of snapping.")
|
|
28330
|
+
), "symbol.animation.price-changes"));
|
|
28275
28331
|
body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
|
|
28276
28332
|
body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
|
|
28277
28333
|
const renderHostSections = (placement) => {
|
|
@@ -28479,6 +28535,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28479
28535
|
this.ui = null;
|
|
28480
28536
|
this.root = null;
|
|
28481
28537
|
this.tabs = [];
|
|
28538
|
+
for (const dispose of this.hintTips) dispose();
|
|
28539
|
+
this.hintTips = [];
|
|
28482
28540
|
ui?.destroy();
|
|
28483
28541
|
}
|
|
28484
28542
|
destroy() {
|
|
@@ -28522,14 +28580,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28522
28580
|
};
|
|
28523
28581
|
if (section.rows) seed(section.rows);
|
|
28524
28582
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28525
|
-
for (const
|
|
28583
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28526
28584
|
for (const inst of section.instances ?? []) {
|
|
28527
28585
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28528
28586
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28529
28587
|
}
|
|
28530
|
-
for (const
|
|
28531
|
-
if (!
|
|
28532
|
-
bag[
|
|
28588
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28589
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28590
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28533
28591
|
}
|
|
28534
28592
|
const refreshers = [];
|
|
28535
28593
|
const put = (key, v) => {
|
|
@@ -28546,16 +28604,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28546
28604
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28547
28605
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28548
28606
|
}
|
|
28549
|
-
for (const
|
|
28550
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28551
|
-
const rows = filterHiddenRows(
|
|
28607
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28608
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28609
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28552
28610
|
if (rows.length === 0) continue;
|
|
28553
|
-
const subMarker = this.section(
|
|
28611
|
+
const subMarker = this.section(sub2.title);
|
|
28554
28612
|
subMarker.dataset.sdStyle = typeId;
|
|
28555
28613
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28556
28614
|
subMarker.dataset.sdSub = "1";
|
|
28557
28615
|
body.append(subMarker);
|
|
28558
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28616
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28559
28617
|
}
|
|
28560
28618
|
for (const r of refreshers) r();
|
|
28561
28619
|
}
|
|
@@ -28892,23 +28950,31 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28892
28950
|
}).el
|
|
28893
28951
|
});
|
|
28894
28952
|
}
|
|
28895
|
-
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28896
|
-
|
|
28897
|
-
|
|
28953
|
+
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28954
|
+
* `info` (see {@link hint}) rides after the label. */
|
|
28955
|
+
boolRow(label, value, onChange, info) {
|
|
28956
|
+
return this.toggleRow(label, value, onChange, [], void 0, info);
|
|
28898
28957
|
}
|
|
28899
28958
|
/** An enable row: checkbox + label in the label column, dependent controls in the
|
|
28900
28959
|
* shared control column; the control group dims and ignores input while the toggle
|
|
28901
28960
|
* is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
|
|
28902
|
-
toggleRow(label, value, onToggle, controls, get) {
|
|
28961
|
+
toggleRow(label, value, onToggle, controls, get, info) {
|
|
28903
28962
|
return fieldRow({
|
|
28904
28963
|
label,
|
|
28905
28964
|
labelSize: "sm",
|
|
28906
28965
|
bool: controls.length === 0,
|
|
28907
28966
|
toggle: { checked: value, onChange: onToggle, get },
|
|
28908
28967
|
control: controls,
|
|
28968
|
+
info,
|
|
28909
28969
|
className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
|
|
28910
28970
|
});
|
|
28911
28971
|
}
|
|
28972
|
+
/** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
|
|
28973
|
+
hint(text) {
|
|
28974
|
+
const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
|
|
28975
|
+
this.hintTips.push(dispose);
|
|
28976
|
+
return el;
|
|
28977
|
+
}
|
|
28912
28978
|
numberRow(label, value, min2, max2, step, onChange) {
|
|
28913
28979
|
return fieldRow({
|
|
28914
28980
|
label,
|
|
@@ -30663,11 +30729,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30663
30729
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30664
30730
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30665
30731
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30666
|
-
down(x, y, mode = "off",
|
|
30732
|
+
down(x, y, mode = "off", shift4 = false) {
|
|
30667
30733
|
const proj = this.deps.projector();
|
|
30668
30734
|
if (this.state.kind === "placing") {
|
|
30669
30735
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30670
|
-
const pivot =
|
|
30736
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30671
30737
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30672
30738
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30673
30739
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30686,7 +30752,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30686
30752
|
}
|
|
30687
30753
|
const hit = this.hitAt(x, y);
|
|
30688
30754
|
if (hit) {
|
|
30689
|
-
if (
|
|
30755
|
+
if (shift4) {
|
|
30690
30756
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30691
30757
|
return;
|
|
30692
30758
|
}
|
|
@@ -30696,7 +30762,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30696
30762
|
}
|
|
30697
30763
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30698
30764
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30699
|
-
move(x, y, mode = "off",
|
|
30765
|
+
move(x, y, mode = "off", shift4 = false) {
|
|
30700
30766
|
if (this.state.kind === "idle") {
|
|
30701
30767
|
const tool = this.deps.activeTool();
|
|
30702
30768
|
const had = this.snapAt != null;
|
|
@@ -30710,7 +30776,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30710
30776
|
this.captureFreehand(x, y);
|
|
30711
30777
|
return;
|
|
30712
30778
|
}
|
|
30713
|
-
const pivot =
|
|
30779
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30714
30780
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30715
30781
|
this.deps.changed();
|
|
30716
30782
|
return;
|
|
@@ -30723,7 +30789,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30723
30789
|
if (this.state.handle >= 0) {
|
|
30724
30790
|
const a = d.anchors[this.state.handle];
|
|
30725
30791
|
if (a) {
|
|
30726
|
-
const pivot =
|
|
30792
|
+
const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30727
30793
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30728
30794
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30729
30795
|
}
|
|
@@ -31421,21 +31487,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31421
31487
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31422
31488
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31423
31489
|
}
|
|
31424
|
-
const
|
|
31425
|
-
if (paths.has("vwap.midColor") &&
|
|
31490
|
+
const vwap2 = drawing.vwap;
|
|
31491
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31426
31492
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31427
31493
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
31428
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
31429
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
31494
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31495
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31430
31496
|
bar.appendChild(
|
|
31431
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31497
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31432
31498
|
label: (v) => `${v}\u03C3`,
|
|
31433
31499
|
labelInTrigger: true
|
|
31434
31500
|
})
|
|
31435
31501
|
);
|
|
31436
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
31437
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
31438
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
31502
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31503
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31504
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31439
31505
|
}
|
|
31440
31506
|
if (paths.has("maxCurvature")) {
|
|
31441
31507
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32181,8 +32247,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32181
32247
|
}
|
|
32182
32248
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
32183
32249
|
* right edge of the flyout rows, beside the favorite star. */
|
|
32184
|
-
setShortcuts(
|
|
32185
|
-
this.shortcuts = new Map(Object.entries(
|
|
32250
|
+
setShortcuts(map2) {
|
|
32251
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
32186
32252
|
}
|
|
32187
32253
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
32188
32254
|
paintStar(el, type) {
|
|
@@ -33084,8 +33150,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33084
33150
|
this.toolbar.setFavorites(types);
|
|
33085
33151
|
}
|
|
33086
33152
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
33087
|
-
setToolShortcuts(
|
|
33088
|
-
this.toolbar.setShortcuts(
|
|
33153
|
+
setToolShortcuts(map2) {
|
|
33154
|
+
this.toolbar.setShortcuts(map2);
|
|
33089
33155
|
}
|
|
33090
33156
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
33091
33157
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -33203,7 +33269,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33203
33269
|
this.render();
|
|
33204
33270
|
}
|
|
33205
33271
|
}
|
|
33206
|
-
pointerDown(x, y, snap = "off",
|
|
33272
|
+
pointerDown(x, y, snap = "off", shift4 = false) {
|
|
33207
33273
|
if (this.eraserMode) {
|
|
33208
33274
|
this.erasing = true;
|
|
33209
33275
|
this.deleteAt(x, y);
|
|
@@ -33223,9 +33289,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33223
33289
|
return;
|
|
33224
33290
|
}
|
|
33225
33291
|
}
|
|
33226
|
-
this.interaction.down(x, y, snap,
|
|
33292
|
+
this.interaction.down(x, y, snap, shift4);
|
|
33227
33293
|
}
|
|
33228
|
-
pointerMove(x, y, snap = "off",
|
|
33294
|
+
pointerMove(x, y, snap = "off", shift4 = false) {
|
|
33229
33295
|
if (this.eraserMode) {
|
|
33230
33296
|
if (this.erasing) this.deleteAt(x, y);
|
|
33231
33297
|
return;
|
|
@@ -33236,7 +33302,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33236
33302
|
this.render();
|
|
33237
33303
|
return;
|
|
33238
33304
|
}
|
|
33239
|
-
this.interaction.move(x, y, snap,
|
|
33305
|
+
this.interaction.move(x, y, snap, shift4);
|
|
33240
33306
|
this.updateHover(x, y);
|
|
33241
33307
|
}
|
|
33242
33308
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -33913,14 +33979,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33913
33979
|
}
|
|
33914
33980
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33915
33981
|
if (box.cs > 1) {
|
|
33916
|
-
let
|
|
33917
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
33918
|
-
if (w2 >
|
|
33982
|
+
let sum2 = 0;
|
|
33983
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
33984
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
33919
33985
|
}
|
|
33920
33986
|
if (box.rs > 1) {
|
|
33921
|
-
let
|
|
33922
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
33923
|
-
if (h2 >
|
|
33987
|
+
let sum2 = 0;
|
|
33988
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
33989
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
33924
33990
|
}
|
|
33925
33991
|
}
|
|
33926
33992
|
let w = 0;
|
|
@@ -34643,24 +34709,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34643
34709
|
drawSvgShapes(el, alpha);
|
|
34644
34710
|
continue;
|
|
34645
34711
|
}
|
|
34646
|
-
const
|
|
34712
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
34647
34713
|
let path = null;
|
|
34648
34714
|
if (tag === "path") {
|
|
34649
34715
|
const d = el.getAttribute("d");
|
|
34650
34716
|
if (d) path = new Path2D(d);
|
|
34651
34717
|
} else if (tag === "circle") {
|
|
34652
34718
|
path = new Path2D();
|
|
34653
|
-
path.arc(
|
|
34719
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
34654
34720
|
} else if (tag === "ellipse") {
|
|
34655
34721
|
path = new Path2D();
|
|
34656
|
-
path.ellipse(
|
|
34722
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
34657
34723
|
} else if (tag === "rect") {
|
|
34658
34724
|
path = new Path2D();
|
|
34659
|
-
path.rect(
|
|
34725
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
34660
34726
|
} else if (tag === "line") {
|
|
34661
34727
|
path = new Path2D();
|
|
34662
|
-
path.moveTo(
|
|
34663
|
-
path.lineTo(
|
|
34728
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
34729
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
34664
34730
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
34665
34731
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
34666
34732
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -34921,7 +34987,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34921
34987
|
var ZOOM_TAU_MS = 70;
|
|
34922
34988
|
var SCALE_TAU_MS = 80;
|
|
34923
34989
|
var FLING_TAU_MS = 110;
|
|
34924
|
-
var LIVE_BAR_TAU_MS = 90;
|
|
34925
34990
|
var SCROLL_TO_TAU_MS = 130;
|
|
34926
34991
|
var FLING_STOP_PX = 0.02;
|
|
34927
34992
|
var PRICE_SCALE_K = 4e-3;
|
|
@@ -34992,9 +35057,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34992
35057
|
/** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
|
|
34993
35058
|
this.historyChordsEnabled = true;
|
|
34994
35059
|
this.liveRegion = null;
|
|
34995
|
-
// ── animation state (eased zoom + inertial pan) ──
|
|
35060
|
+
// ── animation state (eased zoom + inertial pan + live-bar glide) ──
|
|
34996
35061
|
this.animZoom = true;
|
|
34997
35062
|
this.animPan = true;
|
|
35063
|
+
this.animLiveBarMs = 0;
|
|
35064
|
+
// forming-bar OHLC glide time-constant; 0 = each tick snaps
|
|
35065
|
+
this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
|
|
35066
|
+
// the duration the settings-dialog on/off toggle restores (last non-zero value configured)
|
|
34998
35067
|
// Brand default candles.
|
|
34999
35068
|
this.candleUp = BULLISH;
|
|
35000
35069
|
this.candleDown = BEARISH;
|
|
@@ -35119,7 +35188,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35119
35188
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
35120
35189
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
35121
35190
|
this.name = "native";
|
|
35122
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
35191
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
35123
35192
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
35124
35193
|
* so moving onto the button doesn't count as leaving). */
|
|
35125
35194
|
this.onScrollProximityMove = (e) => {
|
|
@@ -35153,6 +35222,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35153
35222
|
this.backendMode = opts.nativeBackend;
|
|
35154
35223
|
this.animZoom = opts.animZoom;
|
|
35155
35224
|
this.animPan = opts.animPan;
|
|
35225
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
|
|
35156
35226
|
this.glowAmount = opts.glow;
|
|
35157
35227
|
this.candleUp = opts.upColor;
|
|
35158
35228
|
this.candleDown = opts.downColor;
|
|
@@ -35202,6 +35272,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35202
35272
|
case "animPan":
|
|
35203
35273
|
this.animPan = Boolean(value);
|
|
35204
35274
|
return;
|
|
35275
|
+
case "animLiveBar":
|
|
35276
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(value));
|
|
35277
|
+
return;
|
|
35278
|
+
// affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
|
|
35205
35279
|
case "intro": {
|
|
35206
35280
|
const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
|
|
35207
35281
|
this.introStyle = s;
|
|
@@ -35338,6 +35412,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35338
35412
|
return this.animZoom;
|
|
35339
35413
|
case "animPan":
|
|
35340
35414
|
return this.animPan;
|
|
35415
|
+
case "animLiveBar":
|
|
35416
|
+
return this.animLiveBarMs;
|
|
35341
35417
|
case "intro":
|
|
35342
35418
|
return this.introStyle;
|
|
35343
35419
|
case "zoomAnchor":
|
|
@@ -35506,7 +35582,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35506
35582
|
labelsVisible: this.scene.showAxisLabels,
|
|
35507
35583
|
currentPriceLine: this.scene.showPriceLine,
|
|
35508
35584
|
priceLabel: this.scene.showPriceLabel,
|
|
35509
|
-
countdown: this.scene.showCountdown
|
|
35585
|
+
countdown: this.scene.showCountdown,
|
|
35586
|
+
animateLastPrice: this.animLiveBarMs > 0
|
|
35510
35587
|
},
|
|
35511
35588
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
35512
35589
|
trades: {
|
|
@@ -35613,6 +35690,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35613
35690
|
this.scene.showPriceLabel = next2.priceScale.priceLabel;
|
|
35614
35691
|
this.scene.showCountdown = next2.priceScale.countdown;
|
|
35615
35692
|
this.syncCountdownTimer();
|
|
35693
|
+
this.animLiveBarMs = next2.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
|
|
35616
35694
|
s.separatorColor = keepInherit(s.separatorColor, next2.panes.separatorColor, prevTheme.borderColor);
|
|
35617
35695
|
this.scene.tradeMarkers = {
|
|
35618
35696
|
visible: next2.trades.visible,
|
|
@@ -36077,8 +36155,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36077
36155
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
36078
36156
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
36079
36157
|
drawingsSnapMode: () => this.snapMode,
|
|
36080
|
-
drawingsPointerDown: (x, y, snap,
|
|
36081
|
-
drawingsPointerMove: (x, y, snap,
|
|
36158
|
+
drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
|
|
36159
|
+
drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
|
|
36082
36160
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
36083
36161
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
36084
36162
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -36395,18 +36473,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36395
36473
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
36396
36474
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
36397
36475
|
const newHeadTime = this.bars[0].time;
|
|
36398
|
-
let
|
|
36476
|
+
let shift4 = 0;
|
|
36399
36477
|
if (newHeadTime !== prevHeadTime) {
|
|
36400
36478
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
36401
|
-
if (prepended > 0)
|
|
36479
|
+
if (prepended > 0) shift4 = prepended;
|
|
36402
36480
|
else {
|
|
36403
36481
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
36404
|
-
if (trimmed > 0)
|
|
36482
|
+
if (trimmed > 0) shift4 = -trimmed;
|
|
36405
36483
|
}
|
|
36406
36484
|
}
|
|
36407
|
-
if (
|
|
36408
|
-
this.zoomAnchorLogical +=
|
|
36409
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
36485
|
+
if (shift4 !== 0) {
|
|
36486
|
+
this.zoomAnchorLogical += shift4;
|
|
36487
|
+
if (this.hoverLogical != null) this.hoverLogical += shift4;
|
|
36410
36488
|
}
|
|
36411
36489
|
}
|
|
36412
36490
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -36429,8 +36507,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36429
36507
|
const last2 = this.bars[n - 1];
|
|
36430
36508
|
if (last2 && bar.time === last2.time) {
|
|
36431
36509
|
this.bars[n - 1] = bar;
|
|
36432
|
-
if (this.liveEaseTime !== bar.time)
|
|
36433
|
-
|
|
36510
|
+
if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
|
|
36511
|
+
this.syncLiveEase(bar);
|
|
36512
|
+
} else {
|
|
36513
|
+
this.animator.start();
|
|
36514
|
+
}
|
|
36434
36515
|
} else if (!last2 || bar.time > last2.time) {
|
|
36435
36516
|
this.bars.push(bar);
|
|
36436
36517
|
this.syncLiveEase(bar);
|
|
@@ -36442,6 +36523,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36442
36523
|
this.scene.bars = this.bars;
|
|
36443
36524
|
this.scheduler.invalidate(4 /* Full */);
|
|
36444
36525
|
}
|
|
36526
|
+
/** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
|
|
36527
|
+
* what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
|
|
36528
|
+
setLiveBarEase(ms) {
|
|
36529
|
+
this.animLiveBarMs = ms;
|
|
36530
|
+
if (ms > 0) this.animLiveBarOnMs = ms;
|
|
36531
|
+
}
|
|
36445
36532
|
/** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
|
|
36446
36533
|
syncLiveEase(bar) {
|
|
36447
36534
|
this.liveEaseTime = bar.time;
|
|
@@ -36454,9 +36541,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36454
36541
|
const target = this.bars[this.bars.length - 1];
|
|
36455
36542
|
if (!target || this.liveEaseTime !== target.time) return false;
|
|
36456
36543
|
const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
|
|
36457
|
-
const
|
|
36458
|
-
const
|
|
36459
|
-
const
|
|
36544
|
+
const tau = this.animLiveBarMs;
|
|
36545
|
+
const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
|
|
36546
|
+
const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
|
|
36547
|
+
const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
|
|
36460
36548
|
const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
|
|
36461
36549
|
this.liveEaseHigh = active ? nh : target.high;
|
|
36462
36550
|
this.liveEaseLow = active ? nl : target.low;
|
|
@@ -38012,15 +38100,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38012
38100
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
38013
38101
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
38014
38102
|
collapsedMasterMap() {
|
|
38015
|
-
const
|
|
38103
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
38016
38104
|
for (const pane of this.scene.panes.values()) {
|
|
38017
38105
|
if (!pane.collapsed) continue;
|
|
38018
38106
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
38019
38107
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
38020
38108
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
38021
|
-
|
|
38109
|
+
map2.set(pane.id, master?.id ?? null);
|
|
38022
38110
|
}
|
|
38023
|
-
return
|
|
38111
|
+
return map2;
|
|
38024
38112
|
}
|
|
38025
38113
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
38026
38114
|
setToolbarGutter(px) {
|
|
@@ -38416,29 +38504,2335 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38416
38504
|
registerNativeIndicator(vpvrDescriptor);
|
|
38417
38505
|
}
|
|
38418
38506
|
|
|
38507
|
+
// src/core/model/identity.ts
|
|
38508
|
+
function stableSeriesId(parts3) {
|
|
38509
|
+
const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
38510
|
+
return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
|
|
38511
|
+
}
|
|
38512
|
+
|
|
38513
|
+
// src/core/native-indicators/classics/define.ts
|
|
38514
|
+
function num3(inputs, key, fallback) {
|
|
38515
|
+
const v = inputs[key];
|
|
38516
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
38517
|
+
}
|
|
38518
|
+
function str3(inputs, key, fallback) {
|
|
38519
|
+
const v = inputs[key];
|
|
38520
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
38521
|
+
}
|
|
38522
|
+
function toPoints(bars, values, colors) {
|
|
38523
|
+
const pts = new Array(bars.length);
|
|
38524
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38525
|
+
const v = values[i];
|
|
38526
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
38527
|
+
const c = colors?.[i];
|
|
38528
|
+
if (c != null) p.color = c;
|
|
38529
|
+
pts[i] = p;
|
|
38530
|
+
}
|
|
38531
|
+
return pts;
|
|
38532
|
+
}
|
|
38533
|
+
var ClassicIndicator = class {
|
|
38534
|
+
constructor(spec) {
|
|
38535
|
+
this.spec = spec;
|
|
38536
|
+
this.inputs = {};
|
|
38537
|
+
}
|
|
38538
|
+
start(ctx, inputs) {
|
|
38539
|
+
this.ctx = ctx;
|
|
38540
|
+
this.inputs = inputs;
|
|
38541
|
+
this.recompute();
|
|
38542
|
+
}
|
|
38543
|
+
onBars() {
|
|
38544
|
+
this.recompute();
|
|
38545
|
+
}
|
|
38546
|
+
onViewport() {
|
|
38547
|
+
}
|
|
38548
|
+
setInputs(inputs) {
|
|
38549
|
+
this.inputs = inputs;
|
|
38550
|
+
this.recompute();
|
|
38551
|
+
}
|
|
38552
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
38553
|
+
suspend() {
|
|
38554
|
+
}
|
|
38555
|
+
resume() {
|
|
38556
|
+
this.recompute();
|
|
38557
|
+
}
|
|
38558
|
+
stop() {
|
|
38559
|
+
}
|
|
38560
|
+
recompute() {
|
|
38561
|
+
const bars = this.ctx.bars();
|
|
38562
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
38563
|
+
const instanceId = this.spec.type;
|
|
38564
|
+
const series = [];
|
|
38565
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
38566
|
+
out.plots.forEach((plot, ordinal) => {
|
|
38567
|
+
const kind = plot.kind ?? "line";
|
|
38568
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
38569
|
+
idsByKey.set(plot.key, id);
|
|
38570
|
+
series.push({
|
|
38571
|
+
id,
|
|
38572
|
+
title: plot.title,
|
|
38573
|
+
paneId: "",
|
|
38574
|
+
// stamped by the orchestrator
|
|
38575
|
+
kind,
|
|
38576
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
38577
|
+
style: {
|
|
38578
|
+
color: plot.color,
|
|
38579
|
+
width: plot.width ?? 1,
|
|
38580
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
38581
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
38582
|
+
},
|
|
38583
|
+
...plot.overlay ? { overlay: true } : {}
|
|
38584
|
+
});
|
|
38585
|
+
});
|
|
38586
|
+
if (out.markers && out.markers.length > 0) {
|
|
38587
|
+
series.push({
|
|
38588
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
38589
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
38590
|
+
paneId: "",
|
|
38591
|
+
kind: "markers",
|
|
38592
|
+
markers: out.markers
|
|
38593
|
+
});
|
|
38594
|
+
}
|
|
38595
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
38596
|
+
const from = idsByKey.get(band.from);
|
|
38597
|
+
const to = idsByKey.get(band.to);
|
|
38598
|
+
if (!from || !to) return [];
|
|
38599
|
+
return [
|
|
38600
|
+
{
|
|
38601
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
38602
|
+
paneId: "",
|
|
38603
|
+
fromSeriesId: from,
|
|
38604
|
+
toSeriesId: to,
|
|
38605
|
+
color: band.color
|
|
38606
|
+
}
|
|
38607
|
+
];
|
|
38608
|
+
});
|
|
38609
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
38610
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
38611
|
+
paneId: "",
|
|
38612
|
+
price: level.price,
|
|
38613
|
+
...level.color != null ? { color: level.color } : {},
|
|
38614
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
38615
|
+
width: 1,
|
|
38616
|
+
...level.title != null ? { title: level.title } : {}
|
|
38617
|
+
}));
|
|
38618
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
38619
|
+
const { key, ...rest } = poly;
|
|
38620
|
+
return {
|
|
38621
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
38622
|
+
paneId: "",
|
|
38623
|
+
...rest
|
|
38624
|
+
};
|
|
38625
|
+
});
|
|
38626
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
38627
|
+
this.ctx.setStatus("idle");
|
|
38628
|
+
}
|
|
38629
|
+
};
|
|
38630
|
+
function classicDescriptor(spec) {
|
|
38631
|
+
return {
|
|
38632
|
+
type: spec.type,
|
|
38633
|
+
title: spec.title,
|
|
38634
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
38635
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
38636
|
+
overlay: spec.overlay,
|
|
38637
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
38638
|
+
multiInstance: true,
|
|
38639
|
+
inputsSchema: () => spec.inputs,
|
|
38640
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
38641
|
+
create: () => new ClassicIndicator(spec)
|
|
38642
|
+
};
|
|
38643
|
+
}
|
|
38644
|
+
|
|
38645
|
+
// src/core/native-indicators/classics/math.ts
|
|
38646
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
38647
|
+
function sourceValues(bars, source) {
|
|
38648
|
+
const out = new Array(bars.length);
|
|
38649
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38650
|
+
const b = bars[i];
|
|
38651
|
+
switch (source) {
|
|
38652
|
+
case "Open":
|
|
38653
|
+
out[i] = b.open;
|
|
38654
|
+
break;
|
|
38655
|
+
case "High":
|
|
38656
|
+
out[i] = b.high;
|
|
38657
|
+
break;
|
|
38658
|
+
case "Low":
|
|
38659
|
+
out[i] = b.low;
|
|
38660
|
+
break;
|
|
38661
|
+
case "HL2":
|
|
38662
|
+
out[i] = (b.high + b.low) / 2;
|
|
38663
|
+
break;
|
|
38664
|
+
case "HLC3":
|
|
38665
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
38666
|
+
break;
|
|
38667
|
+
case "OHLC4":
|
|
38668
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
38669
|
+
break;
|
|
38670
|
+
case "HLCC4":
|
|
38671
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
38672
|
+
break;
|
|
38673
|
+
default:
|
|
38674
|
+
out[i] = b.close;
|
|
38675
|
+
}
|
|
38676
|
+
}
|
|
38677
|
+
return out;
|
|
38678
|
+
}
|
|
38679
|
+
function volumes(bars) {
|
|
38680
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
38681
|
+
}
|
|
38682
|
+
function highs(bars) {
|
|
38683
|
+
return bars.map((b) => b.high);
|
|
38684
|
+
}
|
|
38685
|
+
function lows(bars) {
|
|
38686
|
+
return bars.map((b) => b.low);
|
|
38687
|
+
}
|
|
38688
|
+
function closes(bars) {
|
|
38689
|
+
return bars.map((b) => b.close);
|
|
38690
|
+
}
|
|
38691
|
+
function opens(bars) {
|
|
38692
|
+
return bars.map((b) => b.open);
|
|
38693
|
+
}
|
|
38694
|
+
function zip(a, b, f) {
|
|
38695
|
+
const out = new Array(a.length);
|
|
38696
|
+
for (let i = 0; i < a.length; i++) {
|
|
38697
|
+
const x = a[i];
|
|
38698
|
+
const y = b[i];
|
|
38699
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
38700
|
+
}
|
|
38701
|
+
return out;
|
|
38702
|
+
}
|
|
38703
|
+
function map(a, f) {
|
|
38704
|
+
const out = new Array(a.length);
|
|
38705
|
+
for (let i = 0; i < a.length; i++) {
|
|
38706
|
+
const x = a[i];
|
|
38707
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
38708
|
+
}
|
|
38709
|
+
return out;
|
|
38710
|
+
}
|
|
38711
|
+
function sub(a, b) {
|
|
38712
|
+
return zip(a, b, (x, y) => x - y);
|
|
38713
|
+
}
|
|
38714
|
+
function windowed(v, len, fold) {
|
|
38715
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38716
|
+
if (len <= 0) return out;
|
|
38717
|
+
let run = 0;
|
|
38718
|
+
for (let i = 0; i < v.length; i++) {
|
|
38719
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
38720
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
38721
|
+
}
|
|
38722
|
+
return out;
|
|
38723
|
+
}
|
|
38724
|
+
function sum(v, len) {
|
|
38725
|
+
return windowed(v, len, (w, from, to) => {
|
|
38726
|
+
let s = 0;
|
|
38727
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38728
|
+
return s;
|
|
38729
|
+
});
|
|
38730
|
+
}
|
|
38731
|
+
function sma(v, len) {
|
|
38732
|
+
return windowed(v, len, (w, from, to) => {
|
|
38733
|
+
let s = 0;
|
|
38734
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38735
|
+
return s / len;
|
|
38736
|
+
});
|
|
38737
|
+
}
|
|
38738
|
+
function wma(v, len) {
|
|
38739
|
+
const norm = len * (len + 1) / 2;
|
|
38740
|
+
return windowed(v, len, (w, from, to) => {
|
|
38741
|
+
let s = 0;
|
|
38742
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
38743
|
+
return s / norm;
|
|
38744
|
+
});
|
|
38745
|
+
}
|
|
38746
|
+
function vwma(src, vol, len) {
|
|
38747
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
38748
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
38749
|
+
}
|
|
38750
|
+
function stdev(v, len) {
|
|
38751
|
+
return windowed(v, len, (w, from, to) => {
|
|
38752
|
+
let s = 0;
|
|
38753
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38754
|
+
const mean = s / len;
|
|
38755
|
+
let sq = 0;
|
|
38756
|
+
for (let k = from; k <= to; k++) {
|
|
38757
|
+
const d = w[k] - mean;
|
|
38758
|
+
sq += d * d;
|
|
38759
|
+
}
|
|
38760
|
+
return Math.sqrt(sq / len);
|
|
38761
|
+
});
|
|
38762
|
+
}
|
|
38763
|
+
function highest(v, len) {
|
|
38764
|
+
return windowed(v, len, (w, from, to) => {
|
|
38765
|
+
let m = -Infinity;
|
|
38766
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
38767
|
+
return m;
|
|
38768
|
+
});
|
|
38769
|
+
}
|
|
38770
|
+
function lowest(v, len) {
|
|
38771
|
+
return windowed(v, len, (w, from, to) => {
|
|
38772
|
+
let m = Infinity;
|
|
38773
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
38774
|
+
return m;
|
|
38775
|
+
});
|
|
38776
|
+
}
|
|
38777
|
+
function barsSinceHighest(v, len) {
|
|
38778
|
+
return windowed(v, len, (w, from, to) => {
|
|
38779
|
+
let m = -Infinity;
|
|
38780
|
+
let at = to;
|
|
38781
|
+
for (let k = from; k <= to; k++) {
|
|
38782
|
+
if (w[k] >= m) {
|
|
38783
|
+
m = w[k];
|
|
38784
|
+
at = k;
|
|
38785
|
+
}
|
|
38786
|
+
}
|
|
38787
|
+
return to - at;
|
|
38788
|
+
});
|
|
38789
|
+
}
|
|
38790
|
+
function barsSinceLowest(v, len) {
|
|
38791
|
+
return windowed(v, len, (w, from, to) => {
|
|
38792
|
+
let m = Infinity;
|
|
38793
|
+
let at = to;
|
|
38794
|
+
for (let k = from; k <= to; k++) {
|
|
38795
|
+
if (w[k] <= m) {
|
|
38796
|
+
m = w[k];
|
|
38797
|
+
at = k;
|
|
38798
|
+
}
|
|
38799
|
+
}
|
|
38800
|
+
return to - at;
|
|
38801
|
+
});
|
|
38802
|
+
}
|
|
38803
|
+
function recursive(v, len, alpha) {
|
|
38804
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38805
|
+
let run = 0;
|
|
38806
|
+
let prev3 = Number.NaN;
|
|
38807
|
+
for (let i = 0; i < v.length; i++) {
|
|
38808
|
+
const x = v[i];
|
|
38809
|
+
if (!Number.isFinite(x)) {
|
|
38810
|
+
run = 0;
|
|
38811
|
+
prev3 = Number.NaN;
|
|
38812
|
+
continue;
|
|
38813
|
+
}
|
|
38814
|
+
run += 1;
|
|
38815
|
+
if (Number.isFinite(prev3)) {
|
|
38816
|
+
prev3 = alpha * x + (1 - alpha) * prev3;
|
|
38817
|
+
out[i] = prev3;
|
|
38818
|
+
} else if (run >= len) {
|
|
38819
|
+
let s = 0;
|
|
38820
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
38821
|
+
prev3 = s / len;
|
|
38822
|
+
out[i] = prev3;
|
|
38823
|
+
}
|
|
38824
|
+
}
|
|
38825
|
+
return out;
|
|
38826
|
+
}
|
|
38827
|
+
function ema(v, len) {
|
|
38828
|
+
return recursive(v, len, 2 / (len + 1));
|
|
38829
|
+
}
|
|
38830
|
+
function rma(v, len) {
|
|
38831
|
+
return recursive(v, len, 1 / len);
|
|
38832
|
+
}
|
|
38833
|
+
function change(v, n = 1) {
|
|
38834
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38835
|
+
for (let i = n; i < v.length; i++) {
|
|
38836
|
+
const a = v[i];
|
|
38837
|
+
const b = v[i - n];
|
|
38838
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
38839
|
+
}
|
|
38840
|
+
return out;
|
|
38841
|
+
}
|
|
38842
|
+
function roc(v, len) {
|
|
38843
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38844
|
+
for (let i = len; i < v.length; i++) {
|
|
38845
|
+
const a = v[i];
|
|
38846
|
+
const b = v[i - len];
|
|
38847
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
38848
|
+
}
|
|
38849
|
+
return out;
|
|
38850
|
+
}
|
|
38851
|
+
function shift3(v, n) {
|
|
38852
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38853
|
+
for (let i = 0; i < v.length; i++) {
|
|
38854
|
+
const j = i - n;
|
|
38855
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
38856
|
+
}
|
|
38857
|
+
return out;
|
|
38858
|
+
}
|
|
38859
|
+
function trueRange(bars) {
|
|
38860
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
38861
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38862
|
+
const b = bars[i];
|
|
38863
|
+
if (i === 0) {
|
|
38864
|
+
out[i] = b.high - b.low;
|
|
38865
|
+
continue;
|
|
38866
|
+
}
|
|
38867
|
+
const pc = bars[i - 1].close;
|
|
38868
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
38869
|
+
}
|
|
38870
|
+
return out;
|
|
38871
|
+
}
|
|
38872
|
+
function atr(bars, len) {
|
|
38873
|
+
return rma(trueRange(bars), len);
|
|
38874
|
+
}
|
|
38875
|
+
function rsi(v, len) {
|
|
38876
|
+
const d = change(v);
|
|
38877
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
38878
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
38879
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
38880
|
+
}
|
|
38881
|
+
function stoch(v, hi, lo, len) {
|
|
38882
|
+
const hh = highest(hi, len);
|
|
38883
|
+
const ll = lowest(lo, len);
|
|
38884
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38885
|
+
for (let i = 0; i < v.length; i++) {
|
|
38886
|
+
const x = v[i];
|
|
38887
|
+
const h = hh[i];
|
|
38888
|
+
const l = ll[i];
|
|
38889
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38890
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
38891
|
+
}
|
|
38892
|
+
return out;
|
|
38893
|
+
}
|
|
38894
|
+
function linreg(v, len) {
|
|
38895
|
+
const sx = (len - 1) * len / 2;
|
|
38896
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
38897
|
+
const denom = len * sxx - sx * sx;
|
|
38898
|
+
return windowed(v, len, (w, from, to) => {
|
|
38899
|
+
let sy = 0;
|
|
38900
|
+
let sxy = 0;
|
|
38901
|
+
for (let k = from; k <= to; k++) {
|
|
38902
|
+
const x = k - from;
|
|
38903
|
+
sy += w[k];
|
|
38904
|
+
sxy += x * w[k];
|
|
38905
|
+
}
|
|
38906
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
38907
|
+
const intercept = (sy - slope * sx) / len;
|
|
38908
|
+
return intercept + slope * (len - 1);
|
|
38909
|
+
});
|
|
38910
|
+
}
|
|
38911
|
+
function swma(v) {
|
|
38912
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38913
|
+
for (let i = 3; i < v.length; i++) {
|
|
38914
|
+
const a = v[i - 3];
|
|
38915
|
+
const b = v[i - 2];
|
|
38916
|
+
const c = v[i - 1];
|
|
38917
|
+
const d = v[i];
|
|
38918
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
38919
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
38920
|
+
}
|
|
38921
|
+
}
|
|
38922
|
+
return out;
|
|
38923
|
+
}
|
|
38924
|
+
function percentRank(v, len) {
|
|
38925
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38926
|
+
for (let i = len; i < v.length; i++) {
|
|
38927
|
+
const x = v[i];
|
|
38928
|
+
if (!Number.isFinite(x)) continue;
|
|
38929
|
+
let below = 0;
|
|
38930
|
+
let count = 0;
|
|
38931
|
+
for (let k = i - len; k < i; k++) {
|
|
38932
|
+
const y = v[k];
|
|
38933
|
+
if (!Number.isFinite(y)) {
|
|
38934
|
+
count = -1;
|
|
38935
|
+
break;
|
|
38936
|
+
}
|
|
38937
|
+
count += 1;
|
|
38938
|
+
if (y <= x) below += 1;
|
|
38939
|
+
}
|
|
38940
|
+
if (count === len) out[i] = 100 * below / len;
|
|
38941
|
+
}
|
|
38942
|
+
return out;
|
|
38943
|
+
}
|
|
38944
|
+
function cumSum(v) {
|
|
38945
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38946
|
+
let acc = 0;
|
|
38947
|
+
let seen = false;
|
|
38948
|
+
for (let i = 0; i < v.length; i++) {
|
|
38949
|
+
const x = v[i];
|
|
38950
|
+
if (Number.isFinite(x)) {
|
|
38951
|
+
acc += x;
|
|
38952
|
+
seen = true;
|
|
38953
|
+
}
|
|
38954
|
+
if (seen) out[i] = acc;
|
|
38955
|
+
}
|
|
38956
|
+
return out;
|
|
38957
|
+
}
|
|
38958
|
+
|
|
38959
|
+
// src/core/native-indicators/classics/shared.ts
|
|
38960
|
+
function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
|
|
38961
|
+
return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
|
|
38962
|
+
}
|
|
38963
|
+
function sourceInput(defval = "Close") {
|
|
38964
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
38965
|
+
}
|
|
38966
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
38967
|
+
return { key, title, type: "color", defval };
|
|
38968
|
+
}
|
|
38969
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
38970
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
38971
|
+
if (!m) return hex;
|
|
38972
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
38973
|
+
return `#${m[1]}${a}`;
|
|
38974
|
+
}
|
|
38975
|
+
|
|
38976
|
+
// src/core/native-indicators/classics/averages.ts
|
|
38977
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
38978
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
38979
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
38980
|
+
return {
|
|
38981
|
+
type,
|
|
38982
|
+
title,
|
|
38983
|
+
shortTitle,
|
|
38984
|
+
overlay: true,
|
|
38985
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
38986
|
+
compute: (bars, inputs) => {
|
|
38987
|
+
const len = num3(inputs, "length", 20);
|
|
38988
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38989
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
38990
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
38991
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38992
|
+
}
|
|
38993
|
+
};
|
|
38994
|
+
}
|
|
38995
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
38996
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
38997
|
+
var movingAverage = {
|
|
38998
|
+
type: "moving-average",
|
|
38999
|
+
title: "Moving Average",
|
|
39000
|
+
shortTitle: "MA",
|
|
39001
|
+
overlay: true,
|
|
39002
|
+
inputs: [
|
|
39003
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
39004
|
+
lengthInput(20),
|
|
39005
|
+
sourceInput(),
|
|
39006
|
+
offsetInput,
|
|
39007
|
+
colorInput()
|
|
39008
|
+
],
|
|
39009
|
+
compute: (bars, inputs) => {
|
|
39010
|
+
const len = num3(inputs, "length", 20);
|
|
39011
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39012
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
39013
|
+
let values;
|
|
39014
|
+
switch (kind) {
|
|
39015
|
+
case "EMA":
|
|
39016
|
+
values = ema(src, len);
|
|
39017
|
+
break;
|
|
39018
|
+
case "WMA":
|
|
39019
|
+
values = wma(src, len);
|
|
39020
|
+
break;
|
|
39021
|
+
case "RMA":
|
|
39022
|
+
values = rma(src, len);
|
|
39023
|
+
break;
|
|
39024
|
+
case "VWMA":
|
|
39025
|
+
values = vwma(src, volumes(bars), len);
|
|
39026
|
+
break;
|
|
39027
|
+
default:
|
|
39028
|
+
values = sma(src, len);
|
|
39029
|
+
}
|
|
39030
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
39031
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
39032
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39033
|
+
}
|
|
39034
|
+
};
|
|
39035
|
+
var smoothedMa = {
|
|
39036
|
+
type: "rma",
|
|
39037
|
+
title: "Smoothed Moving Average",
|
|
39038
|
+
shortTitle: "RMA",
|
|
39039
|
+
overlay: true,
|
|
39040
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39041
|
+
compute: (bars, inputs) => ({
|
|
39042
|
+
plots: [
|
|
39043
|
+
{
|
|
39044
|
+
key: "rma",
|
|
39045
|
+
title: "RMA",
|
|
39046
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39047
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39048
|
+
width: 2
|
|
39049
|
+
}
|
|
39050
|
+
]
|
|
39051
|
+
})
|
|
39052
|
+
};
|
|
39053
|
+
var zlema = {
|
|
39054
|
+
type: "zlema",
|
|
39055
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
39056
|
+
shortTitle: "ZLEMA",
|
|
39057
|
+
overlay: true,
|
|
39058
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39059
|
+
compute: (bars, inputs) => {
|
|
39060
|
+
const len = num3(inputs, "length", 14);
|
|
39061
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39062
|
+
const lag = Math.floor((len - 1) / 2);
|
|
39063
|
+
const lagged = shift3(src, lag);
|
|
39064
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
39065
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39066
|
+
}
|
|
39067
|
+
};
|
|
39068
|
+
var vidya = {
|
|
39069
|
+
type: "vidya",
|
|
39070
|
+
title: "Variable Index Dynamic Average",
|
|
39071
|
+
shortTitle: "VIDYA",
|
|
39072
|
+
overlay: true,
|
|
39073
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
39074
|
+
compute: (bars, inputs) => {
|
|
39075
|
+
const len = num3(inputs, "length", 14);
|
|
39076
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
39077
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39078
|
+
const d = change(src);
|
|
39079
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
39080
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
39081
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
39082
|
+
let u = 0;
|
|
39083
|
+
let w = 0;
|
|
39084
|
+
let ok = true;
|
|
39085
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
39086
|
+
const x = d[k];
|
|
39087
|
+
if (!Number.isFinite(x)) {
|
|
39088
|
+
ok = false;
|
|
39089
|
+
break;
|
|
39090
|
+
}
|
|
39091
|
+
if (x > 0) u += x;
|
|
39092
|
+
else w -= x;
|
|
39093
|
+
}
|
|
39094
|
+
if (ok) {
|
|
39095
|
+
su[i] = u;
|
|
39096
|
+
sd[i] = w;
|
|
39097
|
+
}
|
|
39098
|
+
}
|
|
39099
|
+
const alpha = 2 / (len + 1);
|
|
39100
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
39101
|
+
let prev3 = Number.NaN;
|
|
39102
|
+
for (let i = 0; i < src.length; i++) {
|
|
39103
|
+
const x = src[i];
|
|
39104
|
+
const u = su[i];
|
|
39105
|
+
const w = sd[i];
|
|
39106
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
39107
|
+
const total = u + w;
|
|
39108
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
39109
|
+
prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
|
|
39110
|
+
out[i] = prev3;
|
|
39111
|
+
}
|
|
39112
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39113
|
+
}
|
|
39114
|
+
};
|
|
39115
|
+
var maEnvelope = {
|
|
39116
|
+
type: "ma-envelope",
|
|
39117
|
+
title: "Moving Average Envelope",
|
|
39118
|
+
shortTitle: "MA Env",
|
|
39119
|
+
overlay: true,
|
|
39120
|
+
inputs: [
|
|
39121
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
39122
|
+
lengthInput(20),
|
|
39123
|
+
sourceInput(),
|
|
39124
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
39125
|
+
colorInput()
|
|
39126
|
+
],
|
|
39127
|
+
compute: (bars, inputs) => {
|
|
39128
|
+
const len = num3(inputs, "length", 20);
|
|
39129
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39130
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
39131
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
39132
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39133
|
+
return {
|
|
39134
|
+
plots: [
|
|
39135
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
39136
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
39137
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
39138
|
+
],
|
|
39139
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39140
|
+
};
|
|
39141
|
+
}
|
|
39142
|
+
};
|
|
39143
|
+
var linearRegression = {
|
|
39144
|
+
type: "linear-regression",
|
|
39145
|
+
title: "Linear Regression Curve",
|
|
39146
|
+
shortTitle: "LinReg",
|
|
39147
|
+
overlay: true,
|
|
39148
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39149
|
+
compute: (bars, inputs) => ({
|
|
39150
|
+
plots: [
|
|
39151
|
+
{
|
|
39152
|
+
key: "linreg",
|
|
39153
|
+
title: "LinReg",
|
|
39154
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39155
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39156
|
+
width: 2
|
|
39157
|
+
}
|
|
39158
|
+
]
|
|
39159
|
+
})
|
|
39160
|
+
};
|
|
39161
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
39162
|
+
|
|
39163
|
+
// src/core/native-indicators/classics/bands.ts
|
|
39164
|
+
function bollinger(bars, inputs) {
|
|
39165
|
+
const len = num3(inputs, "length", 20);
|
|
39166
|
+
const mult = num3(inputs, "mult", 2);
|
|
39167
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39168
|
+
const basis = sma(src, len);
|
|
39169
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
39170
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
39171
|
+
}
|
|
39172
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
39173
|
+
var bollingerBands = {
|
|
39174
|
+
type: "bollinger-bands",
|
|
39175
|
+
title: "Bollinger Bands",
|
|
39176
|
+
shortTitle: "BB",
|
|
39177
|
+
overlay: true,
|
|
39178
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39179
|
+
compute: (bars, inputs) => {
|
|
39180
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39181
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39182
|
+
return {
|
|
39183
|
+
plots: [
|
|
39184
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39185
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39186
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39187
|
+
],
|
|
39188
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39189
|
+
};
|
|
39190
|
+
}
|
|
39191
|
+
};
|
|
39192
|
+
var percentB = {
|
|
39193
|
+
type: "percent-b",
|
|
39194
|
+
title: "Bollinger Bands %B",
|
|
39195
|
+
shortTitle: "%B",
|
|
39196
|
+
overlay: false,
|
|
39197
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39198
|
+
compute: (bars, inputs) => {
|
|
39199
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
39200
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39201
|
+
const values = src.map((x, i) => {
|
|
39202
|
+
const u = upper[i];
|
|
39203
|
+
const l = lower[i];
|
|
39204
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
39205
|
+
});
|
|
39206
|
+
return {
|
|
39207
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39208
|
+
levels: [
|
|
39209
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
39210
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
39211
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
39212
|
+
]
|
|
39213
|
+
};
|
|
39214
|
+
}
|
|
39215
|
+
};
|
|
39216
|
+
var bandwidth = {
|
|
39217
|
+
type: "bandwidth",
|
|
39218
|
+
title: "Bollinger Bands Width",
|
|
39219
|
+
shortTitle: "BandWidth",
|
|
39220
|
+
overlay: false,
|
|
39221
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39222
|
+
compute: (bars, inputs) => {
|
|
39223
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39224
|
+
const values = basis.map((b, i) => {
|
|
39225
|
+
const u = upper[i];
|
|
39226
|
+
const l = lower[i];
|
|
39227
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
39228
|
+
});
|
|
39229
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39230
|
+
}
|
|
39231
|
+
};
|
|
39232
|
+
var keltner = {
|
|
39233
|
+
type: "keltner-channels",
|
|
39234
|
+
title: "Keltner Channels",
|
|
39235
|
+
shortTitle: "KC",
|
|
39236
|
+
overlay: true,
|
|
39237
|
+
inputs: [
|
|
39238
|
+
lengthInput(20),
|
|
39239
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39240
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39241
|
+
sourceInput(),
|
|
39242
|
+
colorInput()
|
|
39243
|
+
],
|
|
39244
|
+
compute: (bars, inputs) => {
|
|
39245
|
+
const len = num3(inputs, "length", 20);
|
|
39246
|
+
const mult = num3(inputs, "mult", 2);
|
|
39247
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
39248
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
39249
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39250
|
+
return {
|
|
39251
|
+
plots: [
|
|
39252
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39253
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
39254
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
39255
|
+
],
|
|
39256
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39257
|
+
};
|
|
39258
|
+
}
|
|
39259
|
+
};
|
|
39260
|
+
var donchian = {
|
|
39261
|
+
type: "donchian-channels",
|
|
39262
|
+
title: "Donchian Channels",
|
|
39263
|
+
shortTitle: "DC",
|
|
39264
|
+
overlay: true,
|
|
39265
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39266
|
+
compute: (bars, inputs) => {
|
|
39267
|
+
const len = num3(inputs, "length", 20);
|
|
39268
|
+
const upper = highest(highs(bars), len);
|
|
39269
|
+
const lower = lowest(lows(bars), len);
|
|
39270
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39271
|
+
return {
|
|
39272
|
+
plots: [
|
|
39273
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
39274
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39275
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39276
|
+
],
|
|
39277
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39278
|
+
};
|
|
39279
|
+
}
|
|
39280
|
+
};
|
|
39281
|
+
var chandelier = {
|
|
39282
|
+
type: "chandelier-exit",
|
|
39283
|
+
title: "Chandelier Exit",
|
|
39284
|
+
shortTitle: "CE",
|
|
39285
|
+
overlay: true,
|
|
39286
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
39287
|
+
compute: (bars, inputs) => {
|
|
39288
|
+
const len = num3(inputs, "length", 22);
|
|
39289
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
39290
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
39291
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
39292
|
+
return {
|
|
39293
|
+
plots: [
|
|
39294
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
39295
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
39296
|
+
]
|
|
39297
|
+
};
|
|
39298
|
+
}
|
|
39299
|
+
};
|
|
39300
|
+
var chandeKroll = {
|
|
39301
|
+
type: "chande-kroll-stop",
|
|
39302
|
+
title: "Chande Kroll Stop",
|
|
39303
|
+
shortTitle: "CKS",
|
|
39304
|
+
overlay: true,
|
|
39305
|
+
inputs: [
|
|
39306
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39307
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
39308
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
39309
|
+
],
|
|
39310
|
+
compute: (bars, inputs) => {
|
|
39311
|
+
const p = num3(inputs, "p", 10);
|
|
39312
|
+
const x = num3(inputs, "x", 1);
|
|
39313
|
+
const q = num3(inputs, "q", 9);
|
|
39314
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
39315
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
39316
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
39317
|
+
return {
|
|
39318
|
+
plots: [
|
|
39319
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
39320
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
39321
|
+
]
|
|
39322
|
+
};
|
|
39323
|
+
}
|
|
39324
|
+
};
|
|
39325
|
+
var supertrend = {
|
|
39326
|
+
type: "supertrend",
|
|
39327
|
+
title: "SuperTrend",
|
|
39328
|
+
shortTitle: "SuperTrend",
|
|
39329
|
+
overlay: true,
|
|
39330
|
+
inputs: [
|
|
39331
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39332
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
39333
|
+
],
|
|
39334
|
+
compute: (bars, inputs) => {
|
|
39335
|
+
const len = num3(inputs, "atrLength", 10);
|
|
39336
|
+
const mult = num3(inputs, "mult", 3);
|
|
39337
|
+
const range = atr(bars, len);
|
|
39338
|
+
const n = bars.length;
|
|
39339
|
+
const st = new Array(n).fill(Number.NaN);
|
|
39340
|
+
const dir = new Array(n).fill(1);
|
|
39341
|
+
let up = Number.NaN;
|
|
39342
|
+
let dn = Number.NaN;
|
|
39343
|
+
let trend = 1;
|
|
39344
|
+
for (let i = 0; i < n; i++) {
|
|
39345
|
+
const r = range[i];
|
|
39346
|
+
if (!Number.isFinite(r)) continue;
|
|
39347
|
+
const b = bars[i];
|
|
39348
|
+
const mid = (b.high + b.low) / 2;
|
|
39349
|
+
const basicUp = mid - mult * r;
|
|
39350
|
+
const basicDn = mid + mult * r;
|
|
39351
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
39352
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
39353
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
39354
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
39355
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
39356
|
+
dir[i] = trend;
|
|
39357
|
+
st[i] = trend === 1 ? up : dn;
|
|
39358
|
+
}
|
|
39359
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
39360
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
39361
|
+
}
|
|
39362
|
+
};
|
|
39363
|
+
var alligator = {
|
|
39364
|
+
type: "williams-alligator",
|
|
39365
|
+
title: "Williams Alligator",
|
|
39366
|
+
shortTitle: "Alligator",
|
|
39367
|
+
overlay: true,
|
|
39368
|
+
inputs: [
|
|
39369
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
39370
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
39371
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
39372
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
39373
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
39374
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
39375
|
+
],
|
|
39376
|
+
compute: (bars, inputs) => {
|
|
39377
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39378
|
+
const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
39379
|
+
const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
39380
|
+
const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
39381
|
+
return {
|
|
39382
|
+
plots: [
|
|
39383
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
39384
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
39385
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
39386
|
+
]
|
|
39387
|
+
};
|
|
39388
|
+
}
|
|
39389
|
+
};
|
|
39390
|
+
var gator = {
|
|
39391
|
+
type: "gator-oscillator",
|
|
39392
|
+
title: "Gator Oscillator",
|
|
39393
|
+
shortTitle: "Gator Oscillator",
|
|
39394
|
+
overlay: false,
|
|
39395
|
+
inputs: [],
|
|
39396
|
+
compute: (bars) => {
|
|
39397
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39398
|
+
const jaw = shift3(rma(hl2, 13), 8);
|
|
39399
|
+
const teeth = shift3(rma(hl2, 8), 5);
|
|
39400
|
+
const lips = shift3(rma(hl2, 5), 3);
|
|
39401
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
39402
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
39403
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
39404
|
+
if (!Number.isFinite(x)) return null;
|
|
39405
|
+
const prev3 = i > 0 ? v[i - 1] : Number.NaN;
|
|
39406
|
+
return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
|
|
39407
|
+
});
|
|
39408
|
+
return {
|
|
39409
|
+
plots: [
|
|
39410
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
39411
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
39412
|
+
]
|
|
39413
|
+
};
|
|
39414
|
+
}
|
|
39415
|
+
};
|
|
39416
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
39417
|
+
|
|
39418
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
39419
|
+
function guideLevels(low, high, mid) {
|
|
39420
|
+
const levels = [
|
|
39421
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
39422
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
39423
|
+
];
|
|
39424
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
39425
|
+
return levels;
|
|
39426
|
+
}
|
|
39427
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
39428
|
+
function histColors(values) {
|
|
39429
|
+
return values.map((x, i) => {
|
|
39430
|
+
if (!Number.isFinite(x)) return null;
|
|
39431
|
+
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
39432
|
+
const rising = Number.isFinite(prev3) ? x >= prev3 : true;
|
|
39433
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
39434
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
39435
|
+
});
|
|
39436
|
+
}
|
|
39437
|
+
var rsiSpec = {
|
|
39438
|
+
type: "rsi",
|
|
39439
|
+
title: "Relative Strength Index",
|
|
39440
|
+
shortTitle: "RSI",
|
|
39441
|
+
overlay: false,
|
|
39442
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39443
|
+
compute: (bars, inputs) => ({
|
|
39444
|
+
plots: [
|
|
39445
|
+
{
|
|
39446
|
+
key: "rsi",
|
|
39447
|
+
title: "RSI",
|
|
39448
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39449
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39450
|
+
width: 2
|
|
39451
|
+
}
|
|
39452
|
+
],
|
|
39453
|
+
levels: guideLevels(30, 70, 50)
|
|
39454
|
+
})
|
|
39455
|
+
};
|
|
39456
|
+
var stochastic = {
|
|
39457
|
+
type: "stochastic",
|
|
39458
|
+
title: "Stochastic",
|
|
39459
|
+
shortTitle: "Stoch",
|
|
39460
|
+
overlay: false,
|
|
39461
|
+
inputs: [
|
|
39462
|
+
lengthInput(14, "kLength", "%K length"),
|
|
39463
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
39464
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
39465
|
+
],
|
|
39466
|
+
compute: (bars, inputs) => {
|
|
39467
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
39468
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39469
|
+
return {
|
|
39470
|
+
plots: [
|
|
39471
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39472
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39473
|
+
],
|
|
39474
|
+
levels: guideLevels(20, 80)
|
|
39475
|
+
};
|
|
39476
|
+
}
|
|
39477
|
+
};
|
|
39478
|
+
var stochasticRsi = {
|
|
39479
|
+
type: "stochastic-rsi",
|
|
39480
|
+
title: "Stochastic RSI",
|
|
39481
|
+
shortTitle: "Stoch RSI",
|
|
39482
|
+
overlay: false,
|
|
39483
|
+
inputs: [
|
|
39484
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
39485
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
39486
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
39487
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
39488
|
+
sourceInput()
|
|
39489
|
+
],
|
|
39490
|
+
compute: (bars, inputs) => {
|
|
39491
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
39492
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
39493
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39494
|
+
return {
|
|
39495
|
+
plots: [
|
|
39496
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39497
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39498
|
+
],
|
|
39499
|
+
levels: guideLevels(20, 80)
|
|
39500
|
+
};
|
|
39501
|
+
}
|
|
39502
|
+
};
|
|
39503
|
+
var macd = {
|
|
39504
|
+
type: "macd",
|
|
39505
|
+
title: "MACD",
|
|
39506
|
+
overlay: false,
|
|
39507
|
+
inputs: [
|
|
39508
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39509
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39510
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39511
|
+
sourceInput()
|
|
39512
|
+
],
|
|
39513
|
+
compute: (bars, inputs) => {
|
|
39514
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39515
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
39516
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39517
|
+
const hist = sub(line, signal);
|
|
39518
|
+
return {
|
|
39519
|
+
plots: [
|
|
39520
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39521
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
39522
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39523
|
+
],
|
|
39524
|
+
levels: ZERO_LEVEL
|
|
39525
|
+
};
|
|
39526
|
+
}
|
|
39527
|
+
};
|
|
39528
|
+
var ppo = {
|
|
39529
|
+
type: "ppo",
|
|
39530
|
+
title: "Percentage Price Oscillator",
|
|
39531
|
+
shortTitle: "PPO",
|
|
39532
|
+
overlay: false,
|
|
39533
|
+
inputs: [
|
|
39534
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39535
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39536
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39537
|
+
sourceInput()
|
|
39538
|
+
],
|
|
39539
|
+
compute: (bars, inputs) => {
|
|
39540
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39541
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
39542
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
39543
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
39544
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39545
|
+
const hist = sub(line, signal);
|
|
39546
|
+
return {
|
|
39547
|
+
plots: [
|
|
39548
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39549
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
39550
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39551
|
+
],
|
|
39552
|
+
levels: ZERO_LEVEL
|
|
39553
|
+
};
|
|
39554
|
+
}
|
|
39555
|
+
};
|
|
39556
|
+
var awesome = {
|
|
39557
|
+
type: "awesome-oscillator",
|
|
39558
|
+
title: "Awesome Oscillator",
|
|
39559
|
+
shortTitle: "AO",
|
|
39560
|
+
overlay: false,
|
|
39561
|
+
inputs: [],
|
|
39562
|
+
compute: (bars) => {
|
|
39563
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39564
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
39565
|
+
const colors = ao.map((x, i) => {
|
|
39566
|
+
if (!Number.isFinite(x)) return null;
|
|
39567
|
+
const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
39568
|
+
return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
|
|
39569
|
+
});
|
|
39570
|
+
return {
|
|
39571
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
39572
|
+
levels: ZERO_LEVEL
|
|
39573
|
+
};
|
|
39574
|
+
}
|
|
39575
|
+
};
|
|
39576
|
+
var cci = {
|
|
39577
|
+
type: "commodity-channel-index",
|
|
39578
|
+
title: "Commodity Channel Index",
|
|
39579
|
+
shortTitle: "CCI",
|
|
39580
|
+
overlay: false,
|
|
39581
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
39582
|
+
compute: (bars, inputs) => {
|
|
39583
|
+
const len = num3(inputs, "length", 20);
|
|
39584
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
39585
|
+
const basis = sma(src, len);
|
|
39586
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
39587
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
39588
|
+
const mean = basis[i];
|
|
39589
|
+
if (!Number.isFinite(mean)) continue;
|
|
39590
|
+
let d = 0;
|
|
39591
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
39592
|
+
dev[i] = d / len;
|
|
39593
|
+
}
|
|
39594
|
+
const values = src.map((x, i) => {
|
|
39595
|
+
const b = basis[i];
|
|
39596
|
+
const d = dev[i];
|
|
39597
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
39598
|
+
});
|
|
39599
|
+
return {
|
|
39600
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39601
|
+
levels: guideLevels(-100, 100, 0)
|
|
39602
|
+
};
|
|
39603
|
+
}
|
|
39604
|
+
};
|
|
39605
|
+
var williamsR = {
|
|
39606
|
+
type: "williams-percent-r",
|
|
39607
|
+
title: "Williams %R",
|
|
39608
|
+
shortTitle: "%R",
|
|
39609
|
+
overlay: false,
|
|
39610
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39611
|
+
compute: (bars, inputs) => {
|
|
39612
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
39613
|
+
return {
|
|
39614
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39615
|
+
levels: guideLevels(-80, -20)
|
|
39616
|
+
};
|
|
39617
|
+
}
|
|
39618
|
+
};
|
|
39619
|
+
var cmo = {
|
|
39620
|
+
type: "chande-momentum-oscillator",
|
|
39621
|
+
title: "Chande Momentum Oscillator",
|
|
39622
|
+
shortTitle: "CMO",
|
|
39623
|
+
overlay: false,
|
|
39624
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39625
|
+
compute: (bars, inputs) => {
|
|
39626
|
+
const len = num3(inputs, "length", 9);
|
|
39627
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
39628
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
39629
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
39630
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
39631
|
+
return {
|
|
39632
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39633
|
+
levels: guideLevels(-50, 50, 0)
|
|
39634
|
+
};
|
|
39635
|
+
}
|
|
39636
|
+
};
|
|
39637
|
+
var connorsRsi = {
|
|
39638
|
+
type: "connors-rsi",
|
|
39639
|
+
title: "Connors RSI",
|
|
39640
|
+
shortTitle: "CRSI",
|
|
39641
|
+
overlay: false,
|
|
39642
|
+
inputs: [
|
|
39643
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
39644
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
39645
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
39646
|
+
colorInput()
|
|
39647
|
+
],
|
|
39648
|
+
compute: (bars, inputs) => {
|
|
39649
|
+
const c = closes(bars);
|
|
39650
|
+
const streak = new Array(c.length).fill(0);
|
|
39651
|
+
for (let i = 1; i < c.length; i++) {
|
|
39652
|
+
const d = c[i] - c[i - 1];
|
|
39653
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
39654
|
+
}
|
|
39655
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
39656
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
39657
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
39658
|
+
const values = c.map((_, i) => {
|
|
39659
|
+
const x = a[i];
|
|
39660
|
+
const y = b[i];
|
|
39661
|
+
const z = r[i];
|
|
39662
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
39663
|
+
});
|
|
39664
|
+
return {
|
|
39665
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39666
|
+
levels: guideLevels(20, 80, 50)
|
|
39667
|
+
};
|
|
39668
|
+
}
|
|
39669
|
+
};
|
|
39670
|
+
var fisher = {
|
|
39671
|
+
type: "fisher-transform",
|
|
39672
|
+
title: "Fisher Transform",
|
|
39673
|
+
shortTitle: "Fisher",
|
|
39674
|
+
overlay: false,
|
|
39675
|
+
inputs: [lengthInput(9)],
|
|
39676
|
+
compute: (bars, inputs) => {
|
|
39677
|
+
const len = num3(inputs, "length", 9);
|
|
39678
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39679
|
+
const hi = highest(hl2, len);
|
|
39680
|
+
const lo = lowest(hl2, len);
|
|
39681
|
+
const n = bars.length;
|
|
39682
|
+
const out = new Array(n).fill(Number.NaN);
|
|
39683
|
+
let value = 0;
|
|
39684
|
+
let fish = 0;
|
|
39685
|
+
for (let i = 0; i < n; i++) {
|
|
39686
|
+
const h = hi[i];
|
|
39687
|
+
const l = lo[i];
|
|
39688
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
39689
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
39690
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
39691
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
39692
|
+
out[i] = fish;
|
|
39693
|
+
}
|
|
39694
|
+
return {
|
|
39695
|
+
plots: [
|
|
39696
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
39697
|
+
{ key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
|
|
39698
|
+
],
|
|
39699
|
+
levels: ZERO_LEVEL
|
|
39700
|
+
};
|
|
39701
|
+
}
|
|
39702
|
+
};
|
|
39703
|
+
var trix = {
|
|
39704
|
+
type: "trix",
|
|
39705
|
+
title: "TRIX",
|
|
39706
|
+
overlay: false,
|
|
39707
|
+
inputs: [lengthInput(18), colorInput()],
|
|
39708
|
+
compute: (bars, inputs) => {
|
|
39709
|
+
const len = num3(inputs, "length", 18);
|
|
39710
|
+
const logClose = map(closes(bars), Math.log);
|
|
39711
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
39712
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
39713
|
+
return {
|
|
39714
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39715
|
+
levels: ZERO_LEVEL
|
|
39716
|
+
};
|
|
39717
|
+
}
|
|
39718
|
+
};
|
|
39719
|
+
function tsiLine(src, long, short) {
|
|
39720
|
+
const mom = change(src);
|
|
39721
|
+
const numer = ema(ema(mom, long), short);
|
|
39722
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
39723
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
39724
|
+
}
|
|
39725
|
+
var tsi = {
|
|
39726
|
+
type: "true-strength-index",
|
|
39727
|
+
title: "True Strength Index",
|
|
39728
|
+
shortTitle: "TSI",
|
|
39729
|
+
overlay: false,
|
|
39730
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39731
|
+
compute: (bars, inputs) => {
|
|
39732
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
39733
|
+
return {
|
|
39734
|
+
plots: [
|
|
39735
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
39736
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39737
|
+
],
|
|
39738
|
+
levels: ZERO_LEVEL
|
|
39739
|
+
};
|
|
39740
|
+
}
|
|
39741
|
+
};
|
|
39742
|
+
var smiErgodic = {
|
|
39743
|
+
type: "smi-ergodic",
|
|
39744
|
+
title: "SMI Ergodic Oscillator",
|
|
39745
|
+
shortTitle: "SMIE",
|
|
39746
|
+
overlay: false,
|
|
39747
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
39748
|
+
compute: (bars, inputs) => {
|
|
39749
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
39750
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
39751
|
+
const osc = sub(line, signal);
|
|
39752
|
+
return {
|
|
39753
|
+
plots: [
|
|
39754
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
39755
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
39756
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39757
|
+
],
|
|
39758
|
+
levels: ZERO_LEVEL
|
|
39759
|
+
};
|
|
39760
|
+
}
|
|
39761
|
+
};
|
|
39762
|
+
var stc = {
|
|
39763
|
+
type: "schaff-trend-cycle",
|
|
39764
|
+
title: "Schaff Trend Cycle",
|
|
39765
|
+
shortTitle: "Schaff Trend Cycle",
|
|
39766
|
+
overlay: false,
|
|
39767
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
39768
|
+
compute: (bars, inputs) => {
|
|
39769
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
39770
|
+
const src = closes(bars);
|
|
39771
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
39772
|
+
const stochOf = (v) => {
|
|
39773
|
+
const lo = lowest(v, len);
|
|
39774
|
+
const hi = highest(v, len);
|
|
39775
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
39776
|
+
let prevRaw = Number.NaN;
|
|
39777
|
+
let smoothed = Number.NaN;
|
|
39778
|
+
for (let i = 0; i < v.length; i++) {
|
|
39779
|
+
const x = v[i];
|
|
39780
|
+
const l = lo[i];
|
|
39781
|
+
const h = hi[i];
|
|
39782
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
39783
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
39784
|
+
prevRaw = raw;
|
|
39785
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
39786
|
+
out[i] = smoothed;
|
|
39787
|
+
}
|
|
39788
|
+
return out;
|
|
39789
|
+
};
|
|
39790
|
+
const values = stochOf(stochOf(macdLine));
|
|
39791
|
+
return {
|
|
39792
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
39793
|
+
levels: guideLevels(25, 75)
|
|
39794
|
+
};
|
|
39795
|
+
}
|
|
39796
|
+
};
|
|
39797
|
+
var kst = {
|
|
39798
|
+
type: "know-sure-thing",
|
|
39799
|
+
title: "Know Sure Thing",
|
|
39800
|
+
shortTitle: "KST",
|
|
39801
|
+
overlay: false,
|
|
39802
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
39803
|
+
compute: (bars, inputs) => {
|
|
39804
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39805
|
+
const line = [
|
|
39806
|
+
sma(roc(src, 10), 10),
|
|
39807
|
+
sma(roc(src, 15), 10),
|
|
39808
|
+
sma(roc(src, 20), 10),
|
|
39809
|
+
sma(roc(src, 30), 15)
|
|
39810
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
39811
|
+
return {
|
|
39812
|
+
plots: [
|
|
39813
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
39814
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
39815
|
+
],
|
|
39816
|
+
levels: ZERO_LEVEL
|
|
39817
|
+
};
|
|
39818
|
+
}
|
|
39819
|
+
};
|
|
39820
|
+
var coppock = {
|
|
39821
|
+
type: "coppock-curve",
|
|
39822
|
+
title: "Coppock Curve",
|
|
39823
|
+
shortTitle: "Coppock",
|
|
39824
|
+
overlay: false,
|
|
39825
|
+
inputs: [
|
|
39826
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
39827
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
39828
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
39829
|
+
sourceInput(),
|
|
39830
|
+
colorInput()
|
|
39831
|
+
],
|
|
39832
|
+
compute: (bars, inputs) => {
|
|
39833
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39834
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
39835
|
+
return {
|
|
39836
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39837
|
+
levels: ZERO_LEVEL
|
|
39838
|
+
};
|
|
39839
|
+
}
|
|
39840
|
+
};
|
|
39841
|
+
var dpo = {
|
|
39842
|
+
type: "detrended-price-oscillator",
|
|
39843
|
+
title: "Detrended Price Oscillator",
|
|
39844
|
+
shortTitle: "DPO",
|
|
39845
|
+
overlay: false,
|
|
39846
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39847
|
+
compute: (bars, inputs) => {
|
|
39848
|
+
const len = num3(inputs, "length", 21);
|
|
39849
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
39850
|
+
const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
|
|
39851
|
+
return {
|
|
39852
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39853
|
+
levels: ZERO_LEVEL
|
|
39854
|
+
};
|
|
39855
|
+
}
|
|
39856
|
+
};
|
|
39857
|
+
var rocSpec = {
|
|
39858
|
+
type: "rate-of-change",
|
|
39859
|
+
title: "Rate of Change",
|
|
39860
|
+
shortTitle: "ROC",
|
|
39861
|
+
overlay: false,
|
|
39862
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39863
|
+
compute: (bars, inputs) => ({
|
|
39864
|
+
plots: [
|
|
39865
|
+
{
|
|
39866
|
+
key: "roc",
|
|
39867
|
+
title: "RoC",
|
|
39868
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
39869
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39870
|
+
width: 2
|
|
39871
|
+
}
|
|
39872
|
+
],
|
|
39873
|
+
levels: ZERO_LEVEL
|
|
39874
|
+
})
|
|
39875
|
+
};
|
|
39876
|
+
var ultimate = {
|
|
39877
|
+
type: "ultimate-oscillator",
|
|
39878
|
+
title: "Ultimate Oscillator",
|
|
39879
|
+
shortTitle: "UO",
|
|
39880
|
+
overlay: false,
|
|
39881
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
39882
|
+
compute: (bars, inputs) => {
|
|
39883
|
+
const n = bars.length;
|
|
39884
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
39885
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
39886
|
+
for (let i = 0; i < n; i++) {
|
|
39887
|
+
const b = bars[i];
|
|
39888
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
39889
|
+
const lo = Math.min(b.low, pc);
|
|
39890
|
+
const hi = Math.max(b.high, pc);
|
|
39891
|
+
bp[i] = b.close - lo;
|
|
39892
|
+
tr[i] = hi - lo;
|
|
39893
|
+
}
|
|
39894
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39895
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
39896
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
39897
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
39898
|
+
const values = a7.map((x, i) => {
|
|
39899
|
+
const y = a14[i];
|
|
39900
|
+
const z = a28[i];
|
|
39901
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
39902
|
+
});
|
|
39903
|
+
return {
|
|
39904
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39905
|
+
levels: guideLevels(30, 70, 50)
|
|
39906
|
+
};
|
|
39907
|
+
}
|
|
39908
|
+
};
|
|
39909
|
+
var rvgi = {
|
|
39910
|
+
type: "relative-vigor-index",
|
|
39911
|
+
title: "Relative Vigor Index",
|
|
39912
|
+
shortTitle: "RVGI",
|
|
39913
|
+
overlay: false,
|
|
39914
|
+
inputs: [lengthInput(10)],
|
|
39915
|
+
compute: (bars, inputs) => {
|
|
39916
|
+
const len = num3(inputs, "length", 10);
|
|
39917
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
39918
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
39919
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
39920
|
+
return {
|
|
39921
|
+
plots: [
|
|
39922
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
39923
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
39924
|
+
],
|
|
39925
|
+
levels: ZERO_LEVEL
|
|
39926
|
+
};
|
|
39927
|
+
}
|
|
39928
|
+
};
|
|
39929
|
+
var relVolatility = {
|
|
39930
|
+
type: "relative-volatility-index",
|
|
39931
|
+
title: "Relative Volatility Index",
|
|
39932
|
+
shortTitle: "RVI",
|
|
39933
|
+
overlay: false,
|
|
39934
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
39935
|
+
compute: (bars, inputs) => {
|
|
39936
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
39937
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39938
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
39939
|
+
const d = change(src);
|
|
39940
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
39941
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
39942
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
39943
|
+
return {
|
|
39944
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39945
|
+
levels: guideLevels(20, 80, 50)
|
|
39946
|
+
};
|
|
39947
|
+
}
|
|
39948
|
+
};
|
|
39949
|
+
var bop = {
|
|
39950
|
+
type: "balance-of-power",
|
|
39951
|
+
title: "Balance of Power",
|
|
39952
|
+
shortTitle: "BOP",
|
|
39953
|
+
overlay: false,
|
|
39954
|
+
inputs: [colorInput()],
|
|
39955
|
+
compute: (bars, inputs) => {
|
|
39956
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
39957
|
+
return {
|
|
39958
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39959
|
+
levels: ZERO_LEVEL
|
|
39960
|
+
};
|
|
39961
|
+
}
|
|
39962
|
+
};
|
|
39963
|
+
var elderRay = {
|
|
39964
|
+
type: "elder-ray",
|
|
39965
|
+
title: "Elder Ray",
|
|
39966
|
+
overlay: false,
|
|
39967
|
+
inputs: [lengthInput(13)],
|
|
39968
|
+
compute: (bars, inputs) => {
|
|
39969
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
39970
|
+
return {
|
|
39971
|
+
plots: [
|
|
39972
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
39973
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
39974
|
+
],
|
|
39975
|
+
levels: ZERO_LEVEL
|
|
39976
|
+
};
|
|
39977
|
+
}
|
|
39978
|
+
};
|
|
39979
|
+
var ttmSqueeze = {
|
|
39980
|
+
type: "ttm-squeeze",
|
|
39981
|
+
title: "TTM Squeeze",
|
|
39982
|
+
overlay: false,
|
|
39983
|
+
inputs: [
|
|
39984
|
+
lengthInput(20),
|
|
39985
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39986
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
39987
|
+
],
|
|
39988
|
+
compute: (bars, inputs) => {
|
|
39989
|
+
const len = num3(inputs, "length", 20);
|
|
39990
|
+
const c = closes(bars);
|
|
39991
|
+
const basis = sma(c, len);
|
|
39992
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
39993
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
39994
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
39995
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
39996
|
+
const mom = linreg(sub(c, anchor), len);
|
|
39997
|
+
const squeezed = c.map((_, i) => {
|
|
39998
|
+
const d = dev[i];
|
|
39999
|
+
const r = kcRange[i];
|
|
40000
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
40001
|
+
});
|
|
40002
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
40003
|
+
return {
|
|
40004
|
+
plots: [
|
|
40005
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
40006
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
40007
|
+
]
|
|
40008
|
+
};
|
|
40009
|
+
}
|
|
40010
|
+
};
|
|
40011
|
+
var oscillatorSpecs = [
|
|
40012
|
+
rsiSpec,
|
|
40013
|
+
stochastic,
|
|
40014
|
+
stochasticRsi,
|
|
40015
|
+
macd,
|
|
40016
|
+
ppo,
|
|
40017
|
+
awesome,
|
|
40018
|
+
cci,
|
|
40019
|
+
williamsR,
|
|
40020
|
+
cmo,
|
|
40021
|
+
connorsRsi,
|
|
40022
|
+
fisher,
|
|
40023
|
+
trix,
|
|
40024
|
+
tsi,
|
|
40025
|
+
smiErgodic,
|
|
40026
|
+
stc,
|
|
40027
|
+
kst,
|
|
40028
|
+
coppock,
|
|
40029
|
+
dpo,
|
|
40030
|
+
rocSpec,
|
|
40031
|
+
ultimate,
|
|
40032
|
+
rvgi,
|
|
40033
|
+
relVolatility,
|
|
40034
|
+
bop,
|
|
40035
|
+
elderRay,
|
|
40036
|
+
ttmSqueeze
|
|
40037
|
+
];
|
|
40038
|
+
|
|
40039
|
+
// src/core/native-indicators/classics/trend.ts
|
|
40040
|
+
var aroon = {
|
|
40041
|
+
type: "aroon",
|
|
40042
|
+
title: "Aroon",
|
|
40043
|
+
overlay: false,
|
|
40044
|
+
inputs: [lengthInput(14)],
|
|
40045
|
+
compute: (bars, inputs) => {
|
|
40046
|
+
const len = num3(inputs, "length", 14);
|
|
40047
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
40048
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
40049
|
+
return {
|
|
40050
|
+
plots: [
|
|
40051
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
40052
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
40053
|
+
],
|
|
40054
|
+
levels: [
|
|
40055
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
40056
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
40057
|
+
]
|
|
40058
|
+
};
|
|
40059
|
+
}
|
|
40060
|
+
};
|
|
40061
|
+
var adx = {
|
|
40062
|
+
type: "average-directional-index",
|
|
40063
|
+
title: "Average Directional Index",
|
|
40064
|
+
shortTitle: "ADX",
|
|
40065
|
+
overlay: false,
|
|
40066
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
40067
|
+
compute: (bars, inputs) => {
|
|
40068
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
40069
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
40070
|
+
const n = bars.length;
|
|
40071
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
40072
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
40073
|
+
for (let i = 1; i < n; i++) {
|
|
40074
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
40075
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
40076
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
40077
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
40078
|
+
}
|
|
40079
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
40080
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40081
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40082
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
40083
|
+
return {
|
|
40084
|
+
plots: [
|
|
40085
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
40086
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
40087
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
40088
|
+
],
|
|
40089
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40090
|
+
};
|
|
40091
|
+
}
|
|
40092
|
+
};
|
|
40093
|
+
var vortex = {
|
|
40094
|
+
type: "vortex-indicator",
|
|
40095
|
+
title: "Vortex Indicator",
|
|
40096
|
+
shortTitle: "VI",
|
|
40097
|
+
overlay: false,
|
|
40098
|
+
inputs: [lengthInput(14)],
|
|
40099
|
+
compute: (bars, inputs) => {
|
|
40100
|
+
const len = num3(inputs, "length", 14);
|
|
40101
|
+
const n = bars.length;
|
|
40102
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
40103
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
40104
|
+
for (let i = 1; i < n; i++) {
|
|
40105
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
40106
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
40107
|
+
}
|
|
40108
|
+
const trSum = sum(trueRange(bars), len);
|
|
40109
|
+
return {
|
|
40110
|
+
plots: [
|
|
40111
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
40112
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
40113
|
+
],
|
|
40114
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40115
|
+
};
|
|
40116
|
+
}
|
|
40117
|
+
};
|
|
40118
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
40119
|
+
|
|
40120
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
40121
|
+
var atrSpec = {
|
|
40122
|
+
type: "average-true-range",
|
|
40123
|
+
title: "Average True Range",
|
|
40124
|
+
shortTitle: "ATR",
|
|
40125
|
+
overlay: false,
|
|
40126
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40127
|
+
compute: (bars, inputs) => ({
|
|
40128
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40129
|
+
})
|
|
40130
|
+
};
|
|
40131
|
+
var historicalVolatility = {
|
|
40132
|
+
type: "historical-volatility",
|
|
40133
|
+
title: "Historical Volatility",
|
|
40134
|
+
shortTitle: "HV",
|
|
40135
|
+
overlay: false,
|
|
40136
|
+
inputs: [
|
|
40137
|
+
lengthInput(10),
|
|
40138
|
+
{ 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)" },
|
|
40139
|
+
colorInput()
|
|
40140
|
+
],
|
|
40141
|
+
compute: (bars, inputs) => {
|
|
40142
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
40143
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
40144
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40145
|
+
}
|
|
40146
|
+
};
|
|
40147
|
+
var chaikinVolatility = {
|
|
40148
|
+
type: "chaikin-volatility",
|
|
40149
|
+
title: "Chaikin Volatility",
|
|
40150
|
+
shortTitle: "CHV",
|
|
40151
|
+
overlay: false,
|
|
40152
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
40153
|
+
compute: (bars, inputs) => {
|
|
40154
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
40155
|
+
return {
|
|
40156
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40157
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40158
|
+
};
|
|
40159
|
+
}
|
|
40160
|
+
};
|
|
40161
|
+
var massIndex = {
|
|
40162
|
+
type: "mass-index",
|
|
40163
|
+
title: "Mass Index",
|
|
40164
|
+
shortTitle: "MI",
|
|
40165
|
+
overlay: false,
|
|
40166
|
+
inputs: [lengthInput(25), colorInput()],
|
|
40167
|
+
compute: (bars, inputs) => {
|
|
40168
|
+
const range = sub(highs(bars), lows(bars));
|
|
40169
|
+
const single = ema(range, 9);
|
|
40170
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
40171
|
+
return {
|
|
40172
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40173
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40174
|
+
};
|
|
40175
|
+
}
|
|
40176
|
+
};
|
|
40177
|
+
var standardDeviation = {
|
|
40178
|
+
type: "standard-deviation",
|
|
40179
|
+
title: "Standard Deviation",
|
|
40180
|
+
shortTitle: "StdDev",
|
|
40181
|
+
overlay: false,
|
|
40182
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
40183
|
+
compute: (bars, inputs) => ({
|
|
40184
|
+
plots: [
|
|
40185
|
+
{
|
|
40186
|
+
key: "stdev",
|
|
40187
|
+
title: "StdDev",
|
|
40188
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
40189
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
40190
|
+
width: 2
|
|
40191
|
+
}
|
|
40192
|
+
]
|
|
40193
|
+
})
|
|
40194
|
+
};
|
|
40195
|
+
var ulcerIndex = {
|
|
40196
|
+
type: "ulcer-index",
|
|
40197
|
+
title: "Ulcer Index",
|
|
40198
|
+
overlay: false,
|
|
40199
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40200
|
+
compute: (bars, inputs) => {
|
|
40201
|
+
const len = num3(inputs, "length", 14);
|
|
40202
|
+
const c = closes(bars);
|
|
40203
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
40204
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
40205
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40206
|
+
}
|
|
40207
|
+
};
|
|
40208
|
+
var choppiness = {
|
|
40209
|
+
type: "choppiness-index",
|
|
40210
|
+
title: "Choppiness Index",
|
|
40211
|
+
shortTitle: "CHOP",
|
|
40212
|
+
overlay: false,
|
|
40213
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40214
|
+
compute: (bars, inputs) => {
|
|
40215
|
+
const len = num3(inputs, "length", 14);
|
|
40216
|
+
const trSum = sum(trueRange(bars), len);
|
|
40217
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
40218
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
40219
|
+
return {
|
|
40220
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40221
|
+
levels: [
|
|
40222
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
40223
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
40224
|
+
]
|
|
40225
|
+
};
|
|
40226
|
+
}
|
|
40227
|
+
};
|
|
40228
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
40229
|
+
|
|
40230
|
+
// src/core/native-indicators/classics/volume.ts
|
|
40231
|
+
function moneyFlowVolume(bars) {
|
|
40232
|
+
return bars.map((b) => {
|
|
40233
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40234
|
+
if (b.high === b.low) return 0;
|
|
40235
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
40236
|
+
});
|
|
40237
|
+
}
|
|
40238
|
+
var obv = {
|
|
40239
|
+
type: "on-balance-volume",
|
|
40240
|
+
title: "On Balance Volume",
|
|
40241
|
+
shortTitle: "OBV",
|
|
40242
|
+
overlay: false,
|
|
40243
|
+
inputs: [colorInput()],
|
|
40244
|
+
compute: (bars, inputs) => {
|
|
40245
|
+
const vol = volumes(bars);
|
|
40246
|
+
const d = change(closes(bars));
|
|
40247
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
40248
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40249
|
+
}
|
|
40250
|
+
};
|
|
40251
|
+
var accDist = {
|
|
40252
|
+
type: "accumulation-distribution",
|
|
40253
|
+
title: "Accumulation / Distribution",
|
|
40254
|
+
shortTitle: "A/D",
|
|
40255
|
+
overlay: false,
|
|
40256
|
+
inputs: [colorInput()],
|
|
40257
|
+
compute: (bars, inputs) => ({
|
|
40258
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40259
|
+
})
|
|
40260
|
+
};
|
|
40261
|
+
var cmf = {
|
|
40262
|
+
type: "chaikin-money-flow",
|
|
40263
|
+
title: "Chaikin Money Flow",
|
|
40264
|
+
shortTitle: "CMF",
|
|
40265
|
+
overlay: false,
|
|
40266
|
+
inputs: [lengthInput(20), colorInput()],
|
|
40267
|
+
compute: (bars, inputs) => {
|
|
40268
|
+
const len = num3(inputs, "length", 20);
|
|
40269
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
40270
|
+
return {
|
|
40271
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40272
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40273
|
+
};
|
|
40274
|
+
}
|
|
40275
|
+
};
|
|
40276
|
+
var chaikinOscillator = {
|
|
40277
|
+
type: "chaikin-oscillator",
|
|
40278
|
+
title: "Chaikin Oscillator",
|
|
40279
|
+
shortTitle: "Chaikin Osc",
|
|
40280
|
+
overlay: false,
|
|
40281
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40282
|
+
compute: (bars, inputs) => {
|
|
40283
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
40284
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
40285
|
+
return {
|
|
40286
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40287
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40288
|
+
};
|
|
40289
|
+
}
|
|
40290
|
+
};
|
|
40291
|
+
var eom = {
|
|
40292
|
+
type: "ease-of-movement",
|
|
40293
|
+
title: "Ease of Movement",
|
|
40294
|
+
shortTitle: "EOM",
|
|
40295
|
+
overlay: false,
|
|
40296
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
40297
|
+
compute: (bars, inputs) => {
|
|
40298
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
40299
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
40300
|
+
const move = change(hl2);
|
|
40301
|
+
const raw = bars.map((b, i) => {
|
|
40302
|
+
const m = move[i];
|
|
40303
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
40304
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
40305
|
+
});
|
|
40306
|
+
return {
|
|
40307
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40308
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40309
|
+
};
|
|
40310
|
+
}
|
|
40311
|
+
};
|
|
40312
|
+
var forceIndex = {
|
|
40313
|
+
type: "force-index",
|
|
40314
|
+
title: "Force Index",
|
|
40315
|
+
shortTitle: "FI",
|
|
40316
|
+
overlay: false,
|
|
40317
|
+
inputs: [lengthInput(13), colorInput()],
|
|
40318
|
+
compute: (bars, inputs) => {
|
|
40319
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
40320
|
+
return {
|
|
40321
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40322
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40323
|
+
};
|
|
40324
|
+
}
|
|
40325
|
+
};
|
|
40326
|
+
var klinger = {
|
|
40327
|
+
type: "klinger-oscillator",
|
|
40328
|
+
title: "Klinger Oscillator",
|
|
40329
|
+
shortTitle: "KVO",
|
|
40330
|
+
overlay: false,
|
|
40331
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
40332
|
+
compute: (bars, inputs) => {
|
|
40333
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
40334
|
+
const trend = change(hlc3);
|
|
40335
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
40336
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
40337
|
+
return {
|
|
40338
|
+
plots: [
|
|
40339
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40340
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
40341
|
+
],
|
|
40342
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40343
|
+
};
|
|
40344
|
+
}
|
|
40345
|
+
};
|
|
40346
|
+
var mfi = {
|
|
40347
|
+
type: "money-flow-index",
|
|
40348
|
+
title: "Money Flow Index",
|
|
40349
|
+
shortTitle: "MFI",
|
|
40350
|
+
overlay: false,
|
|
40351
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40352
|
+
compute: (bars, inputs) => {
|
|
40353
|
+
const len = num3(inputs, "length", 14);
|
|
40354
|
+
const tp = sourceValues(bars, "HLC3");
|
|
40355
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
40356
|
+
const d = change(tp);
|
|
40357
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
40358
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
40359
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
40360
|
+
return {
|
|
40361
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40362
|
+
levels: [
|
|
40363
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
40364
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
40365
|
+
]
|
|
40366
|
+
};
|
|
40367
|
+
}
|
|
40368
|
+
};
|
|
40369
|
+
function volumeIndex(bars, when) {
|
|
40370
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
40371
|
+
let index = 1e3;
|
|
40372
|
+
let started = false;
|
|
40373
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40374
|
+
const v = bars[i].volume;
|
|
40375
|
+
const pv = bars[i - 1].volume;
|
|
40376
|
+
const pc = bars[i - 1].close;
|
|
40377
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
40378
|
+
if (started) out[i] = index;
|
|
40379
|
+
continue;
|
|
40380
|
+
}
|
|
40381
|
+
started = true;
|
|
40382
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
40383
|
+
out[i] = index;
|
|
40384
|
+
}
|
|
40385
|
+
return out;
|
|
40386
|
+
}
|
|
40387
|
+
var nvi = {
|
|
40388
|
+
type: "negative-volume-index",
|
|
40389
|
+
title: "Negative Volume Index",
|
|
40390
|
+
shortTitle: "Negative Volume Index",
|
|
40391
|
+
overlay: false,
|
|
40392
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40393
|
+
compute: (bars, inputs) => {
|
|
40394
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
40395
|
+
return {
|
|
40396
|
+
plots: [
|
|
40397
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40398
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40399
|
+
]
|
|
40400
|
+
};
|
|
40401
|
+
}
|
|
40402
|
+
};
|
|
40403
|
+
var pvi = {
|
|
40404
|
+
type: "positive-volume-index",
|
|
40405
|
+
title: "Positive Volume Index",
|
|
40406
|
+
shortTitle: "Positive Volume Index",
|
|
40407
|
+
overlay: false,
|
|
40408
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40409
|
+
compute: (bars, inputs) => {
|
|
40410
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
40411
|
+
return {
|
|
40412
|
+
plots: [
|
|
40413
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40414
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40415
|
+
]
|
|
40416
|
+
};
|
|
40417
|
+
}
|
|
40418
|
+
};
|
|
40419
|
+
var pvt = {
|
|
40420
|
+
type: "price-volume-trend",
|
|
40421
|
+
title: "Price Volume Trend",
|
|
40422
|
+
shortTitle: "PVT",
|
|
40423
|
+
overlay: false,
|
|
40424
|
+
inputs: [colorInput()],
|
|
40425
|
+
compute: (bars, inputs) => {
|
|
40426
|
+
const c = closes(bars);
|
|
40427
|
+
const raw = bars.map((b, i) => {
|
|
40428
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40429
|
+
const pc = c[i - 1];
|
|
40430
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
40431
|
+
});
|
|
40432
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40433
|
+
}
|
|
40434
|
+
};
|
|
40435
|
+
var volumeOscillator = {
|
|
40436
|
+
type: "volume-oscillator",
|
|
40437
|
+
title: "Volume Oscillator",
|
|
40438
|
+
shortTitle: "Vol Osc",
|
|
40439
|
+
overlay: false,
|
|
40440
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40441
|
+
compute: (bars, inputs) => {
|
|
40442
|
+
const vol = volumes(bars);
|
|
40443
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
40444
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
40445
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40446
|
+
return {
|
|
40447
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40448
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40449
|
+
};
|
|
40450
|
+
}
|
|
40451
|
+
};
|
|
40452
|
+
var pvo = {
|
|
40453
|
+
type: "pvo",
|
|
40454
|
+
title: "Percentage Volume Oscillator",
|
|
40455
|
+
shortTitle: "PVO",
|
|
40456
|
+
overlay: false,
|
|
40457
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
40458
|
+
compute: (bars, inputs) => {
|
|
40459
|
+
const vol = volumes(bars);
|
|
40460
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
40461
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
40462
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40463
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
40464
|
+
const hist = sub(line, signal);
|
|
40465
|
+
return {
|
|
40466
|
+
plots: [
|
|
40467
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
40468
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40469
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
40470
|
+
],
|
|
40471
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40472
|
+
};
|
|
40473
|
+
}
|
|
40474
|
+
};
|
|
40475
|
+
var vfi = {
|
|
40476
|
+
type: "volume-flow-indicator",
|
|
40477
|
+
title: "Volume Flow Indicator",
|
|
40478
|
+
shortTitle: "Volume Flow Indicator",
|
|
40479
|
+
overlay: false,
|
|
40480
|
+
inputs: [
|
|
40481
|
+
lengthInput(130),
|
|
40482
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
40483
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
40484
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
40485
|
+
],
|
|
40486
|
+
compute: (bars, inputs) => {
|
|
40487
|
+
const len = num3(inputs, "length", 130);
|
|
40488
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
40489
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
40490
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
40491
|
+
const inter = change(typical);
|
|
40492
|
+
const vInter = stdev(inter, 30);
|
|
40493
|
+
const vol = volumes(bars);
|
|
40494
|
+
const vAve = sma(vol, len);
|
|
40495
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
40496
|
+
const raw = bars.map((b, i) => {
|
|
40497
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
40498
|
+
const v = vol[i];
|
|
40499
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40500
|
+
const m = mf[i];
|
|
40501
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
40502
|
+
const capped = Math.min(v, ave * volCoef);
|
|
40503
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
40504
|
+
});
|
|
40505
|
+
const values = bars.map((_, i) => {
|
|
40506
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40507
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
40508
|
+
});
|
|
40509
|
+
const flowSum = sum(raw, len);
|
|
40510
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
40511
|
+
return {
|
|
40512
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
40513
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40514
|
+
};
|
|
40515
|
+
}
|
|
40516
|
+
};
|
|
40517
|
+
var intradayIntensity = {
|
|
40518
|
+
type: "intraday-intensity",
|
|
40519
|
+
title: "Intraday Intensity",
|
|
40520
|
+
shortTitle: "Intraday Intensity",
|
|
40521
|
+
overlay: false,
|
|
40522
|
+
inputs: [lengthInput(21), colorInput()],
|
|
40523
|
+
compute: (bars, inputs) => {
|
|
40524
|
+
const len = num3(inputs, "length", 21);
|
|
40525
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
40526
|
+
return {
|
|
40527
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40528
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40529
|
+
};
|
|
40530
|
+
}
|
|
40531
|
+
};
|
|
40532
|
+
var volumeSpecs = [
|
|
40533
|
+
obv,
|
|
40534
|
+
accDist,
|
|
40535
|
+
cmf,
|
|
40536
|
+
chaikinOscillator,
|
|
40537
|
+
eom,
|
|
40538
|
+
forceIndex,
|
|
40539
|
+
klinger,
|
|
40540
|
+
mfi,
|
|
40541
|
+
nvi,
|
|
40542
|
+
pvi,
|
|
40543
|
+
pvt,
|
|
40544
|
+
volumeOscillator,
|
|
40545
|
+
pvo,
|
|
40546
|
+
vfi,
|
|
40547
|
+
intradayIntensity
|
|
40548
|
+
];
|
|
40549
|
+
|
|
40550
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
40551
|
+
var DAY_MS2 = 864e5;
|
|
40552
|
+
function periodKey(anchor, time) {
|
|
40553
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
40554
|
+
if (anchor === "Month") {
|
|
40555
|
+
const d = new Date(time);
|
|
40556
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
40557
|
+
}
|
|
40558
|
+
return Math.floor(time / DAY_MS2);
|
|
40559
|
+
}
|
|
40560
|
+
var parabolicSar = {
|
|
40561
|
+
type: "parabolic-sar",
|
|
40562
|
+
title: "Parabolic SAR",
|
|
40563
|
+
shortTitle: "SAR",
|
|
40564
|
+
overlay: true,
|
|
40565
|
+
inputs: [
|
|
40566
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40567
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40568
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
40569
|
+
],
|
|
40570
|
+
compute: (bars, inputs) => {
|
|
40571
|
+
const start = num3(inputs, "start", 0.02);
|
|
40572
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
40573
|
+
const max2 = num3(inputs, "maximum", 0.2);
|
|
40574
|
+
const n = bars.length;
|
|
40575
|
+
const values = new Array(n).fill(Number.NaN);
|
|
40576
|
+
const colors = new Array(n).fill(null);
|
|
40577
|
+
if (n >= 2) {
|
|
40578
|
+
let long = bars[1].close >= bars[0].close;
|
|
40579
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
40580
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
40581
|
+
let af = start;
|
|
40582
|
+
for (let i = 1; i < n; i++) {
|
|
40583
|
+
const b = bars[i];
|
|
40584
|
+
sar += af * (ep - sar);
|
|
40585
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
40586
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
40587
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
40588
|
+
if (reversed) {
|
|
40589
|
+
long = !long;
|
|
40590
|
+
sar = ep;
|
|
40591
|
+
ep = long ? b.high : b.low;
|
|
40592
|
+
af = start;
|
|
40593
|
+
} else if (long && b.high > ep) {
|
|
40594
|
+
ep = b.high;
|
|
40595
|
+
af = Math.min(max2, af + inc);
|
|
40596
|
+
} else if (!long && b.low < ep) {
|
|
40597
|
+
ep = b.low;
|
|
40598
|
+
af = Math.min(max2, af + inc);
|
|
40599
|
+
}
|
|
40600
|
+
values[i] = sar;
|
|
40601
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
40602
|
+
}
|
|
40603
|
+
}
|
|
40604
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
40605
|
+
}
|
|
40606
|
+
};
|
|
40607
|
+
var vwap = {
|
|
40608
|
+
type: "vwap",
|
|
40609
|
+
title: "Volume Weighted Average Price",
|
|
40610
|
+
shortTitle: "VWAP",
|
|
40611
|
+
overlay: true,
|
|
40612
|
+
inputs: [
|
|
40613
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
40614
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
40615
|
+
colorInput(INFO)
|
|
40616
|
+
],
|
|
40617
|
+
compute: (bars, inputs) => {
|
|
40618
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40619
|
+
const source = str3(inputs, "source", "HLC3");
|
|
40620
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
40621
|
+
let period = Number.NaN;
|
|
40622
|
+
let cumPV = 0;
|
|
40623
|
+
let cumV = 0;
|
|
40624
|
+
for (let i = 0; i < bars.length; i++) {
|
|
40625
|
+
const b = bars[i];
|
|
40626
|
+
const key = periodKey(anchor, b.time);
|
|
40627
|
+
if (key !== period) {
|
|
40628
|
+
period = key;
|
|
40629
|
+
cumPV = 0;
|
|
40630
|
+
cumV = 0;
|
|
40631
|
+
}
|
|
40632
|
+
const v = b.volume;
|
|
40633
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
40634
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
40635
|
+
cumPV += tp * v;
|
|
40636
|
+
cumV += v;
|
|
40637
|
+
}
|
|
40638
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
40639
|
+
}
|
|
40640
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
40641
|
+
}
|
|
40642
|
+
};
|
|
40643
|
+
var pivotPoints = {
|
|
40644
|
+
type: "pivot-points",
|
|
40645
|
+
title: "Pivot Points",
|
|
40646
|
+
shortTitle: "Pivots",
|
|
40647
|
+
overlay: true,
|
|
40648
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
40649
|
+
compute: (bars, inputs) => {
|
|
40650
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40651
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
40652
|
+
for (const b of bars) {
|
|
40653
|
+
const key = periodKey(anchor, b.time);
|
|
40654
|
+
const a = aggs.get(key);
|
|
40655
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
40656
|
+
else {
|
|
40657
|
+
a.high = Math.max(a.high, b.high);
|
|
40658
|
+
a.low = Math.min(a.low, b.low);
|
|
40659
|
+
a.close = b.close;
|
|
40660
|
+
}
|
|
40661
|
+
}
|
|
40662
|
+
const n = bars.length;
|
|
40663
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
40664
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
40665
|
+
for (let i = 0; i < n; i++) {
|
|
40666
|
+
const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
40667
|
+
if (!prev3) continue;
|
|
40668
|
+
const p = (prev3.high + prev3.low + prev3.close) / 3;
|
|
40669
|
+
levels.p[i] = p;
|
|
40670
|
+
levels.r1[i] = 2 * p - prev3.low;
|
|
40671
|
+
levels.s1[i] = 2 * p - prev3.high;
|
|
40672
|
+
levels.r2[i] = p + (prev3.high - prev3.low);
|
|
40673
|
+
levels.s2[i] = p - (prev3.high - prev3.low);
|
|
40674
|
+
levels.r3[i] = prev3.high + 2 * (p - prev3.low);
|
|
40675
|
+
levels.s3[i] = prev3.low - 2 * (prev3.high - p);
|
|
40676
|
+
}
|
|
40677
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
40678
|
+
return {
|
|
40679
|
+
plots: [
|
|
40680
|
+
plot("p", "P", WARNING),
|
|
40681
|
+
plot("r1", "R1", BEARISH),
|
|
40682
|
+
plot("s1", "S1", BULLISH),
|
|
40683
|
+
plot("r2", "R2", BEARISH),
|
|
40684
|
+
plot("s2", "S2", BULLISH),
|
|
40685
|
+
plot("r3", "R3", BEARISH),
|
|
40686
|
+
plot("s3", "S3", BULLISH)
|
|
40687
|
+
]
|
|
40688
|
+
};
|
|
40689
|
+
}
|
|
40690
|
+
};
|
|
40691
|
+
var fiftyTwoWeek = {
|
|
40692
|
+
type: "52-week-high-low",
|
|
40693
|
+
title: "52 Week High/Low",
|
|
40694
|
+
shortTitle: "52W H/L",
|
|
40695
|
+
overlay: true,
|
|
40696
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
40697
|
+
compute: (bars, inputs) => {
|
|
40698
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
40699
|
+
const n = bars.length;
|
|
40700
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
40701
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
40702
|
+
const maxIdx = [];
|
|
40703
|
+
const minIdx = [];
|
|
40704
|
+
let from = 0;
|
|
40705
|
+
for (let i = 0; i < n; i++) {
|
|
40706
|
+
const b = bars[i];
|
|
40707
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
40708
|
+
maxIdx.push(i);
|
|
40709
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
40710
|
+
minIdx.push(i);
|
|
40711
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
40712
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
40713
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
40714
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
40715
|
+
lo[i] = bars[minIdx[0]].low;
|
|
40716
|
+
}
|
|
40717
|
+
return {
|
|
40718
|
+
plots: [
|
|
40719
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
40720
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
40721
|
+
]
|
|
40722
|
+
};
|
|
40723
|
+
}
|
|
40724
|
+
};
|
|
40725
|
+
var zigzag = {
|
|
40726
|
+
type: "zigzag",
|
|
40727
|
+
title: "ZigZag",
|
|
40728
|
+
overlay: true,
|
|
40729
|
+
inputs: [
|
|
40730
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
40731
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
40732
|
+
colorInput()
|
|
40733
|
+
],
|
|
40734
|
+
compute: (bars, inputs) => {
|
|
40735
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
40736
|
+
const depth = num3(inputs, "depth", 10);
|
|
40737
|
+
const pivots = [];
|
|
40738
|
+
if (bars.length > 1) {
|
|
40739
|
+
let up = bars[1].close >= bars[0].close;
|
|
40740
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
40741
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40742
|
+
const b = bars[i];
|
|
40743
|
+
if (up) {
|
|
40744
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
40745
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40746
|
+
pivots.push(ext);
|
|
40747
|
+
up = false;
|
|
40748
|
+
ext = { i, price: b.low };
|
|
40749
|
+
}
|
|
40750
|
+
} else if (b.low <= ext.price) {
|
|
40751
|
+
ext = { i, price: b.low };
|
|
40752
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40753
|
+
pivots.push(ext);
|
|
40754
|
+
up = true;
|
|
40755
|
+
ext = { i, price: b.high };
|
|
40756
|
+
}
|
|
40757
|
+
}
|
|
40758
|
+
pivots.push(ext);
|
|
40759
|
+
}
|
|
40760
|
+
return {
|
|
40761
|
+
plots: [],
|
|
40762
|
+
polylines: [
|
|
40763
|
+
{
|
|
40764
|
+
key: "zigzag",
|
|
40765
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
40766
|
+
curved: false,
|
|
40767
|
+
closed: false,
|
|
40768
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
40769
|
+
lineWidth: 2,
|
|
40770
|
+
lineStyle: "solid",
|
|
40771
|
+
arrowLeft: false,
|
|
40772
|
+
arrowRight: false
|
|
40773
|
+
}
|
|
40774
|
+
]
|
|
40775
|
+
};
|
|
40776
|
+
}
|
|
40777
|
+
};
|
|
40778
|
+
var williamsFractal = {
|
|
40779
|
+
type: "williams-fractal",
|
|
40780
|
+
title: "Williams Fractal",
|
|
40781
|
+
shortTitle: "Fractals",
|
|
40782
|
+
overlay: true,
|
|
40783
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
40784
|
+
compute: (bars, inputs) => {
|
|
40785
|
+
const p = num3(inputs, "periods", 2);
|
|
40786
|
+
const markers = [];
|
|
40787
|
+
const isExtreme = (i, pick, better) => {
|
|
40788
|
+
const v = pick(bars[i]);
|
|
40789
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
40790
|
+
if (k === i) continue;
|
|
40791
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
40792
|
+
}
|
|
40793
|
+
return true;
|
|
40794
|
+
};
|
|
40795
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
40796
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
40797
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
40798
|
+
}
|
|
40799
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
40800
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
40801
|
+
}
|
|
40802
|
+
}
|
|
40803
|
+
return { plots: [], markers };
|
|
40804
|
+
}
|
|
40805
|
+
};
|
|
40806
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
40807
|
+
|
|
40808
|
+
// src/core/native-indicators/classics/index.ts
|
|
40809
|
+
var classicSpecs = [
|
|
40810
|
+
...averageSpecs,
|
|
40811
|
+
...bandSpecs,
|
|
40812
|
+
...oscillatorSpecs,
|
|
40813
|
+
...trendSpecs,
|
|
40814
|
+
...volatilitySpecs,
|
|
40815
|
+
...volumeSpecs,
|
|
40816
|
+
...overlaySpecs
|
|
40817
|
+
];
|
|
40818
|
+
function registerClassicIndicators() {
|
|
40819
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
40820
|
+
}
|
|
40821
|
+
|
|
38419
40822
|
// src/Vela.ts
|
|
38420
40823
|
var Vela = class {
|
|
38421
40824
|
constructor(container, options = {}, deps = {}) {
|
|
38422
40825
|
registerBuiltinChartTypes();
|
|
38423
40826
|
registerVolume();
|
|
38424
40827
|
registerVpvr();
|
|
40828
|
+
registerClassicIndicators();
|
|
38425
40829
|
const element = resolveElement(container);
|
|
38426
40830
|
const theme = resolveTheme(options.theme);
|
|
38427
|
-
let animZoom = true;
|
|
38428
|
-
let animPan = true;
|
|
38429
|
-
if (typeof options.animations === "boolean") {
|
|
38430
|
-
animZoom = options.animations;
|
|
38431
|
-
animPan = options.animations;
|
|
38432
|
-
} else if (options.animations) {
|
|
38433
|
-
animZoom = options.animations.zoom ?? true;
|
|
38434
|
-
animPan = options.animations.pan ?? true;
|
|
38435
|
-
}
|
|
38436
40831
|
const display = {
|
|
38437
40832
|
currentPriceLine: options.currentPriceLine ?? true,
|
|
38438
40833
|
logScale: options.logScale ?? false,
|
|
38439
40834
|
nativeBackend: options.nativeBackend ?? "auto",
|
|
38440
|
-
|
|
38441
|
-
animPan,
|
|
40835
|
+
...resolveAnimations(options.animations),
|
|
38442
40836
|
glow: options.glow ?? 0,
|
|
38443
40837
|
upColor: options.upColor ?? BULLISH,
|
|
38444
40838
|
downColor: options.downColor ?? BEARISH,
|
|
@@ -38496,25 +40890,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38496
40890
|
}
|
|
38497
40891
|
/**
|
|
38498
40892
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
38499
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
38500
|
-
* events)
|
|
38501
|
-
*
|
|
40893
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
40894
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
40895
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
40896
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
38502
40897
|
*/
|
|
38503
40898
|
addNativeIndicator(type, options) {
|
|
38504
40899
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
38505
40900
|
}
|
|
38506
40901
|
/**
|
|
38507
40902
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
38508
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
38509
|
-
*
|
|
38510
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
38511
|
-
*
|
|
40903
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
40904
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
40905
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
40906
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
40907
|
+
* provider (a type may need data the symbol lacks).
|
|
38512
40908
|
*/
|
|
38513
40909
|
availableNativeIndicators() {
|
|
38514
40910
|
return this.orchestrator.availableNativeIndicators();
|
|
38515
40911
|
}
|
|
38516
40912
|
/**
|
|
38517
|
-
* The native-indicator types PRESENT on the chart right now
|
|
40913
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
40914
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
38518
40915
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
38519
40916
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
38520
40917
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -38881,12 +41278,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38881
41278
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
38882
41279
|
}
|
|
38883
41280
|
|
|
38884
|
-
// src/core/model/identity.ts
|
|
38885
|
-
function stableSeriesId(parts3) {
|
|
38886
|
-
const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
38887
|
-
return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
|
|
38888
|
-
}
|
|
38889
|
-
|
|
38890
41281
|
// src/data/symbol-base.ts
|
|
38891
41282
|
function baseOf(d) {
|
|
38892
41283
|
const fromDesc = d.description?.split("/")[0]?.trim();
|
|
@@ -38988,9 +41379,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38988
41379
|
for (const b of bars) byTime.set(b.time, b);
|
|
38989
41380
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
38990
41381
|
}
|
|
38991
|
-
function aggregate(
|
|
41382
|
+
function aggregate(sub2, bucketMs) {
|
|
38992
41383
|
const buckets = /* @__PURE__ */ new Map();
|
|
38993
|
-
for (const b of
|
|
41384
|
+
for (const b of sub2) {
|
|
38994
41385
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
38995
41386
|
const cur = buckets.get(key);
|
|
38996
41387
|
if (!cur) {
|
|
@@ -39042,8 +41433,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39042
41433
|
}
|
|
39043
41434
|
const ratio = targetMin / subMin;
|
|
39044
41435
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39045
|
-
const
|
|
39046
|
-
const agg = aggregate(
|
|
41436
|
+
const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
|
|
41437
|
+
const agg = aggregate(sub2, targetMin * 6e4);
|
|
39047
41438
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39048
41439
|
} catch (e) {
|
|
39049
41440
|
console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39374,9 +41765,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39374
41765
|
for (const b of bars) byTime.set(b.time, b);
|
|
39375
41766
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39376
41767
|
}
|
|
39377
|
-
function aggregate2(
|
|
41768
|
+
function aggregate2(sub2, bucketMs) {
|
|
39378
41769
|
const buckets = /* @__PURE__ */ new Map();
|
|
39379
|
-
for (const b of
|
|
41770
|
+
for (const b of sub2) {
|
|
39380
41771
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
39381
41772
|
const cur = buckets.get(key);
|
|
39382
41773
|
if (!cur) {
|
|
@@ -39427,8 +41818,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39427
41818
|
}
|
|
39428
41819
|
const ratio = targetMin / subMin;
|
|
39429
41820
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39430
|
-
const
|
|
39431
|
-
const agg = aggregate2(
|
|
41821
|
+
const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
|
|
41822
|
+
const agg = aggregate2(sub2, targetMin * 6e4);
|
|
39432
41823
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39433
41824
|
} catch (e) {
|
|
39434
41825
|
console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39780,15 +42171,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39780
42171
|
for (const b of bars) byTime.set(b.time, b);
|
|
39781
42172
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39782
42173
|
}
|
|
39783
|
-
function aggregate3(
|
|
39784
|
-
return aggregateBy(
|
|
42174
|
+
function aggregate3(sub2, bucketMs) {
|
|
42175
|
+
return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
|
|
39785
42176
|
}
|
|
39786
|
-
function aggregateCalendar(
|
|
39787
|
-
return aggregateBy(
|
|
42177
|
+
function aggregateCalendar(sub2, unit) {
|
|
42178
|
+
return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
|
|
39788
42179
|
}
|
|
39789
|
-
function aggregateBy(
|
|
42180
|
+
function aggregateBy(sub2, keyMs) {
|
|
39790
42181
|
const buckets = /* @__PURE__ */ new Map();
|
|
39791
|
-
for (const b of
|
|
42182
|
+
for (const b of sub2) {
|
|
39792
42183
|
const key = keyMs(b.time);
|
|
39793
42184
|
const cur = buckets.get(key);
|
|
39794
42185
|
if (!cur) {
|
|
@@ -39842,8 +42233,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39842
42233
|
if (tf === "W" || tf === "M") {
|
|
39843
42234
|
const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
|
|
39844
42235
|
const subRange2 = range.from != null ? range : { ...range, limit: range.limit != null ? Math.ceil(range.limit * span / MS_PER_DAY) + 31 : void 0 };
|
|
39845
|
-
const
|
|
39846
|
-
return clampLimit(aggregateCalendar(
|
|
42236
|
+
const sub3 = await this.fetchCandles(product, 86400, subRange2);
|
|
42237
|
+
return clampLimit(aggregateCalendar(sub3, tf), range.limit);
|
|
39847
42238
|
}
|
|
39848
42239
|
const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
|
|
39849
42240
|
const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
|
|
@@ -39853,8 +42244,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39853
42244
|
}
|
|
39854
42245
|
const ratio = targetMin / subMin;
|
|
39855
42246
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39856
|
-
const
|
|
39857
|
-
return clampLimit(aggregate3(
|
|
42247
|
+
const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
|
|
42248
|
+
return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
|
|
39858
42249
|
} catch (e) {
|
|
39859
42250
|
console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
39860
42251
|
return [];
|