@luxalgo/vela 0.6.15 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-J5QIR2Z3.js → chunk-GIJYO4LO.js} +2436 -115
- package/dist/{chunk-32A7KHFM.js → chunk-I77F2SVE.js} +7 -1
- package/dist/{chunk-MWDNPGU3.js → chunk-MGU3USTP.js} +22 -15
- package/dist/{chunk-MWS4CUJL.js → chunk-Q3TFJNJW.js} +1 -1
- package/dist/{chunk-7C7NWEXH.js → chunk-SEOO6YG7.js} +82 -55
- package/dist/{contributions-x7iamlnd.d.cts → contributions-BEkxwA5F.d.cts} +27 -11
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-CoAX7_SF.d.ts} +27 -11
- package/dist/index.cjs +2665 -337
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/{plugin-BLaNPh55.d.ts → plugin-CNUgbRNq.d.ts} +1 -1
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-Dxyq35Zf.d.cts} +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-CbPza-Wc.d.cts} +1 -1
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-D7cWkw8A.d.ts} +1 -1
- package/dist/ui.cjs +22 -15
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +2501 -173
- package/dist/vela.global.min.js +52 -52
- package/dist/widget.cjs +2563 -202
- package/dist/widget.d.cts +4 -4
- package/dist/widget.d.ts +4 -4
- package/dist/widget.js +6 -6
- package/dist/workspace.cjs +2563 -202
- package/dist/workspace.d.cts +23 -12
- package/dist/workspace.d.ts +23 -12
- package/dist/workspace.js +5 -5
- package/package.json +1 -1
- package/dist/chunk-H26BEHF4.js +0 -7
package/dist/vela.global.js
CHANGED
|
@@ -111,7 +111,7 @@ var Vela = (function (exports) {
|
|
|
111
111
|
|
|
112
112
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
113
113
|
var IndicatorHandleImpl = class {
|
|
114
|
-
constructor(id, title, controller, source) {
|
|
114
|
+
constructor(id, title, controller, source, nativeType) {
|
|
115
115
|
this.controller = controller;
|
|
116
116
|
this.schema = [];
|
|
117
117
|
this.propsSchema = [];
|
|
@@ -120,6 +120,7 @@ var Vela = (function (exports) {
|
|
|
120
120
|
this.id = id;
|
|
121
121
|
this.title = title;
|
|
122
122
|
if (source !== void 0) this.source = source;
|
|
123
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
123
124
|
}
|
|
124
125
|
get inputs() {
|
|
125
126
|
return this.schema;
|
|
@@ -210,24 +211,24 @@ var Vela = (function (exports) {
|
|
|
210
211
|
}
|
|
211
212
|
function inspectModels(models) {
|
|
212
213
|
const indicators = models.map(summarizeModel);
|
|
213
|
-
const
|
|
214
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
214
215
|
return {
|
|
215
216
|
indicators,
|
|
216
217
|
totals: {
|
|
217
218
|
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:
|
|
219
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
220
|
+
fills: sum2((s) => s.fills),
|
|
221
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
222
|
+
priceLines: sum2((s) => s.priceLines),
|
|
223
|
+
lines: sum2((s) => s.lines),
|
|
224
|
+
boxes: sum2((s) => s.boxes),
|
|
225
|
+
labels: sum2((s) => s.labels),
|
|
226
|
+
polylines: sum2((s) => s.polylines),
|
|
227
|
+
linefills: sum2((s) => s.linefills),
|
|
228
|
+
tables: sum2((s) => s.tables),
|
|
229
|
+
barColors: sum2((s) => s.barColors),
|
|
230
|
+
trades: sum2((s) => s.trades),
|
|
231
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
231
232
|
}
|
|
232
233
|
};
|
|
233
234
|
}
|
|
@@ -6707,8 +6708,8 @@ var Vela = (function (exports) {
|
|
|
6707
6708
|
this.port?.setExternalGhost?.(doc);
|
|
6708
6709
|
}
|
|
6709
6710
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6710
|
-
setToolShortcuts(
|
|
6711
|
-
this.port?.setToolShortcuts?.(
|
|
6711
|
+
setToolShortcuts(map2) {
|
|
6712
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6712
6713
|
}
|
|
6713
6714
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6714
6715
|
add(type, init = {}) {
|
|
@@ -8432,16 +8433,20 @@ var Vela = (function (exports) {
|
|
|
8432
8433
|
/**
|
|
8433
8434
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8434
8435
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8435
|
-
* INSTANCE per type
|
|
8436
|
-
*
|
|
8436
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8437
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8438
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8439
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8437
8440
|
*/
|
|
8438
8441
|
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
8442
|
const descriptor = getNativeIndicator(type);
|
|
8443
|
+
if (!descriptor?.multiInstance) {
|
|
8444
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8445
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8446
|
+
}
|
|
8442
8447
|
const id = this.registry.nextId("native");
|
|
8443
8448
|
const title = descriptor?.title ?? type;
|
|
8444
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8449
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8445
8450
|
this.handles.set(id, handle);
|
|
8446
8451
|
if (!descriptor) {
|
|
8447
8452
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8453,16 +8458,18 @@ var Vela = (function (exports) {
|
|
|
8453
8458
|
void this.startNativeIndicator(id, handle);
|
|
8454
8459
|
return handle;
|
|
8455
8460
|
}
|
|
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). */
|
|
8461
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8462
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8463
8463
|
presentNativeIndicators() {
|
|
8464
8464
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8465
8465
|
}
|
|
8466
|
+
/**
|
|
8467
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8468
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8469
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8470
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8471
|
+
* probe the provider for a required capability.
|
|
8472
|
+
*/
|
|
8466
8473
|
async availableNativeIndicators() {
|
|
8467
8474
|
const symbol = this.qualifiedSymbol();
|
|
8468
8475
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8472,7 +8479,8 @@ var Vela = (function (exports) {
|
|
|
8472
8479
|
title: d.title,
|
|
8473
8480
|
supported: await this.isNativeSupported(d, symbol),
|
|
8474
8481
|
present: present.has(d.type),
|
|
8475
|
-
beta: d.beta
|
|
8482
|
+
beta: d.beta,
|
|
8483
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8476
8484
|
}))
|
|
8477
8485
|
);
|
|
8478
8486
|
}
|
|
@@ -9756,7 +9764,7 @@ var Vela = (function (exports) {
|
|
|
9756
9764
|
|
|
9757
9765
|
// src/data/MultiProviderFeed.ts
|
|
9758
9766
|
function intervalMs(timeframe) {
|
|
9759
|
-
const
|
|
9767
|
+
const map2 = {
|
|
9760
9768
|
"1m": 6e4,
|
|
9761
9769
|
"3m": 18e4,
|
|
9762
9770
|
"5m": 3e5,
|
|
@@ -9778,7 +9786,7 @@ var Vela = (function (exports) {
|
|
|
9778
9786
|
W: 6048e5,
|
|
9779
9787
|
M: 2592e6
|
|
9780
9788
|
};
|
|
9781
|
-
return
|
|
9789
|
+
return map2[timeframe] ?? 36e5;
|
|
9782
9790
|
}
|
|
9783
9791
|
var MultiProviderFeed = class {
|
|
9784
9792
|
constructor(store = sharedBarStore) {
|
|
@@ -10165,8 +10173,8 @@ var Vela = (function (exports) {
|
|
|
10165
10173
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10166
10174
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10167
10175
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10168
|
-
setToolShortcuts(
|
|
10169
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10176
|
+
setToolShortcuts(map2) {
|
|
10177
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10170
10178
|
return this;
|
|
10171
10179
|
}
|
|
10172
10180
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10631,7 +10639,7 @@ var Vela = (function (exports) {
|
|
|
10631
10639
|
}
|
|
10632
10640
|
|
|
10633
10641
|
// node_modules/@zag-js/core/dist/merge-props.mjs
|
|
10634
|
-
var clsx = (...args) => args.map((
|
|
10642
|
+
var clsx = (...args) => args.map((str4) => str4?.trim?.()).filter(Boolean).join(" ");
|
|
10635
10643
|
var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
|
|
10636
10644
|
var serialize = (style) => {
|
|
10637
10645
|
const res = {};
|
|
@@ -10855,12 +10863,12 @@ var Vela = (function (exports) {
|
|
|
10855
10863
|
return {
|
|
10856
10864
|
and: (...guards) => {
|
|
10857
10865
|
return function andGuard(params) {
|
|
10858
|
-
return guards.every((
|
|
10866
|
+
return guards.every((str4) => params.guard(str4));
|
|
10859
10867
|
};
|
|
10860
10868
|
},
|
|
10861
10869
|
or: (...guards) => {
|
|
10862
10870
|
return function orGuard(params) {
|
|
10863
|
-
return guards.some((
|
|
10871
|
+
return guards.some((str4) => params.guard(str4));
|
|
10864
10872
|
};
|
|
10865
10873
|
},
|
|
10866
10874
|
not: (guard) => {
|
|
@@ -11421,9 +11429,9 @@ var Vela = (function (exports) {
|
|
|
11421
11429
|
const { attributes, callback: fn } = options;
|
|
11422
11430
|
const win = node.ownerDocument.defaultView || window;
|
|
11423
11431
|
const obs = new win.MutationObserver((changes) => {
|
|
11424
|
-
for (const
|
|
11425
|
-
if (
|
|
11426
|
-
fn(
|
|
11432
|
+
for (const change2 of changes) {
|
|
11433
|
+
if (change2.type === "attributes" && change2.attributeName && attributes.includes(change2.attributeName)) {
|
|
11434
|
+
fn(change2);
|
|
11427
11435
|
}
|
|
11428
11436
|
}
|
|
11429
11437
|
});
|
|
@@ -11497,7 +11505,7 @@ var Vela = (function (exports) {
|
|
|
11497
11505
|
}
|
|
11498
11506
|
|
|
11499
11507
|
// node_modules/@zag-js/dom-query/dist/searchable.mjs
|
|
11500
|
-
var sanitize = (
|
|
11508
|
+
var sanitize = (str4) => str4.split("").map((char) => {
|
|
11501
11509
|
const code = char.charCodeAt(0);
|
|
11502
11510
|
if (code > 0 && code < 128) return char;
|
|
11503
11511
|
if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
|
|
@@ -12197,10 +12205,10 @@ var Vela = (function (exports) {
|
|
|
12197
12205
|
fn?.(this.getParams());
|
|
12198
12206
|
}
|
|
12199
12207
|
});
|
|
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(
|
|
12208
|
+
__publicField(this, "guard", (str4) => {
|
|
12209
|
+
if (isFunction(str4)) return str4(this.getParams());
|
|
12210
|
+
const fn = this.machine.implementations?.guards?.[str4];
|
|
12211
|
+
if (!fn) warn(`[zag-js] No implementation found for guard "${JSON.stringify(str4)}"`);
|
|
12204
12212
|
return fn?.(this.getParams());
|
|
12205
12213
|
});
|
|
12206
12214
|
__publicField(this, "effect", (keys) => {
|
|
@@ -16978,19 +16986,19 @@ var Vela = (function (exports) {
|
|
|
16978
16986
|
}
|
|
16979
16987
|
setItems(items) {
|
|
16980
16988
|
this.items = items;
|
|
16981
|
-
for (const
|
|
16989
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16982
16990
|
this.subs.clear();
|
|
16983
16991
|
this.build();
|
|
16984
16992
|
const api = this.api;
|
|
16985
|
-
for (const
|
|
16986
|
-
api.setChild(
|
|
16987
|
-
|
|
16993
|
+
for (const sub2 of this.subs.values()) {
|
|
16994
|
+
api.setChild(sub2.handle.service);
|
|
16995
|
+
sub2.api.setParent(this.handle.service);
|
|
16988
16996
|
}
|
|
16989
16997
|
this.handle.flush();
|
|
16990
16998
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
16991
16999
|
}
|
|
16992
17000
|
destroy() {
|
|
16993
|
-
for (const
|
|
17001
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
16994
17002
|
this.subs.clear();
|
|
16995
17003
|
this.handle.stop();
|
|
16996
17004
|
this.positioner.remove();
|
|
@@ -17004,9 +17012,9 @@ var Vela = (function (exports) {
|
|
|
17004
17012
|
for (const li of this.list.children) {
|
|
17005
17013
|
const id = li.dataset.veiId;
|
|
17006
17014
|
if (!id) continue;
|
|
17007
|
-
const
|
|
17008
|
-
if (
|
|
17009
|
-
spreadProps(li, api.getTriggerItemProps(
|
|
17015
|
+
const sub2 = this.subs.get(id);
|
|
17016
|
+
if (sub2) {
|
|
17017
|
+
spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
17010
17018
|
continue;
|
|
17011
17019
|
}
|
|
17012
17020
|
const item = this.byId(id);
|
|
@@ -17083,7 +17091,7 @@ var Vela = (function (exports) {
|
|
|
17083
17091
|
const arrow3 = iconEl("chevron-right", doc);
|
|
17084
17092
|
arrow3.classList.add("vela-menu-arrow");
|
|
17085
17093
|
li.appendChild(arrow3);
|
|
17086
|
-
const
|
|
17094
|
+
const sub2 = new _Surface(doc, {
|
|
17087
17095
|
host: this.host,
|
|
17088
17096
|
placement: "right-start",
|
|
17089
17097
|
onSelect: this.onSelect,
|
|
@@ -17091,8 +17099,8 @@ var Vela = (function (exports) {
|
|
|
17091
17099
|
checkmarks: this.checkmarks,
|
|
17092
17100
|
id: `${this.mid}--${item.id}`
|
|
17093
17101
|
});
|
|
17094
|
-
|
|
17095
|
-
this.subs.set(item.id,
|
|
17102
|
+
sub2.setItems(item.submenu ?? []);
|
|
17103
|
+
this.subs.set(item.id, sub2);
|
|
17096
17104
|
}
|
|
17097
17105
|
this.list.appendChild(li);
|
|
17098
17106
|
}
|
|
@@ -20047,8 +20055,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20047
20055
|
};
|
|
20048
20056
|
const recentRow = document.createElement("div");
|
|
20049
20057
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
20058
|
+
const recentSwatches = document.createElement("div");
|
|
20059
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
20060
|
+
const add = document.createElement("label");
|
|
20061
|
+
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;`;
|
|
20062
|
+
add.textContent = "+";
|
|
20063
|
+
const customInput = document.createElement("input");
|
|
20064
|
+
customInput.type = "color";
|
|
20065
|
+
customInput.value = curHex;
|
|
20066
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
20067
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
20068
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
20069
|
+
add.appendChild(customInput);
|
|
20070
|
+
recentRow.append(recentSwatches, add);
|
|
20050
20071
|
const renderRecents = () => {
|
|
20051
|
-
|
|
20072
|
+
recentSwatches.replaceChildren();
|
|
20052
20073
|
for (const c of recents) {
|
|
20053
20074
|
const sw = document.createElement("button");
|
|
20054
20075
|
sw.type = "button";
|
|
@@ -20057,18 +20078,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20057
20078
|
e.stopPropagation();
|
|
20058
20079
|
pickHex(c);
|
|
20059
20080
|
});
|
|
20060
|
-
|
|
20081
|
+
recentSwatches.appendChild(sw);
|
|
20061
20082
|
}
|
|
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
20083
|
};
|
|
20073
20084
|
const opLabel = document.createElement("div");
|
|
20074
20085
|
opLabel.textContent = "Opacity";
|
|
@@ -20108,14 +20119,18 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20108
20119
|
function emit() {
|
|
20109
20120
|
onChange(combineColor(curHex, curAlpha));
|
|
20110
20121
|
}
|
|
20111
|
-
function
|
|
20122
|
+
function previewHex(hex) {
|
|
20112
20123
|
curHex = splitColor(hex).hex6;
|
|
20113
|
-
addRecent(curHex);
|
|
20114
20124
|
paintSelection();
|
|
20115
20125
|
paintOpacity();
|
|
20116
|
-
if (custom) renderRecents();
|
|
20117
20126
|
emit();
|
|
20118
20127
|
}
|
|
20128
|
+
function pickHex(hex, custom = false) {
|
|
20129
|
+
previewHex(hex);
|
|
20130
|
+
addRecent(curHex);
|
|
20131
|
+
customInput.value = curHex;
|
|
20132
|
+
if (custom) renderRecents();
|
|
20133
|
+
}
|
|
20119
20134
|
renderRecents();
|
|
20120
20135
|
paintSelection();
|
|
20121
20136
|
paintOpacity();
|
|
@@ -22021,8 +22036,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22021
22036
|
lg.style.top = `${top}px`;
|
|
22022
22037
|
}
|
|
22023
22038
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22024
|
-
setCollapsedPanes(
|
|
22025
|
-
this.paneCollapse =
|
|
22039
|
+
setCollapsedPanes(map2) {
|
|
22040
|
+
this.paneCollapse = map2;
|
|
22026
22041
|
}
|
|
22027
22042
|
/**
|
|
22028
22043
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -22985,9 +23000,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22985
23000
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
22986
23001
|
addRows(inst.rows);
|
|
22987
23002
|
}
|
|
22988
|
-
for (const
|
|
22989
|
-
if (
|
|
22990
|
-
addRows(
|
|
23003
|
+
for (const sub2 of section.subsections ?? []) {
|
|
23004
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
23005
|
+
addRows(sub2.rows);
|
|
22991
23006
|
}
|
|
22992
23007
|
if (!section.instances) addRows(section.rows);
|
|
22993
23008
|
bag[t.id] = defaults2;
|
|
@@ -24316,11 +24331,11 @@ void main() {
|
|
|
24316
24331
|
}
|
|
24317
24332
|
}
|
|
24318
24333
|
if (!any) return EMPTY_BARCOLORS;
|
|
24319
|
-
const
|
|
24334
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24320
24335
|
for (const m of indicators.values()) {
|
|
24321
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24336
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24322
24337
|
}
|
|
24323
|
-
return
|
|
24338
|
+
return map2;
|
|
24324
24339
|
}
|
|
24325
24340
|
|
|
24326
24341
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24617,9 +24632,9 @@ void main() {
|
|
|
24617
24632
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24618
24633
|
return rightOffset + deltaX / barSpacing;
|
|
24619
24634
|
}
|
|
24620
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24635
|
+
function wheelPanDelta(deltaX, deltaY, shift4) {
|
|
24621
24636
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24622
|
-
if (
|
|
24637
|
+
if (shift4) return deltaY;
|
|
24623
24638
|
return null;
|
|
24624
24639
|
}
|
|
24625
24640
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -26241,11 +26256,11 @@ void main() {
|
|
|
26241
26256
|
}
|
|
26242
26257
|
}
|
|
26243
26258
|
if (!any) return EMPTY_BARCOLORS2;
|
|
26244
|
-
const
|
|
26259
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
26245
26260
|
for (const m of indicators.values()) {
|
|
26246
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26261
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
26247
26262
|
}
|
|
26248
|
-
return
|
|
26263
|
+
return map2;
|
|
26249
26264
|
}
|
|
26250
26265
|
|
|
26251
26266
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27961,9 +27976,9 @@ void main() {
|
|
|
27961
27976
|
};
|
|
27962
27977
|
if (section.rows) addRows(section.rows);
|
|
27963
27978
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27964
|
-
for (const
|
|
27965
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27966
|
-
addRows(
|
|
27979
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27980
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27981
|
+
addRows(sub2.rows);
|
|
27967
27982
|
}
|
|
27968
27983
|
}
|
|
27969
27984
|
for (const hs of hostSections) {
|
|
@@ -28522,14 +28537,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28522
28537
|
};
|
|
28523
28538
|
if (section.rows) seed(section.rows);
|
|
28524
28539
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28525
|
-
for (const
|
|
28540
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28526
28541
|
for (const inst of section.instances ?? []) {
|
|
28527
28542
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28528
28543
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28529
28544
|
}
|
|
28530
|
-
for (const
|
|
28531
|
-
if (!
|
|
28532
|
-
bag[
|
|
28545
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28546
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28547
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28533
28548
|
}
|
|
28534
28549
|
const refreshers = [];
|
|
28535
28550
|
const put = (key, v) => {
|
|
@@ -28546,16 +28561,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
28546
28561
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28547
28562
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28548
28563
|
}
|
|
28549
|
-
for (const
|
|
28550
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28551
|
-
const rows = filterHiddenRows(
|
|
28564
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28565
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28566
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28552
28567
|
if (rows.length === 0) continue;
|
|
28553
|
-
const subMarker = this.section(
|
|
28568
|
+
const subMarker = this.section(sub2.title);
|
|
28554
28569
|
subMarker.dataset.sdStyle = typeId;
|
|
28555
28570
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28556
28571
|
subMarker.dataset.sdSub = "1";
|
|
28557
28572
|
body.append(subMarker);
|
|
28558
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28573
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28559
28574
|
}
|
|
28560
28575
|
for (const r of refreshers) r();
|
|
28561
28576
|
}
|
|
@@ -30663,11 +30678,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30663
30678
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30664
30679
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30665
30680
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30666
|
-
down(x, y, mode = "off",
|
|
30681
|
+
down(x, y, mode = "off", shift4 = false) {
|
|
30667
30682
|
const proj = this.deps.projector();
|
|
30668
30683
|
if (this.state.kind === "placing") {
|
|
30669
30684
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30670
|
-
const pivot =
|
|
30685
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30671
30686
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30672
30687
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30673
30688
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30686,7 +30701,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30686
30701
|
}
|
|
30687
30702
|
const hit = this.hitAt(x, y);
|
|
30688
30703
|
if (hit) {
|
|
30689
|
-
if (
|
|
30704
|
+
if (shift4) {
|
|
30690
30705
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30691
30706
|
return;
|
|
30692
30707
|
}
|
|
@@ -30696,7 +30711,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30696
30711
|
}
|
|
30697
30712
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30698
30713
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30699
|
-
move(x, y, mode = "off",
|
|
30714
|
+
move(x, y, mode = "off", shift4 = false) {
|
|
30700
30715
|
if (this.state.kind === "idle") {
|
|
30701
30716
|
const tool = this.deps.activeTool();
|
|
30702
30717
|
const had = this.snapAt != null;
|
|
@@ -30710,7 +30725,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30710
30725
|
this.captureFreehand(x, y);
|
|
30711
30726
|
return;
|
|
30712
30727
|
}
|
|
30713
|
-
const pivot =
|
|
30728
|
+
const pivot = shift4 ? this.placingPivot(this.state.draft) : null;
|
|
30714
30729
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30715
30730
|
this.deps.changed();
|
|
30716
30731
|
return;
|
|
@@ -30723,7 +30738,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30723
30738
|
if (this.state.handle >= 0) {
|
|
30724
30739
|
const a = d.anchors[this.state.handle];
|
|
30725
30740
|
if (a) {
|
|
30726
|
-
const pivot =
|
|
30741
|
+
const pivot = shift4 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30727
30742
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30728
30743
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30729
30744
|
}
|
|
@@ -31421,21 +31436,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31421
31436
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31422
31437
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31423
31438
|
}
|
|
31424
|
-
const
|
|
31425
|
-
if (paths.has("vwap.midColor") &&
|
|
31439
|
+
const vwap2 = drawing.vwap;
|
|
31440
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31426
31441
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31427
31442
|
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,
|
|
31443
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31444
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31430
31445
|
bar.appendChild(
|
|
31431
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31446
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31432
31447
|
label: (v) => `${v}\u03C3`,
|
|
31433
31448
|
labelInTrigger: true
|
|
31434
31449
|
})
|
|
31435
31450
|
);
|
|
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,
|
|
31451
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31452
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31453
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31439
31454
|
}
|
|
31440
31455
|
if (paths.has("maxCurvature")) {
|
|
31441
31456
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32181,8 +32196,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32181
32196
|
}
|
|
32182
32197
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
32183
32198
|
* right edge of the flyout rows, beside the favorite star. */
|
|
32184
|
-
setShortcuts(
|
|
32185
|
-
this.shortcuts = new Map(Object.entries(
|
|
32199
|
+
setShortcuts(map2) {
|
|
32200
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
32186
32201
|
}
|
|
32187
32202
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
32188
32203
|
paintStar(el, type) {
|
|
@@ -33084,8 +33099,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33084
33099
|
this.toolbar.setFavorites(types);
|
|
33085
33100
|
}
|
|
33086
33101
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
33087
|
-
setToolShortcuts(
|
|
33088
|
-
this.toolbar.setShortcuts(
|
|
33102
|
+
setToolShortcuts(map2) {
|
|
33103
|
+
this.toolbar.setShortcuts(map2);
|
|
33089
33104
|
}
|
|
33090
33105
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
33091
33106
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -33203,7 +33218,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33203
33218
|
this.render();
|
|
33204
33219
|
}
|
|
33205
33220
|
}
|
|
33206
|
-
pointerDown(x, y, snap = "off",
|
|
33221
|
+
pointerDown(x, y, snap = "off", shift4 = false) {
|
|
33207
33222
|
if (this.eraserMode) {
|
|
33208
33223
|
this.erasing = true;
|
|
33209
33224
|
this.deleteAt(x, y);
|
|
@@ -33223,9 +33238,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33223
33238
|
return;
|
|
33224
33239
|
}
|
|
33225
33240
|
}
|
|
33226
|
-
this.interaction.down(x, y, snap,
|
|
33241
|
+
this.interaction.down(x, y, snap, shift4);
|
|
33227
33242
|
}
|
|
33228
|
-
pointerMove(x, y, snap = "off",
|
|
33243
|
+
pointerMove(x, y, snap = "off", shift4 = false) {
|
|
33229
33244
|
if (this.eraserMode) {
|
|
33230
33245
|
if (this.erasing) this.deleteAt(x, y);
|
|
33231
33246
|
return;
|
|
@@ -33236,7 +33251,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33236
33251
|
this.render();
|
|
33237
33252
|
return;
|
|
33238
33253
|
}
|
|
33239
|
-
this.interaction.move(x, y, snap,
|
|
33254
|
+
this.interaction.move(x, y, snap, shift4);
|
|
33240
33255
|
this.updateHover(x, y);
|
|
33241
33256
|
}
|
|
33242
33257
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -33913,14 +33928,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33913
33928
|
}
|
|
33914
33929
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33915
33930
|
if (box.cs > 1) {
|
|
33916
|
-
let
|
|
33917
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
33918
|
-
if (w2 >
|
|
33931
|
+
let sum2 = 0;
|
|
33932
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
33933
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
33919
33934
|
}
|
|
33920
33935
|
if (box.rs > 1) {
|
|
33921
|
-
let
|
|
33922
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
33923
|
-
if (h2 >
|
|
33936
|
+
let sum2 = 0;
|
|
33937
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
33938
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
33924
33939
|
}
|
|
33925
33940
|
}
|
|
33926
33941
|
let w = 0;
|
|
@@ -34643,24 +34658,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34643
34658
|
drawSvgShapes(el, alpha);
|
|
34644
34659
|
continue;
|
|
34645
34660
|
}
|
|
34646
|
-
const
|
|
34661
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
34647
34662
|
let path = null;
|
|
34648
34663
|
if (tag === "path") {
|
|
34649
34664
|
const d = el.getAttribute("d");
|
|
34650
34665
|
if (d) path = new Path2D(d);
|
|
34651
34666
|
} else if (tag === "circle") {
|
|
34652
34667
|
path = new Path2D();
|
|
34653
|
-
path.arc(
|
|
34668
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
34654
34669
|
} else if (tag === "ellipse") {
|
|
34655
34670
|
path = new Path2D();
|
|
34656
|
-
path.ellipse(
|
|
34671
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
34657
34672
|
} else if (tag === "rect") {
|
|
34658
34673
|
path = new Path2D();
|
|
34659
|
-
path.rect(
|
|
34674
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
34660
34675
|
} else if (tag === "line") {
|
|
34661
34676
|
path = new Path2D();
|
|
34662
|
-
path.moveTo(
|
|
34663
|
-
path.lineTo(
|
|
34677
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
34678
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
34664
34679
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
34665
34680
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
34666
34681
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -36077,8 +36092,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36077
36092
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
36078
36093
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
36079
36094
|
drawingsSnapMode: () => this.snapMode,
|
|
36080
|
-
drawingsPointerDown: (x, y, snap,
|
|
36081
|
-
drawingsPointerMove: (x, y, snap,
|
|
36095
|
+
drawingsPointerDown: (x, y, snap, shift4) => this.userDrawings?.pointerDown(x, y, snap, shift4),
|
|
36096
|
+
drawingsPointerMove: (x, y, snap, shift4) => this.userDrawings?.pointerMove(x, y, snap, shift4),
|
|
36082
36097
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
36083
36098
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
36084
36099
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -36395,18 +36410,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36395
36410
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
36396
36411
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
36397
36412
|
const newHeadTime = this.bars[0].time;
|
|
36398
|
-
let
|
|
36413
|
+
let shift4 = 0;
|
|
36399
36414
|
if (newHeadTime !== prevHeadTime) {
|
|
36400
36415
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
36401
|
-
if (prepended > 0)
|
|
36416
|
+
if (prepended > 0) shift4 = prepended;
|
|
36402
36417
|
else {
|
|
36403
36418
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
36404
|
-
if (trimmed > 0)
|
|
36419
|
+
if (trimmed > 0) shift4 = -trimmed;
|
|
36405
36420
|
}
|
|
36406
36421
|
}
|
|
36407
|
-
if (
|
|
36408
|
-
this.zoomAnchorLogical +=
|
|
36409
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
36422
|
+
if (shift4 !== 0) {
|
|
36423
|
+
this.zoomAnchorLogical += shift4;
|
|
36424
|
+
if (this.hoverLogical != null) this.hoverLogical += shift4;
|
|
36410
36425
|
}
|
|
36411
36426
|
}
|
|
36412
36427
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -38012,15 +38027,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38012
38027
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
38013
38028
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
38014
38029
|
collapsedMasterMap() {
|
|
38015
|
-
const
|
|
38030
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
38016
38031
|
for (const pane of this.scene.panes.values()) {
|
|
38017
38032
|
if (!pane.collapsed) continue;
|
|
38018
38033
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
38019
38034
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
38020
38035
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
38021
|
-
|
|
38036
|
+
map2.set(pane.id, master?.id ?? null);
|
|
38022
38037
|
}
|
|
38023
|
-
return
|
|
38038
|
+
return map2;
|
|
38024
38039
|
}
|
|
38025
38040
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
38026
38041
|
setToolbarGutter(px) {
|
|
@@ -38416,12 +38431,2328 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38416
38431
|
registerNativeIndicator(vpvrDescriptor);
|
|
38417
38432
|
}
|
|
38418
38433
|
|
|
38434
|
+
// src/core/model/identity.ts
|
|
38435
|
+
function stableSeriesId(parts3) {
|
|
38436
|
+
const normTitle = parts3.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
38437
|
+
return `${parts3.instanceId}:${parts3.kind}:${normTitle}#${parts3.ordinal}`;
|
|
38438
|
+
}
|
|
38439
|
+
|
|
38440
|
+
// src/core/native-indicators/classics/define.ts
|
|
38441
|
+
function num3(inputs, key, fallback) {
|
|
38442
|
+
const v = inputs[key];
|
|
38443
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
38444
|
+
}
|
|
38445
|
+
function str3(inputs, key, fallback) {
|
|
38446
|
+
const v = inputs[key];
|
|
38447
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
38448
|
+
}
|
|
38449
|
+
function toPoints(bars, values, colors) {
|
|
38450
|
+
const pts = new Array(bars.length);
|
|
38451
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38452
|
+
const v = values[i];
|
|
38453
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
38454
|
+
const c = colors?.[i];
|
|
38455
|
+
if (c != null) p.color = c;
|
|
38456
|
+
pts[i] = p;
|
|
38457
|
+
}
|
|
38458
|
+
return pts;
|
|
38459
|
+
}
|
|
38460
|
+
var ClassicIndicator = class {
|
|
38461
|
+
constructor(spec) {
|
|
38462
|
+
this.spec = spec;
|
|
38463
|
+
this.inputs = {};
|
|
38464
|
+
}
|
|
38465
|
+
start(ctx, inputs) {
|
|
38466
|
+
this.ctx = ctx;
|
|
38467
|
+
this.inputs = inputs;
|
|
38468
|
+
this.recompute();
|
|
38469
|
+
}
|
|
38470
|
+
onBars() {
|
|
38471
|
+
this.recompute();
|
|
38472
|
+
}
|
|
38473
|
+
onViewport() {
|
|
38474
|
+
}
|
|
38475
|
+
setInputs(inputs) {
|
|
38476
|
+
this.inputs = inputs;
|
|
38477
|
+
this.recompute();
|
|
38478
|
+
}
|
|
38479
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
38480
|
+
suspend() {
|
|
38481
|
+
}
|
|
38482
|
+
resume() {
|
|
38483
|
+
this.recompute();
|
|
38484
|
+
}
|
|
38485
|
+
stop() {
|
|
38486
|
+
}
|
|
38487
|
+
recompute() {
|
|
38488
|
+
const bars = this.ctx.bars();
|
|
38489
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
38490
|
+
const instanceId = this.spec.type;
|
|
38491
|
+
const series = [];
|
|
38492
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
38493
|
+
out.plots.forEach((plot, ordinal) => {
|
|
38494
|
+
const kind = plot.kind ?? "line";
|
|
38495
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
38496
|
+
idsByKey.set(plot.key, id);
|
|
38497
|
+
series.push({
|
|
38498
|
+
id,
|
|
38499
|
+
title: plot.title,
|
|
38500
|
+
paneId: "",
|
|
38501
|
+
// stamped by the orchestrator
|
|
38502
|
+
kind,
|
|
38503
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
38504
|
+
style: {
|
|
38505
|
+
color: plot.color,
|
|
38506
|
+
width: plot.width ?? 1,
|
|
38507
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
38508
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
38509
|
+
},
|
|
38510
|
+
...plot.overlay ? { overlay: true } : {}
|
|
38511
|
+
});
|
|
38512
|
+
});
|
|
38513
|
+
if (out.markers && out.markers.length > 0) {
|
|
38514
|
+
series.push({
|
|
38515
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
38516
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
38517
|
+
paneId: "",
|
|
38518
|
+
kind: "markers",
|
|
38519
|
+
markers: out.markers
|
|
38520
|
+
});
|
|
38521
|
+
}
|
|
38522
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
38523
|
+
const from = idsByKey.get(band.from);
|
|
38524
|
+
const to = idsByKey.get(band.to);
|
|
38525
|
+
if (!from || !to) return [];
|
|
38526
|
+
return [
|
|
38527
|
+
{
|
|
38528
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
38529
|
+
paneId: "",
|
|
38530
|
+
fromSeriesId: from,
|
|
38531
|
+
toSeriesId: to,
|
|
38532
|
+
color: band.color
|
|
38533
|
+
}
|
|
38534
|
+
];
|
|
38535
|
+
});
|
|
38536
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
38537
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
38538
|
+
paneId: "",
|
|
38539
|
+
price: level.price,
|
|
38540
|
+
...level.color != null ? { color: level.color } : {},
|
|
38541
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
38542
|
+
width: 1,
|
|
38543
|
+
...level.title != null ? { title: level.title } : {}
|
|
38544
|
+
}));
|
|
38545
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
38546
|
+
const { key, ...rest } = poly;
|
|
38547
|
+
return {
|
|
38548
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
38549
|
+
paneId: "",
|
|
38550
|
+
...rest
|
|
38551
|
+
};
|
|
38552
|
+
});
|
|
38553
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
38554
|
+
this.ctx.setStatus("idle");
|
|
38555
|
+
}
|
|
38556
|
+
};
|
|
38557
|
+
function classicDescriptor(spec) {
|
|
38558
|
+
return {
|
|
38559
|
+
type: spec.type,
|
|
38560
|
+
title: spec.title,
|
|
38561
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
38562
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
38563
|
+
overlay: spec.overlay,
|
|
38564
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
38565
|
+
multiInstance: true,
|
|
38566
|
+
inputsSchema: () => spec.inputs,
|
|
38567
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
38568
|
+
create: () => new ClassicIndicator(spec)
|
|
38569
|
+
};
|
|
38570
|
+
}
|
|
38571
|
+
|
|
38572
|
+
// src/core/native-indicators/classics/math.ts
|
|
38573
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
38574
|
+
function sourceValues(bars, source) {
|
|
38575
|
+
const out = new Array(bars.length);
|
|
38576
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38577
|
+
const b = bars[i];
|
|
38578
|
+
switch (source) {
|
|
38579
|
+
case "Open":
|
|
38580
|
+
out[i] = b.open;
|
|
38581
|
+
break;
|
|
38582
|
+
case "High":
|
|
38583
|
+
out[i] = b.high;
|
|
38584
|
+
break;
|
|
38585
|
+
case "Low":
|
|
38586
|
+
out[i] = b.low;
|
|
38587
|
+
break;
|
|
38588
|
+
case "HL2":
|
|
38589
|
+
out[i] = (b.high + b.low) / 2;
|
|
38590
|
+
break;
|
|
38591
|
+
case "HLC3":
|
|
38592
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
38593
|
+
break;
|
|
38594
|
+
case "OHLC4":
|
|
38595
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
38596
|
+
break;
|
|
38597
|
+
case "HLCC4":
|
|
38598
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
38599
|
+
break;
|
|
38600
|
+
default:
|
|
38601
|
+
out[i] = b.close;
|
|
38602
|
+
}
|
|
38603
|
+
}
|
|
38604
|
+
return out;
|
|
38605
|
+
}
|
|
38606
|
+
function volumes(bars) {
|
|
38607
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
38608
|
+
}
|
|
38609
|
+
function highs(bars) {
|
|
38610
|
+
return bars.map((b) => b.high);
|
|
38611
|
+
}
|
|
38612
|
+
function lows(bars) {
|
|
38613
|
+
return bars.map((b) => b.low);
|
|
38614
|
+
}
|
|
38615
|
+
function closes(bars) {
|
|
38616
|
+
return bars.map((b) => b.close);
|
|
38617
|
+
}
|
|
38618
|
+
function opens(bars) {
|
|
38619
|
+
return bars.map((b) => b.open);
|
|
38620
|
+
}
|
|
38621
|
+
function zip(a, b, f) {
|
|
38622
|
+
const out = new Array(a.length);
|
|
38623
|
+
for (let i = 0; i < a.length; i++) {
|
|
38624
|
+
const x = a[i];
|
|
38625
|
+
const y = b[i];
|
|
38626
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
38627
|
+
}
|
|
38628
|
+
return out;
|
|
38629
|
+
}
|
|
38630
|
+
function map(a, f) {
|
|
38631
|
+
const out = new Array(a.length);
|
|
38632
|
+
for (let i = 0; i < a.length; i++) {
|
|
38633
|
+
const x = a[i];
|
|
38634
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
38635
|
+
}
|
|
38636
|
+
return out;
|
|
38637
|
+
}
|
|
38638
|
+
function sub(a, b) {
|
|
38639
|
+
return zip(a, b, (x, y) => x - y);
|
|
38640
|
+
}
|
|
38641
|
+
function windowed(v, len, fold) {
|
|
38642
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38643
|
+
if (len <= 0) return out;
|
|
38644
|
+
let run = 0;
|
|
38645
|
+
for (let i = 0; i < v.length; i++) {
|
|
38646
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
38647
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
38648
|
+
}
|
|
38649
|
+
return out;
|
|
38650
|
+
}
|
|
38651
|
+
function sum(v, len) {
|
|
38652
|
+
return windowed(v, len, (w, from, to) => {
|
|
38653
|
+
let s = 0;
|
|
38654
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38655
|
+
return s;
|
|
38656
|
+
});
|
|
38657
|
+
}
|
|
38658
|
+
function sma(v, len) {
|
|
38659
|
+
return windowed(v, len, (w, from, to) => {
|
|
38660
|
+
let s = 0;
|
|
38661
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38662
|
+
return s / len;
|
|
38663
|
+
});
|
|
38664
|
+
}
|
|
38665
|
+
function wma(v, len) {
|
|
38666
|
+
const norm = len * (len + 1) / 2;
|
|
38667
|
+
return windowed(v, len, (w, from, to) => {
|
|
38668
|
+
let s = 0;
|
|
38669
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
38670
|
+
return s / norm;
|
|
38671
|
+
});
|
|
38672
|
+
}
|
|
38673
|
+
function vwma(src, vol, len) {
|
|
38674
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
38675
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
38676
|
+
}
|
|
38677
|
+
function stdev(v, len) {
|
|
38678
|
+
return windowed(v, len, (w, from, to) => {
|
|
38679
|
+
let s = 0;
|
|
38680
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
38681
|
+
const mean = s / len;
|
|
38682
|
+
let sq = 0;
|
|
38683
|
+
for (let k = from; k <= to; k++) {
|
|
38684
|
+
const d = w[k] - mean;
|
|
38685
|
+
sq += d * d;
|
|
38686
|
+
}
|
|
38687
|
+
return Math.sqrt(sq / len);
|
|
38688
|
+
});
|
|
38689
|
+
}
|
|
38690
|
+
function highest(v, len) {
|
|
38691
|
+
return windowed(v, len, (w, from, to) => {
|
|
38692
|
+
let m = -Infinity;
|
|
38693
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
38694
|
+
return m;
|
|
38695
|
+
});
|
|
38696
|
+
}
|
|
38697
|
+
function lowest(v, len) {
|
|
38698
|
+
return windowed(v, len, (w, from, to) => {
|
|
38699
|
+
let m = Infinity;
|
|
38700
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
38701
|
+
return m;
|
|
38702
|
+
});
|
|
38703
|
+
}
|
|
38704
|
+
function barsSinceHighest(v, len) {
|
|
38705
|
+
return windowed(v, len, (w, from, to) => {
|
|
38706
|
+
let m = -Infinity;
|
|
38707
|
+
let at = to;
|
|
38708
|
+
for (let k = from; k <= to; k++) {
|
|
38709
|
+
if (w[k] >= m) {
|
|
38710
|
+
m = w[k];
|
|
38711
|
+
at = k;
|
|
38712
|
+
}
|
|
38713
|
+
}
|
|
38714
|
+
return to - at;
|
|
38715
|
+
});
|
|
38716
|
+
}
|
|
38717
|
+
function barsSinceLowest(v, len) {
|
|
38718
|
+
return windowed(v, len, (w, from, to) => {
|
|
38719
|
+
let m = Infinity;
|
|
38720
|
+
let at = to;
|
|
38721
|
+
for (let k = from; k <= to; k++) {
|
|
38722
|
+
if (w[k] <= m) {
|
|
38723
|
+
m = w[k];
|
|
38724
|
+
at = k;
|
|
38725
|
+
}
|
|
38726
|
+
}
|
|
38727
|
+
return to - at;
|
|
38728
|
+
});
|
|
38729
|
+
}
|
|
38730
|
+
function recursive(v, len, alpha) {
|
|
38731
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38732
|
+
let run = 0;
|
|
38733
|
+
let prev3 = Number.NaN;
|
|
38734
|
+
for (let i = 0; i < v.length; i++) {
|
|
38735
|
+
const x = v[i];
|
|
38736
|
+
if (!Number.isFinite(x)) {
|
|
38737
|
+
run = 0;
|
|
38738
|
+
prev3 = Number.NaN;
|
|
38739
|
+
continue;
|
|
38740
|
+
}
|
|
38741
|
+
run += 1;
|
|
38742
|
+
if (Number.isFinite(prev3)) {
|
|
38743
|
+
prev3 = alpha * x + (1 - alpha) * prev3;
|
|
38744
|
+
out[i] = prev3;
|
|
38745
|
+
} else if (run >= len) {
|
|
38746
|
+
let s = 0;
|
|
38747
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
38748
|
+
prev3 = s / len;
|
|
38749
|
+
out[i] = prev3;
|
|
38750
|
+
}
|
|
38751
|
+
}
|
|
38752
|
+
return out;
|
|
38753
|
+
}
|
|
38754
|
+
function ema(v, len) {
|
|
38755
|
+
return recursive(v, len, 2 / (len + 1));
|
|
38756
|
+
}
|
|
38757
|
+
function rma(v, len) {
|
|
38758
|
+
return recursive(v, len, 1 / len);
|
|
38759
|
+
}
|
|
38760
|
+
function change(v, n = 1) {
|
|
38761
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38762
|
+
for (let i = n; i < v.length; i++) {
|
|
38763
|
+
const a = v[i];
|
|
38764
|
+
const b = v[i - n];
|
|
38765
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
38766
|
+
}
|
|
38767
|
+
return out;
|
|
38768
|
+
}
|
|
38769
|
+
function roc(v, len) {
|
|
38770
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38771
|
+
for (let i = len; i < v.length; i++) {
|
|
38772
|
+
const a = v[i];
|
|
38773
|
+
const b = v[i - len];
|
|
38774
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
38775
|
+
}
|
|
38776
|
+
return out;
|
|
38777
|
+
}
|
|
38778
|
+
function shift3(v, n) {
|
|
38779
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38780
|
+
for (let i = 0; i < v.length; i++) {
|
|
38781
|
+
const j = i - n;
|
|
38782
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
38783
|
+
}
|
|
38784
|
+
return out;
|
|
38785
|
+
}
|
|
38786
|
+
function trueRange(bars) {
|
|
38787
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
38788
|
+
for (let i = 0; i < bars.length; i++) {
|
|
38789
|
+
const b = bars[i];
|
|
38790
|
+
if (i === 0) {
|
|
38791
|
+
out[i] = b.high - b.low;
|
|
38792
|
+
continue;
|
|
38793
|
+
}
|
|
38794
|
+
const pc = bars[i - 1].close;
|
|
38795
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
38796
|
+
}
|
|
38797
|
+
return out;
|
|
38798
|
+
}
|
|
38799
|
+
function atr(bars, len) {
|
|
38800
|
+
return rma(trueRange(bars), len);
|
|
38801
|
+
}
|
|
38802
|
+
function rsi(v, len) {
|
|
38803
|
+
const d = change(v);
|
|
38804
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
38805
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
38806
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
38807
|
+
}
|
|
38808
|
+
function stoch(v, hi, lo, len) {
|
|
38809
|
+
const hh = highest(hi, len);
|
|
38810
|
+
const ll = lowest(lo, len);
|
|
38811
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38812
|
+
for (let i = 0; i < v.length; i++) {
|
|
38813
|
+
const x = v[i];
|
|
38814
|
+
const h = hh[i];
|
|
38815
|
+
const l = ll[i];
|
|
38816
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38817
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
38818
|
+
}
|
|
38819
|
+
return out;
|
|
38820
|
+
}
|
|
38821
|
+
function linreg(v, len) {
|
|
38822
|
+
const sx = (len - 1) * len / 2;
|
|
38823
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
38824
|
+
const denom = len * sxx - sx * sx;
|
|
38825
|
+
return windowed(v, len, (w, from, to) => {
|
|
38826
|
+
let sy = 0;
|
|
38827
|
+
let sxy = 0;
|
|
38828
|
+
for (let k = from; k <= to; k++) {
|
|
38829
|
+
const x = k - from;
|
|
38830
|
+
sy += w[k];
|
|
38831
|
+
sxy += x * w[k];
|
|
38832
|
+
}
|
|
38833
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
38834
|
+
const intercept = (sy - slope * sx) / len;
|
|
38835
|
+
return intercept + slope * (len - 1);
|
|
38836
|
+
});
|
|
38837
|
+
}
|
|
38838
|
+
function swma(v) {
|
|
38839
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38840
|
+
for (let i = 3; i < v.length; i++) {
|
|
38841
|
+
const a = v[i - 3];
|
|
38842
|
+
const b = v[i - 2];
|
|
38843
|
+
const c = v[i - 1];
|
|
38844
|
+
const d = v[i];
|
|
38845
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
38846
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
38847
|
+
}
|
|
38848
|
+
}
|
|
38849
|
+
return out;
|
|
38850
|
+
}
|
|
38851
|
+
function percentRank(v, len) {
|
|
38852
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38853
|
+
for (let i = len; i < v.length; i++) {
|
|
38854
|
+
const x = v[i];
|
|
38855
|
+
if (!Number.isFinite(x)) continue;
|
|
38856
|
+
let below = 0;
|
|
38857
|
+
let count = 0;
|
|
38858
|
+
for (let k = i - len; k < i; k++) {
|
|
38859
|
+
const y = v[k];
|
|
38860
|
+
if (!Number.isFinite(y)) {
|
|
38861
|
+
count = -1;
|
|
38862
|
+
break;
|
|
38863
|
+
}
|
|
38864
|
+
count += 1;
|
|
38865
|
+
if (y <= x) below += 1;
|
|
38866
|
+
}
|
|
38867
|
+
if (count === len) out[i] = 100 * below / len;
|
|
38868
|
+
}
|
|
38869
|
+
return out;
|
|
38870
|
+
}
|
|
38871
|
+
function cumSum(v) {
|
|
38872
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38873
|
+
let acc = 0;
|
|
38874
|
+
let seen = false;
|
|
38875
|
+
for (let i = 0; i < v.length; i++) {
|
|
38876
|
+
const x = v[i];
|
|
38877
|
+
if (Number.isFinite(x)) {
|
|
38878
|
+
acc += x;
|
|
38879
|
+
seen = true;
|
|
38880
|
+
}
|
|
38881
|
+
if (seen) out[i] = acc;
|
|
38882
|
+
}
|
|
38883
|
+
return out;
|
|
38884
|
+
}
|
|
38885
|
+
|
|
38886
|
+
// src/core/native-indicators/classics/shared.ts
|
|
38887
|
+
function lengthInput(defval, key = "length", title = "Length", max2 = 5e3) {
|
|
38888
|
+
return { key, title, type: "int", defval, min: 1, max: max2, step: 1 };
|
|
38889
|
+
}
|
|
38890
|
+
function sourceInput(defval = "Close") {
|
|
38891
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
38892
|
+
}
|
|
38893
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
38894
|
+
return { key, title, type: "color", defval };
|
|
38895
|
+
}
|
|
38896
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
38897
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
38898
|
+
if (!m) return hex;
|
|
38899
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
38900
|
+
return `#${m[1]}${a}`;
|
|
38901
|
+
}
|
|
38902
|
+
|
|
38903
|
+
// src/core/native-indicators/classics/averages.ts
|
|
38904
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
38905
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
38906
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
38907
|
+
return {
|
|
38908
|
+
type,
|
|
38909
|
+
title,
|
|
38910
|
+
shortTitle,
|
|
38911
|
+
overlay: true,
|
|
38912
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
38913
|
+
compute: (bars, inputs) => {
|
|
38914
|
+
const len = num3(inputs, "length", 20);
|
|
38915
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38916
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
38917
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
38918
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38919
|
+
}
|
|
38920
|
+
};
|
|
38921
|
+
}
|
|
38922
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
38923
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
38924
|
+
var movingAverage = {
|
|
38925
|
+
type: "moving-average",
|
|
38926
|
+
title: "Moving Average",
|
|
38927
|
+
shortTitle: "MA",
|
|
38928
|
+
overlay: true,
|
|
38929
|
+
inputs: [
|
|
38930
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
38931
|
+
lengthInput(20),
|
|
38932
|
+
sourceInput(),
|
|
38933
|
+
offsetInput,
|
|
38934
|
+
colorInput()
|
|
38935
|
+
],
|
|
38936
|
+
compute: (bars, inputs) => {
|
|
38937
|
+
const len = num3(inputs, "length", 20);
|
|
38938
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38939
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
38940
|
+
let values;
|
|
38941
|
+
switch (kind) {
|
|
38942
|
+
case "EMA":
|
|
38943
|
+
values = ema(src, len);
|
|
38944
|
+
break;
|
|
38945
|
+
case "WMA":
|
|
38946
|
+
values = wma(src, len);
|
|
38947
|
+
break;
|
|
38948
|
+
case "RMA":
|
|
38949
|
+
values = rma(src, len);
|
|
38950
|
+
break;
|
|
38951
|
+
case "VWMA":
|
|
38952
|
+
values = vwma(src, volumes(bars), len);
|
|
38953
|
+
break;
|
|
38954
|
+
default:
|
|
38955
|
+
values = sma(src, len);
|
|
38956
|
+
}
|
|
38957
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
38958
|
+
if (offset3 !== 0) values = shift3(values, offset3);
|
|
38959
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38960
|
+
}
|
|
38961
|
+
};
|
|
38962
|
+
var smoothedMa = {
|
|
38963
|
+
type: "rma",
|
|
38964
|
+
title: "Smoothed Moving Average",
|
|
38965
|
+
shortTitle: "RMA",
|
|
38966
|
+
overlay: true,
|
|
38967
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38968
|
+
compute: (bars, inputs) => ({
|
|
38969
|
+
plots: [
|
|
38970
|
+
{
|
|
38971
|
+
key: "rma",
|
|
38972
|
+
title: "RMA",
|
|
38973
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38974
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38975
|
+
width: 2
|
|
38976
|
+
}
|
|
38977
|
+
]
|
|
38978
|
+
})
|
|
38979
|
+
};
|
|
38980
|
+
var zlema = {
|
|
38981
|
+
type: "zlema",
|
|
38982
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
38983
|
+
shortTitle: "ZLEMA",
|
|
38984
|
+
overlay: true,
|
|
38985
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38986
|
+
compute: (bars, inputs) => {
|
|
38987
|
+
const len = num3(inputs, "length", 14);
|
|
38988
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38989
|
+
const lag = Math.floor((len - 1) / 2);
|
|
38990
|
+
const lagged = shift3(src, lag);
|
|
38991
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
38992
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38993
|
+
}
|
|
38994
|
+
};
|
|
38995
|
+
var vidya = {
|
|
38996
|
+
type: "vidya",
|
|
38997
|
+
title: "Variable Index Dynamic Average",
|
|
38998
|
+
shortTitle: "VIDYA",
|
|
38999
|
+
overlay: true,
|
|
39000
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
39001
|
+
compute: (bars, inputs) => {
|
|
39002
|
+
const len = num3(inputs, "length", 14);
|
|
39003
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
39004
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39005
|
+
const d = change(src);
|
|
39006
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
39007
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
39008
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
39009
|
+
let u = 0;
|
|
39010
|
+
let w = 0;
|
|
39011
|
+
let ok = true;
|
|
39012
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
39013
|
+
const x = d[k];
|
|
39014
|
+
if (!Number.isFinite(x)) {
|
|
39015
|
+
ok = false;
|
|
39016
|
+
break;
|
|
39017
|
+
}
|
|
39018
|
+
if (x > 0) u += x;
|
|
39019
|
+
else w -= x;
|
|
39020
|
+
}
|
|
39021
|
+
if (ok) {
|
|
39022
|
+
su[i] = u;
|
|
39023
|
+
sd[i] = w;
|
|
39024
|
+
}
|
|
39025
|
+
}
|
|
39026
|
+
const alpha = 2 / (len + 1);
|
|
39027
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
39028
|
+
let prev3 = Number.NaN;
|
|
39029
|
+
for (let i = 0; i < src.length; i++) {
|
|
39030
|
+
const x = src[i];
|
|
39031
|
+
const u = su[i];
|
|
39032
|
+
const w = sd[i];
|
|
39033
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
39034
|
+
const total = u + w;
|
|
39035
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
39036
|
+
prev3 = Number.isFinite(prev3) ? alpha * k * x + (1 - alpha * k) * prev3 : x;
|
|
39037
|
+
out[i] = prev3;
|
|
39038
|
+
}
|
|
39039
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39040
|
+
}
|
|
39041
|
+
};
|
|
39042
|
+
var maEnvelope = {
|
|
39043
|
+
type: "ma-envelope",
|
|
39044
|
+
title: "Moving Average Envelope",
|
|
39045
|
+
shortTitle: "MA Env",
|
|
39046
|
+
overlay: true,
|
|
39047
|
+
inputs: [
|
|
39048
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
39049
|
+
lengthInput(20),
|
|
39050
|
+
sourceInput(),
|
|
39051
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
39052
|
+
colorInput()
|
|
39053
|
+
],
|
|
39054
|
+
compute: (bars, inputs) => {
|
|
39055
|
+
const len = num3(inputs, "length", 20);
|
|
39056
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39057
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
39058
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
39059
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39060
|
+
return {
|
|
39061
|
+
plots: [
|
|
39062
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
39063
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
39064
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
39065
|
+
],
|
|
39066
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39067
|
+
};
|
|
39068
|
+
}
|
|
39069
|
+
};
|
|
39070
|
+
var linearRegression = {
|
|
39071
|
+
type: "linear-regression",
|
|
39072
|
+
title: "Linear Regression Curve",
|
|
39073
|
+
shortTitle: "LinReg",
|
|
39074
|
+
overlay: true,
|
|
39075
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39076
|
+
compute: (bars, inputs) => ({
|
|
39077
|
+
plots: [
|
|
39078
|
+
{
|
|
39079
|
+
key: "linreg",
|
|
39080
|
+
title: "LinReg",
|
|
39081
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39082
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39083
|
+
width: 2
|
|
39084
|
+
}
|
|
39085
|
+
]
|
|
39086
|
+
})
|
|
39087
|
+
};
|
|
39088
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
39089
|
+
|
|
39090
|
+
// src/core/native-indicators/classics/bands.ts
|
|
39091
|
+
function bollinger(bars, inputs) {
|
|
39092
|
+
const len = num3(inputs, "length", 20);
|
|
39093
|
+
const mult = num3(inputs, "mult", 2);
|
|
39094
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39095
|
+
const basis = sma(src, len);
|
|
39096
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
39097
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
39098
|
+
}
|
|
39099
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
39100
|
+
var bollingerBands = {
|
|
39101
|
+
type: "bollinger-bands",
|
|
39102
|
+
title: "Bollinger Bands",
|
|
39103
|
+
shortTitle: "BB",
|
|
39104
|
+
overlay: true,
|
|
39105
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39106
|
+
compute: (bars, inputs) => {
|
|
39107
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39108
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39109
|
+
return {
|
|
39110
|
+
plots: [
|
|
39111
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39112
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39113
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39114
|
+
],
|
|
39115
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39116
|
+
};
|
|
39117
|
+
}
|
|
39118
|
+
};
|
|
39119
|
+
var percentB = {
|
|
39120
|
+
type: "percent-b",
|
|
39121
|
+
title: "Bollinger Bands %B",
|
|
39122
|
+
shortTitle: "%B",
|
|
39123
|
+
overlay: false,
|
|
39124
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39125
|
+
compute: (bars, inputs) => {
|
|
39126
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
39127
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39128
|
+
const values = src.map((x, i) => {
|
|
39129
|
+
const u = upper[i];
|
|
39130
|
+
const l = lower[i];
|
|
39131
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
39132
|
+
});
|
|
39133
|
+
return {
|
|
39134
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39135
|
+
levels: [
|
|
39136
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
39137
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
39138
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
39139
|
+
]
|
|
39140
|
+
};
|
|
39141
|
+
}
|
|
39142
|
+
};
|
|
39143
|
+
var bandwidth = {
|
|
39144
|
+
type: "bandwidth",
|
|
39145
|
+
title: "Bollinger Bands Width",
|
|
39146
|
+
shortTitle: "BandWidth",
|
|
39147
|
+
overlay: false,
|
|
39148
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
39149
|
+
compute: (bars, inputs) => {
|
|
39150
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
39151
|
+
const values = basis.map((b, i) => {
|
|
39152
|
+
const u = upper[i];
|
|
39153
|
+
const l = lower[i];
|
|
39154
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
39155
|
+
});
|
|
39156
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39157
|
+
}
|
|
39158
|
+
};
|
|
39159
|
+
var keltner = {
|
|
39160
|
+
type: "keltner-channels",
|
|
39161
|
+
title: "Keltner Channels",
|
|
39162
|
+
shortTitle: "KC",
|
|
39163
|
+
overlay: true,
|
|
39164
|
+
inputs: [
|
|
39165
|
+
lengthInput(20),
|
|
39166
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39167
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39168
|
+
sourceInput(),
|
|
39169
|
+
colorInput()
|
|
39170
|
+
],
|
|
39171
|
+
compute: (bars, inputs) => {
|
|
39172
|
+
const len = num3(inputs, "length", 20);
|
|
39173
|
+
const mult = num3(inputs, "mult", 2);
|
|
39174
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
39175
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
39176
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39177
|
+
return {
|
|
39178
|
+
plots: [
|
|
39179
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
39180
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
39181
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
39182
|
+
],
|
|
39183
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39184
|
+
};
|
|
39185
|
+
}
|
|
39186
|
+
};
|
|
39187
|
+
var donchian = {
|
|
39188
|
+
type: "donchian-channels",
|
|
39189
|
+
title: "Donchian Channels",
|
|
39190
|
+
shortTitle: "DC",
|
|
39191
|
+
overlay: true,
|
|
39192
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39193
|
+
compute: (bars, inputs) => {
|
|
39194
|
+
const len = num3(inputs, "length", 20);
|
|
39195
|
+
const upper = highest(highs(bars), len);
|
|
39196
|
+
const lower = lowest(lows(bars), len);
|
|
39197
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
39198
|
+
return {
|
|
39199
|
+
plots: [
|
|
39200
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
39201
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
39202
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
39203
|
+
],
|
|
39204
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
39205
|
+
};
|
|
39206
|
+
}
|
|
39207
|
+
};
|
|
39208
|
+
var chandelier = {
|
|
39209
|
+
type: "chandelier-exit",
|
|
39210
|
+
title: "Chandelier Exit",
|
|
39211
|
+
shortTitle: "CE",
|
|
39212
|
+
overlay: true,
|
|
39213
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
39214
|
+
compute: (bars, inputs) => {
|
|
39215
|
+
const len = num3(inputs, "length", 22);
|
|
39216
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
39217
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
39218
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
39219
|
+
return {
|
|
39220
|
+
plots: [
|
|
39221
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
39222
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
39223
|
+
]
|
|
39224
|
+
};
|
|
39225
|
+
}
|
|
39226
|
+
};
|
|
39227
|
+
var chandeKroll = {
|
|
39228
|
+
type: "chande-kroll-stop",
|
|
39229
|
+
title: "Chande Kroll Stop",
|
|
39230
|
+
shortTitle: "CKS",
|
|
39231
|
+
overlay: true,
|
|
39232
|
+
inputs: [
|
|
39233
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39234
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
39235
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
39236
|
+
],
|
|
39237
|
+
compute: (bars, inputs) => {
|
|
39238
|
+
const p = num3(inputs, "p", 10);
|
|
39239
|
+
const x = num3(inputs, "x", 1);
|
|
39240
|
+
const q = num3(inputs, "q", 9);
|
|
39241
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
39242
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
39243
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
39244
|
+
return {
|
|
39245
|
+
plots: [
|
|
39246
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
39247
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
39248
|
+
]
|
|
39249
|
+
};
|
|
39250
|
+
}
|
|
39251
|
+
};
|
|
39252
|
+
var supertrend = {
|
|
39253
|
+
type: "supertrend",
|
|
39254
|
+
title: "SuperTrend",
|
|
39255
|
+
shortTitle: "SuperTrend",
|
|
39256
|
+
overlay: true,
|
|
39257
|
+
inputs: [
|
|
39258
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39259
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
39260
|
+
],
|
|
39261
|
+
compute: (bars, inputs) => {
|
|
39262
|
+
const len = num3(inputs, "atrLength", 10);
|
|
39263
|
+
const mult = num3(inputs, "mult", 3);
|
|
39264
|
+
const range = atr(bars, len);
|
|
39265
|
+
const n = bars.length;
|
|
39266
|
+
const st = new Array(n).fill(Number.NaN);
|
|
39267
|
+
const dir = new Array(n).fill(1);
|
|
39268
|
+
let up = Number.NaN;
|
|
39269
|
+
let dn = Number.NaN;
|
|
39270
|
+
let trend = 1;
|
|
39271
|
+
for (let i = 0; i < n; i++) {
|
|
39272
|
+
const r = range[i];
|
|
39273
|
+
if (!Number.isFinite(r)) continue;
|
|
39274
|
+
const b = bars[i];
|
|
39275
|
+
const mid = (b.high + b.low) / 2;
|
|
39276
|
+
const basicUp = mid - mult * r;
|
|
39277
|
+
const basicDn = mid + mult * r;
|
|
39278
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
39279
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
39280
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
39281
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
39282
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
39283
|
+
dir[i] = trend;
|
|
39284
|
+
st[i] = trend === 1 ? up : dn;
|
|
39285
|
+
}
|
|
39286
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
39287
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
39288
|
+
}
|
|
39289
|
+
};
|
|
39290
|
+
var alligator = {
|
|
39291
|
+
type: "williams-alligator",
|
|
39292
|
+
title: "Williams Alligator",
|
|
39293
|
+
shortTitle: "Alligator",
|
|
39294
|
+
overlay: true,
|
|
39295
|
+
inputs: [
|
|
39296
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
39297
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
39298
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
39299
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
39300
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
39301
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
39302
|
+
],
|
|
39303
|
+
compute: (bars, inputs) => {
|
|
39304
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39305
|
+
const jaw = shift3(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
39306
|
+
const teeth = shift3(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
39307
|
+
const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
39308
|
+
return {
|
|
39309
|
+
plots: [
|
|
39310
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
39311
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
39312
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
39313
|
+
]
|
|
39314
|
+
};
|
|
39315
|
+
}
|
|
39316
|
+
};
|
|
39317
|
+
var gator = {
|
|
39318
|
+
type: "gator-oscillator",
|
|
39319
|
+
title: "Gator Oscillator",
|
|
39320
|
+
shortTitle: "Gator Oscillator",
|
|
39321
|
+
overlay: false,
|
|
39322
|
+
inputs: [],
|
|
39323
|
+
compute: (bars) => {
|
|
39324
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39325
|
+
const jaw = shift3(rma(hl2, 13), 8);
|
|
39326
|
+
const teeth = shift3(rma(hl2, 8), 5);
|
|
39327
|
+
const lips = shift3(rma(hl2, 5), 3);
|
|
39328
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
39329
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
39330
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
39331
|
+
if (!Number.isFinite(x)) return null;
|
|
39332
|
+
const prev3 = i > 0 ? v[i - 1] : Number.NaN;
|
|
39333
|
+
return Number.isFinite(prev3) && Math.abs(x) > Math.abs(prev3) ? BULLISH : BEARISH;
|
|
39334
|
+
});
|
|
39335
|
+
return {
|
|
39336
|
+
plots: [
|
|
39337
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
39338
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
39339
|
+
]
|
|
39340
|
+
};
|
|
39341
|
+
}
|
|
39342
|
+
};
|
|
39343
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
39344
|
+
|
|
39345
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
39346
|
+
function guideLevels(low, high, mid) {
|
|
39347
|
+
const levels = [
|
|
39348
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
39349
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
39350
|
+
];
|
|
39351
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
39352
|
+
return levels;
|
|
39353
|
+
}
|
|
39354
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
39355
|
+
function histColors(values) {
|
|
39356
|
+
return values.map((x, i) => {
|
|
39357
|
+
if (!Number.isFinite(x)) return null;
|
|
39358
|
+
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
39359
|
+
const rising = Number.isFinite(prev3) ? x >= prev3 : true;
|
|
39360
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
39361
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
39362
|
+
});
|
|
39363
|
+
}
|
|
39364
|
+
var rsiSpec = {
|
|
39365
|
+
type: "rsi",
|
|
39366
|
+
title: "Relative Strength Index",
|
|
39367
|
+
shortTitle: "RSI",
|
|
39368
|
+
overlay: false,
|
|
39369
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
39370
|
+
compute: (bars, inputs) => ({
|
|
39371
|
+
plots: [
|
|
39372
|
+
{
|
|
39373
|
+
key: "rsi",
|
|
39374
|
+
title: "RSI",
|
|
39375
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
39376
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39377
|
+
width: 2
|
|
39378
|
+
}
|
|
39379
|
+
],
|
|
39380
|
+
levels: guideLevels(30, 70, 50)
|
|
39381
|
+
})
|
|
39382
|
+
};
|
|
39383
|
+
var stochastic = {
|
|
39384
|
+
type: "stochastic",
|
|
39385
|
+
title: "Stochastic",
|
|
39386
|
+
shortTitle: "Stoch",
|
|
39387
|
+
overlay: false,
|
|
39388
|
+
inputs: [
|
|
39389
|
+
lengthInput(14, "kLength", "%K length"),
|
|
39390
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
39391
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
39392
|
+
],
|
|
39393
|
+
compute: (bars, inputs) => {
|
|
39394
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
39395
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39396
|
+
return {
|
|
39397
|
+
plots: [
|
|
39398
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39399
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39400
|
+
],
|
|
39401
|
+
levels: guideLevels(20, 80)
|
|
39402
|
+
};
|
|
39403
|
+
}
|
|
39404
|
+
};
|
|
39405
|
+
var stochasticRsi = {
|
|
39406
|
+
type: "stochastic-rsi",
|
|
39407
|
+
title: "Stochastic RSI",
|
|
39408
|
+
shortTitle: "Stoch RSI",
|
|
39409
|
+
overlay: false,
|
|
39410
|
+
inputs: [
|
|
39411
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
39412
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
39413
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
39414
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
39415
|
+
sourceInput()
|
|
39416
|
+
],
|
|
39417
|
+
compute: (bars, inputs) => {
|
|
39418
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
39419
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
39420
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
39421
|
+
return {
|
|
39422
|
+
plots: [
|
|
39423
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
39424
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
39425
|
+
],
|
|
39426
|
+
levels: guideLevels(20, 80)
|
|
39427
|
+
};
|
|
39428
|
+
}
|
|
39429
|
+
};
|
|
39430
|
+
var macd = {
|
|
39431
|
+
type: "macd",
|
|
39432
|
+
title: "MACD",
|
|
39433
|
+
overlay: false,
|
|
39434
|
+
inputs: [
|
|
39435
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39436
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39437
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39438
|
+
sourceInput()
|
|
39439
|
+
],
|
|
39440
|
+
compute: (bars, inputs) => {
|
|
39441
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39442
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
39443
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39444
|
+
const hist = sub(line, signal);
|
|
39445
|
+
return {
|
|
39446
|
+
plots: [
|
|
39447
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39448
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
39449
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39450
|
+
],
|
|
39451
|
+
levels: ZERO_LEVEL
|
|
39452
|
+
};
|
|
39453
|
+
}
|
|
39454
|
+
};
|
|
39455
|
+
var ppo = {
|
|
39456
|
+
type: "ppo",
|
|
39457
|
+
title: "Percentage Price Oscillator",
|
|
39458
|
+
shortTitle: "PPO",
|
|
39459
|
+
overlay: false,
|
|
39460
|
+
inputs: [
|
|
39461
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
39462
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
39463
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
39464
|
+
sourceInput()
|
|
39465
|
+
],
|
|
39466
|
+
compute: (bars, inputs) => {
|
|
39467
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39468
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
39469
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
39470
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
39471
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39472
|
+
const hist = sub(line, signal);
|
|
39473
|
+
return {
|
|
39474
|
+
plots: [
|
|
39475
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
39476
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
39477
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39478
|
+
],
|
|
39479
|
+
levels: ZERO_LEVEL
|
|
39480
|
+
};
|
|
39481
|
+
}
|
|
39482
|
+
};
|
|
39483
|
+
var awesome = {
|
|
39484
|
+
type: "awesome-oscillator",
|
|
39485
|
+
title: "Awesome Oscillator",
|
|
39486
|
+
shortTitle: "AO",
|
|
39487
|
+
overlay: false,
|
|
39488
|
+
inputs: [],
|
|
39489
|
+
compute: (bars) => {
|
|
39490
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39491
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
39492
|
+
const colors = ao.map((x, i) => {
|
|
39493
|
+
if (!Number.isFinite(x)) return null;
|
|
39494
|
+
const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
39495
|
+
return Number.isFinite(prev3) && x < prev3 ? BEARISH : BULLISH;
|
|
39496
|
+
});
|
|
39497
|
+
return {
|
|
39498
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
39499
|
+
levels: ZERO_LEVEL
|
|
39500
|
+
};
|
|
39501
|
+
}
|
|
39502
|
+
};
|
|
39503
|
+
var cci = {
|
|
39504
|
+
type: "commodity-channel-index",
|
|
39505
|
+
title: "Commodity Channel Index",
|
|
39506
|
+
shortTitle: "CCI",
|
|
39507
|
+
overlay: false,
|
|
39508
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
39509
|
+
compute: (bars, inputs) => {
|
|
39510
|
+
const len = num3(inputs, "length", 20);
|
|
39511
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
39512
|
+
const basis = sma(src, len);
|
|
39513
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
39514
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
39515
|
+
const mean = basis[i];
|
|
39516
|
+
if (!Number.isFinite(mean)) continue;
|
|
39517
|
+
let d = 0;
|
|
39518
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
39519
|
+
dev[i] = d / len;
|
|
39520
|
+
}
|
|
39521
|
+
const values = src.map((x, i) => {
|
|
39522
|
+
const b = basis[i];
|
|
39523
|
+
const d = dev[i];
|
|
39524
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
39525
|
+
});
|
|
39526
|
+
return {
|
|
39527
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39528
|
+
levels: guideLevels(-100, 100, 0)
|
|
39529
|
+
};
|
|
39530
|
+
}
|
|
39531
|
+
};
|
|
39532
|
+
var williamsR = {
|
|
39533
|
+
type: "williams-percent-r",
|
|
39534
|
+
title: "Williams %R",
|
|
39535
|
+
shortTitle: "%R",
|
|
39536
|
+
overlay: false,
|
|
39537
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39538
|
+
compute: (bars, inputs) => {
|
|
39539
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
39540
|
+
return {
|
|
39541
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39542
|
+
levels: guideLevels(-80, -20)
|
|
39543
|
+
};
|
|
39544
|
+
}
|
|
39545
|
+
};
|
|
39546
|
+
var cmo = {
|
|
39547
|
+
type: "chande-momentum-oscillator",
|
|
39548
|
+
title: "Chande Momentum Oscillator",
|
|
39549
|
+
shortTitle: "CMO",
|
|
39550
|
+
overlay: false,
|
|
39551
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39552
|
+
compute: (bars, inputs) => {
|
|
39553
|
+
const len = num3(inputs, "length", 9);
|
|
39554
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
39555
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
39556
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
39557
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
39558
|
+
return {
|
|
39559
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39560
|
+
levels: guideLevels(-50, 50, 0)
|
|
39561
|
+
};
|
|
39562
|
+
}
|
|
39563
|
+
};
|
|
39564
|
+
var connorsRsi = {
|
|
39565
|
+
type: "connors-rsi",
|
|
39566
|
+
title: "Connors RSI",
|
|
39567
|
+
shortTitle: "CRSI",
|
|
39568
|
+
overlay: false,
|
|
39569
|
+
inputs: [
|
|
39570
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
39571
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
39572
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
39573
|
+
colorInput()
|
|
39574
|
+
],
|
|
39575
|
+
compute: (bars, inputs) => {
|
|
39576
|
+
const c = closes(bars);
|
|
39577
|
+
const streak = new Array(c.length).fill(0);
|
|
39578
|
+
for (let i = 1; i < c.length; i++) {
|
|
39579
|
+
const d = c[i] - c[i - 1];
|
|
39580
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
39581
|
+
}
|
|
39582
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
39583
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
39584
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
39585
|
+
const values = c.map((_, i) => {
|
|
39586
|
+
const x = a[i];
|
|
39587
|
+
const y = b[i];
|
|
39588
|
+
const z = r[i];
|
|
39589
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
39590
|
+
});
|
|
39591
|
+
return {
|
|
39592
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39593
|
+
levels: guideLevels(20, 80, 50)
|
|
39594
|
+
};
|
|
39595
|
+
}
|
|
39596
|
+
};
|
|
39597
|
+
var fisher = {
|
|
39598
|
+
type: "fisher-transform",
|
|
39599
|
+
title: "Fisher Transform",
|
|
39600
|
+
shortTitle: "Fisher",
|
|
39601
|
+
overlay: false,
|
|
39602
|
+
inputs: [lengthInput(9)],
|
|
39603
|
+
compute: (bars, inputs) => {
|
|
39604
|
+
const len = num3(inputs, "length", 9);
|
|
39605
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39606
|
+
const hi = highest(hl2, len);
|
|
39607
|
+
const lo = lowest(hl2, len);
|
|
39608
|
+
const n = bars.length;
|
|
39609
|
+
const out = new Array(n).fill(Number.NaN);
|
|
39610
|
+
let value = 0;
|
|
39611
|
+
let fish = 0;
|
|
39612
|
+
for (let i = 0; i < n; i++) {
|
|
39613
|
+
const h = hi[i];
|
|
39614
|
+
const l = lo[i];
|
|
39615
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
39616
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
39617
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
39618
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
39619
|
+
out[i] = fish;
|
|
39620
|
+
}
|
|
39621
|
+
return {
|
|
39622
|
+
plots: [
|
|
39623
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
39624
|
+
{ key: "trigger", title: "Trigger", values: shift3(out, 1), color: WARNING }
|
|
39625
|
+
],
|
|
39626
|
+
levels: ZERO_LEVEL
|
|
39627
|
+
};
|
|
39628
|
+
}
|
|
39629
|
+
};
|
|
39630
|
+
var trix = {
|
|
39631
|
+
type: "trix",
|
|
39632
|
+
title: "TRIX",
|
|
39633
|
+
overlay: false,
|
|
39634
|
+
inputs: [lengthInput(18), colorInput()],
|
|
39635
|
+
compute: (bars, inputs) => {
|
|
39636
|
+
const len = num3(inputs, "length", 18);
|
|
39637
|
+
const logClose = map(closes(bars), Math.log);
|
|
39638
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
39639
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
39640
|
+
return {
|
|
39641
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39642
|
+
levels: ZERO_LEVEL
|
|
39643
|
+
};
|
|
39644
|
+
}
|
|
39645
|
+
};
|
|
39646
|
+
function tsiLine(src, long, short) {
|
|
39647
|
+
const mom = change(src);
|
|
39648
|
+
const numer = ema(ema(mom, long), short);
|
|
39649
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
39650
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
39651
|
+
}
|
|
39652
|
+
var tsi = {
|
|
39653
|
+
type: "true-strength-index",
|
|
39654
|
+
title: "True Strength Index",
|
|
39655
|
+
shortTitle: "TSI",
|
|
39656
|
+
overlay: false,
|
|
39657
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39658
|
+
compute: (bars, inputs) => {
|
|
39659
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
39660
|
+
return {
|
|
39661
|
+
plots: [
|
|
39662
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
39663
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39664
|
+
],
|
|
39665
|
+
levels: ZERO_LEVEL
|
|
39666
|
+
};
|
|
39667
|
+
}
|
|
39668
|
+
};
|
|
39669
|
+
var smiErgodic = {
|
|
39670
|
+
type: "smi-ergodic",
|
|
39671
|
+
title: "SMI Ergodic Oscillator",
|
|
39672
|
+
shortTitle: "SMIE",
|
|
39673
|
+
overlay: false,
|
|
39674
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
39675
|
+
compute: (bars, inputs) => {
|
|
39676
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
39677
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
39678
|
+
const osc = sub(line, signal);
|
|
39679
|
+
return {
|
|
39680
|
+
plots: [
|
|
39681
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
39682
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
39683
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39684
|
+
],
|
|
39685
|
+
levels: ZERO_LEVEL
|
|
39686
|
+
};
|
|
39687
|
+
}
|
|
39688
|
+
};
|
|
39689
|
+
var stc = {
|
|
39690
|
+
type: "schaff-trend-cycle",
|
|
39691
|
+
title: "Schaff Trend Cycle",
|
|
39692
|
+
shortTitle: "Schaff Trend Cycle",
|
|
39693
|
+
overlay: false,
|
|
39694
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
39695
|
+
compute: (bars, inputs) => {
|
|
39696
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
39697
|
+
const src = closes(bars);
|
|
39698
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
39699
|
+
const stochOf = (v) => {
|
|
39700
|
+
const lo = lowest(v, len);
|
|
39701
|
+
const hi = highest(v, len);
|
|
39702
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
39703
|
+
let prevRaw = Number.NaN;
|
|
39704
|
+
let smoothed = Number.NaN;
|
|
39705
|
+
for (let i = 0; i < v.length; i++) {
|
|
39706
|
+
const x = v[i];
|
|
39707
|
+
const l = lo[i];
|
|
39708
|
+
const h = hi[i];
|
|
39709
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
39710
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
39711
|
+
prevRaw = raw;
|
|
39712
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
39713
|
+
out[i] = smoothed;
|
|
39714
|
+
}
|
|
39715
|
+
return out;
|
|
39716
|
+
};
|
|
39717
|
+
const values = stochOf(stochOf(macdLine));
|
|
39718
|
+
return {
|
|
39719
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
39720
|
+
levels: guideLevels(25, 75)
|
|
39721
|
+
};
|
|
39722
|
+
}
|
|
39723
|
+
};
|
|
39724
|
+
var kst = {
|
|
39725
|
+
type: "know-sure-thing",
|
|
39726
|
+
title: "Know Sure Thing",
|
|
39727
|
+
shortTitle: "KST",
|
|
39728
|
+
overlay: false,
|
|
39729
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
39730
|
+
compute: (bars, inputs) => {
|
|
39731
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39732
|
+
const line = [
|
|
39733
|
+
sma(roc(src, 10), 10),
|
|
39734
|
+
sma(roc(src, 15), 10),
|
|
39735
|
+
sma(roc(src, 20), 10),
|
|
39736
|
+
sma(roc(src, 30), 15)
|
|
39737
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
39738
|
+
return {
|
|
39739
|
+
plots: [
|
|
39740
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
39741
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
39742
|
+
],
|
|
39743
|
+
levels: ZERO_LEVEL
|
|
39744
|
+
};
|
|
39745
|
+
}
|
|
39746
|
+
};
|
|
39747
|
+
var coppock = {
|
|
39748
|
+
type: "coppock-curve",
|
|
39749
|
+
title: "Coppock Curve",
|
|
39750
|
+
shortTitle: "Coppock",
|
|
39751
|
+
overlay: false,
|
|
39752
|
+
inputs: [
|
|
39753
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
39754
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
39755
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
39756
|
+
sourceInput(),
|
|
39757
|
+
colorInput()
|
|
39758
|
+
],
|
|
39759
|
+
compute: (bars, inputs) => {
|
|
39760
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39761
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
39762
|
+
return {
|
|
39763
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39764
|
+
levels: ZERO_LEVEL
|
|
39765
|
+
};
|
|
39766
|
+
}
|
|
39767
|
+
};
|
|
39768
|
+
var dpo = {
|
|
39769
|
+
type: "detrended-price-oscillator",
|
|
39770
|
+
title: "Detrended Price Oscillator",
|
|
39771
|
+
shortTitle: "DPO",
|
|
39772
|
+
overlay: false,
|
|
39773
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39774
|
+
compute: (bars, inputs) => {
|
|
39775
|
+
const len = num3(inputs, "length", 21);
|
|
39776
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
39777
|
+
const values = sub(closes(bars), shift3(sma(closes(bars), len), barsback));
|
|
39778
|
+
return {
|
|
39779
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39780
|
+
levels: ZERO_LEVEL
|
|
39781
|
+
};
|
|
39782
|
+
}
|
|
39783
|
+
};
|
|
39784
|
+
var rocSpec = {
|
|
39785
|
+
type: "rate-of-change",
|
|
39786
|
+
title: "Rate of Change",
|
|
39787
|
+
shortTitle: "ROC",
|
|
39788
|
+
overlay: false,
|
|
39789
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
39790
|
+
compute: (bars, inputs) => ({
|
|
39791
|
+
plots: [
|
|
39792
|
+
{
|
|
39793
|
+
key: "roc",
|
|
39794
|
+
title: "RoC",
|
|
39795
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
39796
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39797
|
+
width: 2
|
|
39798
|
+
}
|
|
39799
|
+
],
|
|
39800
|
+
levels: ZERO_LEVEL
|
|
39801
|
+
})
|
|
39802
|
+
};
|
|
39803
|
+
var ultimate = {
|
|
39804
|
+
type: "ultimate-oscillator",
|
|
39805
|
+
title: "Ultimate Oscillator",
|
|
39806
|
+
shortTitle: "UO",
|
|
39807
|
+
overlay: false,
|
|
39808
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
39809
|
+
compute: (bars, inputs) => {
|
|
39810
|
+
const n = bars.length;
|
|
39811
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
39812
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
39813
|
+
for (let i = 0; i < n; i++) {
|
|
39814
|
+
const b = bars[i];
|
|
39815
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
39816
|
+
const lo = Math.min(b.low, pc);
|
|
39817
|
+
const hi = Math.max(b.high, pc);
|
|
39818
|
+
bp[i] = b.close - lo;
|
|
39819
|
+
tr[i] = hi - lo;
|
|
39820
|
+
}
|
|
39821
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39822
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
39823
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
39824
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
39825
|
+
const values = a7.map((x, i) => {
|
|
39826
|
+
const y = a14[i];
|
|
39827
|
+
const z = a28[i];
|
|
39828
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
39829
|
+
});
|
|
39830
|
+
return {
|
|
39831
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39832
|
+
levels: guideLevels(30, 70, 50)
|
|
39833
|
+
};
|
|
39834
|
+
}
|
|
39835
|
+
};
|
|
39836
|
+
var rvgi = {
|
|
39837
|
+
type: "relative-vigor-index",
|
|
39838
|
+
title: "Relative Vigor Index",
|
|
39839
|
+
shortTitle: "RVGI",
|
|
39840
|
+
overlay: false,
|
|
39841
|
+
inputs: [lengthInput(10)],
|
|
39842
|
+
compute: (bars, inputs) => {
|
|
39843
|
+
const len = num3(inputs, "length", 10);
|
|
39844
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
39845
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
39846
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
39847
|
+
return {
|
|
39848
|
+
plots: [
|
|
39849
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
39850
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
39851
|
+
],
|
|
39852
|
+
levels: ZERO_LEVEL
|
|
39853
|
+
};
|
|
39854
|
+
}
|
|
39855
|
+
};
|
|
39856
|
+
var relVolatility = {
|
|
39857
|
+
type: "relative-volatility-index",
|
|
39858
|
+
title: "Relative Volatility Index",
|
|
39859
|
+
shortTitle: "RVI",
|
|
39860
|
+
overlay: false,
|
|
39861
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
39862
|
+
compute: (bars, inputs) => {
|
|
39863
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
39864
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
39865
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
39866
|
+
const d = change(src);
|
|
39867
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
39868
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
39869
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
39870
|
+
return {
|
|
39871
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39872
|
+
levels: guideLevels(20, 80, 50)
|
|
39873
|
+
};
|
|
39874
|
+
}
|
|
39875
|
+
};
|
|
39876
|
+
var bop = {
|
|
39877
|
+
type: "balance-of-power",
|
|
39878
|
+
title: "Balance of Power",
|
|
39879
|
+
shortTitle: "BOP",
|
|
39880
|
+
overlay: false,
|
|
39881
|
+
inputs: [colorInput()],
|
|
39882
|
+
compute: (bars, inputs) => {
|
|
39883
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
39884
|
+
return {
|
|
39885
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39886
|
+
levels: ZERO_LEVEL
|
|
39887
|
+
};
|
|
39888
|
+
}
|
|
39889
|
+
};
|
|
39890
|
+
var elderRay = {
|
|
39891
|
+
type: "elder-ray",
|
|
39892
|
+
title: "Elder Ray",
|
|
39893
|
+
overlay: false,
|
|
39894
|
+
inputs: [lengthInput(13)],
|
|
39895
|
+
compute: (bars, inputs) => {
|
|
39896
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
39897
|
+
return {
|
|
39898
|
+
plots: [
|
|
39899
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
39900
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
39901
|
+
],
|
|
39902
|
+
levels: ZERO_LEVEL
|
|
39903
|
+
};
|
|
39904
|
+
}
|
|
39905
|
+
};
|
|
39906
|
+
var ttmSqueeze = {
|
|
39907
|
+
type: "ttm-squeeze",
|
|
39908
|
+
title: "TTM Squeeze",
|
|
39909
|
+
overlay: false,
|
|
39910
|
+
inputs: [
|
|
39911
|
+
lengthInput(20),
|
|
39912
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
39913
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
39914
|
+
],
|
|
39915
|
+
compute: (bars, inputs) => {
|
|
39916
|
+
const len = num3(inputs, "length", 20);
|
|
39917
|
+
const c = closes(bars);
|
|
39918
|
+
const basis = sma(c, len);
|
|
39919
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
39920
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
39921
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
39922
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
39923
|
+
const mom = linreg(sub(c, anchor), len);
|
|
39924
|
+
const squeezed = c.map((_, i) => {
|
|
39925
|
+
const d = dev[i];
|
|
39926
|
+
const r = kcRange[i];
|
|
39927
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
39928
|
+
});
|
|
39929
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
39930
|
+
return {
|
|
39931
|
+
plots: [
|
|
39932
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
39933
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
39934
|
+
]
|
|
39935
|
+
};
|
|
39936
|
+
}
|
|
39937
|
+
};
|
|
39938
|
+
var oscillatorSpecs = [
|
|
39939
|
+
rsiSpec,
|
|
39940
|
+
stochastic,
|
|
39941
|
+
stochasticRsi,
|
|
39942
|
+
macd,
|
|
39943
|
+
ppo,
|
|
39944
|
+
awesome,
|
|
39945
|
+
cci,
|
|
39946
|
+
williamsR,
|
|
39947
|
+
cmo,
|
|
39948
|
+
connorsRsi,
|
|
39949
|
+
fisher,
|
|
39950
|
+
trix,
|
|
39951
|
+
tsi,
|
|
39952
|
+
smiErgodic,
|
|
39953
|
+
stc,
|
|
39954
|
+
kst,
|
|
39955
|
+
coppock,
|
|
39956
|
+
dpo,
|
|
39957
|
+
rocSpec,
|
|
39958
|
+
ultimate,
|
|
39959
|
+
rvgi,
|
|
39960
|
+
relVolatility,
|
|
39961
|
+
bop,
|
|
39962
|
+
elderRay,
|
|
39963
|
+
ttmSqueeze
|
|
39964
|
+
];
|
|
39965
|
+
|
|
39966
|
+
// src/core/native-indicators/classics/trend.ts
|
|
39967
|
+
var aroon = {
|
|
39968
|
+
type: "aroon",
|
|
39969
|
+
title: "Aroon",
|
|
39970
|
+
overlay: false,
|
|
39971
|
+
inputs: [lengthInput(14)],
|
|
39972
|
+
compute: (bars, inputs) => {
|
|
39973
|
+
const len = num3(inputs, "length", 14);
|
|
39974
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
39975
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
39976
|
+
return {
|
|
39977
|
+
plots: [
|
|
39978
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
39979
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
39980
|
+
],
|
|
39981
|
+
levels: [
|
|
39982
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
39983
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
39984
|
+
]
|
|
39985
|
+
};
|
|
39986
|
+
}
|
|
39987
|
+
};
|
|
39988
|
+
var adx = {
|
|
39989
|
+
type: "average-directional-index",
|
|
39990
|
+
title: "Average Directional Index",
|
|
39991
|
+
shortTitle: "ADX",
|
|
39992
|
+
overlay: false,
|
|
39993
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
39994
|
+
compute: (bars, inputs) => {
|
|
39995
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
39996
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
39997
|
+
const n = bars.length;
|
|
39998
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
39999
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
40000
|
+
for (let i = 1; i < n; i++) {
|
|
40001
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
40002
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
40003
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
40004
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
40005
|
+
}
|
|
40006
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
40007
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40008
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
40009
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
40010
|
+
return {
|
|
40011
|
+
plots: [
|
|
40012
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
40013
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
40014
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
40015
|
+
],
|
|
40016
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40017
|
+
};
|
|
40018
|
+
}
|
|
40019
|
+
};
|
|
40020
|
+
var vortex = {
|
|
40021
|
+
type: "vortex-indicator",
|
|
40022
|
+
title: "Vortex Indicator",
|
|
40023
|
+
shortTitle: "VI",
|
|
40024
|
+
overlay: false,
|
|
40025
|
+
inputs: [lengthInput(14)],
|
|
40026
|
+
compute: (bars, inputs) => {
|
|
40027
|
+
const len = num3(inputs, "length", 14);
|
|
40028
|
+
const n = bars.length;
|
|
40029
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
40030
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
40031
|
+
for (let i = 1; i < n; i++) {
|
|
40032
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
40033
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
40034
|
+
}
|
|
40035
|
+
const trSum = sum(trueRange(bars), len);
|
|
40036
|
+
return {
|
|
40037
|
+
plots: [
|
|
40038
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
40039
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
40040
|
+
],
|
|
40041
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40042
|
+
};
|
|
40043
|
+
}
|
|
40044
|
+
};
|
|
40045
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
40046
|
+
|
|
40047
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
40048
|
+
var atrSpec = {
|
|
40049
|
+
type: "average-true-range",
|
|
40050
|
+
title: "Average True Range",
|
|
40051
|
+
shortTitle: "ATR",
|
|
40052
|
+
overlay: false,
|
|
40053
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40054
|
+
compute: (bars, inputs) => ({
|
|
40055
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40056
|
+
})
|
|
40057
|
+
};
|
|
40058
|
+
var historicalVolatility = {
|
|
40059
|
+
type: "historical-volatility",
|
|
40060
|
+
title: "Historical Volatility",
|
|
40061
|
+
shortTitle: "HV",
|
|
40062
|
+
overlay: false,
|
|
40063
|
+
inputs: [
|
|
40064
|
+
lengthInput(10),
|
|
40065
|
+
{ 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)" },
|
|
40066
|
+
colorInput()
|
|
40067
|
+
],
|
|
40068
|
+
compute: (bars, inputs) => {
|
|
40069
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
40070
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
40071
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40072
|
+
}
|
|
40073
|
+
};
|
|
40074
|
+
var chaikinVolatility = {
|
|
40075
|
+
type: "chaikin-volatility",
|
|
40076
|
+
title: "Chaikin Volatility",
|
|
40077
|
+
shortTitle: "CHV",
|
|
40078
|
+
overlay: false,
|
|
40079
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
40080
|
+
compute: (bars, inputs) => {
|
|
40081
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
40082
|
+
return {
|
|
40083
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40084
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40085
|
+
};
|
|
40086
|
+
}
|
|
40087
|
+
};
|
|
40088
|
+
var massIndex = {
|
|
40089
|
+
type: "mass-index",
|
|
40090
|
+
title: "Mass Index",
|
|
40091
|
+
shortTitle: "MI",
|
|
40092
|
+
overlay: false,
|
|
40093
|
+
inputs: [lengthInput(25), colorInput()],
|
|
40094
|
+
compute: (bars, inputs) => {
|
|
40095
|
+
const range = sub(highs(bars), lows(bars));
|
|
40096
|
+
const single = ema(range, 9);
|
|
40097
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
40098
|
+
return {
|
|
40099
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40100
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
40101
|
+
};
|
|
40102
|
+
}
|
|
40103
|
+
};
|
|
40104
|
+
var standardDeviation = {
|
|
40105
|
+
type: "standard-deviation",
|
|
40106
|
+
title: "Standard Deviation",
|
|
40107
|
+
shortTitle: "StdDev",
|
|
40108
|
+
overlay: false,
|
|
40109
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
40110
|
+
compute: (bars, inputs) => ({
|
|
40111
|
+
plots: [
|
|
40112
|
+
{
|
|
40113
|
+
key: "stdev",
|
|
40114
|
+
title: "StdDev",
|
|
40115
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
40116
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
40117
|
+
width: 2
|
|
40118
|
+
}
|
|
40119
|
+
]
|
|
40120
|
+
})
|
|
40121
|
+
};
|
|
40122
|
+
var ulcerIndex = {
|
|
40123
|
+
type: "ulcer-index",
|
|
40124
|
+
title: "Ulcer Index",
|
|
40125
|
+
overlay: false,
|
|
40126
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40127
|
+
compute: (bars, inputs) => {
|
|
40128
|
+
const len = num3(inputs, "length", 14);
|
|
40129
|
+
const c = closes(bars);
|
|
40130
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
40131
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
40132
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40133
|
+
}
|
|
40134
|
+
};
|
|
40135
|
+
var choppiness = {
|
|
40136
|
+
type: "choppiness-index",
|
|
40137
|
+
title: "Choppiness Index",
|
|
40138
|
+
shortTitle: "CHOP",
|
|
40139
|
+
overlay: false,
|
|
40140
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40141
|
+
compute: (bars, inputs) => {
|
|
40142
|
+
const len = num3(inputs, "length", 14);
|
|
40143
|
+
const trSum = sum(trueRange(bars), len);
|
|
40144
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
40145
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
40146
|
+
return {
|
|
40147
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40148
|
+
levels: [
|
|
40149
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
40150
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
40151
|
+
]
|
|
40152
|
+
};
|
|
40153
|
+
}
|
|
40154
|
+
};
|
|
40155
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
40156
|
+
|
|
40157
|
+
// src/core/native-indicators/classics/volume.ts
|
|
40158
|
+
function moneyFlowVolume(bars) {
|
|
40159
|
+
return bars.map((b) => {
|
|
40160
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40161
|
+
if (b.high === b.low) return 0;
|
|
40162
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
40163
|
+
});
|
|
40164
|
+
}
|
|
40165
|
+
var obv = {
|
|
40166
|
+
type: "on-balance-volume",
|
|
40167
|
+
title: "On Balance Volume",
|
|
40168
|
+
shortTitle: "OBV",
|
|
40169
|
+
overlay: false,
|
|
40170
|
+
inputs: [colorInput()],
|
|
40171
|
+
compute: (bars, inputs) => {
|
|
40172
|
+
const vol = volumes(bars);
|
|
40173
|
+
const d = change(closes(bars));
|
|
40174
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
40175
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40176
|
+
}
|
|
40177
|
+
};
|
|
40178
|
+
var accDist = {
|
|
40179
|
+
type: "accumulation-distribution",
|
|
40180
|
+
title: "Accumulation / Distribution",
|
|
40181
|
+
shortTitle: "A/D",
|
|
40182
|
+
overlay: false,
|
|
40183
|
+
inputs: [colorInput()],
|
|
40184
|
+
compute: (bars, inputs) => ({
|
|
40185
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
40186
|
+
})
|
|
40187
|
+
};
|
|
40188
|
+
var cmf = {
|
|
40189
|
+
type: "chaikin-money-flow",
|
|
40190
|
+
title: "Chaikin Money Flow",
|
|
40191
|
+
shortTitle: "CMF",
|
|
40192
|
+
overlay: false,
|
|
40193
|
+
inputs: [lengthInput(20), colorInput()],
|
|
40194
|
+
compute: (bars, inputs) => {
|
|
40195
|
+
const len = num3(inputs, "length", 20);
|
|
40196
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
40197
|
+
return {
|
|
40198
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40199
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40200
|
+
};
|
|
40201
|
+
}
|
|
40202
|
+
};
|
|
40203
|
+
var chaikinOscillator = {
|
|
40204
|
+
type: "chaikin-oscillator",
|
|
40205
|
+
title: "Chaikin Oscillator",
|
|
40206
|
+
shortTitle: "Chaikin Osc",
|
|
40207
|
+
overlay: false,
|
|
40208
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40209
|
+
compute: (bars, inputs) => {
|
|
40210
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
40211
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
40212
|
+
return {
|
|
40213
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40214
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40215
|
+
};
|
|
40216
|
+
}
|
|
40217
|
+
};
|
|
40218
|
+
var eom = {
|
|
40219
|
+
type: "ease-of-movement",
|
|
40220
|
+
title: "Ease of Movement",
|
|
40221
|
+
shortTitle: "EOM",
|
|
40222
|
+
overlay: false,
|
|
40223
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
40224
|
+
compute: (bars, inputs) => {
|
|
40225
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
40226
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
40227
|
+
const move = change(hl2);
|
|
40228
|
+
const raw = bars.map((b, i) => {
|
|
40229
|
+
const m = move[i];
|
|
40230
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
40231
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
40232
|
+
});
|
|
40233
|
+
return {
|
|
40234
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40235
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40236
|
+
};
|
|
40237
|
+
}
|
|
40238
|
+
};
|
|
40239
|
+
var forceIndex = {
|
|
40240
|
+
type: "force-index",
|
|
40241
|
+
title: "Force Index",
|
|
40242
|
+
shortTitle: "FI",
|
|
40243
|
+
overlay: false,
|
|
40244
|
+
inputs: [lengthInput(13), colorInput()],
|
|
40245
|
+
compute: (bars, inputs) => {
|
|
40246
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
40247
|
+
return {
|
|
40248
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40249
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40250
|
+
};
|
|
40251
|
+
}
|
|
40252
|
+
};
|
|
40253
|
+
var klinger = {
|
|
40254
|
+
type: "klinger-oscillator",
|
|
40255
|
+
title: "Klinger Oscillator",
|
|
40256
|
+
shortTitle: "KVO",
|
|
40257
|
+
overlay: false,
|
|
40258
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
40259
|
+
compute: (bars, inputs) => {
|
|
40260
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
40261
|
+
const trend = change(hlc3);
|
|
40262
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
40263
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
40264
|
+
return {
|
|
40265
|
+
plots: [
|
|
40266
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40267
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
40268
|
+
],
|
|
40269
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40270
|
+
};
|
|
40271
|
+
}
|
|
40272
|
+
};
|
|
40273
|
+
var mfi = {
|
|
40274
|
+
type: "money-flow-index",
|
|
40275
|
+
title: "Money Flow Index",
|
|
40276
|
+
shortTitle: "MFI",
|
|
40277
|
+
overlay: false,
|
|
40278
|
+
inputs: [lengthInput(14), colorInput()],
|
|
40279
|
+
compute: (bars, inputs) => {
|
|
40280
|
+
const len = num3(inputs, "length", 14);
|
|
40281
|
+
const tp = sourceValues(bars, "HLC3");
|
|
40282
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
40283
|
+
const d = change(tp);
|
|
40284
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
40285
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
40286
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
40287
|
+
return {
|
|
40288
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40289
|
+
levels: [
|
|
40290
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
40291
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
40292
|
+
]
|
|
40293
|
+
};
|
|
40294
|
+
}
|
|
40295
|
+
};
|
|
40296
|
+
function volumeIndex(bars, when) {
|
|
40297
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
40298
|
+
let index = 1e3;
|
|
40299
|
+
let started = false;
|
|
40300
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40301
|
+
const v = bars[i].volume;
|
|
40302
|
+
const pv = bars[i - 1].volume;
|
|
40303
|
+
const pc = bars[i - 1].close;
|
|
40304
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
40305
|
+
if (started) out[i] = index;
|
|
40306
|
+
continue;
|
|
40307
|
+
}
|
|
40308
|
+
started = true;
|
|
40309
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
40310
|
+
out[i] = index;
|
|
40311
|
+
}
|
|
40312
|
+
return out;
|
|
40313
|
+
}
|
|
40314
|
+
var nvi = {
|
|
40315
|
+
type: "negative-volume-index",
|
|
40316
|
+
title: "Negative Volume Index",
|
|
40317
|
+
shortTitle: "Negative Volume Index",
|
|
40318
|
+
overlay: false,
|
|
40319
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40320
|
+
compute: (bars, inputs) => {
|
|
40321
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
40322
|
+
return {
|
|
40323
|
+
plots: [
|
|
40324
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40325
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40326
|
+
]
|
|
40327
|
+
};
|
|
40328
|
+
}
|
|
40329
|
+
};
|
|
40330
|
+
var pvi = {
|
|
40331
|
+
type: "positive-volume-index",
|
|
40332
|
+
title: "Positive Volume Index",
|
|
40333
|
+
shortTitle: "Positive Volume Index",
|
|
40334
|
+
overlay: false,
|
|
40335
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
40336
|
+
compute: (bars, inputs) => {
|
|
40337
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
40338
|
+
return {
|
|
40339
|
+
plots: [
|
|
40340
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
40341
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
40342
|
+
]
|
|
40343
|
+
};
|
|
40344
|
+
}
|
|
40345
|
+
};
|
|
40346
|
+
var pvt = {
|
|
40347
|
+
type: "price-volume-trend",
|
|
40348
|
+
title: "Price Volume Trend",
|
|
40349
|
+
shortTitle: "PVT",
|
|
40350
|
+
overlay: false,
|
|
40351
|
+
inputs: [colorInput()],
|
|
40352
|
+
compute: (bars, inputs) => {
|
|
40353
|
+
const c = closes(bars);
|
|
40354
|
+
const raw = bars.map((b, i) => {
|
|
40355
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
40356
|
+
const pc = c[i - 1];
|
|
40357
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
40358
|
+
});
|
|
40359
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
40360
|
+
}
|
|
40361
|
+
};
|
|
40362
|
+
var volumeOscillator = {
|
|
40363
|
+
type: "volume-oscillator",
|
|
40364
|
+
title: "Volume Oscillator",
|
|
40365
|
+
shortTitle: "Vol Osc",
|
|
40366
|
+
overlay: false,
|
|
40367
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
40368
|
+
compute: (bars, inputs) => {
|
|
40369
|
+
const vol = volumes(bars);
|
|
40370
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
40371
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
40372
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40373
|
+
return {
|
|
40374
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40375
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40376
|
+
};
|
|
40377
|
+
}
|
|
40378
|
+
};
|
|
40379
|
+
var pvo = {
|
|
40380
|
+
type: "pvo",
|
|
40381
|
+
title: "Percentage Volume Oscillator",
|
|
40382
|
+
shortTitle: "PVO",
|
|
40383
|
+
overlay: false,
|
|
40384
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
40385
|
+
compute: (bars, inputs) => {
|
|
40386
|
+
const vol = volumes(bars);
|
|
40387
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
40388
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
40389
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
40390
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
40391
|
+
const hist = sub(line, signal);
|
|
40392
|
+
return {
|
|
40393
|
+
plots: [
|
|
40394
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
40395
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
40396
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
40397
|
+
],
|
|
40398
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40399
|
+
};
|
|
40400
|
+
}
|
|
40401
|
+
};
|
|
40402
|
+
var vfi = {
|
|
40403
|
+
type: "volume-flow-indicator",
|
|
40404
|
+
title: "Volume Flow Indicator",
|
|
40405
|
+
shortTitle: "Volume Flow Indicator",
|
|
40406
|
+
overlay: false,
|
|
40407
|
+
inputs: [
|
|
40408
|
+
lengthInput(130),
|
|
40409
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
40410
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
40411
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
40412
|
+
],
|
|
40413
|
+
compute: (bars, inputs) => {
|
|
40414
|
+
const len = num3(inputs, "length", 130);
|
|
40415
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
40416
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
40417
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
40418
|
+
const inter = change(typical);
|
|
40419
|
+
const vInter = stdev(inter, 30);
|
|
40420
|
+
const vol = volumes(bars);
|
|
40421
|
+
const vAve = sma(vol, len);
|
|
40422
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
40423
|
+
const raw = bars.map((b, i) => {
|
|
40424
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
40425
|
+
const v = vol[i];
|
|
40426
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40427
|
+
const m = mf[i];
|
|
40428
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
40429
|
+
const capped = Math.min(v, ave * volCoef);
|
|
40430
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
40431
|
+
});
|
|
40432
|
+
const values = bars.map((_, i) => {
|
|
40433
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
40434
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
40435
|
+
});
|
|
40436
|
+
const flowSum = sum(raw, len);
|
|
40437
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
40438
|
+
return {
|
|
40439
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
40440
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40441
|
+
};
|
|
40442
|
+
}
|
|
40443
|
+
};
|
|
40444
|
+
var intradayIntensity = {
|
|
40445
|
+
type: "intraday-intensity",
|
|
40446
|
+
title: "Intraday Intensity",
|
|
40447
|
+
shortTitle: "Intraday Intensity",
|
|
40448
|
+
overlay: false,
|
|
40449
|
+
inputs: [lengthInput(21), colorInput()],
|
|
40450
|
+
compute: (bars, inputs) => {
|
|
40451
|
+
const len = num3(inputs, "length", 21);
|
|
40452
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
40453
|
+
return {
|
|
40454
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
40455
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
40456
|
+
};
|
|
40457
|
+
}
|
|
40458
|
+
};
|
|
40459
|
+
var volumeSpecs = [
|
|
40460
|
+
obv,
|
|
40461
|
+
accDist,
|
|
40462
|
+
cmf,
|
|
40463
|
+
chaikinOscillator,
|
|
40464
|
+
eom,
|
|
40465
|
+
forceIndex,
|
|
40466
|
+
klinger,
|
|
40467
|
+
mfi,
|
|
40468
|
+
nvi,
|
|
40469
|
+
pvi,
|
|
40470
|
+
pvt,
|
|
40471
|
+
volumeOscillator,
|
|
40472
|
+
pvo,
|
|
40473
|
+
vfi,
|
|
40474
|
+
intradayIntensity
|
|
40475
|
+
];
|
|
40476
|
+
|
|
40477
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
40478
|
+
var DAY_MS2 = 864e5;
|
|
40479
|
+
function periodKey(anchor, time) {
|
|
40480
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
40481
|
+
if (anchor === "Month") {
|
|
40482
|
+
const d = new Date(time);
|
|
40483
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
40484
|
+
}
|
|
40485
|
+
return Math.floor(time / DAY_MS2);
|
|
40486
|
+
}
|
|
40487
|
+
var parabolicSar = {
|
|
40488
|
+
type: "parabolic-sar",
|
|
40489
|
+
title: "Parabolic SAR",
|
|
40490
|
+
shortTitle: "SAR",
|
|
40491
|
+
overlay: true,
|
|
40492
|
+
inputs: [
|
|
40493
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40494
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
40495
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
40496
|
+
],
|
|
40497
|
+
compute: (bars, inputs) => {
|
|
40498
|
+
const start = num3(inputs, "start", 0.02);
|
|
40499
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
40500
|
+
const max2 = num3(inputs, "maximum", 0.2);
|
|
40501
|
+
const n = bars.length;
|
|
40502
|
+
const values = new Array(n).fill(Number.NaN);
|
|
40503
|
+
const colors = new Array(n).fill(null);
|
|
40504
|
+
if (n >= 2) {
|
|
40505
|
+
let long = bars[1].close >= bars[0].close;
|
|
40506
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
40507
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
40508
|
+
let af = start;
|
|
40509
|
+
for (let i = 1; i < n; i++) {
|
|
40510
|
+
const b = bars[i];
|
|
40511
|
+
sar += af * (ep - sar);
|
|
40512
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
40513
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
40514
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
40515
|
+
if (reversed) {
|
|
40516
|
+
long = !long;
|
|
40517
|
+
sar = ep;
|
|
40518
|
+
ep = long ? b.high : b.low;
|
|
40519
|
+
af = start;
|
|
40520
|
+
} else if (long && b.high > ep) {
|
|
40521
|
+
ep = b.high;
|
|
40522
|
+
af = Math.min(max2, af + inc);
|
|
40523
|
+
} else if (!long && b.low < ep) {
|
|
40524
|
+
ep = b.low;
|
|
40525
|
+
af = Math.min(max2, af + inc);
|
|
40526
|
+
}
|
|
40527
|
+
values[i] = sar;
|
|
40528
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
40529
|
+
}
|
|
40530
|
+
}
|
|
40531
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
40532
|
+
}
|
|
40533
|
+
};
|
|
40534
|
+
var vwap = {
|
|
40535
|
+
type: "vwap",
|
|
40536
|
+
title: "Volume Weighted Average Price",
|
|
40537
|
+
shortTitle: "VWAP",
|
|
40538
|
+
overlay: true,
|
|
40539
|
+
inputs: [
|
|
40540
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
40541
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
40542
|
+
colorInput(INFO)
|
|
40543
|
+
],
|
|
40544
|
+
compute: (bars, inputs) => {
|
|
40545
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40546
|
+
const source = str3(inputs, "source", "HLC3");
|
|
40547
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
40548
|
+
let period = Number.NaN;
|
|
40549
|
+
let cumPV = 0;
|
|
40550
|
+
let cumV = 0;
|
|
40551
|
+
for (let i = 0; i < bars.length; i++) {
|
|
40552
|
+
const b = bars[i];
|
|
40553
|
+
const key = periodKey(anchor, b.time);
|
|
40554
|
+
if (key !== period) {
|
|
40555
|
+
period = key;
|
|
40556
|
+
cumPV = 0;
|
|
40557
|
+
cumV = 0;
|
|
40558
|
+
}
|
|
40559
|
+
const v = b.volume;
|
|
40560
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
40561
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
40562
|
+
cumPV += tp * v;
|
|
40563
|
+
cumV += v;
|
|
40564
|
+
}
|
|
40565
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
40566
|
+
}
|
|
40567
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
40568
|
+
}
|
|
40569
|
+
};
|
|
40570
|
+
var pivotPoints = {
|
|
40571
|
+
type: "pivot-points",
|
|
40572
|
+
title: "Pivot Points",
|
|
40573
|
+
shortTitle: "Pivots",
|
|
40574
|
+
overlay: true,
|
|
40575
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
40576
|
+
compute: (bars, inputs) => {
|
|
40577
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
40578
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
40579
|
+
for (const b of bars) {
|
|
40580
|
+
const key = periodKey(anchor, b.time);
|
|
40581
|
+
const a = aggs.get(key);
|
|
40582
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
40583
|
+
else {
|
|
40584
|
+
a.high = Math.max(a.high, b.high);
|
|
40585
|
+
a.low = Math.min(a.low, b.low);
|
|
40586
|
+
a.close = b.close;
|
|
40587
|
+
}
|
|
40588
|
+
}
|
|
40589
|
+
const n = bars.length;
|
|
40590
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
40591
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
40592
|
+
for (let i = 0; i < n; i++) {
|
|
40593
|
+
const prev3 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
40594
|
+
if (!prev3) continue;
|
|
40595
|
+
const p = (prev3.high + prev3.low + prev3.close) / 3;
|
|
40596
|
+
levels.p[i] = p;
|
|
40597
|
+
levels.r1[i] = 2 * p - prev3.low;
|
|
40598
|
+
levels.s1[i] = 2 * p - prev3.high;
|
|
40599
|
+
levels.r2[i] = p + (prev3.high - prev3.low);
|
|
40600
|
+
levels.s2[i] = p - (prev3.high - prev3.low);
|
|
40601
|
+
levels.r3[i] = prev3.high + 2 * (p - prev3.low);
|
|
40602
|
+
levels.s3[i] = prev3.low - 2 * (prev3.high - p);
|
|
40603
|
+
}
|
|
40604
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
40605
|
+
return {
|
|
40606
|
+
plots: [
|
|
40607
|
+
plot("p", "P", WARNING),
|
|
40608
|
+
plot("r1", "R1", BEARISH),
|
|
40609
|
+
plot("s1", "S1", BULLISH),
|
|
40610
|
+
plot("r2", "R2", BEARISH),
|
|
40611
|
+
plot("s2", "S2", BULLISH),
|
|
40612
|
+
plot("r3", "R3", BEARISH),
|
|
40613
|
+
plot("s3", "S3", BULLISH)
|
|
40614
|
+
]
|
|
40615
|
+
};
|
|
40616
|
+
}
|
|
40617
|
+
};
|
|
40618
|
+
var fiftyTwoWeek = {
|
|
40619
|
+
type: "52-week-high-low",
|
|
40620
|
+
title: "52 Week High/Low",
|
|
40621
|
+
shortTitle: "52W H/L",
|
|
40622
|
+
overlay: true,
|
|
40623
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
40624
|
+
compute: (bars, inputs) => {
|
|
40625
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
40626
|
+
const n = bars.length;
|
|
40627
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
40628
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
40629
|
+
const maxIdx = [];
|
|
40630
|
+
const minIdx = [];
|
|
40631
|
+
let from = 0;
|
|
40632
|
+
for (let i = 0; i < n; i++) {
|
|
40633
|
+
const b = bars[i];
|
|
40634
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
40635
|
+
maxIdx.push(i);
|
|
40636
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
40637
|
+
minIdx.push(i);
|
|
40638
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
40639
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
40640
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
40641
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
40642
|
+
lo[i] = bars[minIdx[0]].low;
|
|
40643
|
+
}
|
|
40644
|
+
return {
|
|
40645
|
+
plots: [
|
|
40646
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
40647
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
40648
|
+
]
|
|
40649
|
+
};
|
|
40650
|
+
}
|
|
40651
|
+
};
|
|
40652
|
+
var zigzag = {
|
|
40653
|
+
type: "zigzag",
|
|
40654
|
+
title: "ZigZag",
|
|
40655
|
+
overlay: true,
|
|
40656
|
+
inputs: [
|
|
40657
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
40658
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
40659
|
+
colorInput()
|
|
40660
|
+
],
|
|
40661
|
+
compute: (bars, inputs) => {
|
|
40662
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
40663
|
+
const depth = num3(inputs, "depth", 10);
|
|
40664
|
+
const pivots = [];
|
|
40665
|
+
if (bars.length > 1) {
|
|
40666
|
+
let up = bars[1].close >= bars[0].close;
|
|
40667
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
40668
|
+
for (let i = 1; i < bars.length; i++) {
|
|
40669
|
+
const b = bars[i];
|
|
40670
|
+
if (up) {
|
|
40671
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
40672
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40673
|
+
pivots.push(ext);
|
|
40674
|
+
up = false;
|
|
40675
|
+
ext = { i, price: b.low };
|
|
40676
|
+
}
|
|
40677
|
+
} else if (b.low <= ext.price) {
|
|
40678
|
+
ext = { i, price: b.low };
|
|
40679
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
40680
|
+
pivots.push(ext);
|
|
40681
|
+
up = true;
|
|
40682
|
+
ext = { i, price: b.high };
|
|
40683
|
+
}
|
|
40684
|
+
}
|
|
40685
|
+
pivots.push(ext);
|
|
40686
|
+
}
|
|
40687
|
+
return {
|
|
40688
|
+
plots: [],
|
|
40689
|
+
polylines: [
|
|
40690
|
+
{
|
|
40691
|
+
key: "zigzag",
|
|
40692
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
40693
|
+
curved: false,
|
|
40694
|
+
closed: false,
|
|
40695
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
40696
|
+
lineWidth: 2,
|
|
40697
|
+
lineStyle: "solid",
|
|
40698
|
+
arrowLeft: false,
|
|
40699
|
+
arrowRight: false
|
|
40700
|
+
}
|
|
40701
|
+
]
|
|
40702
|
+
};
|
|
40703
|
+
}
|
|
40704
|
+
};
|
|
40705
|
+
var williamsFractal = {
|
|
40706
|
+
type: "williams-fractal",
|
|
40707
|
+
title: "Williams Fractal",
|
|
40708
|
+
shortTitle: "Fractals",
|
|
40709
|
+
overlay: true,
|
|
40710
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
40711
|
+
compute: (bars, inputs) => {
|
|
40712
|
+
const p = num3(inputs, "periods", 2);
|
|
40713
|
+
const markers = [];
|
|
40714
|
+
const isExtreme = (i, pick, better) => {
|
|
40715
|
+
const v = pick(bars[i]);
|
|
40716
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
40717
|
+
if (k === i) continue;
|
|
40718
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
40719
|
+
}
|
|
40720
|
+
return true;
|
|
40721
|
+
};
|
|
40722
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
40723
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
40724
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
40725
|
+
}
|
|
40726
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
40727
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
40728
|
+
}
|
|
40729
|
+
}
|
|
40730
|
+
return { plots: [], markers };
|
|
40731
|
+
}
|
|
40732
|
+
};
|
|
40733
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
40734
|
+
|
|
40735
|
+
// src/core/native-indicators/classics/index.ts
|
|
40736
|
+
var classicSpecs = [
|
|
40737
|
+
...averageSpecs,
|
|
40738
|
+
...bandSpecs,
|
|
40739
|
+
...oscillatorSpecs,
|
|
40740
|
+
...trendSpecs,
|
|
40741
|
+
...volatilitySpecs,
|
|
40742
|
+
...volumeSpecs,
|
|
40743
|
+
...overlaySpecs
|
|
40744
|
+
];
|
|
40745
|
+
function registerClassicIndicators() {
|
|
40746
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
40747
|
+
}
|
|
40748
|
+
|
|
38419
40749
|
// src/Vela.ts
|
|
38420
40750
|
var Vela = class {
|
|
38421
40751
|
constructor(container, options = {}, deps = {}) {
|
|
38422
40752
|
registerBuiltinChartTypes();
|
|
38423
40753
|
registerVolume();
|
|
38424
40754
|
registerVpvr();
|
|
40755
|
+
registerClassicIndicators();
|
|
38425
40756
|
const element = resolveElement(container);
|
|
38426
40757
|
const theme = resolveTheme(options.theme);
|
|
38427
40758
|
let animZoom = true;
|
|
@@ -38496,25 +40827,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38496
40827
|
}
|
|
38497
40828
|
/**
|
|
38498
40829
|
* 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
|
-
*
|
|
40830
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
40831
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
40832
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
40833
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
38502
40834
|
*/
|
|
38503
40835
|
addNativeIndicator(type, options) {
|
|
38504
40836
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
38505
40837
|
}
|
|
38506
40838
|
/**
|
|
38507
40839
|
* 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
|
-
*
|
|
40840
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
40841
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
40842
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
40843
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
40844
|
+
* provider (a type may need data the symbol lacks).
|
|
38512
40845
|
*/
|
|
38513
40846
|
availableNativeIndicators() {
|
|
38514
40847
|
return this.orchestrator.availableNativeIndicators();
|
|
38515
40848
|
}
|
|
38516
40849
|
/**
|
|
38517
|
-
* The native-indicator types PRESENT on the chart right now
|
|
40850
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
40851
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
38518
40852
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
38519
40853
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
38520
40854
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -38881,12 +41215,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38881
41215
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
38882
41216
|
}
|
|
38883
41217
|
|
|
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
41218
|
// src/data/symbol-base.ts
|
|
38891
41219
|
function baseOf(d) {
|
|
38892
41220
|
const fromDesc = d.description?.split("/")[0]?.trim();
|
|
@@ -38988,9 +41316,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38988
41316
|
for (const b of bars) byTime.set(b.time, b);
|
|
38989
41317
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
38990
41318
|
}
|
|
38991
|
-
function aggregate(
|
|
41319
|
+
function aggregate(sub2, bucketMs) {
|
|
38992
41320
|
const buckets = /* @__PURE__ */ new Map();
|
|
38993
|
-
for (const b of
|
|
41321
|
+
for (const b of sub2) {
|
|
38994
41322
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
38995
41323
|
const cur = buckets.get(key);
|
|
38996
41324
|
if (!cur) {
|
|
@@ -39042,8 +41370,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39042
41370
|
}
|
|
39043
41371
|
const ratio = targetMin / subMin;
|
|
39044
41372
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39045
|
-
const
|
|
39046
|
-
const agg = aggregate(
|
|
41373
|
+
const sub2 = klinesToOHLCV(await this.fetchKlines(base, apiSymbol, MIN_TO_INTERVAL[subMin], subRange));
|
|
41374
|
+
const agg = aggregate(sub2, targetMin * 6e4);
|
|
39047
41375
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39048
41376
|
} catch (e) {
|
|
39049
41377
|
console.warn(`[vela] Binance: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39374,9 +41702,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39374
41702
|
for (const b of bars) byTime.set(b.time, b);
|
|
39375
41703
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39376
41704
|
}
|
|
39377
|
-
function aggregate2(
|
|
41705
|
+
function aggregate2(sub2, bucketMs) {
|
|
39378
41706
|
const buckets = /* @__PURE__ */ new Map();
|
|
39379
|
-
for (const b of
|
|
41707
|
+
for (const b of sub2) {
|
|
39380
41708
|
const key = Math.floor(b.time / bucketMs) * bucketMs;
|
|
39381
41709
|
const cur = buckets.get(key);
|
|
39382
41710
|
if (!cur) {
|
|
@@ -39427,8 +41755,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39427
41755
|
}
|
|
39428
41756
|
const ratio = targetMin / subMin;
|
|
39429
41757
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39430
|
-
const
|
|
39431
|
-
const agg = aggregate2(
|
|
41758
|
+
const sub2 = (await this.fetchCandles(coin, MIN_TO_INTERVAL2[subMin], subMin * 6e4, subRange)).map(candleToOHLCV);
|
|
41759
|
+
const agg = aggregate2(sub2, targetMin * 6e4);
|
|
39432
41760
|
return range.limit != null && agg.length > range.limit ? agg.slice(-range.limit) : agg;
|
|
39433
41761
|
} catch (e) {
|
|
39434
41762
|
console.warn(`[vela] Hyperliquid: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -39780,15 +42108,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39780
42108
|
for (const b of bars) byTime.set(b.time, b);
|
|
39781
42109
|
return [...byTime.values()].sort((a, b) => a.time - b.time);
|
|
39782
42110
|
}
|
|
39783
|
-
function aggregate3(
|
|
39784
|
-
return aggregateBy(
|
|
42111
|
+
function aggregate3(sub2, bucketMs) {
|
|
42112
|
+
return aggregateBy(sub2, (t) => Math.floor(t / bucketMs) * bucketMs);
|
|
39785
42113
|
}
|
|
39786
|
-
function aggregateCalendar(
|
|
39787
|
-
return aggregateBy(
|
|
42114
|
+
function aggregateCalendar(sub2, unit) {
|
|
42115
|
+
return aggregateBy(sub2, unit === "W" ? weekStartUTC : monthStartUTC);
|
|
39788
42116
|
}
|
|
39789
|
-
function aggregateBy(
|
|
42117
|
+
function aggregateBy(sub2, keyMs) {
|
|
39790
42118
|
const buckets = /* @__PURE__ */ new Map();
|
|
39791
|
-
for (const b of
|
|
42119
|
+
for (const b of sub2) {
|
|
39792
42120
|
const key = keyMs(b.time);
|
|
39793
42121
|
const cur = buckets.get(key);
|
|
39794
42122
|
if (!cur) {
|
|
@@ -39842,8 +42170,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39842
42170
|
if (tf === "W" || tf === "M") {
|
|
39843
42171
|
const span = tf === "W" ? 7 * MS_PER_DAY : 31 * MS_PER_DAY;
|
|
39844
42172
|
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(
|
|
42173
|
+
const sub3 = await this.fetchCandles(product, 86400, subRange2);
|
|
42174
|
+
return clampLimit(aggregateCalendar(sub3, tf), range.limit);
|
|
39847
42175
|
}
|
|
39848
42176
|
const targetMin = /^\d+$/.test(tf) ? parseInt(tf, 10) : null;
|
|
39849
42177
|
const subMin = targetMin != null ? selectSubTf3(targetMin) : null;
|
|
@@ -39853,8 +42181,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39853
42181
|
}
|
|
39854
42182
|
const ratio = targetMin / subMin;
|
|
39855
42183
|
const subRange = { ...range, limit: range.limit != null ? range.limit * ratio + ratio : void 0 };
|
|
39856
|
-
const
|
|
39857
|
-
return clampLimit(aggregate3(
|
|
42184
|
+
const sub2 = await this.fetchCandles(product, MIN_TO_GRAN[subMin], subRange);
|
|
42185
|
+
return clampLimit(aggregate3(sub2, targetMin * 6e4), range.limit);
|
|
39858
42186
|
} catch (e) {
|
|
39859
42187
|
console.warn(`[vela] Coinbase: failed to fetch ${ticker} ${timeframe} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
39860
42188
|
return [];
|