@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/widget.cjs
CHANGED
|
@@ -667,7 +667,7 @@ function attachForming(closed, forming) {
|
|
|
667
667
|
|
|
668
668
|
// src/data/MultiProviderFeed.ts
|
|
669
669
|
function intervalMs(timeframe) {
|
|
670
|
-
const
|
|
670
|
+
const map2 = {
|
|
671
671
|
"1m": 6e4,
|
|
672
672
|
"3m": 18e4,
|
|
673
673
|
"5m": 3e5,
|
|
@@ -689,7 +689,7 @@ function intervalMs(timeframe) {
|
|
|
689
689
|
W: 6048e5,
|
|
690
690
|
M: 2592e6
|
|
691
691
|
};
|
|
692
|
-
return
|
|
692
|
+
return map2[timeframe] ?? 36e5;
|
|
693
693
|
}
|
|
694
694
|
var MultiProviderFeed = class {
|
|
695
695
|
constructor(store = sharedBarStore) {
|
|
@@ -1714,19 +1714,19 @@ var Surface = class _Surface {
|
|
|
1714
1714
|
}
|
|
1715
1715
|
setItems(items) {
|
|
1716
1716
|
this.items = items;
|
|
1717
|
-
for (const
|
|
1717
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1718
1718
|
this.subs.clear();
|
|
1719
1719
|
this.build();
|
|
1720
1720
|
const api = this.api;
|
|
1721
|
-
for (const
|
|
1722
|
-
api.setChild(
|
|
1723
|
-
|
|
1721
|
+
for (const sub2 of this.subs.values()) {
|
|
1722
|
+
api.setChild(sub2.handle.service);
|
|
1723
|
+
sub2.api.setParent(this.handle.service);
|
|
1724
1724
|
}
|
|
1725
1725
|
this.handle.flush();
|
|
1726
1726
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
1727
1727
|
}
|
|
1728
1728
|
destroy() {
|
|
1729
|
-
for (const
|
|
1729
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1730
1730
|
this.subs.clear();
|
|
1731
1731
|
this.handle.stop();
|
|
1732
1732
|
this.positioner.remove();
|
|
@@ -1740,9 +1740,9 @@ var Surface = class _Surface {
|
|
|
1740
1740
|
for (const li of this.list.children) {
|
|
1741
1741
|
const id = li.dataset.veiId;
|
|
1742
1742
|
if (!id) continue;
|
|
1743
|
-
const
|
|
1744
|
-
if (
|
|
1745
|
-
vanilla.spreadProps(li, api.getTriggerItemProps(
|
|
1743
|
+
const sub2 = this.subs.get(id);
|
|
1744
|
+
if (sub2) {
|
|
1745
|
+
vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
1746
1746
|
continue;
|
|
1747
1747
|
}
|
|
1748
1748
|
const item = this.byId(id);
|
|
@@ -1819,7 +1819,7 @@ var Surface = class _Surface {
|
|
|
1819
1819
|
const arrow = iconEl("chevron-right", doc);
|
|
1820
1820
|
arrow.classList.add("vela-menu-arrow");
|
|
1821
1821
|
li.appendChild(arrow);
|
|
1822
|
-
const
|
|
1822
|
+
const sub2 = new _Surface(doc, {
|
|
1823
1823
|
host: this.host,
|
|
1824
1824
|
placement: "right-start",
|
|
1825
1825
|
onSelect: this.onSelect,
|
|
@@ -1827,8 +1827,8 @@ var Surface = class _Surface {
|
|
|
1827
1827
|
checkmarks: this.checkmarks,
|
|
1828
1828
|
id: `${this.mid}--${item.id}`
|
|
1829
1829
|
});
|
|
1830
|
-
|
|
1831
|
-
this.subs.set(item.id,
|
|
1830
|
+
sub2.setItems(item.submenu ?? []);
|
|
1831
|
+
this.subs.set(item.id, sub2);
|
|
1832
1832
|
}
|
|
1833
1833
|
this.list.appendChild(li);
|
|
1834
1834
|
}
|
|
@@ -3528,8 +3528,21 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3528
3528
|
};
|
|
3529
3529
|
const recentRow = document.createElement("div");
|
|
3530
3530
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
3531
|
+
const recentSwatches = document.createElement("div");
|
|
3532
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
3533
|
+
const add = document.createElement("label");
|
|
3534
|
+
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;`;
|
|
3535
|
+
add.textContent = "+";
|
|
3536
|
+
const customInput = document.createElement("input");
|
|
3537
|
+
customInput.type = "color";
|
|
3538
|
+
customInput.value = curHex;
|
|
3539
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
3540
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
3541
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
3542
|
+
add.appendChild(customInput);
|
|
3543
|
+
recentRow.append(recentSwatches, add);
|
|
3531
3544
|
const renderRecents = () => {
|
|
3532
|
-
|
|
3545
|
+
recentSwatches.replaceChildren();
|
|
3533
3546
|
for (const c of recents) {
|
|
3534
3547
|
const sw = document.createElement("button");
|
|
3535
3548
|
sw.type = "button";
|
|
@@ -3538,18 +3551,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3538
3551
|
e.stopPropagation();
|
|
3539
3552
|
pickHex(c);
|
|
3540
3553
|
});
|
|
3541
|
-
|
|
3554
|
+
recentSwatches.appendChild(sw);
|
|
3542
3555
|
}
|
|
3543
|
-
const add = document.createElement("label");
|
|
3544
|
-
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;`;
|
|
3545
|
-
add.textContent = "+";
|
|
3546
|
-
const input = document.createElement("input");
|
|
3547
|
-
input.type = "color";
|
|
3548
|
-
input.value = curHex;
|
|
3549
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
3550
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
3551
|
-
add.appendChild(input);
|
|
3552
|
-
recentRow.appendChild(add);
|
|
3553
3556
|
};
|
|
3554
3557
|
const opLabel = document.createElement("div");
|
|
3555
3558
|
opLabel.textContent = "Opacity";
|
|
@@ -3589,14 +3592,18 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3589
3592
|
function emit() {
|
|
3590
3593
|
onChange(combineColor(curHex, curAlpha));
|
|
3591
3594
|
}
|
|
3592
|
-
function
|
|
3595
|
+
function previewHex(hex) {
|
|
3593
3596
|
curHex = splitColor(hex).hex6;
|
|
3594
|
-
addRecent(curHex);
|
|
3595
3597
|
paintSelection();
|
|
3596
3598
|
paintOpacity();
|
|
3597
|
-
if (custom) renderRecents();
|
|
3598
3599
|
emit();
|
|
3599
3600
|
}
|
|
3601
|
+
function pickHex(hex, custom = false) {
|
|
3602
|
+
previewHex(hex);
|
|
3603
|
+
addRecent(curHex);
|
|
3604
|
+
customInput.value = curHex;
|
|
3605
|
+
if (custom) renderRecents();
|
|
3606
|
+
}
|
|
3600
3607
|
renderRecents();
|
|
3601
3608
|
paintSelection();
|
|
3602
3609
|
paintOpacity();
|
|
@@ -5167,9 +5174,9 @@ function factoryResetConfig(factory) {
|
|
|
5167
5174
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
5168
5175
|
addRows(inst.rows);
|
|
5169
5176
|
}
|
|
5170
|
-
for (const
|
|
5171
|
-
if (
|
|
5172
|
-
addRows(
|
|
5177
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5178
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
5179
|
+
addRows(sub2.rows);
|
|
5173
5180
|
}
|
|
5174
5181
|
if (!section.instances) addRows(section.rows);
|
|
5175
5182
|
bag[t.id] = defaults2;
|
|
@@ -14098,17 +14105,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
14098
14105
|
}
|
|
14099
14106
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
14100
14107
|
var CSS8 = `
|
|
14101
|
-
/*
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
z-index: 1;
|
|
14108
|
-
background: var(--vela-surface);
|
|
14109
|
-
margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
|
|
14110
|
-
padding: var(--vela-space-4) var(--vela-space-4) 0;
|
|
14111
|
-
}
|
|
14108
|
+
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
14109
|
+
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
14110
|
+
scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
|
|
14111
|
+
scroller's content box, so the head lands one padding below the top and covers
|
|
14112
|
+
the first row. */
|
|
14113
|
+
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
14112
14114
|
.vela-sp-searchrow {
|
|
14113
14115
|
display: flex;
|
|
14114
14116
|
align-items: center;
|
|
@@ -14133,12 +14135,13 @@ var CSS8 = `
|
|
|
14133
14135
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
14134
14136
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
14135
14137
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
14136
|
-
overflowing the body, and the result list stops capping itself \u2014
|
|
14137
|
-
the
|
|
14138
|
+
overflowing the body, and the result list stops capping itself \u2014 it fills the
|
|
14139
|
+
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
14140
|
+
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
14138
14141
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
14139
14142
|
[data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
|
|
14140
14143
|
[data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
|
|
14141
|
-
[data-layout='mobile'] .vela-sp-list { max-height: none; }
|
|
14144
|
+
[data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
|
|
14142
14145
|
.vela-sp-tab {
|
|
14143
14146
|
all: unset;
|
|
14144
14147
|
padding: 3px 10px;
|
|
@@ -14150,7 +14153,12 @@ var CSS8 = `
|
|
|
14150
14153
|
}
|
|
14151
14154
|
.vela-sp-tab:hover { color: var(--vela-fg); }
|
|
14152
14155
|
.vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
14153
|
-
.vela-sp-list {
|
|
14156
|
+
.vela-sp-list {
|
|
14157
|
+
min-height: 0;
|
|
14158
|
+
margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
|
|
14159
|
+
max-height: 46vh;
|
|
14160
|
+
overflow: auto;
|
|
14161
|
+
}
|
|
14154
14162
|
.vela-sp-list::-webkit-scrollbar { width: 8px; }
|
|
14155
14163
|
.vela-sp-list::-webkit-scrollbar-thumb {
|
|
14156
14164
|
background: var(--vela-scroll);
|
|
@@ -14243,8 +14251,8 @@ var SymbolPicker = class {
|
|
|
14243
14251
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14244
14252
|
this.tabs = doc.createElement("div");
|
|
14245
14253
|
this.tabs.className = "vela-sp-tabs";
|
|
14246
|
-
const
|
|
14247
|
-
|
|
14254
|
+
const head = doc.createElement("div");
|
|
14255
|
+
head.className = "vela-sp-head";
|
|
14248
14256
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14249
14257
|
const b = doc.createElement("button");
|
|
14250
14258
|
b.className = "vela-sp-tab";
|
|
@@ -14258,7 +14266,7 @@ var SymbolPicker = class {
|
|
|
14258
14266
|
});
|
|
14259
14267
|
this.tabs.appendChild(b);
|
|
14260
14268
|
}
|
|
14261
|
-
|
|
14269
|
+
head.append(searchRow, this.tabs);
|
|
14262
14270
|
this.list = doc.createElement("div");
|
|
14263
14271
|
this.list.className = "vela-sp-list";
|
|
14264
14272
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14271,7 +14279,8 @@ var SymbolPicker = class {
|
|
|
14271
14279
|
title: "Symbol Search",
|
|
14272
14280
|
host: opts.host,
|
|
14273
14281
|
closeOnInteractOutside: true,
|
|
14274
|
-
|
|
14282
|
+
flush: true,
|
|
14283
|
+
content: (body) => body.append(head, this.list),
|
|
14275
14284
|
onOpenChange: (open2) => {
|
|
14276
14285
|
if (open2) {
|
|
14277
14286
|
this.input.value = this.seed.toUpperCase();
|
|
@@ -16042,11 +16051,11 @@ var PriceScaleDrawer = class {
|
|
|
16042
16051
|
for (const item of items) {
|
|
16043
16052
|
if (item.id === "labels" && item.submenu) {
|
|
16044
16053
|
section("Labels");
|
|
16045
|
-
for (const
|
|
16046
|
-
row(
|
|
16047
|
-
checked:
|
|
16054
|
+
for (const sub2 of item.submenu) {
|
|
16055
|
+
row(sub2.label, {
|
|
16056
|
+
checked: sub2.checked,
|
|
16048
16057
|
onClick: () => {
|
|
16049
|
-
const feature =
|
|
16058
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
16050
16059
|
chart.renderer.set(feature, !this.flag(feature));
|
|
16051
16060
|
this.render();
|
|
16052
16061
|
}
|
|
@@ -16056,11 +16065,11 @@ var PriceScaleDrawer = class {
|
|
|
16056
16065
|
}
|
|
16057
16066
|
if (item.id === "levels" && item.submenu) {
|
|
16058
16067
|
section("Levels");
|
|
16059
|
-
for (const
|
|
16060
|
-
row(
|
|
16061
|
-
checked:
|
|
16068
|
+
for (const sub2 of item.submenu) {
|
|
16069
|
+
row(sub2.label, {
|
|
16070
|
+
checked: sub2.checked,
|
|
16062
16071
|
onClick: () => {
|
|
16063
|
-
const feature =
|
|
16072
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
16064
16073
|
chart.renderer.set(feature, !this.flag(feature));
|
|
16065
16074
|
this.render();
|
|
16066
16075
|
}
|
|
@@ -16634,8 +16643,8 @@ var DrawingController = class {
|
|
|
16634
16643
|
this.port?.setExternalGhost?.(doc);
|
|
16635
16644
|
}
|
|
16636
16645
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
16637
|
-
setToolShortcuts(
|
|
16638
|
-
this.port?.setToolShortcuts?.(
|
|
16646
|
+
setToolShortcuts(map2) {
|
|
16647
|
+
this.port?.setToolShortcuts?.(map2);
|
|
16639
16648
|
}
|
|
16640
16649
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
16641
16650
|
add(type, init = {}) {
|
|
@@ -17306,8 +17315,8 @@ var DrawingToolbar = class {
|
|
|
17306
17315
|
}
|
|
17307
17316
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
17308
17317
|
* right edge of the flyout rows, beside the favorite star. */
|
|
17309
|
-
setShortcuts(
|
|
17310
|
-
this.shortcuts = new Map(Object.entries(
|
|
17318
|
+
setShortcuts(map2) {
|
|
17319
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
17311
17320
|
}
|
|
17312
17321
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
17313
17322
|
paintStar(el, type) {
|
|
@@ -18254,7 +18263,7 @@ var LiveSession = class {
|
|
|
18254
18263
|
|
|
18255
18264
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
18256
18265
|
var IndicatorHandleImpl = class {
|
|
18257
|
-
constructor(id, title, controller, source) {
|
|
18266
|
+
constructor(id, title, controller, source, nativeType) {
|
|
18258
18267
|
this.controller = controller;
|
|
18259
18268
|
this.schema = [];
|
|
18260
18269
|
this.propsSchema = [];
|
|
@@ -18263,6 +18272,7 @@ var IndicatorHandleImpl = class {
|
|
|
18263
18272
|
this.id = id;
|
|
18264
18273
|
this.title = title;
|
|
18265
18274
|
if (source !== void 0) this.source = source;
|
|
18275
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
18266
18276
|
}
|
|
18267
18277
|
get inputs() {
|
|
18268
18278
|
return this.schema;
|
|
@@ -18353,24 +18363,24 @@ function summarizeModel(model) {
|
|
|
18353
18363
|
}
|
|
18354
18364
|
function inspectModels(models) {
|
|
18355
18365
|
const indicators = models.map(summarizeModel);
|
|
18356
|
-
const
|
|
18366
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
18357
18367
|
return {
|
|
18358
18368
|
indicators,
|
|
18359
18369
|
totals: {
|
|
18360
18370
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
18361
|
-
series:
|
|
18362
|
-
fills:
|
|
18363
|
-
backgrounds:
|
|
18364
|
-
priceLines:
|
|
18365
|
-
lines:
|
|
18366
|
-
boxes:
|
|
18367
|
-
labels:
|
|
18368
|
-
polylines:
|
|
18369
|
-
linefills:
|
|
18370
|
-
tables:
|
|
18371
|
-
barColors:
|
|
18372
|
-
trades:
|
|
18373
|
-
forcedOverlay:
|
|
18371
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
18372
|
+
fills: sum2((s) => s.fills),
|
|
18373
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
18374
|
+
priceLines: sum2((s) => s.priceLines),
|
|
18375
|
+
lines: sum2((s) => s.lines),
|
|
18376
|
+
boxes: sum2((s) => s.boxes),
|
|
18377
|
+
labels: sum2((s) => s.labels),
|
|
18378
|
+
polylines: sum2((s) => s.polylines),
|
|
18379
|
+
linefills: sum2((s) => s.linefills),
|
|
18380
|
+
tables: sum2((s) => s.tables),
|
|
18381
|
+
barColors: sum2((s) => s.barColors),
|
|
18382
|
+
trades: sum2((s) => s.trades),
|
|
18383
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
18374
18384
|
}
|
|
18375
18385
|
};
|
|
18376
18386
|
}
|
|
@@ -19439,16 +19449,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19439
19449
|
/**
|
|
19440
19450
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
19441
19451
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
19442
|
-
* INSTANCE per type
|
|
19443
|
-
*
|
|
19452
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
19453
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
19454
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
19455
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
19444
19456
|
*/
|
|
19445
19457
|
addNativeIndicator(type, options = {}) {
|
|
19446
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19447
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
19448
19458
|
const descriptor = getNativeIndicator(type);
|
|
19459
|
+
if (!descriptor?.multiInstance) {
|
|
19460
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19461
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
19462
|
+
}
|
|
19449
19463
|
const id = this.registry.nextId("native");
|
|
19450
19464
|
const title = descriptor?.title ?? type;
|
|
19451
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
19465
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
19452
19466
|
this.handles.set(id, handle);
|
|
19453
19467
|
if (!descriptor) {
|
|
19454
19468
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -19460,16 +19474,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19460
19474
|
void this.startNativeIndicator(id, handle);
|
|
19461
19475
|
return handle;
|
|
19462
19476
|
}
|
|
19463
|
-
/**
|
|
19464
|
-
*
|
|
19465
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
19466
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
19467
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
19468
|
-
*/
|
|
19469
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
19477
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
19478
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
19470
19479
|
presentNativeIndicators() {
|
|
19471
19480
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
19472
19481
|
}
|
|
19482
|
+
/**
|
|
19483
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
19484
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
19485
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
19486
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
19487
|
+
* probe the provider for a required capability.
|
|
19488
|
+
*/
|
|
19473
19489
|
async availableNativeIndicators() {
|
|
19474
19490
|
const symbol = this.qualifiedSymbol();
|
|
19475
19491
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -19479,7 +19495,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19479
19495
|
title: d.title,
|
|
19480
19496
|
supported: await this.isNativeSupported(d, symbol),
|
|
19481
19497
|
present: present.has(d.type),
|
|
19482
|
-
beta: d.beta
|
|
19498
|
+
beta: d.beta,
|
|
19499
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
19483
19500
|
}))
|
|
19484
19501
|
);
|
|
19485
19502
|
}
|
|
@@ -20619,8 +20636,8 @@ var DrawingsControl = class {
|
|
|
20619
20636
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
20620
20637
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
20621
20638
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
20622
|
-
setToolShortcuts(
|
|
20623
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
20639
|
+
setToolShortcuts(map2) {
|
|
20640
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
20624
20641
|
return this;
|
|
20625
20642
|
}
|
|
20626
20643
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -22101,8 +22118,8 @@ var InputsUI = class {
|
|
|
22101
22118
|
lg.style.top = `${top}px`;
|
|
22102
22119
|
}
|
|
22103
22120
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22104
|
-
setCollapsedPanes(
|
|
22105
|
-
this.paneCollapse =
|
|
22121
|
+
setCollapsedPanes(map2) {
|
|
22122
|
+
this.paneCollapse = map2;
|
|
22106
22123
|
}
|
|
22107
22124
|
/**
|
|
22108
22125
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -24111,11 +24128,11 @@ function mergeBarColors(indicators) {
|
|
|
24111
24128
|
}
|
|
24112
24129
|
}
|
|
24113
24130
|
if (!any) return EMPTY_BARCOLORS;
|
|
24114
|
-
const
|
|
24131
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24115
24132
|
for (const m of indicators.values()) {
|
|
24116
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24133
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24117
24134
|
}
|
|
24118
|
-
return
|
|
24135
|
+
return map2;
|
|
24119
24136
|
}
|
|
24120
24137
|
|
|
24121
24138
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24412,9 +24429,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
24412
24429
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24413
24430
|
return rightOffset + deltaX / barSpacing;
|
|
24414
24431
|
}
|
|
24415
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24432
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
24416
24433
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24417
|
-
if (
|
|
24434
|
+
if (shift2) return deltaY;
|
|
24418
24435
|
return null;
|
|
24419
24436
|
}
|
|
24420
24437
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -26036,11 +26053,11 @@ function mergeBarColors2(indicators) {
|
|
|
26036
26053
|
}
|
|
26037
26054
|
}
|
|
26038
26055
|
if (!any) return EMPTY_BARCOLORS2;
|
|
26039
|
-
const
|
|
26056
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
26040
26057
|
for (const m of indicators.values()) {
|
|
26041
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26058
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
26042
26059
|
}
|
|
26043
|
-
return
|
|
26060
|
+
return map2;
|
|
26044
26061
|
}
|
|
26045
26062
|
|
|
26046
26063
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27679,9 +27696,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
27679
27696
|
};
|
|
27680
27697
|
if (section.rows) addRows(section.rows);
|
|
27681
27698
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27682
|
-
for (const
|
|
27683
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27684
|
-
addRows(
|
|
27699
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27700
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27701
|
+
addRows(sub2.rows);
|
|
27685
27702
|
}
|
|
27686
27703
|
}
|
|
27687
27704
|
for (const hs of hostSections) {
|
|
@@ -28240,14 +28257,14 @@ var SettingsDialog = class {
|
|
|
28240
28257
|
};
|
|
28241
28258
|
if (section.rows) seed(section.rows);
|
|
28242
28259
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28243
|
-
for (const
|
|
28260
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28244
28261
|
for (const inst of section.instances ?? []) {
|
|
28245
28262
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28246
28263
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28247
28264
|
}
|
|
28248
|
-
for (const
|
|
28249
|
-
if (!
|
|
28250
|
-
bag[
|
|
28265
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28266
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28267
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28251
28268
|
}
|
|
28252
28269
|
const refreshers = [];
|
|
28253
28270
|
const put = (key, v) => {
|
|
@@ -28264,16 +28281,16 @@ var SettingsDialog = class {
|
|
|
28264
28281
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28265
28282
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28266
28283
|
}
|
|
28267
|
-
for (const
|
|
28268
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28269
|
-
const rows = filterHiddenRows(
|
|
28284
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28285
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28286
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28270
28287
|
if (rows.length === 0) continue;
|
|
28271
|
-
const subMarker = this.section(
|
|
28288
|
+
const subMarker = this.section(sub2.title);
|
|
28272
28289
|
subMarker.dataset.sdStyle = typeId;
|
|
28273
28290
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28274
28291
|
subMarker.dataset.sdSub = "1";
|
|
28275
28292
|
body.append(subMarker);
|
|
28276
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28293
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28277
28294
|
}
|
|
28278
28295
|
for (const r of refreshers) r();
|
|
28279
28296
|
}
|
|
@@ -30368,11 +30385,11 @@ var DrawingInteraction = class {
|
|
|
30368
30385
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30369
30386
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30370
30387
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30371
|
-
down(x, y, mode = "off",
|
|
30388
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
30372
30389
|
const proj = this.deps.projector();
|
|
30373
30390
|
if (this.state.kind === "placing") {
|
|
30374
30391
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30375
|
-
const pivot =
|
|
30392
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30376
30393
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30377
30394
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30378
30395
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30391,7 +30408,7 @@ var DrawingInteraction = class {
|
|
|
30391
30408
|
}
|
|
30392
30409
|
const hit = this.hitAt(x, y);
|
|
30393
30410
|
if (hit) {
|
|
30394
|
-
if (
|
|
30411
|
+
if (shift2) {
|
|
30395
30412
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30396
30413
|
return;
|
|
30397
30414
|
}
|
|
@@ -30401,7 +30418,7 @@ var DrawingInteraction = class {
|
|
|
30401
30418
|
}
|
|
30402
30419
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30403
30420
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30404
|
-
move(x, y, mode = "off",
|
|
30421
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
30405
30422
|
if (this.state.kind === "idle") {
|
|
30406
30423
|
const tool = this.deps.activeTool();
|
|
30407
30424
|
const had = this.snapAt != null;
|
|
@@ -30415,7 +30432,7 @@ var DrawingInteraction = class {
|
|
|
30415
30432
|
this.captureFreehand(x, y);
|
|
30416
30433
|
return;
|
|
30417
30434
|
}
|
|
30418
|
-
const pivot =
|
|
30435
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30419
30436
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30420
30437
|
this.deps.changed();
|
|
30421
30438
|
return;
|
|
@@ -30428,7 +30445,7 @@ var DrawingInteraction = class {
|
|
|
30428
30445
|
if (this.state.handle >= 0) {
|
|
30429
30446
|
const a = d.anchors[this.state.handle];
|
|
30430
30447
|
if (a) {
|
|
30431
|
-
const pivot =
|
|
30448
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30432
30449
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30433
30450
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30434
30451
|
}
|
|
@@ -31126,21 +31143,21 @@ var DrawingSettingsPopup = class {
|
|
|
31126
31143
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31127
31144
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31128
31145
|
}
|
|
31129
|
-
const
|
|
31130
|
-
if (paths.has("vwap.midColor") &&
|
|
31146
|
+
const vwap2 = drawing.vwap;
|
|
31147
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31131
31148
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31132
31149
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
31133
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
31134
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
31150
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31151
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31135
31152
|
bar.appendChild(
|
|
31136
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31153
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31137
31154
|
label: (v) => `${v}\u03C3`,
|
|
31138
31155
|
labelInTrigger: true
|
|
31139
31156
|
})
|
|
31140
31157
|
);
|
|
31141
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
31142
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
31143
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
31158
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31159
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31160
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31144
31161
|
}
|
|
31145
31162
|
if (paths.has("maxCurvature")) {
|
|
31146
31163
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32157,8 +32174,8 @@ var UserDrawingController = class {
|
|
|
32157
32174
|
this.toolbar.setFavorites(types);
|
|
32158
32175
|
}
|
|
32159
32176
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
32160
|
-
setToolShortcuts(
|
|
32161
|
-
this.toolbar.setShortcuts(
|
|
32177
|
+
setToolShortcuts(map2) {
|
|
32178
|
+
this.toolbar.setShortcuts(map2);
|
|
32162
32179
|
}
|
|
32163
32180
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
32164
32181
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -32276,7 +32293,7 @@ var UserDrawingController = class {
|
|
|
32276
32293
|
this.render();
|
|
32277
32294
|
}
|
|
32278
32295
|
}
|
|
32279
|
-
pointerDown(x, y, snap = "off",
|
|
32296
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
32280
32297
|
if (this.eraserMode) {
|
|
32281
32298
|
this.erasing = true;
|
|
32282
32299
|
this.deleteAt(x, y);
|
|
@@ -32296,9 +32313,9 @@ var UserDrawingController = class {
|
|
|
32296
32313
|
return;
|
|
32297
32314
|
}
|
|
32298
32315
|
}
|
|
32299
|
-
this.interaction.down(x, y, snap,
|
|
32316
|
+
this.interaction.down(x, y, snap, shift2);
|
|
32300
32317
|
}
|
|
32301
|
-
pointerMove(x, y, snap = "off",
|
|
32318
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
32302
32319
|
if (this.eraserMode) {
|
|
32303
32320
|
if (this.erasing) this.deleteAt(x, y);
|
|
32304
32321
|
return;
|
|
@@ -32309,7 +32326,7 @@ var UserDrawingController = class {
|
|
|
32309
32326
|
this.render();
|
|
32310
32327
|
return;
|
|
32311
32328
|
}
|
|
32312
|
-
this.interaction.move(x, y, snap,
|
|
32329
|
+
this.interaction.move(x, y, snap, shift2);
|
|
32313
32330
|
this.updateHover(x, y);
|
|
32314
32331
|
}
|
|
32315
32332
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -32986,14 +33003,14 @@ function layoutTable(ctx, t, args) {
|
|
|
32986
33003
|
}
|
|
32987
33004
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
32988
33005
|
if (box.cs > 1) {
|
|
32989
|
-
let
|
|
32990
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
32991
|
-
if (w2 >
|
|
33006
|
+
let sum2 = 0;
|
|
33007
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
33008
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
32992
33009
|
}
|
|
32993
33010
|
if (box.rs > 1) {
|
|
32994
|
-
let
|
|
32995
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
32996
|
-
if (h2 >
|
|
33011
|
+
let sum2 = 0;
|
|
33012
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
33013
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
32997
33014
|
}
|
|
32998
33015
|
}
|
|
32999
33016
|
let w = 0;
|
|
@@ -33580,24 +33597,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
33580
33597
|
drawSvgShapes(el, alpha);
|
|
33581
33598
|
continue;
|
|
33582
33599
|
}
|
|
33583
|
-
const
|
|
33600
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
33584
33601
|
let path = null;
|
|
33585
33602
|
if (tag === "path") {
|
|
33586
33603
|
const d = el.getAttribute("d");
|
|
33587
33604
|
if (d) path = new Path2D(d);
|
|
33588
33605
|
} else if (tag === "circle") {
|
|
33589
33606
|
path = new Path2D();
|
|
33590
|
-
path.arc(
|
|
33607
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
33591
33608
|
} else if (tag === "ellipse") {
|
|
33592
33609
|
path = new Path2D();
|
|
33593
|
-
path.ellipse(
|
|
33610
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
33594
33611
|
} else if (tag === "rect") {
|
|
33595
33612
|
path = new Path2D();
|
|
33596
|
-
path.rect(
|
|
33613
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
33597
33614
|
} else if (tag === "line") {
|
|
33598
33615
|
path = new Path2D();
|
|
33599
|
-
path.moveTo(
|
|
33600
|
-
path.lineTo(
|
|
33616
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
33617
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
33601
33618
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
33602
33619
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
33603
33620
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -35014,8 +35031,8 @@ var NativeRenderer = class {
|
|
|
35014
35031
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
35015
35032
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
35016
35033
|
drawingsSnapMode: () => this.snapMode,
|
|
35017
|
-
drawingsPointerDown: (x, y, snap,
|
|
35018
|
-
drawingsPointerMove: (x, y, snap,
|
|
35034
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
35035
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
35019
35036
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
35020
35037
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
35021
35038
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -35332,18 +35349,18 @@ var NativeRenderer = class {
|
|
|
35332
35349
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
35333
35350
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
35334
35351
|
const newHeadTime = this.bars[0].time;
|
|
35335
|
-
let
|
|
35352
|
+
let shift2 = 0;
|
|
35336
35353
|
if (newHeadTime !== prevHeadTime) {
|
|
35337
35354
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
35338
|
-
if (prepended > 0)
|
|
35355
|
+
if (prepended > 0) shift2 = prepended;
|
|
35339
35356
|
else {
|
|
35340
35357
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
35341
|
-
if (trimmed > 0)
|
|
35358
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
35342
35359
|
}
|
|
35343
35360
|
}
|
|
35344
|
-
if (
|
|
35345
|
-
this.zoomAnchorLogical +=
|
|
35346
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
35361
|
+
if (shift2 !== 0) {
|
|
35362
|
+
this.zoomAnchorLogical += shift2;
|
|
35363
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
35347
35364
|
}
|
|
35348
35365
|
}
|
|
35349
35366
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -36949,15 +36966,15 @@ var NativeRenderer = class {
|
|
|
36949
36966
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
36950
36967
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
36951
36968
|
collapsedMasterMap() {
|
|
36952
|
-
const
|
|
36969
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
36953
36970
|
for (const pane of this.scene.panes.values()) {
|
|
36954
36971
|
if (!pane.collapsed) continue;
|
|
36955
36972
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
36956
36973
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
36957
36974
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
36958
|
-
|
|
36975
|
+
map2.set(pane.id, master?.id ?? null);
|
|
36959
36976
|
}
|
|
36960
|
-
return
|
|
36977
|
+
return map2;
|
|
36961
36978
|
}
|
|
36962
36979
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
36963
36980
|
setToolbarGutter(px) {
|
|
@@ -37324,12 +37341,2328 @@ function registerVpvr() {
|
|
|
37324
37341
|
registerNativeIndicator(vpvrDescriptor);
|
|
37325
37342
|
}
|
|
37326
37343
|
|
|
37344
|
+
// src/core/model/identity.ts
|
|
37345
|
+
function stableSeriesId(parts) {
|
|
37346
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
37347
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
37348
|
+
}
|
|
37349
|
+
|
|
37350
|
+
// src/core/native-indicators/classics/define.ts
|
|
37351
|
+
function num3(inputs, key, fallback) {
|
|
37352
|
+
const v = inputs[key];
|
|
37353
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
37354
|
+
}
|
|
37355
|
+
function str3(inputs, key, fallback) {
|
|
37356
|
+
const v = inputs[key];
|
|
37357
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
37358
|
+
}
|
|
37359
|
+
function toPoints(bars, values, colors) {
|
|
37360
|
+
const pts = new Array(bars.length);
|
|
37361
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37362
|
+
const v = values[i];
|
|
37363
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
37364
|
+
const c = colors?.[i];
|
|
37365
|
+
if (c != null) p.color = c;
|
|
37366
|
+
pts[i] = p;
|
|
37367
|
+
}
|
|
37368
|
+
return pts;
|
|
37369
|
+
}
|
|
37370
|
+
var ClassicIndicator = class {
|
|
37371
|
+
constructor(spec) {
|
|
37372
|
+
this.spec = spec;
|
|
37373
|
+
this.inputs = {};
|
|
37374
|
+
}
|
|
37375
|
+
start(ctx, inputs) {
|
|
37376
|
+
this.ctx = ctx;
|
|
37377
|
+
this.inputs = inputs;
|
|
37378
|
+
this.recompute();
|
|
37379
|
+
}
|
|
37380
|
+
onBars() {
|
|
37381
|
+
this.recompute();
|
|
37382
|
+
}
|
|
37383
|
+
onViewport() {
|
|
37384
|
+
}
|
|
37385
|
+
setInputs(inputs) {
|
|
37386
|
+
this.inputs = inputs;
|
|
37387
|
+
this.recompute();
|
|
37388
|
+
}
|
|
37389
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
37390
|
+
suspend() {
|
|
37391
|
+
}
|
|
37392
|
+
resume() {
|
|
37393
|
+
this.recompute();
|
|
37394
|
+
}
|
|
37395
|
+
stop() {
|
|
37396
|
+
}
|
|
37397
|
+
recompute() {
|
|
37398
|
+
const bars = this.ctx.bars();
|
|
37399
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
37400
|
+
const instanceId = this.spec.type;
|
|
37401
|
+
const series = [];
|
|
37402
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
37403
|
+
out.plots.forEach((plot, ordinal) => {
|
|
37404
|
+
const kind = plot.kind ?? "line";
|
|
37405
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
37406
|
+
idsByKey.set(plot.key, id);
|
|
37407
|
+
series.push({
|
|
37408
|
+
id,
|
|
37409
|
+
title: plot.title,
|
|
37410
|
+
paneId: "",
|
|
37411
|
+
// stamped by the orchestrator
|
|
37412
|
+
kind,
|
|
37413
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
37414
|
+
style: {
|
|
37415
|
+
color: plot.color,
|
|
37416
|
+
width: plot.width ?? 1,
|
|
37417
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
37418
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
37419
|
+
},
|
|
37420
|
+
...plot.overlay ? { overlay: true } : {}
|
|
37421
|
+
});
|
|
37422
|
+
});
|
|
37423
|
+
if (out.markers && out.markers.length > 0) {
|
|
37424
|
+
series.push({
|
|
37425
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
37426
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
37427
|
+
paneId: "",
|
|
37428
|
+
kind: "markers",
|
|
37429
|
+
markers: out.markers
|
|
37430
|
+
});
|
|
37431
|
+
}
|
|
37432
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
37433
|
+
const from = idsByKey.get(band.from);
|
|
37434
|
+
const to = idsByKey.get(band.to);
|
|
37435
|
+
if (!from || !to) return [];
|
|
37436
|
+
return [
|
|
37437
|
+
{
|
|
37438
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
37439
|
+
paneId: "",
|
|
37440
|
+
fromSeriesId: from,
|
|
37441
|
+
toSeriesId: to,
|
|
37442
|
+
color: band.color
|
|
37443
|
+
}
|
|
37444
|
+
];
|
|
37445
|
+
});
|
|
37446
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
37447
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
37448
|
+
paneId: "",
|
|
37449
|
+
price: level.price,
|
|
37450
|
+
...level.color != null ? { color: level.color } : {},
|
|
37451
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
37452
|
+
width: 1,
|
|
37453
|
+
...level.title != null ? { title: level.title } : {}
|
|
37454
|
+
}));
|
|
37455
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
37456
|
+
const { key, ...rest } = poly;
|
|
37457
|
+
return {
|
|
37458
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
37459
|
+
paneId: "",
|
|
37460
|
+
...rest
|
|
37461
|
+
};
|
|
37462
|
+
});
|
|
37463
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
37464
|
+
this.ctx.setStatus("idle");
|
|
37465
|
+
}
|
|
37466
|
+
};
|
|
37467
|
+
function classicDescriptor(spec) {
|
|
37468
|
+
return {
|
|
37469
|
+
type: spec.type,
|
|
37470
|
+
title: spec.title,
|
|
37471
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
37472
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
37473
|
+
overlay: spec.overlay,
|
|
37474
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
37475
|
+
multiInstance: true,
|
|
37476
|
+
inputsSchema: () => spec.inputs,
|
|
37477
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
37478
|
+
create: () => new ClassicIndicator(spec)
|
|
37479
|
+
};
|
|
37480
|
+
}
|
|
37481
|
+
|
|
37482
|
+
// src/core/native-indicators/classics/math.ts
|
|
37483
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
37484
|
+
function sourceValues(bars, source) {
|
|
37485
|
+
const out = new Array(bars.length);
|
|
37486
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37487
|
+
const b = bars[i];
|
|
37488
|
+
switch (source) {
|
|
37489
|
+
case "Open":
|
|
37490
|
+
out[i] = b.open;
|
|
37491
|
+
break;
|
|
37492
|
+
case "High":
|
|
37493
|
+
out[i] = b.high;
|
|
37494
|
+
break;
|
|
37495
|
+
case "Low":
|
|
37496
|
+
out[i] = b.low;
|
|
37497
|
+
break;
|
|
37498
|
+
case "HL2":
|
|
37499
|
+
out[i] = (b.high + b.low) / 2;
|
|
37500
|
+
break;
|
|
37501
|
+
case "HLC3":
|
|
37502
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
37503
|
+
break;
|
|
37504
|
+
case "OHLC4":
|
|
37505
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
37506
|
+
break;
|
|
37507
|
+
case "HLCC4":
|
|
37508
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
37509
|
+
break;
|
|
37510
|
+
default:
|
|
37511
|
+
out[i] = b.close;
|
|
37512
|
+
}
|
|
37513
|
+
}
|
|
37514
|
+
return out;
|
|
37515
|
+
}
|
|
37516
|
+
function volumes(bars) {
|
|
37517
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
37518
|
+
}
|
|
37519
|
+
function highs(bars) {
|
|
37520
|
+
return bars.map((b) => b.high);
|
|
37521
|
+
}
|
|
37522
|
+
function lows(bars) {
|
|
37523
|
+
return bars.map((b) => b.low);
|
|
37524
|
+
}
|
|
37525
|
+
function closes(bars) {
|
|
37526
|
+
return bars.map((b) => b.close);
|
|
37527
|
+
}
|
|
37528
|
+
function opens(bars) {
|
|
37529
|
+
return bars.map((b) => b.open);
|
|
37530
|
+
}
|
|
37531
|
+
function zip(a, b, f) {
|
|
37532
|
+
const out = new Array(a.length);
|
|
37533
|
+
for (let i = 0; i < a.length; i++) {
|
|
37534
|
+
const x = a[i];
|
|
37535
|
+
const y = b[i];
|
|
37536
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
37537
|
+
}
|
|
37538
|
+
return out;
|
|
37539
|
+
}
|
|
37540
|
+
function map(a, f) {
|
|
37541
|
+
const out = new Array(a.length);
|
|
37542
|
+
for (let i = 0; i < a.length; i++) {
|
|
37543
|
+
const x = a[i];
|
|
37544
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
37545
|
+
}
|
|
37546
|
+
return out;
|
|
37547
|
+
}
|
|
37548
|
+
function sub(a, b) {
|
|
37549
|
+
return zip(a, b, (x, y) => x - y);
|
|
37550
|
+
}
|
|
37551
|
+
function windowed(v, len, fold) {
|
|
37552
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37553
|
+
if (len <= 0) return out;
|
|
37554
|
+
let run = 0;
|
|
37555
|
+
for (let i = 0; i < v.length; i++) {
|
|
37556
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
37557
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
37558
|
+
}
|
|
37559
|
+
return out;
|
|
37560
|
+
}
|
|
37561
|
+
function sum(v, len) {
|
|
37562
|
+
return windowed(v, len, (w, from, to) => {
|
|
37563
|
+
let s = 0;
|
|
37564
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37565
|
+
return s;
|
|
37566
|
+
});
|
|
37567
|
+
}
|
|
37568
|
+
function sma(v, len) {
|
|
37569
|
+
return windowed(v, len, (w, from, to) => {
|
|
37570
|
+
let s = 0;
|
|
37571
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37572
|
+
return s / len;
|
|
37573
|
+
});
|
|
37574
|
+
}
|
|
37575
|
+
function wma(v, len) {
|
|
37576
|
+
const norm = len * (len + 1) / 2;
|
|
37577
|
+
return windowed(v, len, (w, from, to) => {
|
|
37578
|
+
let s = 0;
|
|
37579
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
37580
|
+
return s / norm;
|
|
37581
|
+
});
|
|
37582
|
+
}
|
|
37583
|
+
function vwma(src, vol, len) {
|
|
37584
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
37585
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
37586
|
+
}
|
|
37587
|
+
function stdev(v, len) {
|
|
37588
|
+
return windowed(v, len, (w, from, to) => {
|
|
37589
|
+
let s = 0;
|
|
37590
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37591
|
+
const mean = s / len;
|
|
37592
|
+
let sq = 0;
|
|
37593
|
+
for (let k = from; k <= to; k++) {
|
|
37594
|
+
const d = w[k] - mean;
|
|
37595
|
+
sq += d * d;
|
|
37596
|
+
}
|
|
37597
|
+
return Math.sqrt(sq / len);
|
|
37598
|
+
});
|
|
37599
|
+
}
|
|
37600
|
+
function highest(v, len) {
|
|
37601
|
+
return windowed(v, len, (w, from, to) => {
|
|
37602
|
+
let m = -Infinity;
|
|
37603
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
37604
|
+
return m;
|
|
37605
|
+
});
|
|
37606
|
+
}
|
|
37607
|
+
function lowest(v, len) {
|
|
37608
|
+
return windowed(v, len, (w, from, to) => {
|
|
37609
|
+
let m = Infinity;
|
|
37610
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
37611
|
+
return m;
|
|
37612
|
+
});
|
|
37613
|
+
}
|
|
37614
|
+
function barsSinceHighest(v, len) {
|
|
37615
|
+
return windowed(v, len, (w, from, to) => {
|
|
37616
|
+
let m = -Infinity;
|
|
37617
|
+
let at = to;
|
|
37618
|
+
for (let k = from; k <= to; k++) {
|
|
37619
|
+
if (w[k] >= m) {
|
|
37620
|
+
m = w[k];
|
|
37621
|
+
at = k;
|
|
37622
|
+
}
|
|
37623
|
+
}
|
|
37624
|
+
return to - at;
|
|
37625
|
+
});
|
|
37626
|
+
}
|
|
37627
|
+
function barsSinceLowest(v, len) {
|
|
37628
|
+
return windowed(v, len, (w, from, to) => {
|
|
37629
|
+
let m = Infinity;
|
|
37630
|
+
let at = to;
|
|
37631
|
+
for (let k = from; k <= to; k++) {
|
|
37632
|
+
if (w[k] <= m) {
|
|
37633
|
+
m = w[k];
|
|
37634
|
+
at = k;
|
|
37635
|
+
}
|
|
37636
|
+
}
|
|
37637
|
+
return to - at;
|
|
37638
|
+
});
|
|
37639
|
+
}
|
|
37640
|
+
function recursive(v, len, alpha) {
|
|
37641
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37642
|
+
let run = 0;
|
|
37643
|
+
let prev2 = Number.NaN;
|
|
37644
|
+
for (let i = 0; i < v.length; i++) {
|
|
37645
|
+
const x = v[i];
|
|
37646
|
+
if (!Number.isFinite(x)) {
|
|
37647
|
+
run = 0;
|
|
37648
|
+
prev2 = Number.NaN;
|
|
37649
|
+
continue;
|
|
37650
|
+
}
|
|
37651
|
+
run += 1;
|
|
37652
|
+
if (Number.isFinite(prev2)) {
|
|
37653
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
37654
|
+
out[i] = prev2;
|
|
37655
|
+
} else if (run >= len) {
|
|
37656
|
+
let s = 0;
|
|
37657
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
37658
|
+
prev2 = s / len;
|
|
37659
|
+
out[i] = prev2;
|
|
37660
|
+
}
|
|
37661
|
+
}
|
|
37662
|
+
return out;
|
|
37663
|
+
}
|
|
37664
|
+
function ema(v, len) {
|
|
37665
|
+
return recursive(v, len, 2 / (len + 1));
|
|
37666
|
+
}
|
|
37667
|
+
function rma(v, len) {
|
|
37668
|
+
return recursive(v, len, 1 / len);
|
|
37669
|
+
}
|
|
37670
|
+
function change(v, n = 1) {
|
|
37671
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37672
|
+
for (let i = n; i < v.length; i++) {
|
|
37673
|
+
const a = v[i];
|
|
37674
|
+
const b = v[i - n];
|
|
37675
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
37676
|
+
}
|
|
37677
|
+
return out;
|
|
37678
|
+
}
|
|
37679
|
+
function roc(v, len) {
|
|
37680
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37681
|
+
for (let i = len; i < v.length; i++) {
|
|
37682
|
+
const a = v[i];
|
|
37683
|
+
const b = v[i - len];
|
|
37684
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
37685
|
+
}
|
|
37686
|
+
return out;
|
|
37687
|
+
}
|
|
37688
|
+
function shift(v, n) {
|
|
37689
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37690
|
+
for (let i = 0; i < v.length; i++) {
|
|
37691
|
+
const j = i - n;
|
|
37692
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
37693
|
+
}
|
|
37694
|
+
return out;
|
|
37695
|
+
}
|
|
37696
|
+
function trueRange(bars) {
|
|
37697
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
37698
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37699
|
+
const b = bars[i];
|
|
37700
|
+
if (i === 0) {
|
|
37701
|
+
out[i] = b.high - b.low;
|
|
37702
|
+
continue;
|
|
37703
|
+
}
|
|
37704
|
+
const pc = bars[i - 1].close;
|
|
37705
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
37706
|
+
}
|
|
37707
|
+
return out;
|
|
37708
|
+
}
|
|
37709
|
+
function atr(bars, len) {
|
|
37710
|
+
return rma(trueRange(bars), len);
|
|
37711
|
+
}
|
|
37712
|
+
function rsi(v, len) {
|
|
37713
|
+
const d = change(v);
|
|
37714
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
37715
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
37716
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
37717
|
+
}
|
|
37718
|
+
function stoch(v, hi, lo, len) {
|
|
37719
|
+
const hh = highest(hi, len);
|
|
37720
|
+
const ll = lowest(lo, len);
|
|
37721
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37722
|
+
for (let i = 0; i < v.length; i++) {
|
|
37723
|
+
const x = v[i];
|
|
37724
|
+
const h = hh[i];
|
|
37725
|
+
const l = ll[i];
|
|
37726
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
37727
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
37728
|
+
}
|
|
37729
|
+
return out;
|
|
37730
|
+
}
|
|
37731
|
+
function linreg(v, len) {
|
|
37732
|
+
const sx = (len - 1) * len / 2;
|
|
37733
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
37734
|
+
const denom = len * sxx - sx * sx;
|
|
37735
|
+
return windowed(v, len, (w, from, to) => {
|
|
37736
|
+
let sy = 0;
|
|
37737
|
+
let sxy = 0;
|
|
37738
|
+
for (let k = from; k <= to; k++) {
|
|
37739
|
+
const x = k - from;
|
|
37740
|
+
sy += w[k];
|
|
37741
|
+
sxy += x * w[k];
|
|
37742
|
+
}
|
|
37743
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
37744
|
+
const intercept = (sy - slope * sx) / len;
|
|
37745
|
+
return intercept + slope * (len - 1);
|
|
37746
|
+
});
|
|
37747
|
+
}
|
|
37748
|
+
function swma(v) {
|
|
37749
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37750
|
+
for (let i = 3; i < v.length; i++) {
|
|
37751
|
+
const a = v[i - 3];
|
|
37752
|
+
const b = v[i - 2];
|
|
37753
|
+
const c = v[i - 1];
|
|
37754
|
+
const d = v[i];
|
|
37755
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
37756
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
37757
|
+
}
|
|
37758
|
+
}
|
|
37759
|
+
return out;
|
|
37760
|
+
}
|
|
37761
|
+
function percentRank(v, len) {
|
|
37762
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37763
|
+
for (let i = len; i < v.length; i++) {
|
|
37764
|
+
const x = v[i];
|
|
37765
|
+
if (!Number.isFinite(x)) continue;
|
|
37766
|
+
let below = 0;
|
|
37767
|
+
let count = 0;
|
|
37768
|
+
for (let k = i - len; k < i; k++) {
|
|
37769
|
+
const y = v[k];
|
|
37770
|
+
if (!Number.isFinite(y)) {
|
|
37771
|
+
count = -1;
|
|
37772
|
+
break;
|
|
37773
|
+
}
|
|
37774
|
+
count += 1;
|
|
37775
|
+
if (y <= x) below += 1;
|
|
37776
|
+
}
|
|
37777
|
+
if (count === len) out[i] = 100 * below / len;
|
|
37778
|
+
}
|
|
37779
|
+
return out;
|
|
37780
|
+
}
|
|
37781
|
+
function cumSum(v) {
|
|
37782
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37783
|
+
let acc = 0;
|
|
37784
|
+
let seen = false;
|
|
37785
|
+
for (let i = 0; i < v.length; i++) {
|
|
37786
|
+
const x = v[i];
|
|
37787
|
+
if (Number.isFinite(x)) {
|
|
37788
|
+
acc += x;
|
|
37789
|
+
seen = true;
|
|
37790
|
+
}
|
|
37791
|
+
if (seen) out[i] = acc;
|
|
37792
|
+
}
|
|
37793
|
+
return out;
|
|
37794
|
+
}
|
|
37795
|
+
|
|
37796
|
+
// src/core/native-indicators/classics/shared.ts
|
|
37797
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
37798
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
37799
|
+
}
|
|
37800
|
+
function sourceInput(defval = "Close") {
|
|
37801
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
37802
|
+
}
|
|
37803
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
37804
|
+
return { key, title, type: "color", defval };
|
|
37805
|
+
}
|
|
37806
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
37807
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
37808
|
+
if (!m) return hex;
|
|
37809
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
37810
|
+
return `#${m[1]}${a}`;
|
|
37811
|
+
}
|
|
37812
|
+
|
|
37813
|
+
// src/core/native-indicators/classics/averages.ts
|
|
37814
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
37815
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
37816
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
37817
|
+
return {
|
|
37818
|
+
type,
|
|
37819
|
+
title,
|
|
37820
|
+
shortTitle,
|
|
37821
|
+
overlay: true,
|
|
37822
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
37823
|
+
compute: (bars, inputs) => {
|
|
37824
|
+
const len = num3(inputs, "length", 20);
|
|
37825
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
37826
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37827
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37828
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37829
|
+
}
|
|
37830
|
+
};
|
|
37831
|
+
}
|
|
37832
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
37833
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
37834
|
+
var movingAverage = {
|
|
37835
|
+
type: "moving-average",
|
|
37836
|
+
title: "Moving Average",
|
|
37837
|
+
shortTitle: "MA",
|
|
37838
|
+
overlay: true,
|
|
37839
|
+
inputs: [
|
|
37840
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
37841
|
+
lengthInput(20),
|
|
37842
|
+
sourceInput(),
|
|
37843
|
+
offsetInput,
|
|
37844
|
+
colorInput()
|
|
37845
|
+
],
|
|
37846
|
+
compute: (bars, inputs) => {
|
|
37847
|
+
const len = num3(inputs, "length", 20);
|
|
37848
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37849
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
37850
|
+
let values;
|
|
37851
|
+
switch (kind) {
|
|
37852
|
+
case "EMA":
|
|
37853
|
+
values = ema(src, len);
|
|
37854
|
+
break;
|
|
37855
|
+
case "WMA":
|
|
37856
|
+
values = wma(src, len);
|
|
37857
|
+
break;
|
|
37858
|
+
case "RMA":
|
|
37859
|
+
values = rma(src, len);
|
|
37860
|
+
break;
|
|
37861
|
+
case "VWMA":
|
|
37862
|
+
values = vwma(src, volumes(bars), len);
|
|
37863
|
+
break;
|
|
37864
|
+
default:
|
|
37865
|
+
values = sma(src, len);
|
|
37866
|
+
}
|
|
37867
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37868
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37869
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37870
|
+
}
|
|
37871
|
+
};
|
|
37872
|
+
var smoothedMa = {
|
|
37873
|
+
type: "rma",
|
|
37874
|
+
title: "Smoothed Moving Average",
|
|
37875
|
+
shortTitle: "RMA",
|
|
37876
|
+
overlay: true,
|
|
37877
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37878
|
+
compute: (bars, inputs) => ({
|
|
37879
|
+
plots: [
|
|
37880
|
+
{
|
|
37881
|
+
key: "rma",
|
|
37882
|
+
title: "RMA",
|
|
37883
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37884
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37885
|
+
width: 2
|
|
37886
|
+
}
|
|
37887
|
+
]
|
|
37888
|
+
})
|
|
37889
|
+
};
|
|
37890
|
+
var zlema = {
|
|
37891
|
+
type: "zlema",
|
|
37892
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
37893
|
+
shortTitle: "ZLEMA",
|
|
37894
|
+
overlay: true,
|
|
37895
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37896
|
+
compute: (bars, inputs) => {
|
|
37897
|
+
const len = num3(inputs, "length", 14);
|
|
37898
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37899
|
+
const lag = Math.floor((len - 1) / 2);
|
|
37900
|
+
const lagged = shift(src, lag);
|
|
37901
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
37902
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37903
|
+
}
|
|
37904
|
+
};
|
|
37905
|
+
var vidya = {
|
|
37906
|
+
type: "vidya",
|
|
37907
|
+
title: "Variable Index Dynamic Average",
|
|
37908
|
+
shortTitle: "VIDYA",
|
|
37909
|
+
overlay: true,
|
|
37910
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
37911
|
+
compute: (bars, inputs) => {
|
|
37912
|
+
const len = num3(inputs, "length", 14);
|
|
37913
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
37914
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37915
|
+
const d = change(src);
|
|
37916
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
37917
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
37918
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
37919
|
+
let u = 0;
|
|
37920
|
+
let w = 0;
|
|
37921
|
+
let ok = true;
|
|
37922
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
37923
|
+
const x = d[k];
|
|
37924
|
+
if (!Number.isFinite(x)) {
|
|
37925
|
+
ok = false;
|
|
37926
|
+
break;
|
|
37927
|
+
}
|
|
37928
|
+
if (x > 0) u += x;
|
|
37929
|
+
else w -= x;
|
|
37930
|
+
}
|
|
37931
|
+
if (ok) {
|
|
37932
|
+
su[i] = u;
|
|
37933
|
+
sd[i] = w;
|
|
37934
|
+
}
|
|
37935
|
+
}
|
|
37936
|
+
const alpha = 2 / (len + 1);
|
|
37937
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
37938
|
+
let prev2 = Number.NaN;
|
|
37939
|
+
for (let i = 0; i < src.length; i++) {
|
|
37940
|
+
const x = src[i];
|
|
37941
|
+
const u = su[i];
|
|
37942
|
+
const w = sd[i];
|
|
37943
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
37944
|
+
const total = u + w;
|
|
37945
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
37946
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
37947
|
+
out[i] = prev2;
|
|
37948
|
+
}
|
|
37949
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37950
|
+
}
|
|
37951
|
+
};
|
|
37952
|
+
var maEnvelope = {
|
|
37953
|
+
type: "ma-envelope",
|
|
37954
|
+
title: "Moving Average Envelope",
|
|
37955
|
+
shortTitle: "MA Env",
|
|
37956
|
+
overlay: true,
|
|
37957
|
+
inputs: [
|
|
37958
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
37959
|
+
lengthInput(20),
|
|
37960
|
+
sourceInput(),
|
|
37961
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
37962
|
+
colorInput()
|
|
37963
|
+
],
|
|
37964
|
+
compute: (bars, inputs) => {
|
|
37965
|
+
const len = num3(inputs, "length", 20);
|
|
37966
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37967
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
37968
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
37969
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
37970
|
+
return {
|
|
37971
|
+
plots: [
|
|
37972
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
37973
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
37974
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
37975
|
+
],
|
|
37976
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
37977
|
+
};
|
|
37978
|
+
}
|
|
37979
|
+
};
|
|
37980
|
+
var linearRegression = {
|
|
37981
|
+
type: "linear-regression",
|
|
37982
|
+
title: "Linear Regression Curve",
|
|
37983
|
+
shortTitle: "LinReg",
|
|
37984
|
+
overlay: true,
|
|
37985
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37986
|
+
compute: (bars, inputs) => ({
|
|
37987
|
+
plots: [
|
|
37988
|
+
{
|
|
37989
|
+
key: "linreg",
|
|
37990
|
+
title: "LinReg",
|
|
37991
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37992
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37993
|
+
width: 2
|
|
37994
|
+
}
|
|
37995
|
+
]
|
|
37996
|
+
})
|
|
37997
|
+
};
|
|
37998
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
37999
|
+
|
|
38000
|
+
// src/core/native-indicators/classics/bands.ts
|
|
38001
|
+
function bollinger(bars, inputs) {
|
|
38002
|
+
const len = num3(inputs, "length", 20);
|
|
38003
|
+
const mult = num3(inputs, "mult", 2);
|
|
38004
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38005
|
+
const basis = sma(src, len);
|
|
38006
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
38007
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
38008
|
+
}
|
|
38009
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
38010
|
+
var bollingerBands = {
|
|
38011
|
+
type: "bollinger-bands",
|
|
38012
|
+
title: "Bollinger Bands",
|
|
38013
|
+
shortTitle: "BB",
|
|
38014
|
+
overlay: true,
|
|
38015
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38016
|
+
compute: (bars, inputs) => {
|
|
38017
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
38018
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38019
|
+
return {
|
|
38020
|
+
plots: [
|
|
38021
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
38022
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38023
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38024
|
+
],
|
|
38025
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38026
|
+
};
|
|
38027
|
+
}
|
|
38028
|
+
};
|
|
38029
|
+
var percentB = {
|
|
38030
|
+
type: "percent-b",
|
|
38031
|
+
title: "Bollinger Bands %B",
|
|
38032
|
+
shortTitle: "%B",
|
|
38033
|
+
overlay: false,
|
|
38034
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38035
|
+
compute: (bars, inputs) => {
|
|
38036
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
38037
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38038
|
+
const values = src.map((x, i) => {
|
|
38039
|
+
const u = upper[i];
|
|
38040
|
+
const l = lower[i];
|
|
38041
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
38042
|
+
});
|
|
38043
|
+
return {
|
|
38044
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38045
|
+
levels: [
|
|
38046
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
38047
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
38048
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
38049
|
+
]
|
|
38050
|
+
};
|
|
38051
|
+
}
|
|
38052
|
+
};
|
|
38053
|
+
var bandwidth = {
|
|
38054
|
+
type: "bandwidth",
|
|
38055
|
+
title: "Bollinger Bands Width",
|
|
38056
|
+
shortTitle: "BandWidth",
|
|
38057
|
+
overlay: false,
|
|
38058
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38059
|
+
compute: (bars, inputs) => {
|
|
38060
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
38061
|
+
const values = basis.map((b, i) => {
|
|
38062
|
+
const u = upper[i];
|
|
38063
|
+
const l = lower[i];
|
|
38064
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
38065
|
+
});
|
|
38066
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38067
|
+
}
|
|
38068
|
+
};
|
|
38069
|
+
var keltner = {
|
|
38070
|
+
type: "keltner-channels",
|
|
38071
|
+
title: "Keltner Channels",
|
|
38072
|
+
shortTitle: "KC",
|
|
38073
|
+
overlay: true,
|
|
38074
|
+
inputs: [
|
|
38075
|
+
lengthInput(20),
|
|
38076
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38077
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38078
|
+
sourceInput(),
|
|
38079
|
+
colorInput()
|
|
38080
|
+
],
|
|
38081
|
+
compute: (bars, inputs) => {
|
|
38082
|
+
const len = num3(inputs, "length", 20);
|
|
38083
|
+
const mult = num3(inputs, "mult", 2);
|
|
38084
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38085
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
38086
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38087
|
+
return {
|
|
38088
|
+
plots: [
|
|
38089
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
38090
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
38091
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
38092
|
+
],
|
|
38093
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38094
|
+
};
|
|
38095
|
+
}
|
|
38096
|
+
};
|
|
38097
|
+
var donchian = {
|
|
38098
|
+
type: "donchian-channels",
|
|
38099
|
+
title: "Donchian Channels",
|
|
38100
|
+
shortTitle: "DC",
|
|
38101
|
+
overlay: true,
|
|
38102
|
+
inputs: [lengthInput(20), colorInput()],
|
|
38103
|
+
compute: (bars, inputs) => {
|
|
38104
|
+
const len = num3(inputs, "length", 20);
|
|
38105
|
+
const upper = highest(highs(bars), len);
|
|
38106
|
+
const lower = lowest(lows(bars), len);
|
|
38107
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38108
|
+
return {
|
|
38109
|
+
plots: [
|
|
38110
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
38111
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38112
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38113
|
+
],
|
|
38114
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38115
|
+
};
|
|
38116
|
+
}
|
|
38117
|
+
};
|
|
38118
|
+
var chandelier = {
|
|
38119
|
+
type: "chandelier-exit",
|
|
38120
|
+
title: "Chandelier Exit",
|
|
38121
|
+
shortTitle: "CE",
|
|
38122
|
+
overlay: true,
|
|
38123
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
38124
|
+
compute: (bars, inputs) => {
|
|
38125
|
+
const len = num3(inputs, "length", 22);
|
|
38126
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
38127
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
38128
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
38129
|
+
return {
|
|
38130
|
+
plots: [
|
|
38131
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
38132
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
38133
|
+
]
|
|
38134
|
+
};
|
|
38135
|
+
}
|
|
38136
|
+
};
|
|
38137
|
+
var chandeKroll = {
|
|
38138
|
+
type: "chande-kroll-stop",
|
|
38139
|
+
title: "Chande Kroll Stop",
|
|
38140
|
+
shortTitle: "CKS",
|
|
38141
|
+
overlay: true,
|
|
38142
|
+
inputs: [
|
|
38143
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38144
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
38145
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
38146
|
+
],
|
|
38147
|
+
compute: (bars, inputs) => {
|
|
38148
|
+
const p = num3(inputs, "p", 10);
|
|
38149
|
+
const x = num3(inputs, "x", 1);
|
|
38150
|
+
const q = num3(inputs, "q", 9);
|
|
38151
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
38152
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
38153
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
38154
|
+
return {
|
|
38155
|
+
plots: [
|
|
38156
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
38157
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
38158
|
+
]
|
|
38159
|
+
};
|
|
38160
|
+
}
|
|
38161
|
+
};
|
|
38162
|
+
var supertrend = {
|
|
38163
|
+
type: "supertrend",
|
|
38164
|
+
title: "SuperTrend",
|
|
38165
|
+
shortTitle: "SuperTrend",
|
|
38166
|
+
overlay: true,
|
|
38167
|
+
inputs: [
|
|
38168
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38169
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
38170
|
+
],
|
|
38171
|
+
compute: (bars, inputs) => {
|
|
38172
|
+
const len = num3(inputs, "atrLength", 10);
|
|
38173
|
+
const mult = num3(inputs, "mult", 3);
|
|
38174
|
+
const range = atr(bars, len);
|
|
38175
|
+
const n = bars.length;
|
|
38176
|
+
const st = new Array(n).fill(Number.NaN);
|
|
38177
|
+
const dir = new Array(n).fill(1);
|
|
38178
|
+
let up = Number.NaN;
|
|
38179
|
+
let dn = Number.NaN;
|
|
38180
|
+
let trend = 1;
|
|
38181
|
+
for (let i = 0; i < n; i++) {
|
|
38182
|
+
const r = range[i];
|
|
38183
|
+
if (!Number.isFinite(r)) continue;
|
|
38184
|
+
const b = bars[i];
|
|
38185
|
+
const mid = (b.high + b.low) / 2;
|
|
38186
|
+
const basicUp = mid - mult * r;
|
|
38187
|
+
const basicDn = mid + mult * r;
|
|
38188
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
38189
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
38190
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
38191
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
38192
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
38193
|
+
dir[i] = trend;
|
|
38194
|
+
st[i] = trend === 1 ? up : dn;
|
|
38195
|
+
}
|
|
38196
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
38197
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
38198
|
+
}
|
|
38199
|
+
};
|
|
38200
|
+
var alligator = {
|
|
38201
|
+
type: "williams-alligator",
|
|
38202
|
+
title: "Williams Alligator",
|
|
38203
|
+
shortTitle: "Alligator",
|
|
38204
|
+
overlay: true,
|
|
38205
|
+
inputs: [
|
|
38206
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
38207
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
38208
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
38209
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
38210
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
38211
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
38212
|
+
],
|
|
38213
|
+
compute: (bars, inputs) => {
|
|
38214
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38215
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
38216
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
38217
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
38218
|
+
return {
|
|
38219
|
+
plots: [
|
|
38220
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
38221
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
38222
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
38223
|
+
]
|
|
38224
|
+
};
|
|
38225
|
+
}
|
|
38226
|
+
};
|
|
38227
|
+
var gator = {
|
|
38228
|
+
type: "gator-oscillator",
|
|
38229
|
+
title: "Gator Oscillator",
|
|
38230
|
+
shortTitle: "Gator Oscillator",
|
|
38231
|
+
overlay: false,
|
|
38232
|
+
inputs: [],
|
|
38233
|
+
compute: (bars) => {
|
|
38234
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38235
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
38236
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
38237
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
38238
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
38239
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
38240
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
38241
|
+
if (!Number.isFinite(x)) return null;
|
|
38242
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
38243
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
38244
|
+
});
|
|
38245
|
+
return {
|
|
38246
|
+
plots: [
|
|
38247
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
38248
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
38249
|
+
]
|
|
38250
|
+
};
|
|
38251
|
+
}
|
|
38252
|
+
};
|
|
38253
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
38254
|
+
|
|
38255
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
38256
|
+
function guideLevels(low, high, mid) {
|
|
38257
|
+
const levels = [
|
|
38258
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
38259
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
38260
|
+
];
|
|
38261
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
38262
|
+
return levels;
|
|
38263
|
+
}
|
|
38264
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
38265
|
+
function histColors(values) {
|
|
38266
|
+
return values.map((x, i) => {
|
|
38267
|
+
if (!Number.isFinite(x)) return null;
|
|
38268
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
38269
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
38270
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
38271
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
38272
|
+
});
|
|
38273
|
+
}
|
|
38274
|
+
var rsiSpec = {
|
|
38275
|
+
type: "rsi",
|
|
38276
|
+
title: "Relative Strength Index",
|
|
38277
|
+
shortTitle: "RSI",
|
|
38278
|
+
overlay: false,
|
|
38279
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38280
|
+
compute: (bars, inputs) => ({
|
|
38281
|
+
plots: [
|
|
38282
|
+
{
|
|
38283
|
+
key: "rsi",
|
|
38284
|
+
title: "RSI",
|
|
38285
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38286
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38287
|
+
width: 2
|
|
38288
|
+
}
|
|
38289
|
+
],
|
|
38290
|
+
levels: guideLevels(30, 70, 50)
|
|
38291
|
+
})
|
|
38292
|
+
};
|
|
38293
|
+
var stochastic = {
|
|
38294
|
+
type: "stochastic",
|
|
38295
|
+
title: "Stochastic",
|
|
38296
|
+
shortTitle: "Stoch",
|
|
38297
|
+
overlay: false,
|
|
38298
|
+
inputs: [
|
|
38299
|
+
lengthInput(14, "kLength", "%K length"),
|
|
38300
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
38301
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
38302
|
+
],
|
|
38303
|
+
compute: (bars, inputs) => {
|
|
38304
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
38305
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38306
|
+
return {
|
|
38307
|
+
plots: [
|
|
38308
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38309
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38310
|
+
],
|
|
38311
|
+
levels: guideLevels(20, 80)
|
|
38312
|
+
};
|
|
38313
|
+
}
|
|
38314
|
+
};
|
|
38315
|
+
var stochasticRsi = {
|
|
38316
|
+
type: "stochastic-rsi",
|
|
38317
|
+
title: "Stochastic RSI",
|
|
38318
|
+
shortTitle: "Stoch RSI",
|
|
38319
|
+
overlay: false,
|
|
38320
|
+
inputs: [
|
|
38321
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
38322
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
38323
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
38324
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
38325
|
+
sourceInput()
|
|
38326
|
+
],
|
|
38327
|
+
compute: (bars, inputs) => {
|
|
38328
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
38329
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
38330
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38331
|
+
return {
|
|
38332
|
+
plots: [
|
|
38333
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38334
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38335
|
+
],
|
|
38336
|
+
levels: guideLevels(20, 80)
|
|
38337
|
+
};
|
|
38338
|
+
}
|
|
38339
|
+
};
|
|
38340
|
+
var macd = {
|
|
38341
|
+
type: "macd",
|
|
38342
|
+
title: "MACD",
|
|
38343
|
+
overlay: false,
|
|
38344
|
+
inputs: [
|
|
38345
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38346
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38347
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38348
|
+
sourceInput()
|
|
38349
|
+
],
|
|
38350
|
+
compute: (bars, inputs) => {
|
|
38351
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38352
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
38353
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38354
|
+
const hist = sub(line, signal);
|
|
38355
|
+
return {
|
|
38356
|
+
plots: [
|
|
38357
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38358
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
38359
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38360
|
+
],
|
|
38361
|
+
levels: ZERO_LEVEL
|
|
38362
|
+
};
|
|
38363
|
+
}
|
|
38364
|
+
};
|
|
38365
|
+
var ppo = {
|
|
38366
|
+
type: "ppo",
|
|
38367
|
+
title: "Percentage Price Oscillator",
|
|
38368
|
+
shortTitle: "PPO",
|
|
38369
|
+
overlay: false,
|
|
38370
|
+
inputs: [
|
|
38371
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38372
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38373
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38374
|
+
sourceInput()
|
|
38375
|
+
],
|
|
38376
|
+
compute: (bars, inputs) => {
|
|
38377
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38378
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
38379
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
38380
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
38381
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38382
|
+
const hist = sub(line, signal);
|
|
38383
|
+
return {
|
|
38384
|
+
plots: [
|
|
38385
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38386
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
38387
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38388
|
+
],
|
|
38389
|
+
levels: ZERO_LEVEL
|
|
38390
|
+
};
|
|
38391
|
+
}
|
|
38392
|
+
};
|
|
38393
|
+
var awesome = {
|
|
38394
|
+
type: "awesome-oscillator",
|
|
38395
|
+
title: "Awesome Oscillator",
|
|
38396
|
+
shortTitle: "AO",
|
|
38397
|
+
overlay: false,
|
|
38398
|
+
inputs: [],
|
|
38399
|
+
compute: (bars) => {
|
|
38400
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38401
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
38402
|
+
const colors = ao.map((x, i) => {
|
|
38403
|
+
if (!Number.isFinite(x)) return null;
|
|
38404
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
38405
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
38406
|
+
});
|
|
38407
|
+
return {
|
|
38408
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
38409
|
+
levels: ZERO_LEVEL
|
|
38410
|
+
};
|
|
38411
|
+
}
|
|
38412
|
+
};
|
|
38413
|
+
var cci = {
|
|
38414
|
+
type: "commodity-channel-index",
|
|
38415
|
+
title: "Commodity Channel Index",
|
|
38416
|
+
shortTitle: "CCI",
|
|
38417
|
+
overlay: false,
|
|
38418
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
38419
|
+
compute: (bars, inputs) => {
|
|
38420
|
+
const len = num3(inputs, "length", 20);
|
|
38421
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
38422
|
+
const basis = sma(src, len);
|
|
38423
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
38424
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
38425
|
+
const mean = basis[i];
|
|
38426
|
+
if (!Number.isFinite(mean)) continue;
|
|
38427
|
+
let d = 0;
|
|
38428
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
38429
|
+
dev[i] = d / len;
|
|
38430
|
+
}
|
|
38431
|
+
const values = src.map((x, i) => {
|
|
38432
|
+
const b = basis[i];
|
|
38433
|
+
const d = dev[i];
|
|
38434
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
38435
|
+
});
|
|
38436
|
+
return {
|
|
38437
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38438
|
+
levels: guideLevels(-100, 100, 0)
|
|
38439
|
+
};
|
|
38440
|
+
}
|
|
38441
|
+
};
|
|
38442
|
+
var williamsR = {
|
|
38443
|
+
type: "williams-percent-r",
|
|
38444
|
+
title: "Williams %R",
|
|
38445
|
+
shortTitle: "%R",
|
|
38446
|
+
overlay: false,
|
|
38447
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38448
|
+
compute: (bars, inputs) => {
|
|
38449
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
38450
|
+
return {
|
|
38451
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38452
|
+
levels: guideLevels(-80, -20)
|
|
38453
|
+
};
|
|
38454
|
+
}
|
|
38455
|
+
};
|
|
38456
|
+
var cmo = {
|
|
38457
|
+
type: "chande-momentum-oscillator",
|
|
38458
|
+
title: "Chande Momentum Oscillator",
|
|
38459
|
+
shortTitle: "CMO",
|
|
38460
|
+
overlay: false,
|
|
38461
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38462
|
+
compute: (bars, inputs) => {
|
|
38463
|
+
const len = num3(inputs, "length", 9);
|
|
38464
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
38465
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
38466
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
38467
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
38468
|
+
return {
|
|
38469
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38470
|
+
levels: guideLevels(-50, 50, 0)
|
|
38471
|
+
};
|
|
38472
|
+
}
|
|
38473
|
+
};
|
|
38474
|
+
var connorsRsi = {
|
|
38475
|
+
type: "connors-rsi",
|
|
38476
|
+
title: "Connors RSI",
|
|
38477
|
+
shortTitle: "CRSI",
|
|
38478
|
+
overlay: false,
|
|
38479
|
+
inputs: [
|
|
38480
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
38481
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
38482
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
38483
|
+
colorInput()
|
|
38484
|
+
],
|
|
38485
|
+
compute: (bars, inputs) => {
|
|
38486
|
+
const c = closes(bars);
|
|
38487
|
+
const streak = new Array(c.length).fill(0);
|
|
38488
|
+
for (let i = 1; i < c.length; i++) {
|
|
38489
|
+
const d = c[i] - c[i - 1];
|
|
38490
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
38491
|
+
}
|
|
38492
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
38493
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
38494
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
38495
|
+
const values = c.map((_, i) => {
|
|
38496
|
+
const x = a[i];
|
|
38497
|
+
const y = b[i];
|
|
38498
|
+
const z = r[i];
|
|
38499
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
38500
|
+
});
|
|
38501
|
+
return {
|
|
38502
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38503
|
+
levels: guideLevels(20, 80, 50)
|
|
38504
|
+
};
|
|
38505
|
+
}
|
|
38506
|
+
};
|
|
38507
|
+
var fisher = {
|
|
38508
|
+
type: "fisher-transform",
|
|
38509
|
+
title: "Fisher Transform",
|
|
38510
|
+
shortTitle: "Fisher",
|
|
38511
|
+
overlay: false,
|
|
38512
|
+
inputs: [lengthInput(9)],
|
|
38513
|
+
compute: (bars, inputs) => {
|
|
38514
|
+
const len = num3(inputs, "length", 9);
|
|
38515
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38516
|
+
const hi = highest(hl2, len);
|
|
38517
|
+
const lo = lowest(hl2, len);
|
|
38518
|
+
const n = bars.length;
|
|
38519
|
+
const out = new Array(n).fill(Number.NaN);
|
|
38520
|
+
let value = 0;
|
|
38521
|
+
let fish = 0;
|
|
38522
|
+
for (let i = 0; i < n; i++) {
|
|
38523
|
+
const h = hi[i];
|
|
38524
|
+
const l = lo[i];
|
|
38525
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38526
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
38527
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
38528
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
38529
|
+
out[i] = fish;
|
|
38530
|
+
}
|
|
38531
|
+
return {
|
|
38532
|
+
plots: [
|
|
38533
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
38534
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
38535
|
+
],
|
|
38536
|
+
levels: ZERO_LEVEL
|
|
38537
|
+
};
|
|
38538
|
+
}
|
|
38539
|
+
};
|
|
38540
|
+
var trix = {
|
|
38541
|
+
type: "trix",
|
|
38542
|
+
title: "TRIX",
|
|
38543
|
+
overlay: false,
|
|
38544
|
+
inputs: [lengthInput(18), colorInput()],
|
|
38545
|
+
compute: (bars, inputs) => {
|
|
38546
|
+
const len = num3(inputs, "length", 18);
|
|
38547
|
+
const logClose = map(closes(bars), Math.log);
|
|
38548
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
38549
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
38550
|
+
return {
|
|
38551
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38552
|
+
levels: ZERO_LEVEL
|
|
38553
|
+
};
|
|
38554
|
+
}
|
|
38555
|
+
};
|
|
38556
|
+
function tsiLine(src, long, short) {
|
|
38557
|
+
const mom = change(src);
|
|
38558
|
+
const numer = ema(ema(mom, long), short);
|
|
38559
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
38560
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
38561
|
+
}
|
|
38562
|
+
var tsi = {
|
|
38563
|
+
type: "true-strength-index",
|
|
38564
|
+
title: "True Strength Index",
|
|
38565
|
+
shortTitle: "TSI",
|
|
38566
|
+
overlay: false,
|
|
38567
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
38568
|
+
compute: (bars, inputs) => {
|
|
38569
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
38570
|
+
return {
|
|
38571
|
+
plots: [
|
|
38572
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
38573
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
38574
|
+
],
|
|
38575
|
+
levels: ZERO_LEVEL
|
|
38576
|
+
};
|
|
38577
|
+
}
|
|
38578
|
+
};
|
|
38579
|
+
var smiErgodic = {
|
|
38580
|
+
type: "smi-ergodic",
|
|
38581
|
+
title: "SMI Ergodic Oscillator",
|
|
38582
|
+
shortTitle: "SMIE",
|
|
38583
|
+
overlay: false,
|
|
38584
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
38585
|
+
compute: (bars, inputs) => {
|
|
38586
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
38587
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
38588
|
+
const osc = sub(line, signal);
|
|
38589
|
+
return {
|
|
38590
|
+
plots: [
|
|
38591
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
38592
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
38593
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38594
|
+
],
|
|
38595
|
+
levels: ZERO_LEVEL
|
|
38596
|
+
};
|
|
38597
|
+
}
|
|
38598
|
+
};
|
|
38599
|
+
var stc = {
|
|
38600
|
+
type: "schaff-trend-cycle",
|
|
38601
|
+
title: "Schaff Trend Cycle",
|
|
38602
|
+
shortTitle: "Schaff Trend Cycle",
|
|
38603
|
+
overlay: false,
|
|
38604
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
38605
|
+
compute: (bars, inputs) => {
|
|
38606
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
38607
|
+
const src = closes(bars);
|
|
38608
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
38609
|
+
const stochOf = (v) => {
|
|
38610
|
+
const lo = lowest(v, len);
|
|
38611
|
+
const hi = highest(v, len);
|
|
38612
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38613
|
+
let prevRaw = Number.NaN;
|
|
38614
|
+
let smoothed = Number.NaN;
|
|
38615
|
+
for (let i = 0; i < v.length; i++) {
|
|
38616
|
+
const x = v[i];
|
|
38617
|
+
const l = lo[i];
|
|
38618
|
+
const h = hi[i];
|
|
38619
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
38620
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
38621
|
+
prevRaw = raw;
|
|
38622
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
38623
|
+
out[i] = smoothed;
|
|
38624
|
+
}
|
|
38625
|
+
return out;
|
|
38626
|
+
};
|
|
38627
|
+
const values = stochOf(stochOf(macdLine));
|
|
38628
|
+
return {
|
|
38629
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
38630
|
+
levels: guideLevels(25, 75)
|
|
38631
|
+
};
|
|
38632
|
+
}
|
|
38633
|
+
};
|
|
38634
|
+
var kst = {
|
|
38635
|
+
type: "know-sure-thing",
|
|
38636
|
+
title: "Know Sure Thing",
|
|
38637
|
+
shortTitle: "KST",
|
|
38638
|
+
overlay: false,
|
|
38639
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
38640
|
+
compute: (bars, inputs) => {
|
|
38641
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38642
|
+
const line = [
|
|
38643
|
+
sma(roc(src, 10), 10),
|
|
38644
|
+
sma(roc(src, 15), 10),
|
|
38645
|
+
sma(roc(src, 20), 10),
|
|
38646
|
+
sma(roc(src, 30), 15)
|
|
38647
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
38648
|
+
return {
|
|
38649
|
+
plots: [
|
|
38650
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
38651
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
38652
|
+
],
|
|
38653
|
+
levels: ZERO_LEVEL
|
|
38654
|
+
};
|
|
38655
|
+
}
|
|
38656
|
+
};
|
|
38657
|
+
var coppock = {
|
|
38658
|
+
type: "coppock-curve",
|
|
38659
|
+
title: "Coppock Curve",
|
|
38660
|
+
shortTitle: "Coppock",
|
|
38661
|
+
overlay: false,
|
|
38662
|
+
inputs: [
|
|
38663
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
38664
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
38665
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
38666
|
+
sourceInput(),
|
|
38667
|
+
colorInput()
|
|
38668
|
+
],
|
|
38669
|
+
compute: (bars, inputs) => {
|
|
38670
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38671
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
38672
|
+
return {
|
|
38673
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38674
|
+
levels: ZERO_LEVEL
|
|
38675
|
+
};
|
|
38676
|
+
}
|
|
38677
|
+
};
|
|
38678
|
+
var dpo = {
|
|
38679
|
+
type: "detrended-price-oscillator",
|
|
38680
|
+
title: "Detrended Price Oscillator",
|
|
38681
|
+
shortTitle: "DPO",
|
|
38682
|
+
overlay: false,
|
|
38683
|
+
inputs: [lengthInput(21), colorInput()],
|
|
38684
|
+
compute: (bars, inputs) => {
|
|
38685
|
+
const len = num3(inputs, "length", 21);
|
|
38686
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
38687
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
38688
|
+
return {
|
|
38689
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38690
|
+
levels: ZERO_LEVEL
|
|
38691
|
+
};
|
|
38692
|
+
}
|
|
38693
|
+
};
|
|
38694
|
+
var rocSpec = {
|
|
38695
|
+
type: "rate-of-change",
|
|
38696
|
+
title: "Rate of Change",
|
|
38697
|
+
shortTitle: "ROC",
|
|
38698
|
+
overlay: false,
|
|
38699
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38700
|
+
compute: (bars, inputs) => ({
|
|
38701
|
+
plots: [
|
|
38702
|
+
{
|
|
38703
|
+
key: "roc",
|
|
38704
|
+
title: "RoC",
|
|
38705
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
38706
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38707
|
+
width: 2
|
|
38708
|
+
}
|
|
38709
|
+
],
|
|
38710
|
+
levels: ZERO_LEVEL
|
|
38711
|
+
})
|
|
38712
|
+
};
|
|
38713
|
+
var ultimate = {
|
|
38714
|
+
type: "ultimate-oscillator",
|
|
38715
|
+
title: "Ultimate Oscillator",
|
|
38716
|
+
shortTitle: "UO",
|
|
38717
|
+
overlay: false,
|
|
38718
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
38719
|
+
compute: (bars, inputs) => {
|
|
38720
|
+
const n = bars.length;
|
|
38721
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
38722
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
38723
|
+
for (let i = 0; i < n; i++) {
|
|
38724
|
+
const b = bars[i];
|
|
38725
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
38726
|
+
const lo = Math.min(b.low, pc);
|
|
38727
|
+
const hi = Math.max(b.high, pc);
|
|
38728
|
+
bp[i] = b.close - lo;
|
|
38729
|
+
tr[i] = hi - lo;
|
|
38730
|
+
}
|
|
38731
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
38732
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
38733
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
38734
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
38735
|
+
const values = a7.map((x, i) => {
|
|
38736
|
+
const y = a14[i];
|
|
38737
|
+
const z = a28[i];
|
|
38738
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
38739
|
+
});
|
|
38740
|
+
return {
|
|
38741
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38742
|
+
levels: guideLevels(30, 70, 50)
|
|
38743
|
+
};
|
|
38744
|
+
}
|
|
38745
|
+
};
|
|
38746
|
+
var rvgi = {
|
|
38747
|
+
type: "relative-vigor-index",
|
|
38748
|
+
title: "Relative Vigor Index",
|
|
38749
|
+
shortTitle: "RVGI",
|
|
38750
|
+
overlay: false,
|
|
38751
|
+
inputs: [lengthInput(10)],
|
|
38752
|
+
compute: (bars, inputs) => {
|
|
38753
|
+
const len = num3(inputs, "length", 10);
|
|
38754
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
38755
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
38756
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
38757
|
+
return {
|
|
38758
|
+
plots: [
|
|
38759
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
38760
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
38761
|
+
],
|
|
38762
|
+
levels: ZERO_LEVEL
|
|
38763
|
+
};
|
|
38764
|
+
}
|
|
38765
|
+
};
|
|
38766
|
+
var relVolatility = {
|
|
38767
|
+
type: "relative-volatility-index",
|
|
38768
|
+
title: "Relative Volatility Index",
|
|
38769
|
+
shortTitle: "RVI",
|
|
38770
|
+
overlay: false,
|
|
38771
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
38772
|
+
compute: (bars, inputs) => {
|
|
38773
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
38774
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38775
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
38776
|
+
const d = change(src);
|
|
38777
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
38778
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
38779
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
38780
|
+
return {
|
|
38781
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38782
|
+
levels: guideLevels(20, 80, 50)
|
|
38783
|
+
};
|
|
38784
|
+
}
|
|
38785
|
+
};
|
|
38786
|
+
var bop = {
|
|
38787
|
+
type: "balance-of-power",
|
|
38788
|
+
title: "Balance of Power",
|
|
38789
|
+
shortTitle: "BOP",
|
|
38790
|
+
overlay: false,
|
|
38791
|
+
inputs: [colorInput()],
|
|
38792
|
+
compute: (bars, inputs) => {
|
|
38793
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
38794
|
+
return {
|
|
38795
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38796
|
+
levels: ZERO_LEVEL
|
|
38797
|
+
};
|
|
38798
|
+
}
|
|
38799
|
+
};
|
|
38800
|
+
var elderRay = {
|
|
38801
|
+
type: "elder-ray",
|
|
38802
|
+
title: "Elder Ray",
|
|
38803
|
+
overlay: false,
|
|
38804
|
+
inputs: [lengthInput(13)],
|
|
38805
|
+
compute: (bars, inputs) => {
|
|
38806
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
38807
|
+
return {
|
|
38808
|
+
plots: [
|
|
38809
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
38810
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
38811
|
+
],
|
|
38812
|
+
levels: ZERO_LEVEL
|
|
38813
|
+
};
|
|
38814
|
+
}
|
|
38815
|
+
};
|
|
38816
|
+
var ttmSqueeze = {
|
|
38817
|
+
type: "ttm-squeeze",
|
|
38818
|
+
title: "TTM Squeeze",
|
|
38819
|
+
overlay: false,
|
|
38820
|
+
inputs: [
|
|
38821
|
+
lengthInput(20),
|
|
38822
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38823
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
38824
|
+
],
|
|
38825
|
+
compute: (bars, inputs) => {
|
|
38826
|
+
const len = num3(inputs, "length", 20);
|
|
38827
|
+
const c = closes(bars);
|
|
38828
|
+
const basis = sma(c, len);
|
|
38829
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
38830
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
38831
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
38832
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
38833
|
+
const mom = linreg(sub(c, anchor), len);
|
|
38834
|
+
const squeezed = c.map((_, i) => {
|
|
38835
|
+
const d = dev[i];
|
|
38836
|
+
const r = kcRange[i];
|
|
38837
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
38838
|
+
});
|
|
38839
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
38840
|
+
return {
|
|
38841
|
+
plots: [
|
|
38842
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
38843
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
38844
|
+
]
|
|
38845
|
+
};
|
|
38846
|
+
}
|
|
38847
|
+
};
|
|
38848
|
+
var oscillatorSpecs = [
|
|
38849
|
+
rsiSpec,
|
|
38850
|
+
stochastic,
|
|
38851
|
+
stochasticRsi,
|
|
38852
|
+
macd,
|
|
38853
|
+
ppo,
|
|
38854
|
+
awesome,
|
|
38855
|
+
cci,
|
|
38856
|
+
williamsR,
|
|
38857
|
+
cmo,
|
|
38858
|
+
connorsRsi,
|
|
38859
|
+
fisher,
|
|
38860
|
+
trix,
|
|
38861
|
+
tsi,
|
|
38862
|
+
smiErgodic,
|
|
38863
|
+
stc,
|
|
38864
|
+
kst,
|
|
38865
|
+
coppock,
|
|
38866
|
+
dpo,
|
|
38867
|
+
rocSpec,
|
|
38868
|
+
ultimate,
|
|
38869
|
+
rvgi,
|
|
38870
|
+
relVolatility,
|
|
38871
|
+
bop,
|
|
38872
|
+
elderRay,
|
|
38873
|
+
ttmSqueeze
|
|
38874
|
+
];
|
|
38875
|
+
|
|
38876
|
+
// src/core/native-indicators/classics/trend.ts
|
|
38877
|
+
var aroon = {
|
|
38878
|
+
type: "aroon",
|
|
38879
|
+
title: "Aroon",
|
|
38880
|
+
overlay: false,
|
|
38881
|
+
inputs: [lengthInput(14)],
|
|
38882
|
+
compute: (bars, inputs) => {
|
|
38883
|
+
const len = num3(inputs, "length", 14);
|
|
38884
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38885
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38886
|
+
return {
|
|
38887
|
+
plots: [
|
|
38888
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
38889
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
38890
|
+
],
|
|
38891
|
+
levels: [
|
|
38892
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
38893
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
38894
|
+
]
|
|
38895
|
+
};
|
|
38896
|
+
}
|
|
38897
|
+
};
|
|
38898
|
+
var adx = {
|
|
38899
|
+
type: "average-directional-index",
|
|
38900
|
+
title: "Average Directional Index",
|
|
38901
|
+
shortTitle: "ADX",
|
|
38902
|
+
overlay: false,
|
|
38903
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
38904
|
+
compute: (bars, inputs) => {
|
|
38905
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
38906
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
38907
|
+
const n = bars.length;
|
|
38908
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
38909
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
38910
|
+
for (let i = 1; i < n; i++) {
|
|
38911
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
38912
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
38913
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
38914
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
38915
|
+
}
|
|
38916
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
38917
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38918
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38919
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
38920
|
+
return {
|
|
38921
|
+
plots: [
|
|
38922
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
38923
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
38924
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
38925
|
+
],
|
|
38926
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38927
|
+
};
|
|
38928
|
+
}
|
|
38929
|
+
};
|
|
38930
|
+
var vortex = {
|
|
38931
|
+
type: "vortex-indicator",
|
|
38932
|
+
title: "Vortex Indicator",
|
|
38933
|
+
shortTitle: "VI",
|
|
38934
|
+
overlay: false,
|
|
38935
|
+
inputs: [lengthInput(14)],
|
|
38936
|
+
compute: (bars, inputs) => {
|
|
38937
|
+
const len = num3(inputs, "length", 14);
|
|
38938
|
+
const n = bars.length;
|
|
38939
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
38940
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
38941
|
+
for (let i = 1; i < n; i++) {
|
|
38942
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
38943
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
38944
|
+
}
|
|
38945
|
+
const trSum = sum(trueRange(bars), len);
|
|
38946
|
+
return {
|
|
38947
|
+
plots: [
|
|
38948
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
38949
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
38950
|
+
],
|
|
38951
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38952
|
+
};
|
|
38953
|
+
}
|
|
38954
|
+
};
|
|
38955
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
38956
|
+
|
|
38957
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
38958
|
+
var atrSpec = {
|
|
38959
|
+
type: "average-true-range",
|
|
38960
|
+
title: "Average True Range",
|
|
38961
|
+
shortTitle: "ATR",
|
|
38962
|
+
overlay: false,
|
|
38963
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38964
|
+
compute: (bars, inputs) => ({
|
|
38965
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
38966
|
+
})
|
|
38967
|
+
};
|
|
38968
|
+
var historicalVolatility = {
|
|
38969
|
+
type: "historical-volatility",
|
|
38970
|
+
title: "Historical Volatility",
|
|
38971
|
+
shortTitle: "HV",
|
|
38972
|
+
overlay: false,
|
|
38973
|
+
inputs: [
|
|
38974
|
+
lengthInput(10),
|
|
38975
|
+
{ 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)" },
|
|
38976
|
+
colorInput()
|
|
38977
|
+
],
|
|
38978
|
+
compute: (bars, inputs) => {
|
|
38979
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
38980
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
38981
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38982
|
+
}
|
|
38983
|
+
};
|
|
38984
|
+
var chaikinVolatility = {
|
|
38985
|
+
type: "chaikin-volatility",
|
|
38986
|
+
title: "Chaikin Volatility",
|
|
38987
|
+
shortTitle: "CHV",
|
|
38988
|
+
overlay: false,
|
|
38989
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
38990
|
+
compute: (bars, inputs) => {
|
|
38991
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
38992
|
+
return {
|
|
38993
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38994
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
38995
|
+
};
|
|
38996
|
+
}
|
|
38997
|
+
};
|
|
38998
|
+
var massIndex = {
|
|
38999
|
+
type: "mass-index",
|
|
39000
|
+
title: "Mass Index",
|
|
39001
|
+
shortTitle: "MI",
|
|
39002
|
+
overlay: false,
|
|
39003
|
+
inputs: [lengthInput(25), colorInput()],
|
|
39004
|
+
compute: (bars, inputs) => {
|
|
39005
|
+
const range = sub(highs(bars), lows(bars));
|
|
39006
|
+
const single = ema(range, 9);
|
|
39007
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39008
|
+
return {
|
|
39009
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39010
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
39011
|
+
};
|
|
39012
|
+
}
|
|
39013
|
+
};
|
|
39014
|
+
var standardDeviation = {
|
|
39015
|
+
type: "standard-deviation",
|
|
39016
|
+
title: "Standard Deviation",
|
|
39017
|
+
shortTitle: "StdDev",
|
|
39018
|
+
overlay: false,
|
|
39019
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
39020
|
+
compute: (bars, inputs) => ({
|
|
39021
|
+
plots: [
|
|
39022
|
+
{
|
|
39023
|
+
key: "stdev",
|
|
39024
|
+
title: "StdDev",
|
|
39025
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
39026
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39027
|
+
width: 2
|
|
39028
|
+
}
|
|
39029
|
+
]
|
|
39030
|
+
})
|
|
39031
|
+
};
|
|
39032
|
+
var ulcerIndex = {
|
|
39033
|
+
type: "ulcer-index",
|
|
39034
|
+
title: "Ulcer Index",
|
|
39035
|
+
overlay: false,
|
|
39036
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39037
|
+
compute: (bars, inputs) => {
|
|
39038
|
+
const len = num3(inputs, "length", 14);
|
|
39039
|
+
const c = closes(bars);
|
|
39040
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
39041
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
39042
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39043
|
+
}
|
|
39044
|
+
};
|
|
39045
|
+
var choppiness = {
|
|
39046
|
+
type: "choppiness-index",
|
|
39047
|
+
title: "Choppiness Index",
|
|
39048
|
+
shortTitle: "CHOP",
|
|
39049
|
+
overlay: false,
|
|
39050
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39051
|
+
compute: (bars, inputs) => {
|
|
39052
|
+
const len = num3(inputs, "length", 14);
|
|
39053
|
+
const trSum = sum(trueRange(bars), len);
|
|
39054
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
39055
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
39056
|
+
return {
|
|
39057
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39058
|
+
levels: [
|
|
39059
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
39060
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
39061
|
+
]
|
|
39062
|
+
};
|
|
39063
|
+
}
|
|
39064
|
+
};
|
|
39065
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
39066
|
+
|
|
39067
|
+
// src/core/native-indicators/classics/volume.ts
|
|
39068
|
+
function moneyFlowVolume(bars) {
|
|
39069
|
+
return bars.map((b) => {
|
|
39070
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39071
|
+
if (b.high === b.low) return 0;
|
|
39072
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
39073
|
+
});
|
|
39074
|
+
}
|
|
39075
|
+
var obv = {
|
|
39076
|
+
type: "on-balance-volume",
|
|
39077
|
+
title: "On Balance Volume",
|
|
39078
|
+
shortTitle: "OBV",
|
|
39079
|
+
overlay: false,
|
|
39080
|
+
inputs: [colorInput()],
|
|
39081
|
+
compute: (bars, inputs) => {
|
|
39082
|
+
const vol = volumes(bars);
|
|
39083
|
+
const d = change(closes(bars));
|
|
39084
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
39085
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39086
|
+
}
|
|
39087
|
+
};
|
|
39088
|
+
var accDist = {
|
|
39089
|
+
type: "accumulation-distribution",
|
|
39090
|
+
title: "Accumulation / Distribution",
|
|
39091
|
+
shortTitle: "A/D",
|
|
39092
|
+
overlay: false,
|
|
39093
|
+
inputs: [colorInput()],
|
|
39094
|
+
compute: (bars, inputs) => ({
|
|
39095
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
39096
|
+
})
|
|
39097
|
+
};
|
|
39098
|
+
var cmf = {
|
|
39099
|
+
type: "chaikin-money-flow",
|
|
39100
|
+
title: "Chaikin Money Flow",
|
|
39101
|
+
shortTitle: "CMF",
|
|
39102
|
+
overlay: false,
|
|
39103
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39104
|
+
compute: (bars, inputs) => {
|
|
39105
|
+
const len = num3(inputs, "length", 20);
|
|
39106
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
39107
|
+
return {
|
|
39108
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39109
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39110
|
+
};
|
|
39111
|
+
}
|
|
39112
|
+
};
|
|
39113
|
+
var chaikinOscillator = {
|
|
39114
|
+
type: "chaikin-oscillator",
|
|
39115
|
+
title: "Chaikin Oscillator",
|
|
39116
|
+
shortTitle: "Chaikin Osc",
|
|
39117
|
+
overlay: false,
|
|
39118
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39119
|
+
compute: (bars, inputs) => {
|
|
39120
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
39121
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
39122
|
+
return {
|
|
39123
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39124
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39125
|
+
};
|
|
39126
|
+
}
|
|
39127
|
+
};
|
|
39128
|
+
var eom = {
|
|
39129
|
+
type: "ease-of-movement",
|
|
39130
|
+
title: "Ease of Movement",
|
|
39131
|
+
shortTitle: "EOM",
|
|
39132
|
+
overlay: false,
|
|
39133
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
39134
|
+
compute: (bars, inputs) => {
|
|
39135
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
39136
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39137
|
+
const move = change(hl2);
|
|
39138
|
+
const raw = bars.map((b, i) => {
|
|
39139
|
+
const m = move[i];
|
|
39140
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
39141
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
39142
|
+
});
|
|
39143
|
+
return {
|
|
39144
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39145
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39146
|
+
};
|
|
39147
|
+
}
|
|
39148
|
+
};
|
|
39149
|
+
var forceIndex = {
|
|
39150
|
+
type: "force-index",
|
|
39151
|
+
title: "Force Index",
|
|
39152
|
+
shortTitle: "FI",
|
|
39153
|
+
overlay: false,
|
|
39154
|
+
inputs: [lengthInput(13), colorInput()],
|
|
39155
|
+
compute: (bars, inputs) => {
|
|
39156
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
39157
|
+
return {
|
|
39158
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39159
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39160
|
+
};
|
|
39161
|
+
}
|
|
39162
|
+
};
|
|
39163
|
+
var klinger = {
|
|
39164
|
+
type: "klinger-oscillator",
|
|
39165
|
+
title: "Klinger Oscillator",
|
|
39166
|
+
shortTitle: "KVO",
|
|
39167
|
+
overlay: false,
|
|
39168
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39169
|
+
compute: (bars, inputs) => {
|
|
39170
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
39171
|
+
const trend = change(hlc3);
|
|
39172
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
39173
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
39174
|
+
return {
|
|
39175
|
+
plots: [
|
|
39176
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39177
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39178
|
+
],
|
|
39179
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39180
|
+
};
|
|
39181
|
+
}
|
|
39182
|
+
};
|
|
39183
|
+
var mfi = {
|
|
39184
|
+
type: "money-flow-index",
|
|
39185
|
+
title: "Money Flow Index",
|
|
39186
|
+
shortTitle: "MFI",
|
|
39187
|
+
overlay: false,
|
|
39188
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39189
|
+
compute: (bars, inputs) => {
|
|
39190
|
+
const len = num3(inputs, "length", 14);
|
|
39191
|
+
const tp = sourceValues(bars, "HLC3");
|
|
39192
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
39193
|
+
const d = change(tp);
|
|
39194
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
39195
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
39196
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
39197
|
+
return {
|
|
39198
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39199
|
+
levels: [
|
|
39200
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
39201
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
39202
|
+
]
|
|
39203
|
+
};
|
|
39204
|
+
}
|
|
39205
|
+
};
|
|
39206
|
+
function volumeIndex(bars, when) {
|
|
39207
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
39208
|
+
let index = 1e3;
|
|
39209
|
+
let started = false;
|
|
39210
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39211
|
+
const v = bars[i].volume;
|
|
39212
|
+
const pv = bars[i - 1].volume;
|
|
39213
|
+
const pc = bars[i - 1].close;
|
|
39214
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
39215
|
+
if (started) out[i] = index;
|
|
39216
|
+
continue;
|
|
39217
|
+
}
|
|
39218
|
+
started = true;
|
|
39219
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
39220
|
+
out[i] = index;
|
|
39221
|
+
}
|
|
39222
|
+
return out;
|
|
39223
|
+
}
|
|
39224
|
+
var nvi = {
|
|
39225
|
+
type: "negative-volume-index",
|
|
39226
|
+
title: "Negative Volume Index",
|
|
39227
|
+
shortTitle: "Negative Volume Index",
|
|
39228
|
+
overlay: false,
|
|
39229
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39230
|
+
compute: (bars, inputs) => {
|
|
39231
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
39232
|
+
return {
|
|
39233
|
+
plots: [
|
|
39234
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39235
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39236
|
+
]
|
|
39237
|
+
};
|
|
39238
|
+
}
|
|
39239
|
+
};
|
|
39240
|
+
var pvi = {
|
|
39241
|
+
type: "positive-volume-index",
|
|
39242
|
+
title: "Positive Volume Index",
|
|
39243
|
+
shortTitle: "Positive Volume Index",
|
|
39244
|
+
overlay: false,
|
|
39245
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39246
|
+
compute: (bars, inputs) => {
|
|
39247
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
39248
|
+
return {
|
|
39249
|
+
plots: [
|
|
39250
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39251
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39252
|
+
]
|
|
39253
|
+
};
|
|
39254
|
+
}
|
|
39255
|
+
};
|
|
39256
|
+
var pvt = {
|
|
39257
|
+
type: "price-volume-trend",
|
|
39258
|
+
title: "Price Volume Trend",
|
|
39259
|
+
shortTitle: "PVT",
|
|
39260
|
+
overlay: false,
|
|
39261
|
+
inputs: [colorInput()],
|
|
39262
|
+
compute: (bars, inputs) => {
|
|
39263
|
+
const c = closes(bars);
|
|
39264
|
+
const raw = bars.map((b, i) => {
|
|
39265
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39266
|
+
const pc = c[i - 1];
|
|
39267
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
39268
|
+
});
|
|
39269
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39270
|
+
}
|
|
39271
|
+
};
|
|
39272
|
+
var volumeOscillator = {
|
|
39273
|
+
type: "volume-oscillator",
|
|
39274
|
+
title: "Volume Oscillator",
|
|
39275
|
+
shortTitle: "Vol Osc",
|
|
39276
|
+
overlay: false,
|
|
39277
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39278
|
+
compute: (bars, inputs) => {
|
|
39279
|
+
const vol = volumes(bars);
|
|
39280
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
39281
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
39282
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39283
|
+
return {
|
|
39284
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39285
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39286
|
+
};
|
|
39287
|
+
}
|
|
39288
|
+
};
|
|
39289
|
+
var pvo = {
|
|
39290
|
+
type: "pvo",
|
|
39291
|
+
title: "Percentage Volume Oscillator",
|
|
39292
|
+
shortTitle: "PVO",
|
|
39293
|
+
overlay: false,
|
|
39294
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
39295
|
+
compute: (bars, inputs) => {
|
|
39296
|
+
const vol = volumes(bars);
|
|
39297
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
39298
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
39299
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39300
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39301
|
+
const hist = sub(line, signal);
|
|
39302
|
+
return {
|
|
39303
|
+
plots: [
|
|
39304
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
39305
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39306
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39307
|
+
],
|
|
39308
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39309
|
+
};
|
|
39310
|
+
}
|
|
39311
|
+
};
|
|
39312
|
+
var vfi = {
|
|
39313
|
+
type: "volume-flow-indicator",
|
|
39314
|
+
title: "Volume Flow Indicator",
|
|
39315
|
+
shortTitle: "Volume Flow Indicator",
|
|
39316
|
+
overlay: false,
|
|
39317
|
+
inputs: [
|
|
39318
|
+
lengthInput(130),
|
|
39319
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
39320
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
39321
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
39322
|
+
],
|
|
39323
|
+
compute: (bars, inputs) => {
|
|
39324
|
+
const len = num3(inputs, "length", 130);
|
|
39325
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
39326
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
39327
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
39328
|
+
const inter = change(typical);
|
|
39329
|
+
const vInter = stdev(inter, 30);
|
|
39330
|
+
const vol = volumes(bars);
|
|
39331
|
+
const vAve = sma(vol, len);
|
|
39332
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
39333
|
+
const raw = bars.map((b, i) => {
|
|
39334
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
39335
|
+
const v = vol[i];
|
|
39336
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39337
|
+
const m = mf[i];
|
|
39338
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
39339
|
+
const capped = Math.min(v, ave * volCoef);
|
|
39340
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
39341
|
+
});
|
|
39342
|
+
const values = bars.map((_, i) => {
|
|
39343
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39344
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
39345
|
+
});
|
|
39346
|
+
const flowSum = sum(raw, len);
|
|
39347
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
39348
|
+
return {
|
|
39349
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
39350
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39351
|
+
};
|
|
39352
|
+
}
|
|
39353
|
+
};
|
|
39354
|
+
var intradayIntensity = {
|
|
39355
|
+
type: "intraday-intensity",
|
|
39356
|
+
title: "Intraday Intensity",
|
|
39357
|
+
shortTitle: "Intraday Intensity",
|
|
39358
|
+
overlay: false,
|
|
39359
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39360
|
+
compute: (bars, inputs) => {
|
|
39361
|
+
const len = num3(inputs, "length", 21);
|
|
39362
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
39363
|
+
return {
|
|
39364
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39365
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39366
|
+
};
|
|
39367
|
+
}
|
|
39368
|
+
};
|
|
39369
|
+
var volumeSpecs = [
|
|
39370
|
+
obv,
|
|
39371
|
+
accDist,
|
|
39372
|
+
cmf,
|
|
39373
|
+
chaikinOscillator,
|
|
39374
|
+
eom,
|
|
39375
|
+
forceIndex,
|
|
39376
|
+
klinger,
|
|
39377
|
+
mfi,
|
|
39378
|
+
nvi,
|
|
39379
|
+
pvi,
|
|
39380
|
+
pvt,
|
|
39381
|
+
volumeOscillator,
|
|
39382
|
+
pvo,
|
|
39383
|
+
vfi,
|
|
39384
|
+
intradayIntensity
|
|
39385
|
+
];
|
|
39386
|
+
|
|
39387
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
39388
|
+
var DAY_MS2 = 864e5;
|
|
39389
|
+
function periodKey(anchor, time) {
|
|
39390
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
39391
|
+
if (anchor === "Month") {
|
|
39392
|
+
const d = new Date(time);
|
|
39393
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
39394
|
+
}
|
|
39395
|
+
return Math.floor(time / DAY_MS2);
|
|
39396
|
+
}
|
|
39397
|
+
var parabolicSar = {
|
|
39398
|
+
type: "parabolic-sar",
|
|
39399
|
+
title: "Parabolic SAR",
|
|
39400
|
+
shortTitle: "SAR",
|
|
39401
|
+
overlay: true,
|
|
39402
|
+
inputs: [
|
|
39403
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39404
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39405
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
39406
|
+
],
|
|
39407
|
+
compute: (bars, inputs) => {
|
|
39408
|
+
const start = num3(inputs, "start", 0.02);
|
|
39409
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
39410
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
39411
|
+
const n = bars.length;
|
|
39412
|
+
const values = new Array(n).fill(Number.NaN);
|
|
39413
|
+
const colors = new Array(n).fill(null);
|
|
39414
|
+
if (n >= 2) {
|
|
39415
|
+
let long = bars[1].close >= bars[0].close;
|
|
39416
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
39417
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
39418
|
+
let af = start;
|
|
39419
|
+
for (let i = 1; i < n; i++) {
|
|
39420
|
+
const b = bars[i];
|
|
39421
|
+
sar += af * (ep - sar);
|
|
39422
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
39423
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
39424
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
39425
|
+
if (reversed) {
|
|
39426
|
+
long = !long;
|
|
39427
|
+
sar = ep;
|
|
39428
|
+
ep = long ? b.high : b.low;
|
|
39429
|
+
af = start;
|
|
39430
|
+
} else if (long && b.high > ep) {
|
|
39431
|
+
ep = b.high;
|
|
39432
|
+
af = Math.min(max, af + inc);
|
|
39433
|
+
} else if (!long && b.low < ep) {
|
|
39434
|
+
ep = b.low;
|
|
39435
|
+
af = Math.min(max, af + inc);
|
|
39436
|
+
}
|
|
39437
|
+
values[i] = sar;
|
|
39438
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
39439
|
+
}
|
|
39440
|
+
}
|
|
39441
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
39442
|
+
}
|
|
39443
|
+
};
|
|
39444
|
+
var vwap = {
|
|
39445
|
+
type: "vwap",
|
|
39446
|
+
title: "Volume Weighted Average Price",
|
|
39447
|
+
shortTitle: "VWAP",
|
|
39448
|
+
overlay: true,
|
|
39449
|
+
inputs: [
|
|
39450
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
39451
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
39452
|
+
colorInput(INFO)
|
|
39453
|
+
],
|
|
39454
|
+
compute: (bars, inputs) => {
|
|
39455
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39456
|
+
const source = str3(inputs, "source", "HLC3");
|
|
39457
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
39458
|
+
let period = Number.NaN;
|
|
39459
|
+
let cumPV = 0;
|
|
39460
|
+
let cumV = 0;
|
|
39461
|
+
for (let i = 0; i < bars.length; i++) {
|
|
39462
|
+
const b = bars[i];
|
|
39463
|
+
const key = periodKey(anchor, b.time);
|
|
39464
|
+
if (key !== period) {
|
|
39465
|
+
period = key;
|
|
39466
|
+
cumPV = 0;
|
|
39467
|
+
cumV = 0;
|
|
39468
|
+
}
|
|
39469
|
+
const v = b.volume;
|
|
39470
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
39471
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
39472
|
+
cumPV += tp * v;
|
|
39473
|
+
cumV += v;
|
|
39474
|
+
}
|
|
39475
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
39476
|
+
}
|
|
39477
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
39478
|
+
}
|
|
39479
|
+
};
|
|
39480
|
+
var pivotPoints = {
|
|
39481
|
+
type: "pivot-points",
|
|
39482
|
+
title: "Pivot Points",
|
|
39483
|
+
shortTitle: "Pivots",
|
|
39484
|
+
overlay: true,
|
|
39485
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
39486
|
+
compute: (bars, inputs) => {
|
|
39487
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39488
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
39489
|
+
for (const b of bars) {
|
|
39490
|
+
const key = periodKey(anchor, b.time);
|
|
39491
|
+
const a = aggs.get(key);
|
|
39492
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
39493
|
+
else {
|
|
39494
|
+
a.high = Math.max(a.high, b.high);
|
|
39495
|
+
a.low = Math.min(a.low, b.low);
|
|
39496
|
+
a.close = b.close;
|
|
39497
|
+
}
|
|
39498
|
+
}
|
|
39499
|
+
const n = bars.length;
|
|
39500
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
39501
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
39502
|
+
for (let i = 0; i < n; i++) {
|
|
39503
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
39504
|
+
if (!prev2) continue;
|
|
39505
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
39506
|
+
levels.p[i] = p;
|
|
39507
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
39508
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
39509
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
39510
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
39511
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
39512
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
39513
|
+
}
|
|
39514
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
39515
|
+
return {
|
|
39516
|
+
plots: [
|
|
39517
|
+
plot("p", "P", WARNING),
|
|
39518
|
+
plot("r1", "R1", BEARISH),
|
|
39519
|
+
plot("s1", "S1", BULLISH),
|
|
39520
|
+
plot("r2", "R2", BEARISH),
|
|
39521
|
+
plot("s2", "S2", BULLISH),
|
|
39522
|
+
plot("r3", "R3", BEARISH),
|
|
39523
|
+
plot("s3", "S3", BULLISH)
|
|
39524
|
+
]
|
|
39525
|
+
};
|
|
39526
|
+
}
|
|
39527
|
+
};
|
|
39528
|
+
var fiftyTwoWeek = {
|
|
39529
|
+
type: "52-week-high-low",
|
|
39530
|
+
title: "52 Week High/Low",
|
|
39531
|
+
shortTitle: "52W H/L",
|
|
39532
|
+
overlay: true,
|
|
39533
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
39534
|
+
compute: (bars, inputs) => {
|
|
39535
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
39536
|
+
const n = bars.length;
|
|
39537
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
39538
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
39539
|
+
const maxIdx = [];
|
|
39540
|
+
const minIdx = [];
|
|
39541
|
+
let from = 0;
|
|
39542
|
+
for (let i = 0; i < n; i++) {
|
|
39543
|
+
const b = bars[i];
|
|
39544
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
39545
|
+
maxIdx.push(i);
|
|
39546
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
39547
|
+
minIdx.push(i);
|
|
39548
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
39549
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
39550
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
39551
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
39552
|
+
lo[i] = bars[minIdx[0]].low;
|
|
39553
|
+
}
|
|
39554
|
+
return {
|
|
39555
|
+
plots: [
|
|
39556
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
39557
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
39558
|
+
]
|
|
39559
|
+
};
|
|
39560
|
+
}
|
|
39561
|
+
};
|
|
39562
|
+
var zigzag = {
|
|
39563
|
+
type: "zigzag",
|
|
39564
|
+
title: "ZigZag",
|
|
39565
|
+
overlay: true,
|
|
39566
|
+
inputs: [
|
|
39567
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
39568
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39569
|
+
colorInput()
|
|
39570
|
+
],
|
|
39571
|
+
compute: (bars, inputs) => {
|
|
39572
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
39573
|
+
const depth = num3(inputs, "depth", 10);
|
|
39574
|
+
const pivots = [];
|
|
39575
|
+
if (bars.length > 1) {
|
|
39576
|
+
let up = bars[1].close >= bars[0].close;
|
|
39577
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
39578
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39579
|
+
const b = bars[i];
|
|
39580
|
+
if (up) {
|
|
39581
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
39582
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39583
|
+
pivots.push(ext);
|
|
39584
|
+
up = false;
|
|
39585
|
+
ext = { i, price: b.low };
|
|
39586
|
+
}
|
|
39587
|
+
} else if (b.low <= ext.price) {
|
|
39588
|
+
ext = { i, price: b.low };
|
|
39589
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39590
|
+
pivots.push(ext);
|
|
39591
|
+
up = true;
|
|
39592
|
+
ext = { i, price: b.high };
|
|
39593
|
+
}
|
|
39594
|
+
}
|
|
39595
|
+
pivots.push(ext);
|
|
39596
|
+
}
|
|
39597
|
+
return {
|
|
39598
|
+
plots: [],
|
|
39599
|
+
polylines: [
|
|
39600
|
+
{
|
|
39601
|
+
key: "zigzag",
|
|
39602
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
39603
|
+
curved: false,
|
|
39604
|
+
closed: false,
|
|
39605
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
39606
|
+
lineWidth: 2,
|
|
39607
|
+
lineStyle: "solid",
|
|
39608
|
+
arrowLeft: false,
|
|
39609
|
+
arrowRight: false
|
|
39610
|
+
}
|
|
39611
|
+
]
|
|
39612
|
+
};
|
|
39613
|
+
}
|
|
39614
|
+
};
|
|
39615
|
+
var williamsFractal = {
|
|
39616
|
+
type: "williams-fractal",
|
|
39617
|
+
title: "Williams Fractal",
|
|
39618
|
+
shortTitle: "Fractals",
|
|
39619
|
+
overlay: true,
|
|
39620
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
39621
|
+
compute: (bars, inputs) => {
|
|
39622
|
+
const p = num3(inputs, "periods", 2);
|
|
39623
|
+
const markers = [];
|
|
39624
|
+
const isExtreme = (i, pick, better) => {
|
|
39625
|
+
const v = pick(bars[i]);
|
|
39626
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
39627
|
+
if (k === i) continue;
|
|
39628
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
39629
|
+
}
|
|
39630
|
+
return true;
|
|
39631
|
+
};
|
|
39632
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
39633
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
39634
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
39635
|
+
}
|
|
39636
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
39637
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
39638
|
+
}
|
|
39639
|
+
}
|
|
39640
|
+
return { plots: [], markers };
|
|
39641
|
+
}
|
|
39642
|
+
};
|
|
39643
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
39644
|
+
|
|
39645
|
+
// src/core/native-indicators/classics/index.ts
|
|
39646
|
+
var classicSpecs = [
|
|
39647
|
+
...averageSpecs,
|
|
39648
|
+
...bandSpecs,
|
|
39649
|
+
...oscillatorSpecs,
|
|
39650
|
+
...trendSpecs,
|
|
39651
|
+
...volatilitySpecs,
|
|
39652
|
+
...volumeSpecs,
|
|
39653
|
+
...overlaySpecs
|
|
39654
|
+
];
|
|
39655
|
+
function registerClassicIndicators() {
|
|
39656
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
39657
|
+
}
|
|
39658
|
+
|
|
37327
39659
|
// src/Vela.ts
|
|
37328
39660
|
var Vela = class {
|
|
37329
39661
|
constructor(container, options = {}, deps = {}) {
|
|
37330
39662
|
registerBuiltinChartTypes();
|
|
37331
39663
|
registerVolume();
|
|
37332
39664
|
registerVpvr();
|
|
39665
|
+
registerClassicIndicators();
|
|
37333
39666
|
const element = resolveElement(container);
|
|
37334
39667
|
const theme = resolveTheme(options.theme);
|
|
37335
39668
|
let animZoom = true;
|
|
@@ -37404,25 +39737,28 @@ var Vela = class {
|
|
|
37404
39737
|
}
|
|
37405
39738
|
/**
|
|
37406
39739
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
37407
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
37408
|
-
* events)
|
|
37409
|
-
*
|
|
39740
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
39741
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
39742
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
39743
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
37410
39744
|
*/
|
|
37411
39745
|
addNativeIndicator(type, options) {
|
|
37412
39746
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
37413
39747
|
}
|
|
37414
39748
|
/**
|
|
37415
39749
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
37416
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
37417
|
-
*
|
|
37418
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
37419
|
-
*
|
|
39750
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
39751
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
39752
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
39753
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
39754
|
+
* provider (a type may need data the symbol lacks).
|
|
37420
39755
|
*/
|
|
37421
39756
|
availableNativeIndicators() {
|
|
37422
39757
|
return this.orchestrator.availableNativeIndicators();
|
|
37423
39758
|
}
|
|
37424
39759
|
/**
|
|
37425
|
-
* The native-indicator types PRESENT on the chart right now
|
|
39760
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
39761
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
37426
39762
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
37427
39763
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
37428
39764
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -38165,7 +40501,7 @@ var Statusline = class {
|
|
|
38165
40501
|
};
|
|
38166
40502
|
|
|
38167
40503
|
// src/widget/session-shading.ts
|
|
38168
|
-
var
|
|
40504
|
+
var DAY_MS3 = 864e5;
|
|
38169
40505
|
var MINUTE_MS = 6e4;
|
|
38170
40506
|
function parseWindow(text, allowOvernight = false) {
|
|
38171
40507
|
if (typeof text !== "string") return null;
|
|
@@ -38244,7 +40580,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38244
40580
|
const dtf = civilFormatter(spec.timezone);
|
|
38245
40581
|
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38246
40582
|
const seen = /* @__PURE__ */ new Set();
|
|
38247
|
-
for (let cursor = from -
|
|
40583
|
+
for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
|
|
38248
40584
|
const civil = civilParts(dtf, cursor);
|
|
38249
40585
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38250
40586
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
@@ -38269,7 +40605,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38269
40605
|
}
|
|
38270
40606
|
return { pre, post, extended: mergeAbutting(extended) };
|
|
38271
40607
|
}
|
|
38272
|
-
var COVER_PAD_MIN_MS = 2 *
|
|
40608
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS3;
|
|
38273
40609
|
var SessionShadingTracker = class {
|
|
38274
40610
|
constructor(onZones) {
|
|
38275
40611
|
this.onZones = onZones;
|
|
@@ -38293,7 +40629,7 @@ var SessionShadingTracker = class {
|
|
|
38293
40629
|
this.covered = null;
|
|
38294
40630
|
this.session = opts.session;
|
|
38295
40631
|
const tfMs = timeframeMs(opts.timeframe);
|
|
38296
|
-
this.intraday = Number.isFinite(tfMs) && tfMs <
|
|
40632
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
|
|
38297
40633
|
this.lastRange = opts.range;
|
|
38298
40634
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
38299
40635
|
if (my !== this.epoch) return;
|
|
@@ -38967,8 +41303,8 @@ var ChartCell = class {
|
|
|
38967
41303
|
/** The volume auto-add rides the cell's first candles (`load:end`); until then the
|
|
38968
41304
|
* registry can't show it and the dehydrated ledger reports the INTENT instead. */
|
|
38969
41305
|
this.volumeMayBePending = true;
|
|
38970
|
-
/** Sync mirror of the chart's
|
|
38971
|
-
*
|
|
41306
|
+
/** Sync mirror of the chart's native instances (id + type) — the removal handler
|
|
41307
|
+
* looks the removed id up here to learn which type an undo must re-add. */
|
|
38972
41308
|
this.presentNatives = [];
|
|
38973
41309
|
this.rangeBars = 0;
|
|
38974
41310
|
this.pendingRange = null;
|
|
@@ -39140,15 +41476,18 @@ var ChartCell = class {
|
|
|
39140
41476
|
redo: () => this.dropInstance(snapshot)
|
|
39141
41477
|
});
|
|
39142
41478
|
} else {
|
|
39143
|
-
const
|
|
39144
|
-
|
|
41479
|
+
const gone = this.presentNatives.find((n) => n.id === id2);
|
|
41480
|
+
if (gone) {
|
|
41481
|
+
const { type } = gone;
|
|
41482
|
+
let revived = null;
|
|
39145
41483
|
this.history.push({
|
|
39146
41484
|
undo: () => {
|
|
39147
|
-
this.inner?.addNativeIndicator(type);
|
|
41485
|
+
revived = this.inner?.addNativeIndicator(type) ?? null;
|
|
39148
41486
|
this.refreshNativeCatalog();
|
|
39149
41487
|
},
|
|
39150
41488
|
redo: () => {
|
|
39151
|
-
|
|
41489
|
+
revived?.remove();
|
|
41490
|
+
revived = null;
|
|
39152
41491
|
this.refreshNativeCatalog();
|
|
39153
41492
|
}
|
|
39154
41493
|
});
|
|
@@ -39453,9 +41792,9 @@ var ChartCell = class {
|
|
|
39453
41792
|
const live = this.inner?.renderer.get("priceStyle");
|
|
39454
41793
|
return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
|
|
39455
41794
|
}
|
|
39456
|
-
/** Manifest instances +
|
|
41795
|
+
/** Manifest instances + native instances — the topbar indicator count. */
|
|
39457
41796
|
get indicatorCount() {
|
|
39458
|
-
return this.instances.length + this.
|
|
41797
|
+
return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
|
|
39459
41798
|
}
|
|
39460
41799
|
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
39461
41800
|
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
@@ -39587,13 +41926,15 @@ var ChartCell = class {
|
|
|
39587
41926
|
if (!chart) return;
|
|
39588
41927
|
this.volumeIntent = led.natives.includes("volume");
|
|
39589
41928
|
this.history.silently(() => {
|
|
39590
|
-
const
|
|
39591
|
-
for (const type of led.natives)
|
|
39592
|
-
|
|
39593
|
-
|
|
39594
|
-
|
|
39595
|
-
if (
|
|
39596
|
-
|
|
41929
|
+
const owed = /* @__PURE__ */ new Map();
|
|
41930
|
+
for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
|
|
41931
|
+
for (const h of this.nativeHandles()) {
|
|
41932
|
+
const type = h.nativeType;
|
|
41933
|
+
const n = owed.get(type) ?? 0;
|
|
41934
|
+
if (n > 0) owed.set(type, n - 1);
|
|
41935
|
+
else h.remove();
|
|
41936
|
+
}
|
|
41937
|
+
for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
|
|
39597
41938
|
for (const it of [...this.instances]) this.dropInstance(it);
|
|
39598
41939
|
if (this.manifest.length > 0) {
|
|
39599
41940
|
for (const item of led.manifest) {
|
|
@@ -39611,34 +41952,42 @@ var ChartCell = class {
|
|
|
39611
41952
|
this.syncPresentNatives();
|
|
39612
41953
|
this.refreshNativeCatalog();
|
|
39613
41954
|
}
|
|
39614
|
-
/** The picker
|
|
41955
|
+
/** The supported natives in picker order: A→Z by title — registration order follows
|
|
41956
|
+
* the catalog's families, which is meaningless to the reader. This is the library
|
|
41957
|
+
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
41958
|
+
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
41959
|
+
supportedNatives() {
|
|
41960
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
|
|
41961
|
+
}
|
|
41962
|
+
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
41963
|
+
* then the manifest in the host's own order. */
|
|
39615
41964
|
libraryRows() {
|
|
39616
41965
|
return [
|
|
39617
|
-
...this.
|
|
41966
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
|
|
39618
41967
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
39619
41968
|
];
|
|
39620
41969
|
}
|
|
39621
|
-
/** The picker's on-chart rows:
|
|
41970
|
+
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
39622
41971
|
onChartRows() {
|
|
39623
41972
|
return [
|
|
39624
|
-
...this.
|
|
41973
|
+
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
39625
41974
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
39626
41975
|
];
|
|
39627
41976
|
}
|
|
39628
41977
|
/** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
|
|
39629
41978
|
addFromLibrary(index) {
|
|
39630
|
-
const natives = this.
|
|
41979
|
+
const natives = this.supportedNatives();
|
|
39631
41980
|
if (index < natives.length) this.addNative(natives[index].type);
|
|
39632
41981
|
else {
|
|
39633
41982
|
const entry = this.manifest[index - natives.length];
|
|
39634
41983
|
if (entry) this.addManifestInstance(entry);
|
|
39635
41984
|
}
|
|
39636
41985
|
}
|
|
39637
|
-
/** Remove by picker ON-CHART index (
|
|
41986
|
+
/** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
|
|
39638
41987
|
removeFromChart(index) {
|
|
39639
|
-
const
|
|
39640
|
-
if (index <
|
|
39641
|
-
else this.removeInstance(index -
|
|
41988
|
+
const natives = this.nativeHandles();
|
|
41989
|
+
if (index < natives.length) this.removeNative(natives[index]);
|
|
41990
|
+
else this.removeInstance(index - natives.length);
|
|
39642
41991
|
}
|
|
39643
41992
|
/**
|
|
39644
41993
|
* Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
|
|
@@ -39698,30 +42047,42 @@ var ChartCell = class {
|
|
|
39698
42047
|
it.handle = null;
|
|
39699
42048
|
this.deps.onIndicatorsChanged(this.id);
|
|
39700
42049
|
}
|
|
39701
|
-
/** Add a native indicator
|
|
42050
|
+
/** Add a native indicator. A multi-instance type gets a fresh instance every time; a
|
|
42051
|
+
* single-instance type already on the chart hands back its existing one — nothing
|
|
42052
|
+
* changed, so nothing enters the undo timeline. */
|
|
39702
42053
|
addNative(type) {
|
|
39703
|
-
this.inner
|
|
42054
|
+
const chart = this.inner;
|
|
42055
|
+
if (!chart) return;
|
|
42056
|
+
const before = new Set(chart.indicators().map((h) => h.id));
|
|
42057
|
+
let added = chart.addNativeIndicator(type);
|
|
39704
42058
|
this.syncPresentNatives();
|
|
39705
42059
|
this.refreshNativeCatalog();
|
|
42060
|
+
if (before.has(added.id)) return;
|
|
39706
42061
|
this.history.push({
|
|
39707
42062
|
undo: () => {
|
|
39708
|
-
|
|
42063
|
+
added?.remove();
|
|
42064
|
+
added = null;
|
|
39709
42065
|
this.refreshNativeCatalog();
|
|
39710
42066
|
},
|
|
39711
42067
|
redo: () => {
|
|
39712
|
-
this.inner?.addNativeIndicator(type);
|
|
42068
|
+
added = this.inner?.addNativeIndicator(type) ?? null;
|
|
39713
42069
|
this.refreshNativeCatalog();
|
|
39714
42070
|
}
|
|
39715
42071
|
});
|
|
39716
42072
|
}
|
|
39717
|
-
removeNative(
|
|
39718
|
-
|
|
42073
|
+
removeNative(handle) {
|
|
42074
|
+
handle.remove();
|
|
39719
42075
|
this.refreshNativeCatalog();
|
|
39720
42076
|
}
|
|
39721
|
-
/**
|
|
39722
|
-
*
|
|
42077
|
+
/** The chart's native instances, insertion order — the picker's on-chart rows and
|
|
42078
|
+
* the removal index space (script instances follow them). */
|
|
42079
|
+
nativeHandles() {
|
|
42080
|
+
return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
|
|
42081
|
+
}
|
|
42082
|
+
/** Sync mirror of the chart's native instances — the removal handler looks the
|
|
42083
|
+
* removed id up here (the registry has already forgotten it) to record its type. */
|
|
39723
42084
|
syncPresentNatives() {
|
|
39724
|
-
this.presentNatives = this.
|
|
42085
|
+
this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
|
|
39725
42086
|
}
|
|
39726
42087
|
/** Refresh the native catalog (supported/present flags) for this cell's market. */
|
|
39727
42088
|
refreshNativeCatalog() {
|