@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/index.cjs
CHANGED
|
@@ -135,7 +135,7 @@ var LiveSession = class {
|
|
|
135
135
|
|
|
136
136
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
137
137
|
var IndicatorHandleImpl = class {
|
|
138
|
-
constructor(id, title, controller, source) {
|
|
138
|
+
constructor(id, title, controller, source, nativeType) {
|
|
139
139
|
this.controller = controller;
|
|
140
140
|
this.schema = [];
|
|
141
141
|
this.propsSchema = [];
|
|
@@ -144,6 +144,7 @@ var IndicatorHandleImpl = class {
|
|
|
144
144
|
this.id = id;
|
|
145
145
|
this.title = title;
|
|
146
146
|
if (source !== void 0) this.source = source;
|
|
147
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
147
148
|
}
|
|
148
149
|
get inputs() {
|
|
149
150
|
return this.schema;
|
|
@@ -234,24 +235,24 @@ function summarizeModel(model) {
|
|
|
234
235
|
}
|
|
235
236
|
function inspectModels(models) {
|
|
236
237
|
const indicators = models.map(summarizeModel);
|
|
237
|
-
const
|
|
238
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
238
239
|
return {
|
|
239
240
|
indicators,
|
|
240
241
|
totals: {
|
|
241
242
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
242
|
-
series:
|
|
243
|
-
fills:
|
|
244
|
-
backgrounds:
|
|
245
|
-
priceLines:
|
|
246
|
-
lines:
|
|
247
|
-
boxes:
|
|
248
|
-
labels:
|
|
249
|
-
polylines:
|
|
250
|
-
linefills:
|
|
251
|
-
tables:
|
|
252
|
-
barColors:
|
|
253
|
-
trades:
|
|
254
|
-
forcedOverlay:
|
|
243
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
244
|
+
fills: sum2((s) => s.fills),
|
|
245
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
246
|
+
priceLines: sum2((s) => s.priceLines),
|
|
247
|
+
lines: sum2((s) => s.lines),
|
|
248
|
+
boxes: sum2((s) => s.boxes),
|
|
249
|
+
labels: sum2((s) => s.labels),
|
|
250
|
+
polylines: sum2((s) => s.polylines),
|
|
251
|
+
linefills: sum2((s) => s.linefills),
|
|
252
|
+
tables: sum2((s) => s.tables),
|
|
253
|
+
barColors: sum2((s) => s.barColors),
|
|
254
|
+
trades: sum2((s) => s.trades),
|
|
255
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
255
256
|
}
|
|
256
257
|
};
|
|
257
258
|
}
|
|
@@ -6731,8 +6732,8 @@ var DrawingController = class {
|
|
|
6731
6732
|
this.port?.setExternalGhost?.(doc);
|
|
6732
6733
|
}
|
|
6733
6734
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
6734
|
-
setToolShortcuts(
|
|
6735
|
-
this.port?.setToolShortcuts?.(
|
|
6735
|
+
setToolShortcuts(map2) {
|
|
6736
|
+
this.port?.setToolShortcuts?.(map2);
|
|
6736
6737
|
}
|
|
6737
6738
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6738
6739
|
add(type, init = {}) {
|
|
@@ -8456,16 +8457,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8456
8457
|
/**
|
|
8457
8458
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
8458
8459
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
8459
|
-
* INSTANCE per type
|
|
8460
|
-
*
|
|
8460
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
8461
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
8462
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
8463
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
8461
8464
|
*/
|
|
8462
8465
|
addNativeIndicator(type, options = {}) {
|
|
8463
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8464
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
8465
8466
|
const descriptor = getNativeIndicator(type);
|
|
8467
|
+
if (!descriptor?.multiInstance) {
|
|
8468
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
8469
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
8470
|
+
}
|
|
8466
8471
|
const id = this.registry.nextId("native");
|
|
8467
8472
|
const title = descriptor?.title ?? type;
|
|
8468
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
8473
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
8469
8474
|
this.handles.set(id, handle);
|
|
8470
8475
|
if (!descriptor) {
|
|
8471
8476
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -8477,16 +8482,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8477
8482
|
void this.startNativeIndicator(id, handle);
|
|
8478
8483
|
return handle;
|
|
8479
8484
|
}
|
|
8480
|
-
/**
|
|
8481
|
-
*
|
|
8482
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
8483
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
8484
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
8485
|
-
*/
|
|
8486
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
8485
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
8486
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
8487
8487
|
presentNativeIndicators() {
|
|
8488
8488
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
8489
8489
|
}
|
|
8490
|
+
/**
|
|
8491
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
8492
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
8493
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
8494
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
8495
|
+
* probe the provider for a required capability.
|
|
8496
|
+
*/
|
|
8490
8497
|
async availableNativeIndicators() {
|
|
8491
8498
|
const symbol = this.qualifiedSymbol();
|
|
8492
8499
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -8496,7 +8503,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8496
8503
|
title: d.title,
|
|
8497
8504
|
supported: await this.isNativeSupported(d, symbol),
|
|
8498
8505
|
present: present.has(d.type),
|
|
8499
|
-
beta: d.beta
|
|
8506
|
+
beta: d.beta,
|
|
8507
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
8500
8508
|
}))
|
|
8501
8509
|
);
|
|
8502
8510
|
}
|
|
@@ -9780,7 +9788,7 @@ function attachForming(closed, forming) {
|
|
|
9780
9788
|
|
|
9781
9789
|
// src/data/MultiProviderFeed.ts
|
|
9782
9790
|
function intervalMs(timeframe) {
|
|
9783
|
-
const
|
|
9791
|
+
const map2 = {
|
|
9784
9792
|
"1m": 6e4,
|
|
9785
9793
|
"3m": 18e4,
|
|
9786
9794
|
"5m": 3e5,
|
|
@@ -9802,7 +9810,7 @@ function intervalMs(timeframe) {
|
|
|
9802
9810
|
W: 6048e5,
|
|
9803
9811
|
M: 2592e6
|
|
9804
9812
|
};
|
|
9805
|
-
return
|
|
9813
|
+
return map2[timeframe] ?? 36e5;
|
|
9806
9814
|
}
|
|
9807
9815
|
var MultiProviderFeed = class {
|
|
9808
9816
|
constructor(store = sharedBarStore) {
|
|
@@ -10189,8 +10197,8 @@ var DrawingsControl = class {
|
|
|
10189
10197
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
10190
10198
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
10191
10199
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
10192
|
-
setToolShortcuts(
|
|
10193
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
10200
|
+
setToolShortcuts(map2) {
|
|
10201
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
10194
10202
|
return this;
|
|
10195
10203
|
}
|
|
10196
10204
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -10751,19 +10759,19 @@ var Surface = class _Surface {
|
|
|
10751
10759
|
}
|
|
10752
10760
|
setItems(items) {
|
|
10753
10761
|
this.items = items;
|
|
10754
|
-
for (const
|
|
10762
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10755
10763
|
this.subs.clear();
|
|
10756
10764
|
this.build();
|
|
10757
10765
|
const api = this.api;
|
|
10758
|
-
for (const
|
|
10759
|
-
api.setChild(
|
|
10760
|
-
|
|
10766
|
+
for (const sub2 of this.subs.values()) {
|
|
10767
|
+
api.setChild(sub2.handle.service);
|
|
10768
|
+
sub2.api.setParent(this.handle.service);
|
|
10761
10769
|
}
|
|
10762
10770
|
this.handle.flush();
|
|
10763
10771
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
10764
10772
|
}
|
|
10765
10773
|
destroy() {
|
|
10766
|
-
for (const
|
|
10774
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
10767
10775
|
this.subs.clear();
|
|
10768
10776
|
this.handle.stop();
|
|
10769
10777
|
this.positioner.remove();
|
|
@@ -10777,9 +10785,9 @@ var Surface = class _Surface {
|
|
|
10777
10785
|
for (const li of this.list.children) {
|
|
10778
10786
|
const id = li.dataset.veiId;
|
|
10779
10787
|
if (!id) continue;
|
|
10780
|
-
const
|
|
10781
|
-
if (
|
|
10782
|
-
vanilla.spreadProps(li, api.getTriggerItemProps(
|
|
10788
|
+
const sub2 = this.subs.get(id);
|
|
10789
|
+
if (sub2) {
|
|
10790
|
+
vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
10783
10791
|
continue;
|
|
10784
10792
|
}
|
|
10785
10793
|
const item = this.byId(id);
|
|
@@ -10856,7 +10864,7 @@ var Surface = class _Surface {
|
|
|
10856
10864
|
const arrow = iconEl("chevron-right", doc);
|
|
10857
10865
|
arrow.classList.add("vela-menu-arrow");
|
|
10858
10866
|
li.appendChild(arrow);
|
|
10859
|
-
const
|
|
10867
|
+
const sub2 = new _Surface(doc, {
|
|
10860
10868
|
host: this.host,
|
|
10861
10869
|
placement: "right-start",
|
|
10862
10870
|
onSelect: this.onSelect,
|
|
@@ -10864,8 +10872,8 @@ var Surface = class _Surface {
|
|
|
10864
10872
|
checkmarks: this.checkmarks,
|
|
10865
10873
|
id: `${this.mid}--${item.id}`
|
|
10866
10874
|
});
|
|
10867
|
-
|
|
10868
|
-
this.subs.set(item.id,
|
|
10875
|
+
sub2.setItems(item.submenu ?? []);
|
|
10876
|
+
this.subs.set(item.id, sub2);
|
|
10869
10877
|
}
|
|
10870
10878
|
this.list.appendChild(li);
|
|
10871
10879
|
}
|
|
@@ -12631,8 +12639,21 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12631
12639
|
};
|
|
12632
12640
|
const recentRow = document.createElement("div");
|
|
12633
12641
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
12642
|
+
const recentSwatches = document.createElement("div");
|
|
12643
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
12644
|
+
const add = document.createElement("label");
|
|
12645
|
+
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;`;
|
|
12646
|
+
add.textContent = "+";
|
|
12647
|
+
const customInput = document.createElement("input");
|
|
12648
|
+
customInput.type = "color";
|
|
12649
|
+
customInput.value = curHex;
|
|
12650
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12651
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
12652
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
12653
|
+
add.appendChild(customInput);
|
|
12654
|
+
recentRow.append(recentSwatches, add);
|
|
12634
12655
|
const renderRecents = () => {
|
|
12635
|
-
|
|
12656
|
+
recentSwatches.replaceChildren();
|
|
12636
12657
|
for (const c of recents) {
|
|
12637
12658
|
const sw = document.createElement("button");
|
|
12638
12659
|
sw.type = "button";
|
|
@@ -12641,18 +12662,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12641
12662
|
e.stopPropagation();
|
|
12642
12663
|
pickHex(c);
|
|
12643
12664
|
});
|
|
12644
|
-
|
|
12665
|
+
recentSwatches.appendChild(sw);
|
|
12645
12666
|
}
|
|
12646
|
-
const add = document.createElement("label");
|
|
12647
|
-
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;`;
|
|
12648
|
-
add.textContent = "+";
|
|
12649
|
-
const input = document.createElement("input");
|
|
12650
|
-
input.type = "color";
|
|
12651
|
-
input.value = curHex;
|
|
12652
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
12653
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
12654
|
-
add.appendChild(input);
|
|
12655
|
-
recentRow.appendChild(add);
|
|
12656
12667
|
};
|
|
12657
12668
|
const opLabel = document.createElement("div");
|
|
12658
12669
|
opLabel.textContent = "Opacity";
|
|
@@ -12692,14 +12703,18 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
12692
12703
|
function emit() {
|
|
12693
12704
|
onChange(combineColor(curHex, curAlpha));
|
|
12694
12705
|
}
|
|
12695
|
-
function
|
|
12706
|
+
function previewHex(hex) {
|
|
12696
12707
|
curHex = splitColor(hex).hex6;
|
|
12697
|
-
addRecent(curHex);
|
|
12698
12708
|
paintSelection();
|
|
12699
12709
|
paintOpacity();
|
|
12700
|
-
if (custom) renderRecents();
|
|
12701
12710
|
emit();
|
|
12702
12711
|
}
|
|
12712
|
+
function pickHex(hex, custom = false) {
|
|
12713
|
+
previewHex(hex);
|
|
12714
|
+
addRecent(curHex);
|
|
12715
|
+
customInput.value = curHex;
|
|
12716
|
+
if (custom) renderRecents();
|
|
12717
|
+
}
|
|
12703
12718
|
renderRecents();
|
|
12704
12719
|
paintSelection();
|
|
12705
12720
|
paintOpacity();
|
|
@@ -14605,8 +14620,8 @@ var InputsUI = class {
|
|
|
14605
14620
|
lg.style.top = `${top}px`;
|
|
14606
14621
|
}
|
|
14607
14622
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
14608
|
-
setCollapsedPanes(
|
|
14609
|
-
this.paneCollapse =
|
|
14623
|
+
setCollapsedPanes(map2) {
|
|
14624
|
+
this.paneCollapse = map2;
|
|
14610
14625
|
}
|
|
14611
14626
|
/**
|
|
14612
14627
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -15569,9 +15584,9 @@ function factoryResetConfig(factory) {
|
|
|
15569
15584
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
15570
15585
|
addRows(inst.rows);
|
|
15571
15586
|
}
|
|
15572
|
-
for (const
|
|
15573
|
-
if (
|
|
15574
|
-
addRows(
|
|
15587
|
+
for (const sub2 of section.subsections ?? []) {
|
|
15588
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
15589
|
+
addRows(sub2.rows);
|
|
15575
15590
|
}
|
|
15576
15591
|
if (!section.instances) addRows(section.rows);
|
|
15577
15592
|
bag[t.id] = defaults2;
|
|
@@ -16900,11 +16915,11 @@ function mergeBarColors(indicators) {
|
|
|
16900
16915
|
}
|
|
16901
16916
|
}
|
|
16902
16917
|
if (!any) return EMPTY_BARCOLORS;
|
|
16903
|
-
const
|
|
16918
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
16904
16919
|
for (const m of indicators.values()) {
|
|
16905
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
16920
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
16906
16921
|
}
|
|
16907
|
-
return
|
|
16922
|
+
return map2;
|
|
16908
16923
|
}
|
|
16909
16924
|
|
|
16910
16925
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -17201,9 +17216,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
17201
17216
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
17202
17217
|
return rightOffset + deltaX / barSpacing;
|
|
17203
17218
|
}
|
|
17204
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
17219
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
17205
17220
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
17206
|
-
if (
|
|
17221
|
+
if (shift2) return deltaY;
|
|
17207
17222
|
return null;
|
|
17208
17223
|
}
|
|
17209
17224
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -18825,11 +18840,11 @@ function mergeBarColors2(indicators) {
|
|
|
18825
18840
|
}
|
|
18826
18841
|
}
|
|
18827
18842
|
if (!any) return EMPTY_BARCOLORS2;
|
|
18828
|
-
const
|
|
18843
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
18829
18844
|
for (const m of indicators.values()) {
|
|
18830
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
18845
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
18831
18846
|
}
|
|
18832
|
-
return
|
|
18847
|
+
return map2;
|
|
18833
18848
|
}
|
|
18834
18849
|
|
|
18835
18850
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -20545,9 +20560,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
20545
20560
|
};
|
|
20546
20561
|
if (section.rows) addRows(section.rows);
|
|
20547
20562
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
20548
|
-
for (const
|
|
20549
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
20550
|
-
addRows(
|
|
20563
|
+
for (const sub2 of section.subsections ?? []) {
|
|
20564
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
20565
|
+
addRows(sub2.rows);
|
|
20551
20566
|
}
|
|
20552
20567
|
}
|
|
20553
20568
|
for (const hs of hostSections) {
|
|
@@ -21106,14 +21121,14 @@ var SettingsDialog = class {
|
|
|
21106
21121
|
};
|
|
21107
21122
|
if (section.rows) seed(section.rows);
|
|
21108
21123
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
21109
|
-
for (const
|
|
21124
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
21110
21125
|
for (const inst of section.instances ?? []) {
|
|
21111
21126
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
21112
21127
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
21113
21128
|
}
|
|
21114
|
-
for (const
|
|
21115
|
-
if (!
|
|
21116
|
-
bag[
|
|
21129
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21130
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
21131
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
21117
21132
|
}
|
|
21118
21133
|
const refreshers = [];
|
|
21119
21134
|
const put = (key, v) => {
|
|
@@ -21130,16 +21145,16 @@ var SettingsDialog = class {
|
|
|
21130
21145
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
21131
21146
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
21132
21147
|
}
|
|
21133
|
-
for (const
|
|
21134
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
21135
|
-
const rows = filterHiddenRows(
|
|
21148
|
+
for (const sub2 of section.subsections ?? []) {
|
|
21149
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
21150
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
21136
21151
|
if (rows.length === 0) continue;
|
|
21137
|
-
const subMarker = this.section(
|
|
21152
|
+
const subMarker = this.section(sub2.title);
|
|
21138
21153
|
subMarker.dataset.sdStyle = typeId;
|
|
21139
21154
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
21140
21155
|
subMarker.dataset.sdSub = "1";
|
|
21141
21156
|
body.append(subMarker);
|
|
21142
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
21157
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
21143
21158
|
}
|
|
21144
21159
|
for (const r of refreshers) r();
|
|
21145
21160
|
}
|
|
@@ -23247,11 +23262,11 @@ var DrawingInteraction = class {
|
|
|
23247
23262
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
23248
23263
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
23249
23264
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
23250
|
-
down(x, y, mode = "off",
|
|
23265
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
23251
23266
|
const proj = this.deps.projector();
|
|
23252
23267
|
if (this.state.kind === "placing") {
|
|
23253
23268
|
if (this.state.draft.placementMode() !== "click") return;
|
|
23254
|
-
const pivot =
|
|
23269
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23255
23270
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23256
23271
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
23257
23272
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -23270,7 +23285,7 @@ var DrawingInteraction = class {
|
|
|
23270
23285
|
}
|
|
23271
23286
|
const hit = this.hitAt(x, y);
|
|
23272
23287
|
if (hit) {
|
|
23273
|
-
if (
|
|
23288
|
+
if (shift2) {
|
|
23274
23289
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
23275
23290
|
return;
|
|
23276
23291
|
}
|
|
@@ -23280,7 +23295,7 @@ var DrawingInteraction = class {
|
|
|
23280
23295
|
}
|
|
23281
23296
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
23282
23297
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
23283
|
-
move(x, y, mode = "off",
|
|
23298
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
23284
23299
|
if (this.state.kind === "idle") {
|
|
23285
23300
|
const tool = this.deps.activeTool();
|
|
23286
23301
|
const had = this.snapAt != null;
|
|
@@ -23294,7 +23309,7 @@ var DrawingInteraction = class {
|
|
|
23294
23309
|
this.captureFreehand(x, y);
|
|
23295
23310
|
return;
|
|
23296
23311
|
}
|
|
23297
|
-
const pivot =
|
|
23312
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
23298
23313
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23299
23314
|
this.deps.changed();
|
|
23300
23315
|
return;
|
|
@@ -23307,7 +23322,7 @@ var DrawingInteraction = class {
|
|
|
23307
23322
|
if (this.state.handle >= 0) {
|
|
23308
23323
|
const a = d.anchors[this.state.handle];
|
|
23309
23324
|
if (a) {
|
|
23310
|
-
const pivot =
|
|
23325
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
23311
23326
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
23312
23327
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
23313
23328
|
}
|
|
@@ -24005,21 +24020,21 @@ var DrawingSettingsPopup = class {
|
|
|
24005
24020
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
24006
24021
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
24007
24022
|
}
|
|
24008
|
-
const
|
|
24009
|
-
if (paths.has("vwap.midColor") &&
|
|
24023
|
+
const vwap2 = drawing.vwap;
|
|
24024
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
24010
24025
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
24011
24026
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
24012
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
24013
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
24027
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
24028
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
24014
24029
|
bar.appendChild(
|
|
24015
|
-
this.dropdown("Band multiplier", MULTS,
|
|
24030
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
24016
24031
|
label: (v) => `${v}\u03C3`,
|
|
24017
24032
|
labelInTrigger: true
|
|
24018
24033
|
})
|
|
24019
24034
|
);
|
|
24020
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
24021
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
24022
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
24035
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
24036
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
24037
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
24023
24038
|
}
|
|
24024
24039
|
if (paths.has("maxCurvature")) {
|
|
24025
24040
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -24765,8 +24780,8 @@ var DrawingToolbar = class {
|
|
|
24765
24780
|
}
|
|
24766
24781
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
24767
24782
|
* right edge of the flyout rows, beside the favorite star. */
|
|
24768
|
-
setShortcuts(
|
|
24769
|
-
this.shortcuts = new Map(Object.entries(
|
|
24783
|
+
setShortcuts(map2) {
|
|
24784
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
24770
24785
|
}
|
|
24771
24786
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
24772
24787
|
paintStar(el, type) {
|
|
@@ -25668,8 +25683,8 @@ var UserDrawingController = class {
|
|
|
25668
25683
|
this.toolbar.setFavorites(types);
|
|
25669
25684
|
}
|
|
25670
25685
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
25671
|
-
setToolShortcuts(
|
|
25672
|
-
this.toolbar.setShortcuts(
|
|
25686
|
+
setToolShortcuts(map2) {
|
|
25687
|
+
this.toolbar.setShortcuts(map2);
|
|
25673
25688
|
}
|
|
25674
25689
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
25675
25690
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -25787,7 +25802,7 @@ var UserDrawingController = class {
|
|
|
25787
25802
|
this.render();
|
|
25788
25803
|
}
|
|
25789
25804
|
}
|
|
25790
|
-
pointerDown(x, y, snap = "off",
|
|
25805
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
25791
25806
|
if (this.eraserMode) {
|
|
25792
25807
|
this.erasing = true;
|
|
25793
25808
|
this.deleteAt(x, y);
|
|
@@ -25807,9 +25822,9 @@ var UserDrawingController = class {
|
|
|
25807
25822
|
return;
|
|
25808
25823
|
}
|
|
25809
25824
|
}
|
|
25810
|
-
this.interaction.down(x, y, snap,
|
|
25825
|
+
this.interaction.down(x, y, snap, shift2);
|
|
25811
25826
|
}
|
|
25812
|
-
pointerMove(x, y, snap = "off",
|
|
25827
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
25813
25828
|
if (this.eraserMode) {
|
|
25814
25829
|
if (this.erasing) this.deleteAt(x, y);
|
|
25815
25830
|
return;
|
|
@@ -25820,7 +25835,7 @@ var UserDrawingController = class {
|
|
|
25820
25835
|
this.render();
|
|
25821
25836
|
return;
|
|
25822
25837
|
}
|
|
25823
|
-
this.interaction.move(x, y, snap,
|
|
25838
|
+
this.interaction.move(x, y, snap, shift2);
|
|
25824
25839
|
this.updateHover(x, y);
|
|
25825
25840
|
}
|
|
25826
25841
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -26497,14 +26512,14 @@ function layoutTable(ctx, t, args) {
|
|
|
26497
26512
|
}
|
|
26498
26513
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
26499
26514
|
if (box.cs > 1) {
|
|
26500
|
-
let
|
|
26501
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
26502
|
-
if (w2 >
|
|
26515
|
+
let sum2 = 0;
|
|
26516
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
26517
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
26503
26518
|
}
|
|
26504
26519
|
if (box.rs > 1) {
|
|
26505
|
-
let
|
|
26506
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
26507
|
-
if (h2 >
|
|
26520
|
+
let sum2 = 0;
|
|
26521
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
26522
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
26508
26523
|
}
|
|
26509
26524
|
}
|
|
26510
26525
|
let w = 0;
|
|
@@ -27227,24 +27242,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
27227
27242
|
drawSvgShapes(el, alpha);
|
|
27228
27243
|
continue;
|
|
27229
27244
|
}
|
|
27230
|
-
const
|
|
27245
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
27231
27246
|
let path = null;
|
|
27232
27247
|
if (tag === "path") {
|
|
27233
27248
|
const d = el.getAttribute("d");
|
|
27234
27249
|
if (d) path = new Path2D(d);
|
|
27235
27250
|
} else if (tag === "circle") {
|
|
27236
27251
|
path = new Path2D();
|
|
27237
|
-
path.arc(
|
|
27252
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
27238
27253
|
} else if (tag === "ellipse") {
|
|
27239
27254
|
path = new Path2D();
|
|
27240
|
-
path.ellipse(
|
|
27255
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
27241
27256
|
} else if (tag === "rect") {
|
|
27242
27257
|
path = new Path2D();
|
|
27243
|
-
path.rect(
|
|
27258
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
27244
27259
|
} else if (tag === "line") {
|
|
27245
27260
|
path = new Path2D();
|
|
27246
|
-
path.moveTo(
|
|
27247
|
-
path.lineTo(
|
|
27261
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
27262
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
27248
27263
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
27249
27264
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
27250
27265
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -28661,8 +28676,8 @@ var NativeRenderer = class {
|
|
|
28661
28676
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
28662
28677
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
28663
28678
|
drawingsSnapMode: () => this.snapMode,
|
|
28664
|
-
drawingsPointerDown: (x, y, snap,
|
|
28665
|
-
drawingsPointerMove: (x, y, snap,
|
|
28679
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
28680
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
28666
28681
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
28667
28682
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
28668
28683
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -28979,18 +28994,18 @@ var NativeRenderer = class {
|
|
|
28979
28994
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
28980
28995
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
28981
28996
|
const newHeadTime = this.bars[0].time;
|
|
28982
|
-
let
|
|
28997
|
+
let shift2 = 0;
|
|
28983
28998
|
if (newHeadTime !== prevHeadTime) {
|
|
28984
28999
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
28985
|
-
if (prepended > 0)
|
|
29000
|
+
if (prepended > 0) shift2 = prepended;
|
|
28986
29001
|
else {
|
|
28987
29002
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
28988
|
-
if (trimmed > 0)
|
|
29003
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
28989
29004
|
}
|
|
28990
29005
|
}
|
|
28991
|
-
if (
|
|
28992
|
-
this.zoomAnchorLogical +=
|
|
28993
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
29006
|
+
if (shift2 !== 0) {
|
|
29007
|
+
this.zoomAnchorLogical += shift2;
|
|
29008
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
28994
29009
|
}
|
|
28995
29010
|
}
|
|
28996
29011
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -30596,15 +30611,15 @@ var NativeRenderer = class {
|
|
|
30596
30611
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
30597
30612
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
30598
30613
|
collapsedMasterMap() {
|
|
30599
|
-
const
|
|
30614
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
30600
30615
|
for (const pane of this.scene.panes.values()) {
|
|
30601
30616
|
if (!pane.collapsed) continue;
|
|
30602
30617
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
30603
30618
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
30604
30619
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
30605
|
-
|
|
30620
|
+
map2.set(pane.id, master?.id ?? null);
|
|
30606
30621
|
}
|
|
30607
|
-
return
|
|
30622
|
+
return map2;
|
|
30608
30623
|
}
|
|
30609
30624
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
30610
30625
|
setToolbarGutter(px) {
|
|
@@ -31000,220 +31015,2539 @@ function registerVpvr() {
|
|
|
31000
31015
|
registerNativeIndicator(vpvrDescriptor);
|
|
31001
31016
|
}
|
|
31002
31017
|
|
|
31003
|
-
// src/
|
|
31004
|
-
|
|
31005
|
-
|
|
31006
|
-
|
|
31007
|
-
|
|
31008
|
-
|
|
31009
|
-
|
|
31010
|
-
|
|
31011
|
-
|
|
31012
|
-
|
|
31013
|
-
|
|
31014
|
-
|
|
31015
|
-
|
|
31016
|
-
|
|
31017
|
-
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
|
|
31026
|
-
glow: options.glow ?? 0,
|
|
31027
|
-
upColor: options.upColor ?? BULLISH,
|
|
31028
|
-
downColor: options.downColor ?? BEARISH,
|
|
31029
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
31030
|
-
};
|
|
31031
|
-
const RendererClass = options.renderer ?? NativeRenderer;
|
|
31032
|
-
if (typeof RendererClass !== "function") {
|
|
31033
|
-
throw new Error(
|
|
31034
|
-
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
31035
|
-
);
|
|
31036
|
-
}
|
|
31037
|
-
const renderer = deps.renderer ?? new RendererClass(display);
|
|
31038
|
-
const engines = deps.engines ?? [];
|
|
31039
|
-
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
31040
|
-
const config = {
|
|
31041
|
-
market: {
|
|
31042
|
-
symbol: options.symbol,
|
|
31043
|
-
timeframe: options.timeframe,
|
|
31044
|
-
bars: options.bars,
|
|
31045
|
-
session: options.session,
|
|
31046
|
-
visibleRange: options.visibleRange,
|
|
31047
|
-
data: options.data
|
|
31048
|
-
},
|
|
31049
|
-
live: options.live ?? false,
|
|
31050
|
-
theme,
|
|
31051
|
-
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
31052
|
-
drawings: options.drawings,
|
|
31053
|
-
volume: options.volume ?? true
|
|
31054
|
-
// the volume indicator is on by default
|
|
31055
|
-
};
|
|
31056
|
-
this.rendererControl = new RendererControl(renderer);
|
|
31057
|
-
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
31058
|
-
this.dataControl = new DataControl(feed);
|
|
31059
|
-
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
31060
|
-
const defaults2 = rendererDefaults();
|
|
31061
|
-
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
31062
|
-
this.panesControl = new PanesControl(this.orchestrator);
|
|
31063
|
-
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
31018
|
+
// src/core/model/identity.ts
|
|
31019
|
+
function stableSeriesId(parts) {
|
|
31020
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31021
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31022
|
+
}
|
|
31023
|
+
|
|
31024
|
+
// src/core/native-indicators/classics/define.ts
|
|
31025
|
+
function num3(inputs, key, fallback) {
|
|
31026
|
+
const v = inputs[key];
|
|
31027
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
31028
|
+
}
|
|
31029
|
+
function str3(inputs, key, fallback) {
|
|
31030
|
+
const v = inputs[key];
|
|
31031
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
31032
|
+
}
|
|
31033
|
+
function toPoints(bars, values, colors) {
|
|
31034
|
+
const pts = new Array(bars.length);
|
|
31035
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31036
|
+
const v = values[i];
|
|
31037
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
31038
|
+
const c = colors?.[i];
|
|
31039
|
+
if (c != null) p.color = c;
|
|
31040
|
+
pts[i] = p;
|
|
31064
31041
|
}
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
* language replaces it.
|
|
31072
|
-
*/
|
|
31073
|
-
registerEngine(language, engine) {
|
|
31074
|
-
this.orchestrator.registerEngine(language, engine);
|
|
31075
|
-
return this;
|
|
31042
|
+
return pts;
|
|
31043
|
+
}
|
|
31044
|
+
var ClassicIndicator = class {
|
|
31045
|
+
constructor(spec) {
|
|
31046
|
+
this.spec = spec;
|
|
31047
|
+
this.inputs = {};
|
|
31076
31048
|
}
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31049
|
+
start(ctx, inputs) {
|
|
31050
|
+
this.ctx = ctx;
|
|
31051
|
+
this.inputs = inputs;
|
|
31052
|
+
this.recompute();
|
|
31080
31053
|
}
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide, remove,
|
|
31084
|
-
* events) and is single-instance per type (a second call returns the existing handle). Returns
|
|
31085
|
-
* a fail-soft handle for an unregistered type. Native renderer only.
|
|
31086
|
-
*/
|
|
31087
|
-
addNativeIndicator(type, options) {
|
|
31088
|
-
return this.orchestrator.addNativeIndicator(type, options);
|
|
31054
|
+
onBars() {
|
|
31055
|
+
this.recompute();
|
|
31089
31056
|
}
|
|
31090
|
-
|
|
31091
|
-
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
31092
|
-
* `type`, `title`, whether it `supported`s the current symbol, and whether it's already `present`
|
|
31093
|
-
* (native indicators are single-instance per type, so a second `addNativeIndicator` is a no-op).
|
|
31094
|
-
* Lets a host "add indicator" UI list them, gate unsupported ones, and avoid duplicates. Async
|
|
31095
|
-
* because support may probe the provider (a type may need data the symbol lacks).
|
|
31096
|
-
*/
|
|
31097
|
-
availableNativeIndicators() {
|
|
31098
|
-
return this.orchestrator.availableNativeIndicators();
|
|
31057
|
+
onViewport() {
|
|
31099
31058
|
}
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
31104
|
-
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
31105
|
-
*/
|
|
31106
|
-
presentNativeIndicators() {
|
|
31107
|
-
return this.orchestrator.presentNativeIndicators();
|
|
31059
|
+
setInputs(inputs) {
|
|
31060
|
+
this.inputs = inputs;
|
|
31061
|
+
this.recompute();
|
|
31108
31062
|
}
|
|
31109
|
-
/**
|
|
31110
|
-
|
|
31111
|
-
indicators() {
|
|
31112
|
-
return this.orchestrator.listIndicators();
|
|
31063
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
31064
|
+
suspend() {
|
|
31113
31065
|
}
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
* host editors and consoles. Resolves `{ ok: true, handle }` after the first
|
|
31117
|
-
* successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
|
|
31118
|
-
* in which case the failed indicator is removed again (no dead legend row).
|
|
31119
|
-
* Never rejects.
|
|
31120
|
-
*/
|
|
31121
|
-
/**
|
|
31122
|
-
* Execute a script and resolve its FIRST computed run — the data-out door for host
|
|
31123
|
-
* editors, consoles and dashboards. The script is injected only if it runs (a failure
|
|
31124
|
-
* removes it again, leaving no dead legend row), and the result carries the run itself
|
|
31125
|
-
* plus the controls for what it put on the chart: `onUpdate` to follow later runs,
|
|
31126
|
-
* `remove` to take it off. Never rejects.
|
|
31127
|
-
*
|
|
31128
|
-
* `runScript` is `runIndicator` with the run as its payload rather than a handle to go
|
|
31129
|
-
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
31130
|
-
*/
|
|
31131
|
-
runScript(source, options) {
|
|
31132
|
-
const handle = this.addIndicator(source, options);
|
|
31133
|
-
const updates = /* @__PURE__ */ new Set();
|
|
31134
|
-
const drop = () => {
|
|
31135
|
-
try {
|
|
31136
|
-
handle.remove();
|
|
31137
|
-
} catch {
|
|
31138
|
-
}
|
|
31139
|
-
};
|
|
31140
|
-
return new Promise((resolve) => {
|
|
31141
|
-
let settled = false;
|
|
31142
|
-
const offRun = this.on("script:run", (run) => {
|
|
31143
|
-
if (run.id !== handle.id) return;
|
|
31144
|
-
if (settled) {
|
|
31145
|
-
for (const handler of updates) handler(run);
|
|
31146
|
-
return;
|
|
31147
|
-
}
|
|
31148
|
-
settled = true;
|
|
31149
|
-
offError();
|
|
31150
|
-
resolve({
|
|
31151
|
-
ok: true,
|
|
31152
|
-
run,
|
|
31153
|
-
error: null,
|
|
31154
|
-
onUpdate: (handler) => {
|
|
31155
|
-
updates.add(handler);
|
|
31156
|
-
return () => updates.delete(handler);
|
|
31157
|
-
},
|
|
31158
|
-
remove: () => {
|
|
31159
|
-
offRun();
|
|
31160
|
-
updates.clear();
|
|
31161
|
-
drop();
|
|
31162
|
-
}
|
|
31163
|
-
});
|
|
31164
|
-
});
|
|
31165
|
-
const offError = handle.on("error", ({ error }) => {
|
|
31166
|
-
if (settled) return;
|
|
31167
|
-
settled = true;
|
|
31168
|
-
offRun();
|
|
31169
|
-
offError();
|
|
31170
|
-
drop();
|
|
31171
|
-
resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
|
|
31172
|
-
});
|
|
31173
|
-
});
|
|
31066
|
+
resume() {
|
|
31067
|
+
this.recompute();
|
|
31174
31068
|
}
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31069
|
+
stop() {
|
|
31070
|
+
}
|
|
31071
|
+
recompute() {
|
|
31072
|
+
const bars = this.ctx.bars();
|
|
31073
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
31074
|
+
const instanceId = this.spec.type;
|
|
31075
|
+
const series = [];
|
|
31076
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
31077
|
+
out.plots.forEach((plot, ordinal) => {
|
|
31078
|
+
const kind = plot.kind ?? "line";
|
|
31079
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
31080
|
+
idsByKey.set(plot.key, id);
|
|
31081
|
+
series.push({
|
|
31082
|
+
id,
|
|
31083
|
+
title: plot.title,
|
|
31084
|
+
paneId: "",
|
|
31085
|
+
// stamped by the orchestrator
|
|
31086
|
+
kind,
|
|
31087
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
31088
|
+
style: {
|
|
31089
|
+
color: plot.color,
|
|
31090
|
+
width: plot.width ?? 1,
|
|
31091
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
31092
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
31093
|
+
},
|
|
31094
|
+
...plot.overlay ? { overlay: true } : {}
|
|
31182
31095
|
});
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
resolve({ ok: false, handle: null, error, context });
|
|
31192
|
-
});
|
|
31096
|
+
});
|
|
31097
|
+
if (out.markers && out.markers.length > 0) {
|
|
31098
|
+
series.push({
|
|
31099
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
31100
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
31101
|
+
paneId: "",
|
|
31102
|
+
kind: "markers",
|
|
31103
|
+
markers: out.markers
|
|
31193
31104
|
});
|
|
31105
|
+
}
|
|
31106
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
31107
|
+
const from = idsByKey.get(band.from);
|
|
31108
|
+
const to = idsByKey.get(band.to);
|
|
31109
|
+
if (!from || !to) return [];
|
|
31110
|
+
return [
|
|
31111
|
+
{
|
|
31112
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
31113
|
+
paneId: "",
|
|
31114
|
+
fromSeriesId: from,
|
|
31115
|
+
toSeriesId: to,
|
|
31116
|
+
color: band.color
|
|
31117
|
+
}
|
|
31118
|
+
];
|
|
31194
31119
|
});
|
|
31120
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
31121
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
31122
|
+
paneId: "",
|
|
31123
|
+
price: level.price,
|
|
31124
|
+
...level.color != null ? { color: level.color } : {},
|
|
31125
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
31126
|
+
width: 1,
|
|
31127
|
+
...level.title != null ? { title: level.title } : {}
|
|
31128
|
+
}));
|
|
31129
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
31130
|
+
const { key, ...rest } = poly;
|
|
31131
|
+
return {
|
|
31132
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
31133
|
+
paneId: "",
|
|
31134
|
+
...rest
|
|
31135
|
+
};
|
|
31136
|
+
});
|
|
31137
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
31138
|
+
this.ctx.setStatus("idle");
|
|
31195
31139
|
}
|
|
31196
|
-
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
31200
|
-
|
|
31201
|
-
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
|
|
31206
|
-
|
|
31207
|
-
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
|
|
31140
|
+
};
|
|
31141
|
+
function classicDescriptor(spec) {
|
|
31142
|
+
return {
|
|
31143
|
+
type: spec.type,
|
|
31144
|
+
title: spec.title,
|
|
31145
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
31146
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
31147
|
+
overlay: spec.overlay,
|
|
31148
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
31149
|
+
multiInstance: true,
|
|
31150
|
+
inputsSchema: () => spec.inputs,
|
|
31151
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
31152
|
+
create: () => new ClassicIndicator(spec)
|
|
31153
|
+
};
|
|
31154
|
+
}
|
|
31155
|
+
|
|
31156
|
+
// src/core/native-indicators/classics/math.ts
|
|
31157
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
31158
|
+
function sourceValues(bars, source) {
|
|
31159
|
+
const out = new Array(bars.length);
|
|
31160
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31161
|
+
const b = bars[i];
|
|
31162
|
+
switch (source) {
|
|
31163
|
+
case "Open":
|
|
31164
|
+
out[i] = b.open;
|
|
31165
|
+
break;
|
|
31166
|
+
case "High":
|
|
31167
|
+
out[i] = b.high;
|
|
31168
|
+
break;
|
|
31169
|
+
case "Low":
|
|
31170
|
+
out[i] = b.low;
|
|
31171
|
+
break;
|
|
31172
|
+
case "HL2":
|
|
31173
|
+
out[i] = (b.high + b.low) / 2;
|
|
31174
|
+
break;
|
|
31175
|
+
case "HLC3":
|
|
31176
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
31177
|
+
break;
|
|
31178
|
+
case "OHLC4":
|
|
31179
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
31180
|
+
break;
|
|
31181
|
+
case "HLCC4":
|
|
31182
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
31183
|
+
break;
|
|
31184
|
+
default:
|
|
31185
|
+
out[i] = b.close;
|
|
31186
|
+
}
|
|
31213
31187
|
}
|
|
31214
|
-
|
|
31215
|
-
|
|
31216
|
-
|
|
31188
|
+
return out;
|
|
31189
|
+
}
|
|
31190
|
+
function volumes(bars) {
|
|
31191
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
31192
|
+
}
|
|
31193
|
+
function highs(bars) {
|
|
31194
|
+
return bars.map((b) => b.high);
|
|
31195
|
+
}
|
|
31196
|
+
function lows(bars) {
|
|
31197
|
+
return bars.map((b) => b.low);
|
|
31198
|
+
}
|
|
31199
|
+
function closes(bars) {
|
|
31200
|
+
return bars.map((b) => b.close);
|
|
31201
|
+
}
|
|
31202
|
+
function opens(bars) {
|
|
31203
|
+
return bars.map((b) => b.open);
|
|
31204
|
+
}
|
|
31205
|
+
function zip(a, b, f) {
|
|
31206
|
+
const out = new Array(a.length);
|
|
31207
|
+
for (let i = 0; i < a.length; i++) {
|
|
31208
|
+
const x = a[i];
|
|
31209
|
+
const y = b[i];
|
|
31210
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
31211
|
+
}
|
|
31212
|
+
return out;
|
|
31213
|
+
}
|
|
31214
|
+
function map(a, f) {
|
|
31215
|
+
const out = new Array(a.length);
|
|
31216
|
+
for (let i = 0; i < a.length; i++) {
|
|
31217
|
+
const x = a[i];
|
|
31218
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
31219
|
+
}
|
|
31220
|
+
return out;
|
|
31221
|
+
}
|
|
31222
|
+
function sub(a, b) {
|
|
31223
|
+
return zip(a, b, (x, y) => x - y);
|
|
31224
|
+
}
|
|
31225
|
+
function windowed(v, len, fold) {
|
|
31226
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31227
|
+
if (len <= 0) return out;
|
|
31228
|
+
let run = 0;
|
|
31229
|
+
for (let i = 0; i < v.length; i++) {
|
|
31230
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
31231
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
31232
|
+
}
|
|
31233
|
+
return out;
|
|
31234
|
+
}
|
|
31235
|
+
function sum(v, len) {
|
|
31236
|
+
return windowed(v, len, (w, from, to) => {
|
|
31237
|
+
let s = 0;
|
|
31238
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31239
|
+
return s;
|
|
31240
|
+
});
|
|
31241
|
+
}
|
|
31242
|
+
function sma(v, len) {
|
|
31243
|
+
return windowed(v, len, (w, from, to) => {
|
|
31244
|
+
let s = 0;
|
|
31245
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31246
|
+
return s / len;
|
|
31247
|
+
});
|
|
31248
|
+
}
|
|
31249
|
+
function wma(v, len) {
|
|
31250
|
+
const norm = len * (len + 1) / 2;
|
|
31251
|
+
return windowed(v, len, (w, from, to) => {
|
|
31252
|
+
let s = 0;
|
|
31253
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
31254
|
+
return s / norm;
|
|
31255
|
+
});
|
|
31256
|
+
}
|
|
31257
|
+
function vwma(src, vol, len) {
|
|
31258
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
31259
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
31260
|
+
}
|
|
31261
|
+
function stdev(v, len) {
|
|
31262
|
+
return windowed(v, len, (w, from, to) => {
|
|
31263
|
+
let s = 0;
|
|
31264
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
31265
|
+
const mean = s / len;
|
|
31266
|
+
let sq = 0;
|
|
31267
|
+
for (let k = from; k <= to; k++) {
|
|
31268
|
+
const d = w[k] - mean;
|
|
31269
|
+
sq += d * d;
|
|
31270
|
+
}
|
|
31271
|
+
return Math.sqrt(sq / len);
|
|
31272
|
+
});
|
|
31273
|
+
}
|
|
31274
|
+
function highest(v, len) {
|
|
31275
|
+
return windowed(v, len, (w, from, to) => {
|
|
31276
|
+
let m = -Infinity;
|
|
31277
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
31278
|
+
return m;
|
|
31279
|
+
});
|
|
31280
|
+
}
|
|
31281
|
+
function lowest(v, len) {
|
|
31282
|
+
return windowed(v, len, (w, from, to) => {
|
|
31283
|
+
let m = Infinity;
|
|
31284
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
31285
|
+
return m;
|
|
31286
|
+
});
|
|
31287
|
+
}
|
|
31288
|
+
function barsSinceHighest(v, len) {
|
|
31289
|
+
return windowed(v, len, (w, from, to) => {
|
|
31290
|
+
let m = -Infinity;
|
|
31291
|
+
let at = to;
|
|
31292
|
+
for (let k = from; k <= to; k++) {
|
|
31293
|
+
if (w[k] >= m) {
|
|
31294
|
+
m = w[k];
|
|
31295
|
+
at = k;
|
|
31296
|
+
}
|
|
31297
|
+
}
|
|
31298
|
+
return to - at;
|
|
31299
|
+
});
|
|
31300
|
+
}
|
|
31301
|
+
function barsSinceLowest(v, len) {
|
|
31302
|
+
return windowed(v, len, (w, from, to) => {
|
|
31303
|
+
let m = Infinity;
|
|
31304
|
+
let at = to;
|
|
31305
|
+
for (let k = from; k <= to; k++) {
|
|
31306
|
+
if (w[k] <= m) {
|
|
31307
|
+
m = w[k];
|
|
31308
|
+
at = k;
|
|
31309
|
+
}
|
|
31310
|
+
}
|
|
31311
|
+
return to - at;
|
|
31312
|
+
});
|
|
31313
|
+
}
|
|
31314
|
+
function recursive(v, len, alpha) {
|
|
31315
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31316
|
+
let run = 0;
|
|
31317
|
+
let prev2 = Number.NaN;
|
|
31318
|
+
for (let i = 0; i < v.length; i++) {
|
|
31319
|
+
const x = v[i];
|
|
31320
|
+
if (!Number.isFinite(x)) {
|
|
31321
|
+
run = 0;
|
|
31322
|
+
prev2 = Number.NaN;
|
|
31323
|
+
continue;
|
|
31324
|
+
}
|
|
31325
|
+
run += 1;
|
|
31326
|
+
if (Number.isFinite(prev2)) {
|
|
31327
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
31328
|
+
out[i] = prev2;
|
|
31329
|
+
} else if (run >= len) {
|
|
31330
|
+
let s = 0;
|
|
31331
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
31332
|
+
prev2 = s / len;
|
|
31333
|
+
out[i] = prev2;
|
|
31334
|
+
}
|
|
31335
|
+
}
|
|
31336
|
+
return out;
|
|
31337
|
+
}
|
|
31338
|
+
function ema(v, len) {
|
|
31339
|
+
return recursive(v, len, 2 / (len + 1));
|
|
31340
|
+
}
|
|
31341
|
+
function rma(v, len) {
|
|
31342
|
+
return recursive(v, len, 1 / len);
|
|
31343
|
+
}
|
|
31344
|
+
function change(v, n = 1) {
|
|
31345
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31346
|
+
for (let i = n; i < v.length; i++) {
|
|
31347
|
+
const a = v[i];
|
|
31348
|
+
const b = v[i - n];
|
|
31349
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
31350
|
+
}
|
|
31351
|
+
return out;
|
|
31352
|
+
}
|
|
31353
|
+
function roc(v, len) {
|
|
31354
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31355
|
+
for (let i = len; i < v.length; i++) {
|
|
31356
|
+
const a = v[i];
|
|
31357
|
+
const b = v[i - len];
|
|
31358
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
31359
|
+
}
|
|
31360
|
+
return out;
|
|
31361
|
+
}
|
|
31362
|
+
function shift(v, n) {
|
|
31363
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31364
|
+
for (let i = 0; i < v.length; i++) {
|
|
31365
|
+
const j = i - n;
|
|
31366
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
31367
|
+
}
|
|
31368
|
+
return out;
|
|
31369
|
+
}
|
|
31370
|
+
function trueRange(bars) {
|
|
31371
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
31372
|
+
for (let i = 0; i < bars.length; i++) {
|
|
31373
|
+
const b = bars[i];
|
|
31374
|
+
if (i === 0) {
|
|
31375
|
+
out[i] = b.high - b.low;
|
|
31376
|
+
continue;
|
|
31377
|
+
}
|
|
31378
|
+
const pc = bars[i - 1].close;
|
|
31379
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
31380
|
+
}
|
|
31381
|
+
return out;
|
|
31382
|
+
}
|
|
31383
|
+
function atr(bars, len) {
|
|
31384
|
+
return rma(trueRange(bars), len);
|
|
31385
|
+
}
|
|
31386
|
+
function rsi(v, len) {
|
|
31387
|
+
const d = change(v);
|
|
31388
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
31389
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
31390
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
31391
|
+
}
|
|
31392
|
+
function stoch(v, hi, lo, len) {
|
|
31393
|
+
const hh = highest(hi, len);
|
|
31394
|
+
const ll = lowest(lo, len);
|
|
31395
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31396
|
+
for (let i = 0; i < v.length; i++) {
|
|
31397
|
+
const x = v[i];
|
|
31398
|
+
const h = hh[i];
|
|
31399
|
+
const l = ll[i];
|
|
31400
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
31401
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
31402
|
+
}
|
|
31403
|
+
return out;
|
|
31404
|
+
}
|
|
31405
|
+
function linreg(v, len) {
|
|
31406
|
+
const sx = (len - 1) * len / 2;
|
|
31407
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
31408
|
+
const denom = len * sxx - sx * sx;
|
|
31409
|
+
return windowed(v, len, (w, from, to) => {
|
|
31410
|
+
let sy = 0;
|
|
31411
|
+
let sxy = 0;
|
|
31412
|
+
for (let k = from; k <= to; k++) {
|
|
31413
|
+
const x = k - from;
|
|
31414
|
+
sy += w[k];
|
|
31415
|
+
sxy += x * w[k];
|
|
31416
|
+
}
|
|
31417
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
31418
|
+
const intercept = (sy - slope * sx) / len;
|
|
31419
|
+
return intercept + slope * (len - 1);
|
|
31420
|
+
});
|
|
31421
|
+
}
|
|
31422
|
+
function swma(v) {
|
|
31423
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31424
|
+
for (let i = 3; i < v.length; i++) {
|
|
31425
|
+
const a = v[i - 3];
|
|
31426
|
+
const b = v[i - 2];
|
|
31427
|
+
const c = v[i - 1];
|
|
31428
|
+
const d = v[i];
|
|
31429
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
31430
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
31431
|
+
}
|
|
31432
|
+
}
|
|
31433
|
+
return out;
|
|
31434
|
+
}
|
|
31435
|
+
function percentRank(v, len) {
|
|
31436
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31437
|
+
for (let i = len; i < v.length; i++) {
|
|
31438
|
+
const x = v[i];
|
|
31439
|
+
if (!Number.isFinite(x)) continue;
|
|
31440
|
+
let below = 0;
|
|
31441
|
+
let count = 0;
|
|
31442
|
+
for (let k = i - len; k < i; k++) {
|
|
31443
|
+
const y = v[k];
|
|
31444
|
+
if (!Number.isFinite(y)) {
|
|
31445
|
+
count = -1;
|
|
31446
|
+
break;
|
|
31447
|
+
}
|
|
31448
|
+
count += 1;
|
|
31449
|
+
if (y <= x) below += 1;
|
|
31450
|
+
}
|
|
31451
|
+
if (count === len) out[i] = 100 * below / len;
|
|
31452
|
+
}
|
|
31453
|
+
return out;
|
|
31454
|
+
}
|
|
31455
|
+
function cumSum(v) {
|
|
31456
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
31457
|
+
let acc = 0;
|
|
31458
|
+
let seen = false;
|
|
31459
|
+
for (let i = 0; i < v.length; i++) {
|
|
31460
|
+
const x = v[i];
|
|
31461
|
+
if (Number.isFinite(x)) {
|
|
31462
|
+
acc += x;
|
|
31463
|
+
seen = true;
|
|
31464
|
+
}
|
|
31465
|
+
if (seen) out[i] = acc;
|
|
31466
|
+
}
|
|
31467
|
+
return out;
|
|
31468
|
+
}
|
|
31469
|
+
|
|
31470
|
+
// src/core/native-indicators/classics/shared.ts
|
|
31471
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
31472
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
31473
|
+
}
|
|
31474
|
+
function sourceInput(defval = "Close") {
|
|
31475
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
31476
|
+
}
|
|
31477
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
31478
|
+
return { key, title, type: "color", defval };
|
|
31479
|
+
}
|
|
31480
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
31481
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
31482
|
+
if (!m) return hex;
|
|
31483
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
31484
|
+
return `#${m[1]}${a}`;
|
|
31485
|
+
}
|
|
31486
|
+
|
|
31487
|
+
// src/core/native-indicators/classics/averages.ts
|
|
31488
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
31489
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
31490
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
31491
|
+
return {
|
|
31492
|
+
type,
|
|
31493
|
+
title,
|
|
31494
|
+
shortTitle,
|
|
31495
|
+
overlay: true,
|
|
31496
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
31497
|
+
compute: (bars, inputs) => {
|
|
31498
|
+
const len = num3(inputs, "length", 20);
|
|
31499
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31500
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31501
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31502
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31503
|
+
}
|
|
31504
|
+
};
|
|
31505
|
+
}
|
|
31506
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
31507
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
31508
|
+
var movingAverage = {
|
|
31509
|
+
type: "moving-average",
|
|
31510
|
+
title: "Moving Average",
|
|
31511
|
+
shortTitle: "MA",
|
|
31512
|
+
overlay: true,
|
|
31513
|
+
inputs: [
|
|
31514
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
31515
|
+
lengthInput(20),
|
|
31516
|
+
sourceInput(),
|
|
31517
|
+
offsetInput,
|
|
31518
|
+
colorInput()
|
|
31519
|
+
],
|
|
31520
|
+
compute: (bars, inputs) => {
|
|
31521
|
+
const len = num3(inputs, "length", 20);
|
|
31522
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31523
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
31524
|
+
let values;
|
|
31525
|
+
switch (kind) {
|
|
31526
|
+
case "EMA":
|
|
31527
|
+
values = ema(src, len);
|
|
31528
|
+
break;
|
|
31529
|
+
case "WMA":
|
|
31530
|
+
values = wma(src, len);
|
|
31531
|
+
break;
|
|
31532
|
+
case "RMA":
|
|
31533
|
+
values = rma(src, len);
|
|
31534
|
+
break;
|
|
31535
|
+
case "VWMA":
|
|
31536
|
+
values = vwma(src, volumes(bars), len);
|
|
31537
|
+
break;
|
|
31538
|
+
default:
|
|
31539
|
+
values = sma(src, len);
|
|
31540
|
+
}
|
|
31541
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
31542
|
+
if (offset !== 0) values = shift(values, offset);
|
|
31543
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31544
|
+
}
|
|
31545
|
+
};
|
|
31546
|
+
var smoothedMa = {
|
|
31547
|
+
type: "rma",
|
|
31548
|
+
title: "Smoothed Moving Average",
|
|
31549
|
+
shortTitle: "RMA",
|
|
31550
|
+
overlay: true,
|
|
31551
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31552
|
+
compute: (bars, inputs) => ({
|
|
31553
|
+
plots: [
|
|
31554
|
+
{
|
|
31555
|
+
key: "rma",
|
|
31556
|
+
title: "RMA",
|
|
31557
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31558
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31559
|
+
width: 2
|
|
31560
|
+
}
|
|
31561
|
+
]
|
|
31562
|
+
})
|
|
31563
|
+
};
|
|
31564
|
+
var zlema = {
|
|
31565
|
+
type: "zlema",
|
|
31566
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
31567
|
+
shortTitle: "ZLEMA",
|
|
31568
|
+
overlay: true,
|
|
31569
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31570
|
+
compute: (bars, inputs) => {
|
|
31571
|
+
const len = num3(inputs, "length", 14);
|
|
31572
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31573
|
+
const lag = Math.floor((len - 1) / 2);
|
|
31574
|
+
const lagged = shift(src, lag);
|
|
31575
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
31576
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31577
|
+
}
|
|
31578
|
+
};
|
|
31579
|
+
var vidya = {
|
|
31580
|
+
type: "vidya",
|
|
31581
|
+
title: "Variable Index Dynamic Average",
|
|
31582
|
+
shortTitle: "VIDYA",
|
|
31583
|
+
overlay: true,
|
|
31584
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
31585
|
+
compute: (bars, inputs) => {
|
|
31586
|
+
const len = num3(inputs, "length", 14);
|
|
31587
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
31588
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31589
|
+
const d = change(src);
|
|
31590
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
31591
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
31592
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
31593
|
+
let u = 0;
|
|
31594
|
+
let w = 0;
|
|
31595
|
+
let ok = true;
|
|
31596
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
31597
|
+
const x = d[k];
|
|
31598
|
+
if (!Number.isFinite(x)) {
|
|
31599
|
+
ok = false;
|
|
31600
|
+
break;
|
|
31601
|
+
}
|
|
31602
|
+
if (x > 0) u += x;
|
|
31603
|
+
else w -= x;
|
|
31604
|
+
}
|
|
31605
|
+
if (ok) {
|
|
31606
|
+
su[i] = u;
|
|
31607
|
+
sd[i] = w;
|
|
31608
|
+
}
|
|
31609
|
+
}
|
|
31610
|
+
const alpha = 2 / (len + 1);
|
|
31611
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
31612
|
+
let prev2 = Number.NaN;
|
|
31613
|
+
for (let i = 0; i < src.length; i++) {
|
|
31614
|
+
const x = src[i];
|
|
31615
|
+
const u = su[i];
|
|
31616
|
+
const w = sd[i];
|
|
31617
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
31618
|
+
const total = u + w;
|
|
31619
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
31620
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
31621
|
+
out[i] = prev2;
|
|
31622
|
+
}
|
|
31623
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31624
|
+
}
|
|
31625
|
+
};
|
|
31626
|
+
var maEnvelope = {
|
|
31627
|
+
type: "ma-envelope",
|
|
31628
|
+
title: "Moving Average Envelope",
|
|
31629
|
+
shortTitle: "MA Env",
|
|
31630
|
+
overlay: true,
|
|
31631
|
+
inputs: [
|
|
31632
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
31633
|
+
lengthInput(20),
|
|
31634
|
+
sourceInput(),
|
|
31635
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
31636
|
+
colorInput()
|
|
31637
|
+
],
|
|
31638
|
+
compute: (bars, inputs) => {
|
|
31639
|
+
const len = num3(inputs, "length", 20);
|
|
31640
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31641
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
31642
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
31643
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31644
|
+
return {
|
|
31645
|
+
plots: [
|
|
31646
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
31647
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
31648
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
31649
|
+
],
|
|
31650
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31651
|
+
};
|
|
31652
|
+
}
|
|
31653
|
+
};
|
|
31654
|
+
var linearRegression = {
|
|
31655
|
+
type: "linear-regression",
|
|
31656
|
+
title: "Linear Regression Curve",
|
|
31657
|
+
shortTitle: "LinReg",
|
|
31658
|
+
overlay: true,
|
|
31659
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31660
|
+
compute: (bars, inputs) => ({
|
|
31661
|
+
plots: [
|
|
31662
|
+
{
|
|
31663
|
+
key: "linreg",
|
|
31664
|
+
title: "LinReg",
|
|
31665
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31666
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31667
|
+
width: 2
|
|
31668
|
+
}
|
|
31669
|
+
]
|
|
31670
|
+
})
|
|
31671
|
+
};
|
|
31672
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
31673
|
+
|
|
31674
|
+
// src/core/native-indicators/classics/bands.ts
|
|
31675
|
+
function bollinger(bars, inputs) {
|
|
31676
|
+
const len = num3(inputs, "length", 20);
|
|
31677
|
+
const mult = num3(inputs, "mult", 2);
|
|
31678
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31679
|
+
const basis = sma(src, len);
|
|
31680
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
31681
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
31682
|
+
}
|
|
31683
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
31684
|
+
var bollingerBands = {
|
|
31685
|
+
type: "bollinger-bands",
|
|
31686
|
+
title: "Bollinger Bands",
|
|
31687
|
+
shortTitle: "BB",
|
|
31688
|
+
overlay: true,
|
|
31689
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31690
|
+
compute: (bars, inputs) => {
|
|
31691
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31692
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31693
|
+
return {
|
|
31694
|
+
plots: [
|
|
31695
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31696
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31697
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31698
|
+
],
|
|
31699
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31700
|
+
};
|
|
31701
|
+
}
|
|
31702
|
+
};
|
|
31703
|
+
var percentB = {
|
|
31704
|
+
type: "percent-b",
|
|
31705
|
+
title: "Bollinger Bands %B",
|
|
31706
|
+
shortTitle: "%B",
|
|
31707
|
+
overlay: false,
|
|
31708
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31709
|
+
compute: (bars, inputs) => {
|
|
31710
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
31711
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
31712
|
+
const values = src.map((x, i) => {
|
|
31713
|
+
const u = upper[i];
|
|
31714
|
+
const l = lower[i];
|
|
31715
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
31716
|
+
});
|
|
31717
|
+
return {
|
|
31718
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
31719
|
+
levels: [
|
|
31720
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
31721
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
31722
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
31723
|
+
]
|
|
31724
|
+
};
|
|
31725
|
+
}
|
|
31726
|
+
};
|
|
31727
|
+
var bandwidth = {
|
|
31728
|
+
type: "bandwidth",
|
|
31729
|
+
title: "Bollinger Bands Width",
|
|
31730
|
+
shortTitle: "BandWidth",
|
|
31731
|
+
overlay: false,
|
|
31732
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
31733
|
+
compute: (bars, inputs) => {
|
|
31734
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
31735
|
+
const values = basis.map((b, i) => {
|
|
31736
|
+
const u = upper[i];
|
|
31737
|
+
const l = lower[i];
|
|
31738
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
31739
|
+
});
|
|
31740
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
31741
|
+
}
|
|
31742
|
+
};
|
|
31743
|
+
var keltner = {
|
|
31744
|
+
type: "keltner-channels",
|
|
31745
|
+
title: "Keltner Channels",
|
|
31746
|
+
shortTitle: "KC",
|
|
31747
|
+
overlay: true,
|
|
31748
|
+
inputs: [
|
|
31749
|
+
lengthInput(20),
|
|
31750
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
31751
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31752
|
+
sourceInput(),
|
|
31753
|
+
colorInput()
|
|
31754
|
+
],
|
|
31755
|
+
compute: (bars, inputs) => {
|
|
31756
|
+
const len = num3(inputs, "length", 20);
|
|
31757
|
+
const mult = num3(inputs, "mult", 2);
|
|
31758
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
31759
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
31760
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31761
|
+
return {
|
|
31762
|
+
plots: [
|
|
31763
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
31764
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
31765
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
31766
|
+
],
|
|
31767
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31768
|
+
};
|
|
31769
|
+
}
|
|
31770
|
+
};
|
|
31771
|
+
var donchian = {
|
|
31772
|
+
type: "donchian-channels",
|
|
31773
|
+
title: "Donchian Channels",
|
|
31774
|
+
shortTitle: "DC",
|
|
31775
|
+
overlay: true,
|
|
31776
|
+
inputs: [lengthInput(20), colorInput()],
|
|
31777
|
+
compute: (bars, inputs) => {
|
|
31778
|
+
const len = num3(inputs, "length", 20);
|
|
31779
|
+
const upper = highest(highs(bars), len);
|
|
31780
|
+
const lower = lowest(lows(bars), len);
|
|
31781
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
31782
|
+
return {
|
|
31783
|
+
plots: [
|
|
31784
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
31785
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
31786
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
31787
|
+
],
|
|
31788
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
31789
|
+
};
|
|
31790
|
+
}
|
|
31791
|
+
};
|
|
31792
|
+
var chandelier = {
|
|
31793
|
+
type: "chandelier-exit",
|
|
31794
|
+
title: "Chandelier Exit",
|
|
31795
|
+
shortTitle: "CE",
|
|
31796
|
+
overlay: true,
|
|
31797
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
31798
|
+
compute: (bars, inputs) => {
|
|
31799
|
+
const len = num3(inputs, "length", 22);
|
|
31800
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
31801
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
31802
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
31803
|
+
return {
|
|
31804
|
+
plots: [
|
|
31805
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
31806
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
31807
|
+
]
|
|
31808
|
+
};
|
|
31809
|
+
}
|
|
31810
|
+
};
|
|
31811
|
+
var chandeKroll = {
|
|
31812
|
+
type: "chande-kroll-stop",
|
|
31813
|
+
title: "Chande Kroll Stop",
|
|
31814
|
+
shortTitle: "CKS",
|
|
31815
|
+
overlay: true,
|
|
31816
|
+
inputs: [
|
|
31817
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31818
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
31819
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
31820
|
+
],
|
|
31821
|
+
compute: (bars, inputs) => {
|
|
31822
|
+
const p = num3(inputs, "p", 10);
|
|
31823
|
+
const x = num3(inputs, "x", 1);
|
|
31824
|
+
const q = num3(inputs, "q", 9);
|
|
31825
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
31826
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
31827
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
31828
|
+
return {
|
|
31829
|
+
plots: [
|
|
31830
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
31831
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
31832
|
+
]
|
|
31833
|
+
};
|
|
31834
|
+
}
|
|
31835
|
+
};
|
|
31836
|
+
var supertrend = {
|
|
31837
|
+
type: "supertrend",
|
|
31838
|
+
title: "SuperTrend",
|
|
31839
|
+
shortTitle: "SuperTrend",
|
|
31840
|
+
overlay: true,
|
|
31841
|
+
inputs: [
|
|
31842
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
31843
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
31844
|
+
],
|
|
31845
|
+
compute: (bars, inputs) => {
|
|
31846
|
+
const len = num3(inputs, "atrLength", 10);
|
|
31847
|
+
const mult = num3(inputs, "mult", 3);
|
|
31848
|
+
const range = atr(bars, len);
|
|
31849
|
+
const n = bars.length;
|
|
31850
|
+
const st = new Array(n).fill(Number.NaN);
|
|
31851
|
+
const dir = new Array(n).fill(1);
|
|
31852
|
+
let up = Number.NaN;
|
|
31853
|
+
let dn = Number.NaN;
|
|
31854
|
+
let trend = 1;
|
|
31855
|
+
for (let i = 0; i < n; i++) {
|
|
31856
|
+
const r = range[i];
|
|
31857
|
+
if (!Number.isFinite(r)) continue;
|
|
31858
|
+
const b = bars[i];
|
|
31859
|
+
const mid = (b.high + b.low) / 2;
|
|
31860
|
+
const basicUp = mid - mult * r;
|
|
31861
|
+
const basicDn = mid + mult * r;
|
|
31862
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
31863
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
31864
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
31865
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
31866
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
31867
|
+
dir[i] = trend;
|
|
31868
|
+
st[i] = trend === 1 ? up : dn;
|
|
31869
|
+
}
|
|
31870
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
31871
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
31872
|
+
}
|
|
31873
|
+
};
|
|
31874
|
+
var alligator = {
|
|
31875
|
+
type: "williams-alligator",
|
|
31876
|
+
title: "Williams Alligator",
|
|
31877
|
+
shortTitle: "Alligator",
|
|
31878
|
+
overlay: true,
|
|
31879
|
+
inputs: [
|
|
31880
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
31881
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
31882
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
31883
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
31884
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
31885
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
31886
|
+
],
|
|
31887
|
+
compute: (bars, inputs) => {
|
|
31888
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31889
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
31890
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
31891
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
31892
|
+
return {
|
|
31893
|
+
plots: [
|
|
31894
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
31895
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
31896
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
31897
|
+
]
|
|
31898
|
+
};
|
|
31899
|
+
}
|
|
31900
|
+
};
|
|
31901
|
+
var gator = {
|
|
31902
|
+
type: "gator-oscillator",
|
|
31903
|
+
title: "Gator Oscillator",
|
|
31904
|
+
shortTitle: "Gator Oscillator",
|
|
31905
|
+
overlay: false,
|
|
31906
|
+
inputs: [],
|
|
31907
|
+
compute: (bars) => {
|
|
31908
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
31909
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
31910
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
31911
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
31912
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
31913
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
31914
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
31915
|
+
if (!Number.isFinite(x)) return null;
|
|
31916
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
31917
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
31918
|
+
});
|
|
31919
|
+
return {
|
|
31920
|
+
plots: [
|
|
31921
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
31922
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
31923
|
+
]
|
|
31924
|
+
};
|
|
31925
|
+
}
|
|
31926
|
+
};
|
|
31927
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
31928
|
+
|
|
31929
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
31930
|
+
function guideLevels(low, high, mid) {
|
|
31931
|
+
const levels = [
|
|
31932
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
31933
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
31934
|
+
];
|
|
31935
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
31936
|
+
return levels;
|
|
31937
|
+
}
|
|
31938
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
31939
|
+
function histColors(values) {
|
|
31940
|
+
return values.map((x, i) => {
|
|
31941
|
+
if (!Number.isFinite(x)) return null;
|
|
31942
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
31943
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
31944
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
31945
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
31946
|
+
});
|
|
31947
|
+
}
|
|
31948
|
+
var rsiSpec = {
|
|
31949
|
+
type: "rsi",
|
|
31950
|
+
title: "Relative Strength Index",
|
|
31951
|
+
shortTitle: "RSI",
|
|
31952
|
+
overlay: false,
|
|
31953
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
31954
|
+
compute: (bars, inputs) => ({
|
|
31955
|
+
plots: [
|
|
31956
|
+
{
|
|
31957
|
+
key: "rsi",
|
|
31958
|
+
title: "RSI",
|
|
31959
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
31960
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
31961
|
+
width: 2
|
|
31962
|
+
}
|
|
31963
|
+
],
|
|
31964
|
+
levels: guideLevels(30, 70, 50)
|
|
31965
|
+
})
|
|
31966
|
+
};
|
|
31967
|
+
var stochastic = {
|
|
31968
|
+
type: "stochastic",
|
|
31969
|
+
title: "Stochastic",
|
|
31970
|
+
shortTitle: "Stoch",
|
|
31971
|
+
overlay: false,
|
|
31972
|
+
inputs: [
|
|
31973
|
+
lengthInput(14, "kLength", "%K length"),
|
|
31974
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
31975
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
31976
|
+
],
|
|
31977
|
+
compute: (bars, inputs) => {
|
|
31978
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
31979
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
31980
|
+
return {
|
|
31981
|
+
plots: [
|
|
31982
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
31983
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
31984
|
+
],
|
|
31985
|
+
levels: guideLevels(20, 80)
|
|
31986
|
+
};
|
|
31987
|
+
}
|
|
31988
|
+
};
|
|
31989
|
+
var stochasticRsi = {
|
|
31990
|
+
type: "stochastic-rsi",
|
|
31991
|
+
title: "Stochastic RSI",
|
|
31992
|
+
shortTitle: "Stoch RSI",
|
|
31993
|
+
overlay: false,
|
|
31994
|
+
inputs: [
|
|
31995
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
31996
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
31997
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
31998
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
31999
|
+
sourceInput()
|
|
32000
|
+
],
|
|
32001
|
+
compute: (bars, inputs) => {
|
|
32002
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
32003
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
32004
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
32005
|
+
return {
|
|
32006
|
+
plots: [
|
|
32007
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
32008
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
32009
|
+
],
|
|
32010
|
+
levels: guideLevels(20, 80)
|
|
32011
|
+
};
|
|
32012
|
+
}
|
|
32013
|
+
};
|
|
32014
|
+
var macd = {
|
|
32015
|
+
type: "macd",
|
|
32016
|
+
title: "MACD",
|
|
32017
|
+
overlay: false,
|
|
32018
|
+
inputs: [
|
|
32019
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32020
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32021
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32022
|
+
sourceInput()
|
|
32023
|
+
],
|
|
32024
|
+
compute: (bars, inputs) => {
|
|
32025
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32026
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
32027
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32028
|
+
const hist = sub(line, signal);
|
|
32029
|
+
return {
|
|
32030
|
+
plots: [
|
|
32031
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32032
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
32033
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32034
|
+
],
|
|
32035
|
+
levels: ZERO_LEVEL
|
|
32036
|
+
};
|
|
32037
|
+
}
|
|
32038
|
+
};
|
|
32039
|
+
var ppo = {
|
|
32040
|
+
type: "ppo",
|
|
32041
|
+
title: "Percentage Price Oscillator",
|
|
32042
|
+
shortTitle: "PPO",
|
|
32043
|
+
overlay: false,
|
|
32044
|
+
inputs: [
|
|
32045
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
32046
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
32047
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
32048
|
+
sourceInput()
|
|
32049
|
+
],
|
|
32050
|
+
compute: (bars, inputs) => {
|
|
32051
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32052
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
32053
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
32054
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
32055
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32056
|
+
const hist = sub(line, signal);
|
|
32057
|
+
return {
|
|
32058
|
+
plots: [
|
|
32059
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
32060
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
32061
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32062
|
+
],
|
|
32063
|
+
levels: ZERO_LEVEL
|
|
32064
|
+
};
|
|
32065
|
+
}
|
|
32066
|
+
};
|
|
32067
|
+
var awesome = {
|
|
32068
|
+
type: "awesome-oscillator",
|
|
32069
|
+
title: "Awesome Oscillator",
|
|
32070
|
+
shortTitle: "AO",
|
|
32071
|
+
overlay: false,
|
|
32072
|
+
inputs: [],
|
|
32073
|
+
compute: (bars) => {
|
|
32074
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32075
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
32076
|
+
const colors = ao.map((x, i) => {
|
|
32077
|
+
if (!Number.isFinite(x)) return null;
|
|
32078
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
32079
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
32080
|
+
});
|
|
32081
|
+
return {
|
|
32082
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
32083
|
+
levels: ZERO_LEVEL
|
|
32084
|
+
};
|
|
32085
|
+
}
|
|
32086
|
+
};
|
|
32087
|
+
var cci = {
|
|
32088
|
+
type: "commodity-channel-index",
|
|
32089
|
+
title: "Commodity Channel Index",
|
|
32090
|
+
shortTitle: "CCI",
|
|
32091
|
+
overlay: false,
|
|
32092
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
32093
|
+
compute: (bars, inputs) => {
|
|
32094
|
+
const len = num3(inputs, "length", 20);
|
|
32095
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
32096
|
+
const basis = sma(src, len);
|
|
32097
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
32098
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
32099
|
+
const mean = basis[i];
|
|
32100
|
+
if (!Number.isFinite(mean)) continue;
|
|
32101
|
+
let d = 0;
|
|
32102
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
32103
|
+
dev[i] = d / len;
|
|
32104
|
+
}
|
|
32105
|
+
const values = src.map((x, i) => {
|
|
32106
|
+
const b = basis[i];
|
|
32107
|
+
const d = dev[i];
|
|
32108
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
32109
|
+
});
|
|
32110
|
+
return {
|
|
32111
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32112
|
+
levels: guideLevels(-100, 100, 0)
|
|
32113
|
+
};
|
|
32114
|
+
}
|
|
32115
|
+
};
|
|
32116
|
+
var williamsR = {
|
|
32117
|
+
type: "williams-percent-r",
|
|
32118
|
+
title: "Williams %R",
|
|
32119
|
+
shortTitle: "%R",
|
|
32120
|
+
overlay: false,
|
|
32121
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32122
|
+
compute: (bars, inputs) => {
|
|
32123
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
32124
|
+
return {
|
|
32125
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32126
|
+
levels: guideLevels(-80, -20)
|
|
32127
|
+
};
|
|
32128
|
+
}
|
|
32129
|
+
};
|
|
32130
|
+
var cmo = {
|
|
32131
|
+
type: "chande-momentum-oscillator",
|
|
32132
|
+
title: "Chande Momentum Oscillator",
|
|
32133
|
+
shortTitle: "CMO",
|
|
32134
|
+
overlay: false,
|
|
32135
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32136
|
+
compute: (bars, inputs) => {
|
|
32137
|
+
const len = num3(inputs, "length", 9);
|
|
32138
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
32139
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
32140
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
32141
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
32142
|
+
return {
|
|
32143
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32144
|
+
levels: guideLevels(-50, 50, 0)
|
|
32145
|
+
};
|
|
32146
|
+
}
|
|
32147
|
+
};
|
|
32148
|
+
var connorsRsi = {
|
|
32149
|
+
type: "connors-rsi",
|
|
32150
|
+
title: "Connors RSI",
|
|
32151
|
+
shortTitle: "CRSI",
|
|
32152
|
+
overlay: false,
|
|
32153
|
+
inputs: [
|
|
32154
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
32155
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
32156
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
32157
|
+
colorInput()
|
|
32158
|
+
],
|
|
32159
|
+
compute: (bars, inputs) => {
|
|
32160
|
+
const c = closes(bars);
|
|
32161
|
+
const streak = new Array(c.length).fill(0);
|
|
32162
|
+
for (let i = 1; i < c.length; i++) {
|
|
32163
|
+
const d = c[i] - c[i - 1];
|
|
32164
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
32165
|
+
}
|
|
32166
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
32167
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
32168
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
32169
|
+
const values = c.map((_, i) => {
|
|
32170
|
+
const x = a[i];
|
|
32171
|
+
const y = b[i];
|
|
32172
|
+
const z = r[i];
|
|
32173
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
32174
|
+
});
|
|
32175
|
+
return {
|
|
32176
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32177
|
+
levels: guideLevels(20, 80, 50)
|
|
32178
|
+
};
|
|
32179
|
+
}
|
|
32180
|
+
};
|
|
32181
|
+
var fisher = {
|
|
32182
|
+
type: "fisher-transform",
|
|
32183
|
+
title: "Fisher Transform",
|
|
32184
|
+
shortTitle: "Fisher",
|
|
32185
|
+
overlay: false,
|
|
32186
|
+
inputs: [lengthInput(9)],
|
|
32187
|
+
compute: (bars, inputs) => {
|
|
32188
|
+
const len = num3(inputs, "length", 9);
|
|
32189
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32190
|
+
const hi = highest(hl2, len);
|
|
32191
|
+
const lo = lowest(hl2, len);
|
|
32192
|
+
const n = bars.length;
|
|
32193
|
+
const out = new Array(n).fill(Number.NaN);
|
|
32194
|
+
let value = 0;
|
|
32195
|
+
let fish = 0;
|
|
32196
|
+
for (let i = 0; i < n; i++) {
|
|
32197
|
+
const h = hi[i];
|
|
32198
|
+
const l = lo[i];
|
|
32199
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
32200
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
32201
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
32202
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
32203
|
+
out[i] = fish;
|
|
32204
|
+
}
|
|
32205
|
+
return {
|
|
32206
|
+
plots: [
|
|
32207
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
32208
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
32209
|
+
],
|
|
32210
|
+
levels: ZERO_LEVEL
|
|
32211
|
+
};
|
|
32212
|
+
}
|
|
32213
|
+
};
|
|
32214
|
+
var trix = {
|
|
32215
|
+
type: "trix",
|
|
32216
|
+
title: "TRIX",
|
|
32217
|
+
overlay: false,
|
|
32218
|
+
inputs: [lengthInput(18), colorInput()],
|
|
32219
|
+
compute: (bars, inputs) => {
|
|
32220
|
+
const len = num3(inputs, "length", 18);
|
|
32221
|
+
const logClose = map(closes(bars), Math.log);
|
|
32222
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
32223
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
32224
|
+
return {
|
|
32225
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32226
|
+
levels: ZERO_LEVEL
|
|
32227
|
+
};
|
|
32228
|
+
}
|
|
32229
|
+
};
|
|
32230
|
+
function tsiLine(src, long, short) {
|
|
32231
|
+
const mom = change(src);
|
|
32232
|
+
const numer = ema(ema(mom, long), short);
|
|
32233
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
32234
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
32235
|
+
}
|
|
32236
|
+
var tsi = {
|
|
32237
|
+
type: "true-strength-index",
|
|
32238
|
+
title: "True Strength Index",
|
|
32239
|
+
shortTitle: "TSI",
|
|
32240
|
+
overlay: false,
|
|
32241
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32242
|
+
compute: (bars, inputs) => {
|
|
32243
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
32244
|
+
return {
|
|
32245
|
+
plots: [
|
|
32246
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
32247
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32248
|
+
],
|
|
32249
|
+
levels: ZERO_LEVEL
|
|
32250
|
+
};
|
|
32251
|
+
}
|
|
32252
|
+
};
|
|
32253
|
+
var smiErgodic = {
|
|
32254
|
+
type: "smi-ergodic",
|
|
32255
|
+
title: "SMI Ergodic Oscillator",
|
|
32256
|
+
shortTitle: "SMIE",
|
|
32257
|
+
overlay: false,
|
|
32258
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
32259
|
+
compute: (bars, inputs) => {
|
|
32260
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
32261
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
32262
|
+
const osc = sub(line, signal);
|
|
32263
|
+
return {
|
|
32264
|
+
plots: [
|
|
32265
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
32266
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
32267
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32268
|
+
],
|
|
32269
|
+
levels: ZERO_LEVEL
|
|
32270
|
+
};
|
|
32271
|
+
}
|
|
32272
|
+
};
|
|
32273
|
+
var stc = {
|
|
32274
|
+
type: "schaff-trend-cycle",
|
|
32275
|
+
title: "Schaff Trend Cycle",
|
|
32276
|
+
shortTitle: "Schaff Trend Cycle",
|
|
32277
|
+
overlay: false,
|
|
32278
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
32279
|
+
compute: (bars, inputs) => {
|
|
32280
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
32281
|
+
const src = closes(bars);
|
|
32282
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
32283
|
+
const stochOf = (v) => {
|
|
32284
|
+
const lo = lowest(v, len);
|
|
32285
|
+
const hi = highest(v, len);
|
|
32286
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
32287
|
+
let prevRaw = Number.NaN;
|
|
32288
|
+
let smoothed = Number.NaN;
|
|
32289
|
+
for (let i = 0; i < v.length; i++) {
|
|
32290
|
+
const x = v[i];
|
|
32291
|
+
const l = lo[i];
|
|
32292
|
+
const h = hi[i];
|
|
32293
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
32294
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
32295
|
+
prevRaw = raw;
|
|
32296
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
32297
|
+
out[i] = smoothed;
|
|
32298
|
+
}
|
|
32299
|
+
return out;
|
|
32300
|
+
};
|
|
32301
|
+
const values = stochOf(stochOf(macdLine));
|
|
32302
|
+
return {
|
|
32303
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
32304
|
+
levels: guideLevels(25, 75)
|
|
32305
|
+
};
|
|
32306
|
+
}
|
|
32307
|
+
};
|
|
32308
|
+
var kst = {
|
|
32309
|
+
type: "know-sure-thing",
|
|
32310
|
+
title: "Know Sure Thing",
|
|
32311
|
+
shortTitle: "KST",
|
|
32312
|
+
overlay: false,
|
|
32313
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
32314
|
+
compute: (bars, inputs) => {
|
|
32315
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32316
|
+
const line = [
|
|
32317
|
+
sma(roc(src, 10), 10),
|
|
32318
|
+
sma(roc(src, 15), 10),
|
|
32319
|
+
sma(roc(src, 20), 10),
|
|
32320
|
+
sma(roc(src, 30), 15)
|
|
32321
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
32322
|
+
return {
|
|
32323
|
+
plots: [
|
|
32324
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
32325
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
32326
|
+
],
|
|
32327
|
+
levels: ZERO_LEVEL
|
|
32328
|
+
};
|
|
32329
|
+
}
|
|
32330
|
+
};
|
|
32331
|
+
var coppock = {
|
|
32332
|
+
type: "coppock-curve",
|
|
32333
|
+
title: "Coppock Curve",
|
|
32334
|
+
shortTitle: "Coppock",
|
|
32335
|
+
overlay: false,
|
|
32336
|
+
inputs: [
|
|
32337
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
32338
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
32339
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
32340
|
+
sourceInput(),
|
|
32341
|
+
colorInput()
|
|
32342
|
+
],
|
|
32343
|
+
compute: (bars, inputs) => {
|
|
32344
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32345
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
32346
|
+
return {
|
|
32347
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32348
|
+
levels: ZERO_LEVEL
|
|
32349
|
+
};
|
|
32350
|
+
}
|
|
32351
|
+
};
|
|
32352
|
+
var dpo = {
|
|
32353
|
+
type: "detrended-price-oscillator",
|
|
32354
|
+
title: "Detrended Price Oscillator",
|
|
32355
|
+
shortTitle: "DPO",
|
|
32356
|
+
overlay: false,
|
|
32357
|
+
inputs: [lengthInput(21), colorInput()],
|
|
32358
|
+
compute: (bars, inputs) => {
|
|
32359
|
+
const len = num3(inputs, "length", 21);
|
|
32360
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
32361
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
32362
|
+
return {
|
|
32363
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32364
|
+
levels: ZERO_LEVEL
|
|
32365
|
+
};
|
|
32366
|
+
}
|
|
32367
|
+
};
|
|
32368
|
+
var rocSpec = {
|
|
32369
|
+
type: "rate-of-change",
|
|
32370
|
+
title: "Rate of Change",
|
|
32371
|
+
shortTitle: "ROC",
|
|
32372
|
+
overlay: false,
|
|
32373
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
32374
|
+
compute: (bars, inputs) => ({
|
|
32375
|
+
plots: [
|
|
32376
|
+
{
|
|
32377
|
+
key: "roc",
|
|
32378
|
+
title: "RoC",
|
|
32379
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
32380
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32381
|
+
width: 2
|
|
32382
|
+
}
|
|
32383
|
+
],
|
|
32384
|
+
levels: ZERO_LEVEL
|
|
32385
|
+
})
|
|
32386
|
+
};
|
|
32387
|
+
var ultimate = {
|
|
32388
|
+
type: "ultimate-oscillator",
|
|
32389
|
+
title: "Ultimate Oscillator",
|
|
32390
|
+
shortTitle: "UO",
|
|
32391
|
+
overlay: false,
|
|
32392
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
32393
|
+
compute: (bars, inputs) => {
|
|
32394
|
+
const n = bars.length;
|
|
32395
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
32396
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
32397
|
+
for (let i = 0; i < n; i++) {
|
|
32398
|
+
const b = bars[i];
|
|
32399
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
32400
|
+
const lo = Math.min(b.low, pc);
|
|
32401
|
+
const hi = Math.max(b.high, pc);
|
|
32402
|
+
bp[i] = b.close - lo;
|
|
32403
|
+
tr[i] = hi - lo;
|
|
32404
|
+
}
|
|
32405
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32406
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
32407
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
32408
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
32409
|
+
const values = a7.map((x, i) => {
|
|
32410
|
+
const y = a14[i];
|
|
32411
|
+
const z = a28[i];
|
|
32412
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
32413
|
+
});
|
|
32414
|
+
return {
|
|
32415
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32416
|
+
levels: guideLevels(30, 70, 50)
|
|
32417
|
+
};
|
|
32418
|
+
}
|
|
32419
|
+
};
|
|
32420
|
+
var rvgi = {
|
|
32421
|
+
type: "relative-vigor-index",
|
|
32422
|
+
title: "Relative Vigor Index",
|
|
32423
|
+
shortTitle: "RVGI",
|
|
32424
|
+
overlay: false,
|
|
32425
|
+
inputs: [lengthInput(10)],
|
|
32426
|
+
compute: (bars, inputs) => {
|
|
32427
|
+
const len = num3(inputs, "length", 10);
|
|
32428
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
32429
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
32430
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
32431
|
+
return {
|
|
32432
|
+
plots: [
|
|
32433
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
32434
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
32435
|
+
],
|
|
32436
|
+
levels: ZERO_LEVEL
|
|
32437
|
+
};
|
|
32438
|
+
}
|
|
32439
|
+
};
|
|
32440
|
+
var relVolatility = {
|
|
32441
|
+
type: "relative-volatility-index",
|
|
32442
|
+
title: "Relative Volatility Index",
|
|
32443
|
+
shortTitle: "RVI",
|
|
32444
|
+
overlay: false,
|
|
32445
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
32446
|
+
compute: (bars, inputs) => {
|
|
32447
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
32448
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
32449
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
32450
|
+
const d = change(src);
|
|
32451
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
32452
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
32453
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
32454
|
+
return {
|
|
32455
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32456
|
+
levels: guideLevels(20, 80, 50)
|
|
32457
|
+
};
|
|
32458
|
+
}
|
|
32459
|
+
};
|
|
32460
|
+
var bop = {
|
|
32461
|
+
type: "balance-of-power",
|
|
32462
|
+
title: "Balance of Power",
|
|
32463
|
+
shortTitle: "BOP",
|
|
32464
|
+
overlay: false,
|
|
32465
|
+
inputs: [colorInput()],
|
|
32466
|
+
compute: (bars, inputs) => {
|
|
32467
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
32468
|
+
return {
|
|
32469
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32470
|
+
levels: ZERO_LEVEL
|
|
32471
|
+
};
|
|
32472
|
+
}
|
|
32473
|
+
};
|
|
32474
|
+
var elderRay = {
|
|
32475
|
+
type: "elder-ray",
|
|
32476
|
+
title: "Elder Ray",
|
|
32477
|
+
overlay: false,
|
|
32478
|
+
inputs: [lengthInput(13)],
|
|
32479
|
+
compute: (bars, inputs) => {
|
|
32480
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
32481
|
+
return {
|
|
32482
|
+
plots: [
|
|
32483
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
32484
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
32485
|
+
],
|
|
32486
|
+
levels: ZERO_LEVEL
|
|
32487
|
+
};
|
|
32488
|
+
}
|
|
32489
|
+
};
|
|
32490
|
+
var ttmSqueeze = {
|
|
32491
|
+
type: "ttm-squeeze",
|
|
32492
|
+
title: "TTM Squeeze",
|
|
32493
|
+
overlay: false,
|
|
32494
|
+
inputs: [
|
|
32495
|
+
lengthInput(20),
|
|
32496
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
32497
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
32498
|
+
],
|
|
32499
|
+
compute: (bars, inputs) => {
|
|
32500
|
+
const len = num3(inputs, "length", 20);
|
|
32501
|
+
const c = closes(bars);
|
|
32502
|
+
const basis = sma(c, len);
|
|
32503
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
32504
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
32505
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
32506
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
32507
|
+
const mom = linreg(sub(c, anchor), len);
|
|
32508
|
+
const squeezed = c.map((_, i) => {
|
|
32509
|
+
const d = dev[i];
|
|
32510
|
+
const r = kcRange[i];
|
|
32511
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
32512
|
+
});
|
|
32513
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
32514
|
+
return {
|
|
32515
|
+
plots: [
|
|
32516
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
32517
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
32518
|
+
]
|
|
32519
|
+
};
|
|
32520
|
+
}
|
|
32521
|
+
};
|
|
32522
|
+
var oscillatorSpecs = [
|
|
32523
|
+
rsiSpec,
|
|
32524
|
+
stochastic,
|
|
32525
|
+
stochasticRsi,
|
|
32526
|
+
macd,
|
|
32527
|
+
ppo,
|
|
32528
|
+
awesome,
|
|
32529
|
+
cci,
|
|
32530
|
+
williamsR,
|
|
32531
|
+
cmo,
|
|
32532
|
+
connorsRsi,
|
|
32533
|
+
fisher,
|
|
32534
|
+
trix,
|
|
32535
|
+
tsi,
|
|
32536
|
+
smiErgodic,
|
|
32537
|
+
stc,
|
|
32538
|
+
kst,
|
|
32539
|
+
coppock,
|
|
32540
|
+
dpo,
|
|
32541
|
+
rocSpec,
|
|
32542
|
+
ultimate,
|
|
32543
|
+
rvgi,
|
|
32544
|
+
relVolatility,
|
|
32545
|
+
bop,
|
|
32546
|
+
elderRay,
|
|
32547
|
+
ttmSqueeze
|
|
32548
|
+
];
|
|
32549
|
+
|
|
32550
|
+
// src/core/native-indicators/classics/trend.ts
|
|
32551
|
+
var aroon = {
|
|
32552
|
+
type: "aroon",
|
|
32553
|
+
title: "Aroon",
|
|
32554
|
+
overlay: false,
|
|
32555
|
+
inputs: [lengthInput(14)],
|
|
32556
|
+
compute: (bars, inputs) => {
|
|
32557
|
+
const len = num3(inputs, "length", 14);
|
|
32558
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32559
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
32560
|
+
return {
|
|
32561
|
+
plots: [
|
|
32562
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
32563
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
32564
|
+
],
|
|
32565
|
+
levels: [
|
|
32566
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
32567
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
32568
|
+
]
|
|
32569
|
+
};
|
|
32570
|
+
}
|
|
32571
|
+
};
|
|
32572
|
+
var adx = {
|
|
32573
|
+
type: "average-directional-index",
|
|
32574
|
+
title: "Average Directional Index",
|
|
32575
|
+
shortTitle: "ADX",
|
|
32576
|
+
overlay: false,
|
|
32577
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
32578
|
+
compute: (bars, inputs) => {
|
|
32579
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
32580
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
32581
|
+
const n = bars.length;
|
|
32582
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
32583
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
32584
|
+
for (let i = 1; i < n; i++) {
|
|
32585
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
32586
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
32587
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
32588
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
32589
|
+
}
|
|
32590
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
32591
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32592
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
32593
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
32594
|
+
return {
|
|
32595
|
+
plots: [
|
|
32596
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
32597
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
32598
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
32599
|
+
],
|
|
32600
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32601
|
+
};
|
|
32602
|
+
}
|
|
32603
|
+
};
|
|
32604
|
+
var vortex = {
|
|
32605
|
+
type: "vortex-indicator",
|
|
32606
|
+
title: "Vortex Indicator",
|
|
32607
|
+
shortTitle: "VI",
|
|
32608
|
+
overlay: false,
|
|
32609
|
+
inputs: [lengthInput(14)],
|
|
32610
|
+
compute: (bars, inputs) => {
|
|
32611
|
+
const len = num3(inputs, "length", 14);
|
|
32612
|
+
const n = bars.length;
|
|
32613
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
32614
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
32615
|
+
for (let i = 1; i < n; i++) {
|
|
32616
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
32617
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
32618
|
+
}
|
|
32619
|
+
const trSum = sum(trueRange(bars), len);
|
|
32620
|
+
return {
|
|
32621
|
+
plots: [
|
|
32622
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
32623
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
32624
|
+
],
|
|
32625
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32626
|
+
};
|
|
32627
|
+
}
|
|
32628
|
+
};
|
|
32629
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
32630
|
+
|
|
32631
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
32632
|
+
var atrSpec = {
|
|
32633
|
+
type: "average-true-range",
|
|
32634
|
+
title: "Average True Range",
|
|
32635
|
+
shortTitle: "ATR",
|
|
32636
|
+
overlay: false,
|
|
32637
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32638
|
+
compute: (bars, inputs) => ({
|
|
32639
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32640
|
+
})
|
|
32641
|
+
};
|
|
32642
|
+
var historicalVolatility = {
|
|
32643
|
+
type: "historical-volatility",
|
|
32644
|
+
title: "Historical Volatility",
|
|
32645
|
+
shortTitle: "HV",
|
|
32646
|
+
overlay: false,
|
|
32647
|
+
inputs: [
|
|
32648
|
+
lengthInput(10),
|
|
32649
|
+
{ 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)" },
|
|
32650
|
+
colorInput()
|
|
32651
|
+
],
|
|
32652
|
+
compute: (bars, inputs) => {
|
|
32653
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
32654
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
32655
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32656
|
+
}
|
|
32657
|
+
};
|
|
32658
|
+
var chaikinVolatility = {
|
|
32659
|
+
type: "chaikin-volatility",
|
|
32660
|
+
title: "Chaikin Volatility",
|
|
32661
|
+
shortTitle: "CHV",
|
|
32662
|
+
overlay: false,
|
|
32663
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
32664
|
+
compute: (bars, inputs) => {
|
|
32665
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
32666
|
+
return {
|
|
32667
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32668
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32669
|
+
};
|
|
32670
|
+
}
|
|
32671
|
+
};
|
|
32672
|
+
var massIndex = {
|
|
32673
|
+
type: "mass-index",
|
|
32674
|
+
title: "Mass Index",
|
|
32675
|
+
shortTitle: "MI",
|
|
32676
|
+
overlay: false,
|
|
32677
|
+
inputs: [lengthInput(25), colorInput()],
|
|
32678
|
+
compute: (bars, inputs) => {
|
|
32679
|
+
const range = sub(highs(bars), lows(bars));
|
|
32680
|
+
const single = ema(range, 9);
|
|
32681
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
32682
|
+
return {
|
|
32683
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32684
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
32685
|
+
};
|
|
32686
|
+
}
|
|
32687
|
+
};
|
|
32688
|
+
var standardDeviation = {
|
|
32689
|
+
type: "standard-deviation",
|
|
32690
|
+
title: "Standard Deviation",
|
|
32691
|
+
shortTitle: "StdDev",
|
|
32692
|
+
overlay: false,
|
|
32693
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
32694
|
+
compute: (bars, inputs) => ({
|
|
32695
|
+
plots: [
|
|
32696
|
+
{
|
|
32697
|
+
key: "stdev",
|
|
32698
|
+
title: "StdDev",
|
|
32699
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
32700
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
32701
|
+
width: 2
|
|
32702
|
+
}
|
|
32703
|
+
]
|
|
32704
|
+
})
|
|
32705
|
+
};
|
|
32706
|
+
var ulcerIndex = {
|
|
32707
|
+
type: "ulcer-index",
|
|
32708
|
+
title: "Ulcer Index",
|
|
32709
|
+
overlay: false,
|
|
32710
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32711
|
+
compute: (bars, inputs) => {
|
|
32712
|
+
const len = num3(inputs, "length", 14);
|
|
32713
|
+
const c = closes(bars);
|
|
32714
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
32715
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
32716
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32717
|
+
}
|
|
32718
|
+
};
|
|
32719
|
+
var choppiness = {
|
|
32720
|
+
type: "choppiness-index",
|
|
32721
|
+
title: "Choppiness Index",
|
|
32722
|
+
shortTitle: "CHOP",
|
|
32723
|
+
overlay: false,
|
|
32724
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32725
|
+
compute: (bars, inputs) => {
|
|
32726
|
+
const len = num3(inputs, "length", 14);
|
|
32727
|
+
const trSum = sum(trueRange(bars), len);
|
|
32728
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
32729
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
32730
|
+
return {
|
|
32731
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32732
|
+
levels: [
|
|
32733
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
32734
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
32735
|
+
]
|
|
32736
|
+
};
|
|
32737
|
+
}
|
|
32738
|
+
};
|
|
32739
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
32740
|
+
|
|
32741
|
+
// src/core/native-indicators/classics/volume.ts
|
|
32742
|
+
function moneyFlowVolume(bars) {
|
|
32743
|
+
return bars.map((b) => {
|
|
32744
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
32745
|
+
if (b.high === b.low) return 0;
|
|
32746
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
32747
|
+
});
|
|
32748
|
+
}
|
|
32749
|
+
var obv = {
|
|
32750
|
+
type: "on-balance-volume",
|
|
32751
|
+
title: "On Balance Volume",
|
|
32752
|
+
shortTitle: "OBV",
|
|
32753
|
+
overlay: false,
|
|
32754
|
+
inputs: [colorInput()],
|
|
32755
|
+
compute: (bars, inputs) => {
|
|
32756
|
+
const vol = volumes(bars);
|
|
32757
|
+
const d = change(closes(bars));
|
|
32758
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
32759
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32760
|
+
}
|
|
32761
|
+
};
|
|
32762
|
+
var accDist = {
|
|
32763
|
+
type: "accumulation-distribution",
|
|
32764
|
+
title: "Accumulation / Distribution",
|
|
32765
|
+
shortTitle: "A/D",
|
|
32766
|
+
overlay: false,
|
|
32767
|
+
inputs: [colorInput()],
|
|
32768
|
+
compute: (bars, inputs) => ({
|
|
32769
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
32770
|
+
})
|
|
32771
|
+
};
|
|
32772
|
+
var cmf = {
|
|
32773
|
+
type: "chaikin-money-flow",
|
|
32774
|
+
title: "Chaikin Money Flow",
|
|
32775
|
+
shortTitle: "CMF",
|
|
32776
|
+
overlay: false,
|
|
32777
|
+
inputs: [lengthInput(20), colorInput()],
|
|
32778
|
+
compute: (bars, inputs) => {
|
|
32779
|
+
const len = num3(inputs, "length", 20);
|
|
32780
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
32781
|
+
return {
|
|
32782
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32783
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32784
|
+
};
|
|
32785
|
+
}
|
|
32786
|
+
};
|
|
32787
|
+
var chaikinOscillator = {
|
|
32788
|
+
type: "chaikin-oscillator",
|
|
32789
|
+
title: "Chaikin Oscillator",
|
|
32790
|
+
shortTitle: "Chaikin Osc",
|
|
32791
|
+
overlay: false,
|
|
32792
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
32793
|
+
compute: (bars, inputs) => {
|
|
32794
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
32795
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
32796
|
+
return {
|
|
32797
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32798
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32799
|
+
};
|
|
32800
|
+
}
|
|
32801
|
+
};
|
|
32802
|
+
var eom = {
|
|
32803
|
+
type: "ease-of-movement",
|
|
32804
|
+
title: "Ease of Movement",
|
|
32805
|
+
shortTitle: "EOM",
|
|
32806
|
+
overlay: false,
|
|
32807
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
32808
|
+
compute: (bars, inputs) => {
|
|
32809
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
32810
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
32811
|
+
const move = change(hl2);
|
|
32812
|
+
const raw = bars.map((b, i) => {
|
|
32813
|
+
const m = move[i];
|
|
32814
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
32815
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
32816
|
+
});
|
|
32817
|
+
return {
|
|
32818
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32819
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32820
|
+
};
|
|
32821
|
+
}
|
|
32822
|
+
};
|
|
32823
|
+
var forceIndex = {
|
|
32824
|
+
type: "force-index",
|
|
32825
|
+
title: "Force Index",
|
|
32826
|
+
shortTitle: "FI",
|
|
32827
|
+
overlay: false,
|
|
32828
|
+
inputs: [lengthInput(13), colorInput()],
|
|
32829
|
+
compute: (bars, inputs) => {
|
|
32830
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
32831
|
+
return {
|
|
32832
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32833
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32834
|
+
};
|
|
32835
|
+
}
|
|
32836
|
+
};
|
|
32837
|
+
var klinger = {
|
|
32838
|
+
type: "klinger-oscillator",
|
|
32839
|
+
title: "Klinger Oscillator",
|
|
32840
|
+
shortTitle: "KVO",
|
|
32841
|
+
overlay: false,
|
|
32842
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
32843
|
+
compute: (bars, inputs) => {
|
|
32844
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
32845
|
+
const trend = change(hlc3);
|
|
32846
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
32847
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
32848
|
+
return {
|
|
32849
|
+
plots: [
|
|
32850
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
32851
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
32852
|
+
],
|
|
32853
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32854
|
+
};
|
|
32855
|
+
}
|
|
32856
|
+
};
|
|
32857
|
+
var mfi = {
|
|
32858
|
+
type: "money-flow-index",
|
|
32859
|
+
title: "Money Flow Index",
|
|
32860
|
+
shortTitle: "MFI",
|
|
32861
|
+
overlay: false,
|
|
32862
|
+
inputs: [lengthInput(14), colorInput()],
|
|
32863
|
+
compute: (bars, inputs) => {
|
|
32864
|
+
const len = num3(inputs, "length", 14);
|
|
32865
|
+
const tp = sourceValues(bars, "HLC3");
|
|
32866
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
32867
|
+
const d = change(tp);
|
|
32868
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
32869
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
32870
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
32871
|
+
return {
|
|
32872
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32873
|
+
levels: [
|
|
32874
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
32875
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
32876
|
+
]
|
|
32877
|
+
};
|
|
32878
|
+
}
|
|
32879
|
+
};
|
|
32880
|
+
function volumeIndex(bars, when) {
|
|
32881
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
32882
|
+
let index = 1e3;
|
|
32883
|
+
let started = false;
|
|
32884
|
+
for (let i = 1; i < bars.length; i++) {
|
|
32885
|
+
const v = bars[i].volume;
|
|
32886
|
+
const pv = bars[i - 1].volume;
|
|
32887
|
+
const pc = bars[i - 1].close;
|
|
32888
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
32889
|
+
if (started) out[i] = index;
|
|
32890
|
+
continue;
|
|
32891
|
+
}
|
|
32892
|
+
started = true;
|
|
32893
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
32894
|
+
out[i] = index;
|
|
32895
|
+
}
|
|
32896
|
+
return out;
|
|
32897
|
+
}
|
|
32898
|
+
var nvi = {
|
|
32899
|
+
type: "negative-volume-index",
|
|
32900
|
+
title: "Negative Volume Index",
|
|
32901
|
+
shortTitle: "Negative Volume Index",
|
|
32902
|
+
overlay: false,
|
|
32903
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32904
|
+
compute: (bars, inputs) => {
|
|
32905
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
32906
|
+
return {
|
|
32907
|
+
plots: [
|
|
32908
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32909
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32910
|
+
]
|
|
32911
|
+
};
|
|
32912
|
+
}
|
|
32913
|
+
};
|
|
32914
|
+
var pvi = {
|
|
32915
|
+
type: "positive-volume-index",
|
|
32916
|
+
title: "Positive Volume Index",
|
|
32917
|
+
shortTitle: "Positive Volume Index",
|
|
32918
|
+
overlay: false,
|
|
32919
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
32920
|
+
compute: (bars, inputs) => {
|
|
32921
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
32922
|
+
return {
|
|
32923
|
+
plots: [
|
|
32924
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
32925
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
32926
|
+
]
|
|
32927
|
+
};
|
|
32928
|
+
}
|
|
32929
|
+
};
|
|
32930
|
+
var pvt = {
|
|
32931
|
+
type: "price-volume-trend",
|
|
32932
|
+
title: "Price Volume Trend",
|
|
32933
|
+
shortTitle: "PVT",
|
|
32934
|
+
overlay: false,
|
|
32935
|
+
inputs: [colorInput()],
|
|
32936
|
+
compute: (bars, inputs) => {
|
|
32937
|
+
const c = closes(bars);
|
|
32938
|
+
const raw = bars.map((b, i) => {
|
|
32939
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
32940
|
+
const pc = c[i - 1];
|
|
32941
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
32942
|
+
});
|
|
32943
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
32944
|
+
}
|
|
32945
|
+
};
|
|
32946
|
+
var volumeOscillator = {
|
|
32947
|
+
type: "volume-oscillator",
|
|
32948
|
+
title: "Volume Oscillator",
|
|
32949
|
+
shortTitle: "Vol Osc",
|
|
32950
|
+
overlay: false,
|
|
32951
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
32952
|
+
compute: (bars, inputs) => {
|
|
32953
|
+
const vol = volumes(bars);
|
|
32954
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
32955
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
32956
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
32957
|
+
return {
|
|
32958
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
32959
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32960
|
+
};
|
|
32961
|
+
}
|
|
32962
|
+
};
|
|
32963
|
+
var pvo = {
|
|
32964
|
+
type: "pvo",
|
|
32965
|
+
title: "Percentage Volume Oscillator",
|
|
32966
|
+
shortTitle: "PVO",
|
|
32967
|
+
overlay: false,
|
|
32968
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
32969
|
+
compute: (bars, inputs) => {
|
|
32970
|
+
const vol = volumes(bars);
|
|
32971
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
32972
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
32973
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
32974
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
32975
|
+
const hist = sub(line, signal);
|
|
32976
|
+
return {
|
|
32977
|
+
plots: [
|
|
32978
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
32979
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
32980
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
32981
|
+
],
|
|
32982
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
32983
|
+
};
|
|
32984
|
+
}
|
|
32985
|
+
};
|
|
32986
|
+
var vfi = {
|
|
32987
|
+
type: "volume-flow-indicator",
|
|
32988
|
+
title: "Volume Flow Indicator",
|
|
32989
|
+
shortTitle: "Volume Flow Indicator",
|
|
32990
|
+
overlay: false,
|
|
32991
|
+
inputs: [
|
|
32992
|
+
lengthInput(130),
|
|
32993
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
32994
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
32995
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
32996
|
+
],
|
|
32997
|
+
compute: (bars, inputs) => {
|
|
32998
|
+
const len = num3(inputs, "length", 130);
|
|
32999
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
33000
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
33001
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
33002
|
+
const inter = change(typical);
|
|
33003
|
+
const vInter = stdev(inter, 30);
|
|
33004
|
+
const vol = volumes(bars);
|
|
33005
|
+
const vAve = sma(vol, len);
|
|
33006
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
33007
|
+
const raw = bars.map((b, i) => {
|
|
33008
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
33009
|
+
const v = vol[i];
|
|
33010
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33011
|
+
const m = mf[i];
|
|
33012
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
33013
|
+
const capped = Math.min(v, ave * volCoef);
|
|
33014
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
33015
|
+
});
|
|
33016
|
+
const values = bars.map((_, i) => {
|
|
33017
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
33018
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
33019
|
+
});
|
|
33020
|
+
const flowSum = sum(raw, len);
|
|
33021
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
33022
|
+
return {
|
|
33023
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
33024
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33025
|
+
};
|
|
33026
|
+
}
|
|
33027
|
+
};
|
|
33028
|
+
var intradayIntensity = {
|
|
33029
|
+
type: "intraday-intensity",
|
|
33030
|
+
title: "Intraday Intensity",
|
|
33031
|
+
shortTitle: "Intraday Intensity",
|
|
33032
|
+
overlay: false,
|
|
33033
|
+
inputs: [lengthInput(21), colorInput()],
|
|
33034
|
+
compute: (bars, inputs) => {
|
|
33035
|
+
const len = num3(inputs, "length", 21);
|
|
33036
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
33037
|
+
return {
|
|
33038
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
33039
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
33040
|
+
};
|
|
33041
|
+
}
|
|
33042
|
+
};
|
|
33043
|
+
var volumeSpecs = [
|
|
33044
|
+
obv,
|
|
33045
|
+
accDist,
|
|
33046
|
+
cmf,
|
|
33047
|
+
chaikinOscillator,
|
|
33048
|
+
eom,
|
|
33049
|
+
forceIndex,
|
|
33050
|
+
klinger,
|
|
33051
|
+
mfi,
|
|
33052
|
+
nvi,
|
|
33053
|
+
pvi,
|
|
33054
|
+
pvt,
|
|
33055
|
+
volumeOscillator,
|
|
33056
|
+
pvo,
|
|
33057
|
+
vfi,
|
|
33058
|
+
intradayIntensity
|
|
33059
|
+
];
|
|
33060
|
+
|
|
33061
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
33062
|
+
var DAY_MS2 = 864e5;
|
|
33063
|
+
function periodKey(anchor, time) {
|
|
33064
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
33065
|
+
if (anchor === "Month") {
|
|
33066
|
+
const d = new Date(time);
|
|
33067
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
33068
|
+
}
|
|
33069
|
+
return Math.floor(time / DAY_MS2);
|
|
33070
|
+
}
|
|
33071
|
+
var parabolicSar = {
|
|
33072
|
+
type: "parabolic-sar",
|
|
33073
|
+
title: "Parabolic SAR",
|
|
33074
|
+
shortTitle: "SAR",
|
|
33075
|
+
overlay: true,
|
|
33076
|
+
inputs: [
|
|
33077
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33078
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
33079
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
33080
|
+
],
|
|
33081
|
+
compute: (bars, inputs) => {
|
|
33082
|
+
const start = num3(inputs, "start", 0.02);
|
|
33083
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
33084
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
33085
|
+
const n = bars.length;
|
|
33086
|
+
const values = new Array(n).fill(Number.NaN);
|
|
33087
|
+
const colors = new Array(n).fill(null);
|
|
33088
|
+
if (n >= 2) {
|
|
33089
|
+
let long = bars[1].close >= bars[0].close;
|
|
33090
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
33091
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
33092
|
+
let af = start;
|
|
33093
|
+
for (let i = 1; i < n; i++) {
|
|
33094
|
+
const b = bars[i];
|
|
33095
|
+
sar += af * (ep - sar);
|
|
33096
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
33097
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
33098
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
33099
|
+
if (reversed) {
|
|
33100
|
+
long = !long;
|
|
33101
|
+
sar = ep;
|
|
33102
|
+
ep = long ? b.high : b.low;
|
|
33103
|
+
af = start;
|
|
33104
|
+
} else if (long && b.high > ep) {
|
|
33105
|
+
ep = b.high;
|
|
33106
|
+
af = Math.min(max, af + inc);
|
|
33107
|
+
} else if (!long && b.low < ep) {
|
|
33108
|
+
ep = b.low;
|
|
33109
|
+
af = Math.min(max, af + inc);
|
|
33110
|
+
}
|
|
33111
|
+
values[i] = sar;
|
|
33112
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
33113
|
+
}
|
|
33114
|
+
}
|
|
33115
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
33116
|
+
}
|
|
33117
|
+
};
|
|
33118
|
+
var vwap = {
|
|
33119
|
+
type: "vwap",
|
|
33120
|
+
title: "Volume Weighted Average Price",
|
|
33121
|
+
shortTitle: "VWAP",
|
|
33122
|
+
overlay: true,
|
|
33123
|
+
inputs: [
|
|
33124
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
33125
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
33126
|
+
colorInput(INFO)
|
|
33127
|
+
],
|
|
33128
|
+
compute: (bars, inputs) => {
|
|
33129
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33130
|
+
const source = str3(inputs, "source", "HLC3");
|
|
33131
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
33132
|
+
let period = Number.NaN;
|
|
33133
|
+
let cumPV = 0;
|
|
33134
|
+
let cumV = 0;
|
|
33135
|
+
for (let i = 0; i < bars.length; i++) {
|
|
33136
|
+
const b = bars[i];
|
|
33137
|
+
const key = periodKey(anchor, b.time);
|
|
33138
|
+
if (key !== period) {
|
|
33139
|
+
period = key;
|
|
33140
|
+
cumPV = 0;
|
|
33141
|
+
cumV = 0;
|
|
33142
|
+
}
|
|
33143
|
+
const v = b.volume;
|
|
33144
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
33145
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
33146
|
+
cumPV += tp * v;
|
|
33147
|
+
cumV += v;
|
|
33148
|
+
}
|
|
33149
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
33150
|
+
}
|
|
33151
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
33152
|
+
}
|
|
33153
|
+
};
|
|
33154
|
+
var pivotPoints = {
|
|
33155
|
+
type: "pivot-points",
|
|
33156
|
+
title: "Pivot Points",
|
|
33157
|
+
shortTitle: "Pivots",
|
|
33158
|
+
overlay: true,
|
|
33159
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
33160
|
+
compute: (bars, inputs) => {
|
|
33161
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
33162
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
33163
|
+
for (const b of bars) {
|
|
33164
|
+
const key = periodKey(anchor, b.time);
|
|
33165
|
+
const a = aggs.get(key);
|
|
33166
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
33167
|
+
else {
|
|
33168
|
+
a.high = Math.max(a.high, b.high);
|
|
33169
|
+
a.low = Math.min(a.low, b.low);
|
|
33170
|
+
a.close = b.close;
|
|
33171
|
+
}
|
|
33172
|
+
}
|
|
33173
|
+
const n = bars.length;
|
|
33174
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
33175
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
33176
|
+
for (let i = 0; i < n; i++) {
|
|
33177
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
33178
|
+
if (!prev2) continue;
|
|
33179
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
33180
|
+
levels.p[i] = p;
|
|
33181
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
33182
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
33183
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
33184
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
33185
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
33186
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
33187
|
+
}
|
|
33188
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
33189
|
+
return {
|
|
33190
|
+
plots: [
|
|
33191
|
+
plot("p", "P", WARNING),
|
|
33192
|
+
plot("r1", "R1", BEARISH),
|
|
33193
|
+
plot("s1", "S1", BULLISH),
|
|
33194
|
+
plot("r2", "R2", BEARISH),
|
|
33195
|
+
plot("s2", "S2", BULLISH),
|
|
33196
|
+
plot("r3", "R3", BEARISH),
|
|
33197
|
+
plot("s3", "S3", BULLISH)
|
|
33198
|
+
]
|
|
33199
|
+
};
|
|
33200
|
+
}
|
|
33201
|
+
};
|
|
33202
|
+
var fiftyTwoWeek = {
|
|
33203
|
+
type: "52-week-high-low",
|
|
33204
|
+
title: "52 Week High/Low",
|
|
33205
|
+
shortTitle: "52W H/L",
|
|
33206
|
+
overlay: true,
|
|
33207
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
33208
|
+
compute: (bars, inputs) => {
|
|
33209
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
33210
|
+
const n = bars.length;
|
|
33211
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
33212
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
33213
|
+
const maxIdx = [];
|
|
33214
|
+
const minIdx = [];
|
|
33215
|
+
let from = 0;
|
|
33216
|
+
for (let i = 0; i < n; i++) {
|
|
33217
|
+
const b = bars[i];
|
|
33218
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
33219
|
+
maxIdx.push(i);
|
|
33220
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
33221
|
+
minIdx.push(i);
|
|
33222
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
33223
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
33224
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
33225
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
33226
|
+
lo[i] = bars[minIdx[0]].low;
|
|
33227
|
+
}
|
|
33228
|
+
return {
|
|
33229
|
+
plots: [
|
|
33230
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
33231
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
33232
|
+
]
|
|
33233
|
+
};
|
|
33234
|
+
}
|
|
33235
|
+
};
|
|
33236
|
+
var zigzag = {
|
|
33237
|
+
type: "zigzag",
|
|
33238
|
+
title: "ZigZag",
|
|
33239
|
+
overlay: true,
|
|
33240
|
+
inputs: [
|
|
33241
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
33242
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
33243
|
+
colorInput()
|
|
33244
|
+
],
|
|
33245
|
+
compute: (bars, inputs) => {
|
|
33246
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
33247
|
+
const depth = num3(inputs, "depth", 10);
|
|
33248
|
+
const pivots = [];
|
|
33249
|
+
if (bars.length > 1) {
|
|
33250
|
+
let up = bars[1].close >= bars[0].close;
|
|
33251
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
33252
|
+
for (let i = 1; i < bars.length; i++) {
|
|
33253
|
+
const b = bars[i];
|
|
33254
|
+
if (up) {
|
|
33255
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
33256
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33257
|
+
pivots.push(ext);
|
|
33258
|
+
up = false;
|
|
33259
|
+
ext = { i, price: b.low };
|
|
33260
|
+
}
|
|
33261
|
+
} else if (b.low <= ext.price) {
|
|
33262
|
+
ext = { i, price: b.low };
|
|
33263
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
33264
|
+
pivots.push(ext);
|
|
33265
|
+
up = true;
|
|
33266
|
+
ext = { i, price: b.high };
|
|
33267
|
+
}
|
|
33268
|
+
}
|
|
33269
|
+
pivots.push(ext);
|
|
33270
|
+
}
|
|
33271
|
+
return {
|
|
33272
|
+
plots: [],
|
|
33273
|
+
polylines: [
|
|
33274
|
+
{
|
|
33275
|
+
key: "zigzag",
|
|
33276
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
33277
|
+
curved: false,
|
|
33278
|
+
closed: false,
|
|
33279
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
33280
|
+
lineWidth: 2,
|
|
33281
|
+
lineStyle: "solid",
|
|
33282
|
+
arrowLeft: false,
|
|
33283
|
+
arrowRight: false
|
|
33284
|
+
}
|
|
33285
|
+
]
|
|
33286
|
+
};
|
|
33287
|
+
}
|
|
33288
|
+
};
|
|
33289
|
+
var williamsFractal = {
|
|
33290
|
+
type: "williams-fractal",
|
|
33291
|
+
title: "Williams Fractal",
|
|
33292
|
+
shortTitle: "Fractals",
|
|
33293
|
+
overlay: true,
|
|
33294
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
33295
|
+
compute: (bars, inputs) => {
|
|
33296
|
+
const p = num3(inputs, "periods", 2);
|
|
33297
|
+
const markers = [];
|
|
33298
|
+
const isExtreme = (i, pick, better) => {
|
|
33299
|
+
const v = pick(bars[i]);
|
|
33300
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
33301
|
+
if (k === i) continue;
|
|
33302
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
33303
|
+
}
|
|
33304
|
+
return true;
|
|
33305
|
+
};
|
|
33306
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
33307
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
33308
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
33309
|
+
}
|
|
33310
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
33311
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
33312
|
+
}
|
|
33313
|
+
}
|
|
33314
|
+
return { plots: [], markers };
|
|
33315
|
+
}
|
|
33316
|
+
};
|
|
33317
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
33318
|
+
|
|
33319
|
+
// src/core/native-indicators/classics/index.ts
|
|
33320
|
+
var classicSpecs = [
|
|
33321
|
+
...averageSpecs,
|
|
33322
|
+
...bandSpecs,
|
|
33323
|
+
...oscillatorSpecs,
|
|
33324
|
+
...trendSpecs,
|
|
33325
|
+
...volatilitySpecs,
|
|
33326
|
+
...volumeSpecs,
|
|
33327
|
+
...overlaySpecs
|
|
33328
|
+
];
|
|
33329
|
+
function registerClassicIndicators() {
|
|
33330
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
33331
|
+
}
|
|
33332
|
+
|
|
33333
|
+
// src/Vela.ts
|
|
33334
|
+
var Vela = class {
|
|
33335
|
+
constructor(container, options = {}, deps = {}) {
|
|
33336
|
+
registerBuiltinChartTypes();
|
|
33337
|
+
registerVolume();
|
|
33338
|
+
registerVpvr();
|
|
33339
|
+
registerClassicIndicators();
|
|
33340
|
+
const element = resolveElement(container);
|
|
33341
|
+
const theme = resolveTheme(options.theme);
|
|
33342
|
+
let animZoom = true;
|
|
33343
|
+
let animPan = true;
|
|
33344
|
+
if (typeof options.animations === "boolean") {
|
|
33345
|
+
animZoom = options.animations;
|
|
33346
|
+
animPan = options.animations;
|
|
33347
|
+
} else if (options.animations) {
|
|
33348
|
+
animZoom = options.animations.zoom ?? true;
|
|
33349
|
+
animPan = options.animations.pan ?? true;
|
|
33350
|
+
}
|
|
33351
|
+
const display = {
|
|
33352
|
+
currentPriceLine: options.currentPriceLine ?? true,
|
|
33353
|
+
logScale: options.logScale ?? false,
|
|
33354
|
+
nativeBackend: options.nativeBackend ?? "auto",
|
|
33355
|
+
animZoom,
|
|
33356
|
+
animPan,
|
|
33357
|
+
glow: options.glow ?? 0,
|
|
33358
|
+
upColor: options.upColor ?? BULLISH,
|
|
33359
|
+
downColor: options.downColor ?? BEARISH,
|
|
33360
|
+
priceStyle: options.priceStyle ?? "candles"
|
|
33361
|
+
};
|
|
33362
|
+
const RendererClass = options.renderer ?? NativeRenderer;
|
|
33363
|
+
if (typeof RendererClass !== "function") {
|
|
33364
|
+
throw new Error(
|
|
33365
|
+
"[vela] options.renderer must be a renderer class, e.g. import { LwcRenderer } from 'vela/renderers/lwc'. The 'lwc'/'native' string options were removed."
|
|
33366
|
+
);
|
|
33367
|
+
}
|
|
33368
|
+
const renderer = deps.renderer ?? new RendererClass(display);
|
|
33369
|
+
const engines = deps.engines ?? [];
|
|
33370
|
+
const feed = deps.dataFeed ?? new MultiProviderFeed();
|
|
33371
|
+
const config = {
|
|
33372
|
+
market: {
|
|
33373
|
+
symbol: options.symbol,
|
|
33374
|
+
timeframe: options.timeframe,
|
|
33375
|
+
bars: options.bars,
|
|
33376
|
+
session: options.session,
|
|
33377
|
+
visibleRange: options.visibleRange,
|
|
33378
|
+
data: options.data
|
|
33379
|
+
},
|
|
33380
|
+
live: options.live ?? false,
|
|
33381
|
+
theme,
|
|
33382
|
+
defaultLanguage: options.defaultLanguage ?? engines[0]?.language ?? "pine",
|
|
33383
|
+
drawings: options.drawings,
|
|
33384
|
+
volume: options.volume ?? true
|
|
33385
|
+
// the volume indicator is on by default
|
|
33386
|
+
};
|
|
33387
|
+
this.rendererControl = new RendererControl(renderer);
|
|
33388
|
+
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
33389
|
+
this.dataControl = new DataControl(feed);
|
|
33390
|
+
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
33391
|
+
const defaults2 = rendererDefaults();
|
|
33392
|
+
if (Object.keys(defaults2).length > 0) this.rendererControl.set(defaults2);
|
|
33393
|
+
this.panesControl = new PanesControl(this.orchestrator);
|
|
33394
|
+
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
33395
|
+
}
|
|
33396
|
+
/**
|
|
33397
|
+
* Register a scripting engine so `addIndicator({ language })` can run that
|
|
33398
|
+
* language. Vela ships NO engine — install the one you need (Pine Script:
|
|
33399
|
+
* `@luxalgo/vela-pinets`) and register it, e.g.
|
|
33400
|
+
* `chart.registerEngine('pine', new PineEngine())`; without one the chart
|
|
33401
|
+
* displays candles, drawings and native indicators only. Re-registering a
|
|
33402
|
+
* language replaces it.
|
|
33403
|
+
*/
|
|
33404
|
+
registerEngine(language, engine) {
|
|
33405
|
+
this.orchestrator.registerEngine(language, engine);
|
|
33406
|
+
return this;
|
|
33407
|
+
}
|
|
33408
|
+
/** Run an indicator script on the chart's market data and render it. */
|
|
33409
|
+
addIndicator(source, options) {
|
|
33410
|
+
return this.orchestrator.addIndicator(source, options);
|
|
33411
|
+
}
|
|
33412
|
+
/**
|
|
33413
|
+
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
33414
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
33415
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
33416
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
33417
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
33418
|
+
*/
|
|
33419
|
+
addNativeIndicator(type, options) {
|
|
33420
|
+
return this.orchestrator.addNativeIndicator(type, options);
|
|
33421
|
+
}
|
|
33422
|
+
/**
|
|
33423
|
+
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
33424
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
33425
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
33426
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
33427
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
33428
|
+
* provider (a type may need data the symbol lacks).
|
|
33429
|
+
*/
|
|
33430
|
+
availableNativeIndicators() {
|
|
33431
|
+
return this.orchestrator.availableNativeIndicators();
|
|
33432
|
+
}
|
|
33433
|
+
/**
|
|
33434
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
33435
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
33436
|
+
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
33437
|
+
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
33438
|
+
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
33439
|
+
*/
|
|
33440
|
+
presentNativeIndicators() {
|
|
33441
|
+
return this.orchestrator.presentNativeIndicators();
|
|
33442
|
+
}
|
|
33443
|
+
/** Live handles of every indicator currently on the chart (script + native) — drive
|
|
33444
|
+
* host panels (object trees, indicator lists) with per-id visibility/removal. */
|
|
33445
|
+
indicators() {
|
|
33446
|
+
return this.orchestrator.listIndicators();
|
|
33447
|
+
}
|
|
33448
|
+
/**
|
|
33449
|
+
* Execute an indicator script and INJECT it only if the run succeeds — the seam for
|
|
33450
|
+
* host editors and consoles. Resolves `{ ok: true, handle }` after the first
|
|
33451
|
+
* successful evaluation, or `{ ok: false, error }` after a compile/runtime failure —
|
|
33452
|
+
* in which case the failed indicator is removed again (no dead legend row).
|
|
33453
|
+
* Never rejects.
|
|
33454
|
+
*/
|
|
33455
|
+
/**
|
|
33456
|
+
* Execute a script and resolve its FIRST computed run — the data-out door for host
|
|
33457
|
+
* editors, consoles and dashboards. The script is injected only if it runs (a failure
|
|
33458
|
+
* removes it again, leaving no dead legend row), and the result carries the run itself
|
|
33459
|
+
* plus the controls for what it put on the chart: `onUpdate` to follow later runs,
|
|
33460
|
+
* `remove` to take it off. Never rejects.
|
|
33461
|
+
*
|
|
33462
|
+
* `runScript` is `runIndicator` with the run as its payload rather than a handle to go
|
|
33463
|
+
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
33464
|
+
*/
|
|
33465
|
+
runScript(source, options) {
|
|
33466
|
+
const handle = this.addIndicator(source, options);
|
|
33467
|
+
const updates = /* @__PURE__ */ new Set();
|
|
33468
|
+
const drop = () => {
|
|
33469
|
+
try {
|
|
33470
|
+
handle.remove();
|
|
33471
|
+
} catch {
|
|
33472
|
+
}
|
|
33473
|
+
};
|
|
33474
|
+
return new Promise((resolve) => {
|
|
33475
|
+
let settled = false;
|
|
33476
|
+
const offRun = this.on("script:run", (run) => {
|
|
33477
|
+
if (run.id !== handle.id) return;
|
|
33478
|
+
if (settled) {
|
|
33479
|
+
for (const handler of updates) handler(run);
|
|
33480
|
+
return;
|
|
33481
|
+
}
|
|
33482
|
+
settled = true;
|
|
33483
|
+
offError();
|
|
33484
|
+
resolve({
|
|
33485
|
+
ok: true,
|
|
33486
|
+
run,
|
|
33487
|
+
error: null,
|
|
33488
|
+
onUpdate: (handler) => {
|
|
33489
|
+
updates.add(handler);
|
|
33490
|
+
return () => updates.delete(handler);
|
|
33491
|
+
},
|
|
33492
|
+
remove: () => {
|
|
33493
|
+
offRun();
|
|
33494
|
+
updates.clear();
|
|
33495
|
+
drop();
|
|
33496
|
+
}
|
|
33497
|
+
});
|
|
33498
|
+
});
|
|
33499
|
+
const offError = handle.on("error", ({ error }) => {
|
|
33500
|
+
if (settled) return;
|
|
33501
|
+
settled = true;
|
|
33502
|
+
offRun();
|
|
33503
|
+
offError();
|
|
33504
|
+
drop();
|
|
33505
|
+
resolve({ ok: false, run: null, error, onUpdate: () => () => void 0, remove: () => void 0 });
|
|
33506
|
+
});
|
|
33507
|
+
});
|
|
33508
|
+
}
|
|
33509
|
+
runIndicator(source, options) {
|
|
33510
|
+
const handle = this.addIndicator(source, options);
|
|
33511
|
+
return new Promise((resolve) => {
|
|
33512
|
+
const offReady = handle.on("ready", () => {
|
|
33513
|
+
offReady();
|
|
33514
|
+
offError();
|
|
33515
|
+
resolve({ ok: true, handle, error: null, context: null });
|
|
33516
|
+
});
|
|
33517
|
+
const offError = handle.on("error", ({ error }) => {
|
|
33518
|
+
offReady();
|
|
33519
|
+
offError();
|
|
33520
|
+
void handle.context().catch(() => null).then((context) => {
|
|
33521
|
+
try {
|
|
33522
|
+
handle.remove();
|
|
33523
|
+
} catch {
|
|
33524
|
+
}
|
|
33525
|
+
resolve({ ok: false, handle: null, error, context });
|
|
33526
|
+
});
|
|
33527
|
+
});
|
|
33528
|
+
});
|
|
33529
|
+
}
|
|
33530
|
+
/**
|
|
33531
|
+
* Switch the chart's market IN PLACE — symbol, provider, timeframe, depth, or offline
|
|
33532
|
+
* data — WITHOUT destroying the chart: indicators re-execute over the new bars, native
|
|
33533
|
+
* indicators restart, and panes, user drawings, renderer config and event
|
|
33534
|
+
* subscriptions all survive. Resolves once the new market's history is painted (a
|
|
33535
|
+
* deep backfill continues behind it — await {@link historyComplete}); a call
|
|
33536
|
+
* superseded by a newer `setMarket` resolves silently. Emits `market:changed`
|
|
33537
|
+
* (with the previous identity) when the market identity changed.
|
|
33538
|
+
*/
|
|
33539
|
+
setMarket(next) {
|
|
33540
|
+
return this.orchestrator.setMarket(next);
|
|
33541
|
+
}
|
|
33542
|
+
/** The current market identity — the read counterpart of {@link setMarket}. A snapshot
|
|
33543
|
+
* of the REQUESTED market: it reflects an in-flight switch immediately (before the
|
|
33544
|
+
* new bars land). Listen to `market:changed` for committed identity changes. */
|
|
33545
|
+
get market() {
|
|
33546
|
+
return this.orchestrator.marketSnapshot();
|
|
33547
|
+
}
|
|
33548
|
+
/** Resolves once the chart is painted and interactive. For a symbol-backed chart this
|
|
33549
|
+
* awaits a provider being registered that resolves the symbol (the parked load). On a
|
|
33550
|
+
* ranged feed the first paint is a small recent head (~200 bars) and the rest of the
|
|
31217
33551
|
* history keeps backfilling BEHIND this — await {@link historyComplete} for the full
|
|
31218
33552
|
* depth. Distinct from `chart.data.ready()`, which awaits only the provider symbol
|
|
31219
33553
|
* indexes. */
|
|
@@ -31465,12 +33799,6 @@ function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_
|
|
|
31465
33799
|
return Math.round(Math.min(Math.max(px, lo), hi));
|
|
31466
33800
|
}
|
|
31467
33801
|
|
|
31468
|
-
// src/core/model/identity.ts
|
|
31469
|
-
function stableSeriesId(parts) {
|
|
31470
|
-
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
31471
|
-
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
31472
|
-
}
|
|
31473
|
-
|
|
31474
33802
|
exports.ACCENT = ACCENT;
|
|
31475
33803
|
exports.ACCENT_BRIGHT = ACCENT_BRIGHT;
|
|
31476
33804
|
exports.BEARISH = BEARISH;
|