@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/workspace.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();
|
|
@@ -5113,9 +5120,9 @@ function factoryResetConfig(factory) {
|
|
|
5113
5120
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
5114
5121
|
addRows(inst.rows);
|
|
5115
5122
|
}
|
|
5116
|
-
for (const
|
|
5117
|
-
if (
|
|
5118
|
-
addRows(
|
|
5123
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5124
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
5125
|
+
addRows(sub2.rows);
|
|
5119
5126
|
}
|
|
5120
5127
|
if (!section.instances) addRows(section.rows);
|
|
5121
5128
|
bag[t.id] = defaults2;
|
|
@@ -14031,17 +14038,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
14031
14038
|
}
|
|
14032
14039
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
14033
14040
|
var CSS8 = `
|
|
14034
|
-
/*
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
z-index: 1;
|
|
14041
|
-
background: var(--vela-surface);
|
|
14042
|
-
margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
|
|
14043
|
-
padding: var(--vela-space-4) var(--vela-space-4) 0;
|
|
14044
|
-
}
|
|
14041
|
+
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
14042
|
+
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
14043
|
+
scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
|
|
14044
|
+
scroller's content box, so the head lands one padding below the top and covers
|
|
14045
|
+
the first row. */
|
|
14046
|
+
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
14045
14047
|
.vela-sp-searchrow {
|
|
14046
14048
|
display: flex;
|
|
14047
14049
|
align-items: center;
|
|
@@ -14066,12 +14068,13 @@ var CSS8 = `
|
|
|
14066
14068
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
14067
14069
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
14068
14070
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
14069
|
-
overflowing the body, and the result list stops capping itself \u2014
|
|
14070
|
-
the
|
|
14071
|
+
overflowing the body, and the result list stops capping itself \u2014 it fills the
|
|
14072
|
+
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
14073
|
+
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
14071
14074
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
14072
14075
|
[data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
|
|
14073
14076
|
[data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
|
|
14074
|
-
[data-layout='mobile'] .vela-sp-list { max-height: none; }
|
|
14077
|
+
[data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
|
|
14075
14078
|
.vela-sp-tab {
|
|
14076
14079
|
all: unset;
|
|
14077
14080
|
padding: 3px 10px;
|
|
@@ -14083,7 +14086,12 @@ var CSS8 = `
|
|
|
14083
14086
|
}
|
|
14084
14087
|
.vela-sp-tab:hover { color: var(--vela-fg); }
|
|
14085
14088
|
.vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
14086
|
-
.vela-sp-list {
|
|
14089
|
+
.vela-sp-list {
|
|
14090
|
+
min-height: 0;
|
|
14091
|
+
margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
|
|
14092
|
+
max-height: 46vh;
|
|
14093
|
+
overflow: auto;
|
|
14094
|
+
}
|
|
14087
14095
|
.vela-sp-list::-webkit-scrollbar { width: 8px; }
|
|
14088
14096
|
.vela-sp-list::-webkit-scrollbar-thumb {
|
|
14089
14097
|
background: var(--vela-scroll);
|
|
@@ -14176,8 +14184,8 @@ var SymbolPicker = class {
|
|
|
14176
14184
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14177
14185
|
this.tabs = doc.createElement("div");
|
|
14178
14186
|
this.tabs.className = "vela-sp-tabs";
|
|
14179
|
-
const
|
|
14180
|
-
|
|
14187
|
+
const head = doc.createElement("div");
|
|
14188
|
+
head.className = "vela-sp-head";
|
|
14181
14189
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14182
14190
|
const b = doc.createElement("button");
|
|
14183
14191
|
b.className = "vela-sp-tab";
|
|
@@ -14191,7 +14199,7 @@ var SymbolPicker = class {
|
|
|
14191
14199
|
});
|
|
14192
14200
|
this.tabs.appendChild(b);
|
|
14193
14201
|
}
|
|
14194
|
-
|
|
14202
|
+
head.append(searchRow, this.tabs);
|
|
14195
14203
|
this.list = doc.createElement("div");
|
|
14196
14204
|
this.list.className = "vela-sp-list";
|
|
14197
14205
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14204,7 +14212,8 @@ var SymbolPicker = class {
|
|
|
14204
14212
|
title: "Symbol Search",
|
|
14205
14213
|
host: opts.host,
|
|
14206
14214
|
closeOnInteractOutside: true,
|
|
14207
|
-
|
|
14215
|
+
flush: true,
|
|
14216
|
+
content: (body) => body.append(head, this.list),
|
|
14208
14217
|
onOpenChange: (open2) => {
|
|
14209
14218
|
if (open2) {
|
|
14210
14219
|
this.input.value = this.seed.toUpperCase();
|
|
@@ -15960,11 +15969,11 @@ var PriceScaleDrawer = class {
|
|
|
15960
15969
|
for (const item of items) {
|
|
15961
15970
|
if (item.id === "labels" && item.submenu) {
|
|
15962
15971
|
section("Labels");
|
|
15963
|
-
for (const
|
|
15964
|
-
row(
|
|
15965
|
-
checked:
|
|
15972
|
+
for (const sub2 of item.submenu) {
|
|
15973
|
+
row(sub2.label, {
|
|
15974
|
+
checked: sub2.checked,
|
|
15966
15975
|
onClick: () => {
|
|
15967
|
-
const feature =
|
|
15976
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
15968
15977
|
chart.renderer.set(feature, !this.flag(feature));
|
|
15969
15978
|
this.render();
|
|
15970
15979
|
}
|
|
@@ -15974,11 +15983,11 @@ var PriceScaleDrawer = class {
|
|
|
15974
15983
|
}
|
|
15975
15984
|
if (item.id === "levels" && item.submenu) {
|
|
15976
15985
|
section("Levels");
|
|
15977
|
-
for (const
|
|
15978
|
-
row(
|
|
15979
|
-
checked:
|
|
15986
|
+
for (const sub2 of item.submenu) {
|
|
15987
|
+
row(sub2.label, {
|
|
15988
|
+
checked: sub2.checked,
|
|
15980
15989
|
onClick: () => {
|
|
15981
|
-
const feature =
|
|
15990
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
15982
15991
|
chart.renderer.set(feature, !this.flag(feature));
|
|
15983
15992
|
this.render();
|
|
15984
15993
|
}
|
|
@@ -16552,8 +16561,8 @@ var DrawingController = class {
|
|
|
16552
16561
|
this.port?.setExternalGhost?.(doc);
|
|
16553
16562
|
}
|
|
16554
16563
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
16555
|
-
setToolShortcuts(
|
|
16556
|
-
this.port?.setToolShortcuts?.(
|
|
16564
|
+
setToolShortcuts(map2) {
|
|
16565
|
+
this.port?.setToolShortcuts?.(map2);
|
|
16557
16566
|
}
|
|
16558
16567
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
16559
16568
|
add(type, init = {}) {
|
|
@@ -17224,8 +17233,8 @@ var DrawingToolbar = class {
|
|
|
17224
17233
|
}
|
|
17225
17234
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
17226
17235
|
* right edge of the flyout rows, beside the favorite star. */
|
|
17227
|
-
setShortcuts(
|
|
17228
|
-
this.shortcuts = new Map(Object.entries(
|
|
17236
|
+
setShortcuts(map2) {
|
|
17237
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
17229
17238
|
}
|
|
17230
17239
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
17231
17240
|
paintStar(el, type) {
|
|
@@ -18186,7 +18195,7 @@ var LiveSession = class {
|
|
|
18186
18195
|
|
|
18187
18196
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
18188
18197
|
var IndicatorHandleImpl = class {
|
|
18189
|
-
constructor(id, title, controller, source) {
|
|
18198
|
+
constructor(id, title, controller, source, nativeType) {
|
|
18190
18199
|
this.controller = controller;
|
|
18191
18200
|
this.schema = [];
|
|
18192
18201
|
this.propsSchema = [];
|
|
@@ -18195,6 +18204,7 @@ var IndicatorHandleImpl = class {
|
|
|
18195
18204
|
this.id = id;
|
|
18196
18205
|
this.title = title;
|
|
18197
18206
|
if (source !== void 0) this.source = source;
|
|
18207
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
18198
18208
|
}
|
|
18199
18209
|
get inputs() {
|
|
18200
18210
|
return this.schema;
|
|
@@ -18285,24 +18295,24 @@ function summarizeModel(model) {
|
|
|
18285
18295
|
}
|
|
18286
18296
|
function inspectModels(models) {
|
|
18287
18297
|
const indicators = models.map(summarizeModel);
|
|
18288
|
-
const
|
|
18298
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
18289
18299
|
return {
|
|
18290
18300
|
indicators,
|
|
18291
18301
|
totals: {
|
|
18292
18302
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
18293
|
-
series:
|
|
18294
|
-
fills:
|
|
18295
|
-
backgrounds:
|
|
18296
|
-
priceLines:
|
|
18297
|
-
lines:
|
|
18298
|
-
boxes:
|
|
18299
|
-
labels:
|
|
18300
|
-
polylines:
|
|
18301
|
-
linefills:
|
|
18302
|
-
tables:
|
|
18303
|
-
barColors:
|
|
18304
|
-
trades:
|
|
18305
|
-
forcedOverlay:
|
|
18303
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
18304
|
+
fills: sum2((s) => s.fills),
|
|
18305
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
18306
|
+
priceLines: sum2((s) => s.priceLines),
|
|
18307
|
+
lines: sum2((s) => s.lines),
|
|
18308
|
+
boxes: sum2((s) => s.boxes),
|
|
18309
|
+
labels: sum2((s) => s.labels),
|
|
18310
|
+
polylines: sum2((s) => s.polylines),
|
|
18311
|
+
linefills: sum2((s) => s.linefills),
|
|
18312
|
+
tables: sum2((s) => s.tables),
|
|
18313
|
+
barColors: sum2((s) => s.barColors),
|
|
18314
|
+
trades: sum2((s) => s.trades),
|
|
18315
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
18306
18316
|
}
|
|
18307
18317
|
};
|
|
18308
18318
|
}
|
|
@@ -19371,16 +19381,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19371
19381
|
/**
|
|
19372
19382
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
19373
19383
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
19374
|
-
* INSTANCE per type
|
|
19375
|
-
*
|
|
19384
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
19385
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
19386
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
19387
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
19376
19388
|
*/
|
|
19377
19389
|
addNativeIndicator(type, options = {}) {
|
|
19378
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19379
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
19380
19390
|
const descriptor = getNativeIndicator(type);
|
|
19391
|
+
if (!descriptor?.multiInstance) {
|
|
19392
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19393
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
19394
|
+
}
|
|
19381
19395
|
const id = this.registry.nextId("native");
|
|
19382
19396
|
const title = descriptor?.title ?? type;
|
|
19383
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
19397
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
19384
19398
|
this.handles.set(id, handle);
|
|
19385
19399
|
if (!descriptor) {
|
|
19386
19400
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -19392,16 +19406,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19392
19406
|
void this.startNativeIndicator(id, handle);
|
|
19393
19407
|
return handle;
|
|
19394
19408
|
}
|
|
19395
|
-
/**
|
|
19396
|
-
*
|
|
19397
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
19398
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
19399
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
19400
|
-
*/
|
|
19401
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
19409
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
19410
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
19402
19411
|
presentNativeIndicators() {
|
|
19403
19412
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
19404
19413
|
}
|
|
19414
|
+
/**
|
|
19415
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
19416
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
19417
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
19418
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
19419
|
+
* probe the provider for a required capability.
|
|
19420
|
+
*/
|
|
19405
19421
|
async availableNativeIndicators() {
|
|
19406
19422
|
const symbol = this.qualifiedSymbol();
|
|
19407
19423
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -19411,7 +19427,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19411
19427
|
title: d.title,
|
|
19412
19428
|
supported: await this.isNativeSupported(d, symbol),
|
|
19413
19429
|
present: present.has(d.type),
|
|
19414
|
-
beta: d.beta
|
|
19430
|
+
beta: d.beta,
|
|
19431
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
19415
19432
|
}))
|
|
19416
19433
|
);
|
|
19417
19434
|
}
|
|
@@ -20551,8 +20568,8 @@ var DrawingsControl = class {
|
|
|
20551
20568
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
20552
20569
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
20553
20570
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
20554
|
-
setToolShortcuts(
|
|
20555
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
20571
|
+
setToolShortcuts(map2) {
|
|
20572
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
20556
20573
|
return this;
|
|
20557
20574
|
}
|
|
20558
20575
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -22033,8 +22050,8 @@ var InputsUI = class {
|
|
|
22033
22050
|
lg.style.top = `${top}px`;
|
|
22034
22051
|
}
|
|
22035
22052
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22036
|
-
setCollapsedPanes(
|
|
22037
|
-
this.paneCollapse =
|
|
22053
|
+
setCollapsedPanes(map2) {
|
|
22054
|
+
this.paneCollapse = map2;
|
|
22038
22055
|
}
|
|
22039
22056
|
/**
|
|
22040
22057
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -24043,11 +24060,11 @@ function mergeBarColors(indicators) {
|
|
|
24043
24060
|
}
|
|
24044
24061
|
}
|
|
24045
24062
|
if (!any) return EMPTY_BARCOLORS;
|
|
24046
|
-
const
|
|
24063
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24047
24064
|
for (const m of indicators.values()) {
|
|
24048
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24065
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24049
24066
|
}
|
|
24050
|
-
return
|
|
24067
|
+
return map2;
|
|
24051
24068
|
}
|
|
24052
24069
|
|
|
24053
24070
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24344,9 +24361,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
24344
24361
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24345
24362
|
return rightOffset + deltaX / barSpacing;
|
|
24346
24363
|
}
|
|
24347
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24364
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
24348
24365
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24349
|
-
if (
|
|
24366
|
+
if (shift2) return deltaY;
|
|
24350
24367
|
return null;
|
|
24351
24368
|
}
|
|
24352
24369
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -25968,11 +25985,11 @@ function mergeBarColors2(indicators) {
|
|
|
25968
25985
|
}
|
|
25969
25986
|
}
|
|
25970
25987
|
if (!any) return EMPTY_BARCOLORS2;
|
|
25971
|
-
const
|
|
25988
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
25972
25989
|
for (const m of indicators.values()) {
|
|
25973
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
25990
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
25974
25991
|
}
|
|
25975
|
-
return
|
|
25992
|
+
return map2;
|
|
25976
25993
|
}
|
|
25977
25994
|
|
|
25978
25995
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27611,9 +27628,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
27611
27628
|
};
|
|
27612
27629
|
if (section.rows) addRows(section.rows);
|
|
27613
27630
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27614
|
-
for (const
|
|
27615
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27616
|
-
addRows(
|
|
27631
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27632
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27633
|
+
addRows(sub2.rows);
|
|
27617
27634
|
}
|
|
27618
27635
|
}
|
|
27619
27636
|
for (const hs of hostSections) {
|
|
@@ -28172,14 +28189,14 @@ var SettingsDialog = class {
|
|
|
28172
28189
|
};
|
|
28173
28190
|
if (section.rows) seed(section.rows);
|
|
28174
28191
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28175
|
-
for (const
|
|
28192
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28176
28193
|
for (const inst of section.instances ?? []) {
|
|
28177
28194
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28178
28195
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28179
28196
|
}
|
|
28180
|
-
for (const
|
|
28181
|
-
if (!
|
|
28182
|
-
bag[
|
|
28197
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28198
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28199
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28183
28200
|
}
|
|
28184
28201
|
const refreshers = [];
|
|
28185
28202
|
const put = (key, v) => {
|
|
@@ -28196,16 +28213,16 @@ var SettingsDialog = class {
|
|
|
28196
28213
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28197
28214
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28198
28215
|
}
|
|
28199
|
-
for (const
|
|
28200
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28201
|
-
const rows = filterHiddenRows(
|
|
28216
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28217
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28218
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28202
28219
|
if (rows.length === 0) continue;
|
|
28203
|
-
const subMarker = this.section(
|
|
28220
|
+
const subMarker = this.section(sub2.title);
|
|
28204
28221
|
subMarker.dataset.sdStyle = typeId;
|
|
28205
28222
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28206
28223
|
subMarker.dataset.sdSub = "1";
|
|
28207
28224
|
body.append(subMarker);
|
|
28208
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28225
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28209
28226
|
}
|
|
28210
28227
|
for (const r of refreshers) r();
|
|
28211
28228
|
}
|
|
@@ -30300,11 +30317,11 @@ var DrawingInteraction = class {
|
|
|
30300
30317
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30301
30318
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30302
30319
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30303
|
-
down(x, y, mode = "off",
|
|
30320
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
30304
30321
|
const proj = this.deps.projector();
|
|
30305
30322
|
if (this.state.kind === "placing") {
|
|
30306
30323
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30307
|
-
const pivot =
|
|
30324
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30308
30325
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30309
30326
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30310
30327
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30323,7 +30340,7 @@ var DrawingInteraction = class {
|
|
|
30323
30340
|
}
|
|
30324
30341
|
const hit = this.hitAt(x, y);
|
|
30325
30342
|
if (hit) {
|
|
30326
|
-
if (
|
|
30343
|
+
if (shift2) {
|
|
30327
30344
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30328
30345
|
return;
|
|
30329
30346
|
}
|
|
@@ -30333,7 +30350,7 @@ var DrawingInteraction = class {
|
|
|
30333
30350
|
}
|
|
30334
30351
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30335
30352
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30336
|
-
move(x, y, mode = "off",
|
|
30353
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
30337
30354
|
if (this.state.kind === "idle") {
|
|
30338
30355
|
const tool = this.deps.activeTool();
|
|
30339
30356
|
const had = this.snapAt != null;
|
|
@@ -30347,7 +30364,7 @@ var DrawingInteraction = class {
|
|
|
30347
30364
|
this.captureFreehand(x, y);
|
|
30348
30365
|
return;
|
|
30349
30366
|
}
|
|
30350
|
-
const pivot =
|
|
30367
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30351
30368
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30352
30369
|
this.deps.changed();
|
|
30353
30370
|
return;
|
|
@@ -30360,7 +30377,7 @@ var DrawingInteraction = class {
|
|
|
30360
30377
|
if (this.state.handle >= 0) {
|
|
30361
30378
|
const a = d.anchors[this.state.handle];
|
|
30362
30379
|
if (a) {
|
|
30363
|
-
const pivot =
|
|
30380
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30364
30381
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30365
30382
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30366
30383
|
}
|
|
@@ -31058,21 +31075,21 @@ var DrawingSettingsPopup = class {
|
|
|
31058
31075
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31059
31076
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31060
31077
|
}
|
|
31061
|
-
const
|
|
31062
|
-
if (paths.has("vwap.midColor") &&
|
|
31078
|
+
const vwap2 = drawing.vwap;
|
|
31079
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31063
31080
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31064
31081
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
31065
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
31066
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
31082
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31083
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31067
31084
|
bar.appendChild(
|
|
31068
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31085
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31069
31086
|
label: (v) => `${v}\u03C3`,
|
|
31070
31087
|
labelInTrigger: true
|
|
31071
31088
|
})
|
|
31072
31089
|
);
|
|
31073
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
31074
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
31075
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
31090
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31091
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31092
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31076
31093
|
}
|
|
31077
31094
|
if (paths.has("maxCurvature")) {
|
|
31078
31095
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32089,8 +32106,8 @@ var UserDrawingController = class {
|
|
|
32089
32106
|
this.toolbar.setFavorites(types);
|
|
32090
32107
|
}
|
|
32091
32108
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
32092
|
-
setToolShortcuts(
|
|
32093
|
-
this.toolbar.setShortcuts(
|
|
32109
|
+
setToolShortcuts(map2) {
|
|
32110
|
+
this.toolbar.setShortcuts(map2);
|
|
32094
32111
|
}
|
|
32095
32112
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
32096
32113
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -32208,7 +32225,7 @@ var UserDrawingController = class {
|
|
|
32208
32225
|
this.render();
|
|
32209
32226
|
}
|
|
32210
32227
|
}
|
|
32211
|
-
pointerDown(x, y, snap = "off",
|
|
32228
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
32212
32229
|
if (this.eraserMode) {
|
|
32213
32230
|
this.erasing = true;
|
|
32214
32231
|
this.deleteAt(x, y);
|
|
@@ -32228,9 +32245,9 @@ var UserDrawingController = class {
|
|
|
32228
32245
|
return;
|
|
32229
32246
|
}
|
|
32230
32247
|
}
|
|
32231
|
-
this.interaction.down(x, y, snap,
|
|
32248
|
+
this.interaction.down(x, y, snap, shift2);
|
|
32232
32249
|
}
|
|
32233
|
-
pointerMove(x, y, snap = "off",
|
|
32250
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
32234
32251
|
if (this.eraserMode) {
|
|
32235
32252
|
if (this.erasing) this.deleteAt(x, y);
|
|
32236
32253
|
return;
|
|
@@ -32241,7 +32258,7 @@ var UserDrawingController = class {
|
|
|
32241
32258
|
this.render();
|
|
32242
32259
|
return;
|
|
32243
32260
|
}
|
|
32244
|
-
this.interaction.move(x, y, snap,
|
|
32261
|
+
this.interaction.move(x, y, snap, shift2);
|
|
32245
32262
|
this.updateHover(x, y);
|
|
32246
32263
|
}
|
|
32247
32264
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -32918,14 +32935,14 @@ function layoutTable(ctx, t, args) {
|
|
|
32918
32935
|
}
|
|
32919
32936
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
32920
32937
|
if (box.cs > 1) {
|
|
32921
|
-
let
|
|
32922
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
32923
|
-
if (w2 >
|
|
32938
|
+
let sum2 = 0;
|
|
32939
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
32940
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
32924
32941
|
}
|
|
32925
32942
|
if (box.rs > 1) {
|
|
32926
|
-
let
|
|
32927
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
32928
|
-
if (h2 >
|
|
32943
|
+
let sum2 = 0;
|
|
32944
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
32945
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
32929
32946
|
}
|
|
32930
32947
|
}
|
|
32931
32948
|
let w = 0;
|
|
@@ -33512,24 +33529,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
33512
33529
|
drawSvgShapes(el, alpha);
|
|
33513
33530
|
continue;
|
|
33514
33531
|
}
|
|
33515
|
-
const
|
|
33532
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
33516
33533
|
let path = null;
|
|
33517
33534
|
if (tag === "path") {
|
|
33518
33535
|
const d = el.getAttribute("d");
|
|
33519
33536
|
if (d) path = new Path2D(d);
|
|
33520
33537
|
} else if (tag === "circle") {
|
|
33521
33538
|
path = new Path2D();
|
|
33522
|
-
path.arc(
|
|
33539
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
33523
33540
|
} else if (tag === "ellipse") {
|
|
33524
33541
|
path = new Path2D();
|
|
33525
|
-
path.ellipse(
|
|
33542
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
33526
33543
|
} else if (tag === "rect") {
|
|
33527
33544
|
path = new Path2D();
|
|
33528
|
-
path.rect(
|
|
33545
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
33529
33546
|
} else if (tag === "line") {
|
|
33530
33547
|
path = new Path2D();
|
|
33531
|
-
path.moveTo(
|
|
33532
|
-
path.lineTo(
|
|
33548
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
33549
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
33533
33550
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
33534
33551
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
33535
33552
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -34946,8 +34963,8 @@ var NativeRenderer = class {
|
|
|
34946
34963
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
34947
34964
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
34948
34965
|
drawingsSnapMode: () => this.snapMode,
|
|
34949
|
-
drawingsPointerDown: (x, y, snap,
|
|
34950
|
-
drawingsPointerMove: (x, y, snap,
|
|
34966
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
34967
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
34951
34968
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
34952
34969
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
34953
34970
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -35264,18 +35281,18 @@ var NativeRenderer = class {
|
|
|
35264
35281
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
35265
35282
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
35266
35283
|
const newHeadTime = this.bars[0].time;
|
|
35267
|
-
let
|
|
35284
|
+
let shift2 = 0;
|
|
35268
35285
|
if (newHeadTime !== prevHeadTime) {
|
|
35269
35286
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
35270
|
-
if (prepended > 0)
|
|
35287
|
+
if (prepended > 0) shift2 = prepended;
|
|
35271
35288
|
else {
|
|
35272
35289
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
35273
|
-
if (trimmed > 0)
|
|
35290
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
35274
35291
|
}
|
|
35275
35292
|
}
|
|
35276
|
-
if (
|
|
35277
|
-
this.zoomAnchorLogical +=
|
|
35278
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
35293
|
+
if (shift2 !== 0) {
|
|
35294
|
+
this.zoomAnchorLogical += shift2;
|
|
35295
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
35279
35296
|
}
|
|
35280
35297
|
}
|
|
35281
35298
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -36881,15 +36898,15 @@ var NativeRenderer = class {
|
|
|
36881
36898
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
36882
36899
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
36883
36900
|
collapsedMasterMap() {
|
|
36884
|
-
const
|
|
36901
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
36885
36902
|
for (const pane of this.scene.panes.values()) {
|
|
36886
36903
|
if (!pane.collapsed) continue;
|
|
36887
36904
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
36888
36905
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
36889
36906
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
36890
|
-
|
|
36907
|
+
map2.set(pane.id, master?.id ?? null);
|
|
36891
36908
|
}
|
|
36892
|
-
return
|
|
36909
|
+
return map2;
|
|
36893
36910
|
}
|
|
36894
36911
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
36895
36912
|
setToolbarGutter(px) {
|
|
@@ -37256,12 +37273,2328 @@ function registerVpvr() {
|
|
|
37256
37273
|
registerNativeIndicator(vpvrDescriptor);
|
|
37257
37274
|
}
|
|
37258
37275
|
|
|
37276
|
+
// src/core/model/identity.ts
|
|
37277
|
+
function stableSeriesId(parts) {
|
|
37278
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
37279
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
37280
|
+
}
|
|
37281
|
+
|
|
37282
|
+
// src/core/native-indicators/classics/define.ts
|
|
37283
|
+
function num3(inputs, key, fallback) {
|
|
37284
|
+
const v = inputs[key];
|
|
37285
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
37286
|
+
}
|
|
37287
|
+
function str3(inputs, key, fallback) {
|
|
37288
|
+
const v = inputs[key];
|
|
37289
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
37290
|
+
}
|
|
37291
|
+
function toPoints(bars, values, colors) {
|
|
37292
|
+
const pts = new Array(bars.length);
|
|
37293
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37294
|
+
const v = values[i];
|
|
37295
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
37296
|
+
const c = colors?.[i];
|
|
37297
|
+
if (c != null) p.color = c;
|
|
37298
|
+
pts[i] = p;
|
|
37299
|
+
}
|
|
37300
|
+
return pts;
|
|
37301
|
+
}
|
|
37302
|
+
var ClassicIndicator = class {
|
|
37303
|
+
constructor(spec) {
|
|
37304
|
+
this.spec = spec;
|
|
37305
|
+
this.inputs = {};
|
|
37306
|
+
}
|
|
37307
|
+
start(ctx, inputs) {
|
|
37308
|
+
this.ctx = ctx;
|
|
37309
|
+
this.inputs = inputs;
|
|
37310
|
+
this.recompute();
|
|
37311
|
+
}
|
|
37312
|
+
onBars() {
|
|
37313
|
+
this.recompute();
|
|
37314
|
+
}
|
|
37315
|
+
onViewport() {
|
|
37316
|
+
}
|
|
37317
|
+
setInputs(inputs) {
|
|
37318
|
+
this.inputs = inputs;
|
|
37319
|
+
this.recompute();
|
|
37320
|
+
}
|
|
37321
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
37322
|
+
suspend() {
|
|
37323
|
+
}
|
|
37324
|
+
resume() {
|
|
37325
|
+
this.recompute();
|
|
37326
|
+
}
|
|
37327
|
+
stop() {
|
|
37328
|
+
}
|
|
37329
|
+
recompute() {
|
|
37330
|
+
const bars = this.ctx.bars();
|
|
37331
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
37332
|
+
const instanceId = this.spec.type;
|
|
37333
|
+
const series = [];
|
|
37334
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
37335
|
+
out.plots.forEach((plot, ordinal) => {
|
|
37336
|
+
const kind = plot.kind ?? "line";
|
|
37337
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
37338
|
+
idsByKey.set(plot.key, id);
|
|
37339
|
+
series.push({
|
|
37340
|
+
id,
|
|
37341
|
+
title: plot.title,
|
|
37342
|
+
paneId: "",
|
|
37343
|
+
// stamped by the orchestrator
|
|
37344
|
+
kind,
|
|
37345
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
37346
|
+
style: {
|
|
37347
|
+
color: plot.color,
|
|
37348
|
+
width: plot.width ?? 1,
|
|
37349
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
37350
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
37351
|
+
},
|
|
37352
|
+
...plot.overlay ? { overlay: true } : {}
|
|
37353
|
+
});
|
|
37354
|
+
});
|
|
37355
|
+
if (out.markers && out.markers.length > 0) {
|
|
37356
|
+
series.push({
|
|
37357
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
37358
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
37359
|
+
paneId: "",
|
|
37360
|
+
kind: "markers",
|
|
37361
|
+
markers: out.markers
|
|
37362
|
+
});
|
|
37363
|
+
}
|
|
37364
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
37365
|
+
const from = idsByKey.get(band.from);
|
|
37366
|
+
const to = idsByKey.get(band.to);
|
|
37367
|
+
if (!from || !to) return [];
|
|
37368
|
+
return [
|
|
37369
|
+
{
|
|
37370
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
37371
|
+
paneId: "",
|
|
37372
|
+
fromSeriesId: from,
|
|
37373
|
+
toSeriesId: to,
|
|
37374
|
+
color: band.color
|
|
37375
|
+
}
|
|
37376
|
+
];
|
|
37377
|
+
});
|
|
37378
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
37379
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
37380
|
+
paneId: "",
|
|
37381
|
+
price: level.price,
|
|
37382
|
+
...level.color != null ? { color: level.color } : {},
|
|
37383
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
37384
|
+
width: 1,
|
|
37385
|
+
...level.title != null ? { title: level.title } : {}
|
|
37386
|
+
}));
|
|
37387
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
37388
|
+
const { key, ...rest } = poly;
|
|
37389
|
+
return {
|
|
37390
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
37391
|
+
paneId: "",
|
|
37392
|
+
...rest
|
|
37393
|
+
};
|
|
37394
|
+
});
|
|
37395
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
37396
|
+
this.ctx.setStatus("idle");
|
|
37397
|
+
}
|
|
37398
|
+
};
|
|
37399
|
+
function classicDescriptor(spec) {
|
|
37400
|
+
return {
|
|
37401
|
+
type: spec.type,
|
|
37402
|
+
title: spec.title,
|
|
37403
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
37404
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
37405
|
+
overlay: spec.overlay,
|
|
37406
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
37407
|
+
multiInstance: true,
|
|
37408
|
+
inputsSchema: () => spec.inputs,
|
|
37409
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
37410
|
+
create: () => new ClassicIndicator(spec)
|
|
37411
|
+
};
|
|
37412
|
+
}
|
|
37413
|
+
|
|
37414
|
+
// src/core/native-indicators/classics/math.ts
|
|
37415
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
37416
|
+
function sourceValues(bars, source) {
|
|
37417
|
+
const out = new Array(bars.length);
|
|
37418
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37419
|
+
const b = bars[i];
|
|
37420
|
+
switch (source) {
|
|
37421
|
+
case "Open":
|
|
37422
|
+
out[i] = b.open;
|
|
37423
|
+
break;
|
|
37424
|
+
case "High":
|
|
37425
|
+
out[i] = b.high;
|
|
37426
|
+
break;
|
|
37427
|
+
case "Low":
|
|
37428
|
+
out[i] = b.low;
|
|
37429
|
+
break;
|
|
37430
|
+
case "HL2":
|
|
37431
|
+
out[i] = (b.high + b.low) / 2;
|
|
37432
|
+
break;
|
|
37433
|
+
case "HLC3":
|
|
37434
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
37435
|
+
break;
|
|
37436
|
+
case "OHLC4":
|
|
37437
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
37438
|
+
break;
|
|
37439
|
+
case "HLCC4":
|
|
37440
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
37441
|
+
break;
|
|
37442
|
+
default:
|
|
37443
|
+
out[i] = b.close;
|
|
37444
|
+
}
|
|
37445
|
+
}
|
|
37446
|
+
return out;
|
|
37447
|
+
}
|
|
37448
|
+
function volumes(bars) {
|
|
37449
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
37450
|
+
}
|
|
37451
|
+
function highs(bars) {
|
|
37452
|
+
return bars.map((b) => b.high);
|
|
37453
|
+
}
|
|
37454
|
+
function lows(bars) {
|
|
37455
|
+
return bars.map((b) => b.low);
|
|
37456
|
+
}
|
|
37457
|
+
function closes(bars) {
|
|
37458
|
+
return bars.map((b) => b.close);
|
|
37459
|
+
}
|
|
37460
|
+
function opens(bars) {
|
|
37461
|
+
return bars.map((b) => b.open);
|
|
37462
|
+
}
|
|
37463
|
+
function zip(a, b, f) {
|
|
37464
|
+
const out = new Array(a.length);
|
|
37465
|
+
for (let i = 0; i < a.length; i++) {
|
|
37466
|
+
const x = a[i];
|
|
37467
|
+
const y = b[i];
|
|
37468
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
37469
|
+
}
|
|
37470
|
+
return out;
|
|
37471
|
+
}
|
|
37472
|
+
function map(a, f) {
|
|
37473
|
+
const out = new Array(a.length);
|
|
37474
|
+
for (let i = 0; i < a.length; i++) {
|
|
37475
|
+
const x = a[i];
|
|
37476
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
37477
|
+
}
|
|
37478
|
+
return out;
|
|
37479
|
+
}
|
|
37480
|
+
function sub(a, b) {
|
|
37481
|
+
return zip(a, b, (x, y) => x - y);
|
|
37482
|
+
}
|
|
37483
|
+
function windowed(v, len, fold) {
|
|
37484
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37485
|
+
if (len <= 0) return out;
|
|
37486
|
+
let run = 0;
|
|
37487
|
+
for (let i = 0; i < v.length; i++) {
|
|
37488
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
37489
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
37490
|
+
}
|
|
37491
|
+
return out;
|
|
37492
|
+
}
|
|
37493
|
+
function sum(v, len) {
|
|
37494
|
+
return windowed(v, len, (w, from, to) => {
|
|
37495
|
+
let s = 0;
|
|
37496
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37497
|
+
return s;
|
|
37498
|
+
});
|
|
37499
|
+
}
|
|
37500
|
+
function sma(v, len) {
|
|
37501
|
+
return windowed(v, len, (w, from, to) => {
|
|
37502
|
+
let s = 0;
|
|
37503
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37504
|
+
return s / len;
|
|
37505
|
+
});
|
|
37506
|
+
}
|
|
37507
|
+
function wma(v, len) {
|
|
37508
|
+
const norm = len * (len + 1) / 2;
|
|
37509
|
+
return windowed(v, len, (w, from, to) => {
|
|
37510
|
+
let s = 0;
|
|
37511
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
37512
|
+
return s / norm;
|
|
37513
|
+
});
|
|
37514
|
+
}
|
|
37515
|
+
function vwma(src, vol, len) {
|
|
37516
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
37517
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
37518
|
+
}
|
|
37519
|
+
function stdev(v, len) {
|
|
37520
|
+
return windowed(v, len, (w, from, to) => {
|
|
37521
|
+
let s = 0;
|
|
37522
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37523
|
+
const mean = s / len;
|
|
37524
|
+
let sq = 0;
|
|
37525
|
+
for (let k = from; k <= to; k++) {
|
|
37526
|
+
const d = w[k] - mean;
|
|
37527
|
+
sq += d * d;
|
|
37528
|
+
}
|
|
37529
|
+
return Math.sqrt(sq / len);
|
|
37530
|
+
});
|
|
37531
|
+
}
|
|
37532
|
+
function highest(v, len) {
|
|
37533
|
+
return windowed(v, len, (w, from, to) => {
|
|
37534
|
+
let m = -Infinity;
|
|
37535
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
37536
|
+
return m;
|
|
37537
|
+
});
|
|
37538
|
+
}
|
|
37539
|
+
function lowest(v, len) {
|
|
37540
|
+
return windowed(v, len, (w, from, to) => {
|
|
37541
|
+
let m = Infinity;
|
|
37542
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
37543
|
+
return m;
|
|
37544
|
+
});
|
|
37545
|
+
}
|
|
37546
|
+
function barsSinceHighest(v, len) {
|
|
37547
|
+
return windowed(v, len, (w, from, to) => {
|
|
37548
|
+
let m = -Infinity;
|
|
37549
|
+
let at = to;
|
|
37550
|
+
for (let k = from; k <= to; k++) {
|
|
37551
|
+
if (w[k] >= m) {
|
|
37552
|
+
m = w[k];
|
|
37553
|
+
at = k;
|
|
37554
|
+
}
|
|
37555
|
+
}
|
|
37556
|
+
return to - at;
|
|
37557
|
+
});
|
|
37558
|
+
}
|
|
37559
|
+
function barsSinceLowest(v, len) {
|
|
37560
|
+
return windowed(v, len, (w, from, to) => {
|
|
37561
|
+
let m = Infinity;
|
|
37562
|
+
let at = to;
|
|
37563
|
+
for (let k = from; k <= to; k++) {
|
|
37564
|
+
if (w[k] <= m) {
|
|
37565
|
+
m = w[k];
|
|
37566
|
+
at = k;
|
|
37567
|
+
}
|
|
37568
|
+
}
|
|
37569
|
+
return to - at;
|
|
37570
|
+
});
|
|
37571
|
+
}
|
|
37572
|
+
function recursive(v, len, alpha) {
|
|
37573
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37574
|
+
let run = 0;
|
|
37575
|
+
let prev2 = Number.NaN;
|
|
37576
|
+
for (let i = 0; i < v.length; i++) {
|
|
37577
|
+
const x = v[i];
|
|
37578
|
+
if (!Number.isFinite(x)) {
|
|
37579
|
+
run = 0;
|
|
37580
|
+
prev2 = Number.NaN;
|
|
37581
|
+
continue;
|
|
37582
|
+
}
|
|
37583
|
+
run += 1;
|
|
37584
|
+
if (Number.isFinite(prev2)) {
|
|
37585
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
37586
|
+
out[i] = prev2;
|
|
37587
|
+
} else if (run >= len) {
|
|
37588
|
+
let s = 0;
|
|
37589
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
37590
|
+
prev2 = s / len;
|
|
37591
|
+
out[i] = prev2;
|
|
37592
|
+
}
|
|
37593
|
+
}
|
|
37594
|
+
return out;
|
|
37595
|
+
}
|
|
37596
|
+
function ema(v, len) {
|
|
37597
|
+
return recursive(v, len, 2 / (len + 1));
|
|
37598
|
+
}
|
|
37599
|
+
function rma(v, len) {
|
|
37600
|
+
return recursive(v, len, 1 / len);
|
|
37601
|
+
}
|
|
37602
|
+
function change(v, n = 1) {
|
|
37603
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37604
|
+
for (let i = n; i < v.length; i++) {
|
|
37605
|
+
const a = v[i];
|
|
37606
|
+
const b = v[i - n];
|
|
37607
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
37608
|
+
}
|
|
37609
|
+
return out;
|
|
37610
|
+
}
|
|
37611
|
+
function roc(v, len) {
|
|
37612
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37613
|
+
for (let i = len; i < v.length; i++) {
|
|
37614
|
+
const a = v[i];
|
|
37615
|
+
const b = v[i - len];
|
|
37616
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
37617
|
+
}
|
|
37618
|
+
return out;
|
|
37619
|
+
}
|
|
37620
|
+
function shift(v, n) {
|
|
37621
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37622
|
+
for (let i = 0; i < v.length; i++) {
|
|
37623
|
+
const j = i - n;
|
|
37624
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
37625
|
+
}
|
|
37626
|
+
return out;
|
|
37627
|
+
}
|
|
37628
|
+
function trueRange(bars) {
|
|
37629
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
37630
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37631
|
+
const b = bars[i];
|
|
37632
|
+
if (i === 0) {
|
|
37633
|
+
out[i] = b.high - b.low;
|
|
37634
|
+
continue;
|
|
37635
|
+
}
|
|
37636
|
+
const pc = bars[i - 1].close;
|
|
37637
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
37638
|
+
}
|
|
37639
|
+
return out;
|
|
37640
|
+
}
|
|
37641
|
+
function atr(bars, len) {
|
|
37642
|
+
return rma(trueRange(bars), len);
|
|
37643
|
+
}
|
|
37644
|
+
function rsi(v, len) {
|
|
37645
|
+
const d = change(v);
|
|
37646
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
37647
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
37648
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
37649
|
+
}
|
|
37650
|
+
function stoch(v, hi, lo, len) {
|
|
37651
|
+
const hh = highest(hi, len);
|
|
37652
|
+
const ll = lowest(lo, len);
|
|
37653
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37654
|
+
for (let i = 0; i < v.length; i++) {
|
|
37655
|
+
const x = v[i];
|
|
37656
|
+
const h = hh[i];
|
|
37657
|
+
const l = ll[i];
|
|
37658
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
37659
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
37660
|
+
}
|
|
37661
|
+
return out;
|
|
37662
|
+
}
|
|
37663
|
+
function linreg(v, len) {
|
|
37664
|
+
const sx = (len - 1) * len / 2;
|
|
37665
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
37666
|
+
const denom = len * sxx - sx * sx;
|
|
37667
|
+
return windowed(v, len, (w, from, to) => {
|
|
37668
|
+
let sy = 0;
|
|
37669
|
+
let sxy = 0;
|
|
37670
|
+
for (let k = from; k <= to; k++) {
|
|
37671
|
+
const x = k - from;
|
|
37672
|
+
sy += w[k];
|
|
37673
|
+
sxy += x * w[k];
|
|
37674
|
+
}
|
|
37675
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
37676
|
+
const intercept = (sy - slope * sx) / len;
|
|
37677
|
+
return intercept + slope * (len - 1);
|
|
37678
|
+
});
|
|
37679
|
+
}
|
|
37680
|
+
function swma(v) {
|
|
37681
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37682
|
+
for (let i = 3; i < v.length; i++) {
|
|
37683
|
+
const a = v[i - 3];
|
|
37684
|
+
const b = v[i - 2];
|
|
37685
|
+
const c = v[i - 1];
|
|
37686
|
+
const d = v[i];
|
|
37687
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
37688
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
37689
|
+
}
|
|
37690
|
+
}
|
|
37691
|
+
return out;
|
|
37692
|
+
}
|
|
37693
|
+
function percentRank(v, len) {
|
|
37694
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37695
|
+
for (let i = len; i < v.length; i++) {
|
|
37696
|
+
const x = v[i];
|
|
37697
|
+
if (!Number.isFinite(x)) continue;
|
|
37698
|
+
let below = 0;
|
|
37699
|
+
let count = 0;
|
|
37700
|
+
for (let k = i - len; k < i; k++) {
|
|
37701
|
+
const y = v[k];
|
|
37702
|
+
if (!Number.isFinite(y)) {
|
|
37703
|
+
count = -1;
|
|
37704
|
+
break;
|
|
37705
|
+
}
|
|
37706
|
+
count += 1;
|
|
37707
|
+
if (y <= x) below += 1;
|
|
37708
|
+
}
|
|
37709
|
+
if (count === len) out[i] = 100 * below / len;
|
|
37710
|
+
}
|
|
37711
|
+
return out;
|
|
37712
|
+
}
|
|
37713
|
+
function cumSum(v) {
|
|
37714
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37715
|
+
let acc = 0;
|
|
37716
|
+
let seen = false;
|
|
37717
|
+
for (let i = 0; i < v.length; i++) {
|
|
37718
|
+
const x = v[i];
|
|
37719
|
+
if (Number.isFinite(x)) {
|
|
37720
|
+
acc += x;
|
|
37721
|
+
seen = true;
|
|
37722
|
+
}
|
|
37723
|
+
if (seen) out[i] = acc;
|
|
37724
|
+
}
|
|
37725
|
+
return out;
|
|
37726
|
+
}
|
|
37727
|
+
|
|
37728
|
+
// src/core/native-indicators/classics/shared.ts
|
|
37729
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
37730
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
37731
|
+
}
|
|
37732
|
+
function sourceInput(defval = "Close") {
|
|
37733
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
37734
|
+
}
|
|
37735
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
37736
|
+
return { key, title, type: "color", defval };
|
|
37737
|
+
}
|
|
37738
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
37739
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
37740
|
+
if (!m) return hex;
|
|
37741
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
37742
|
+
return `#${m[1]}${a}`;
|
|
37743
|
+
}
|
|
37744
|
+
|
|
37745
|
+
// src/core/native-indicators/classics/averages.ts
|
|
37746
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
37747
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
37748
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
37749
|
+
return {
|
|
37750
|
+
type,
|
|
37751
|
+
title,
|
|
37752
|
+
shortTitle,
|
|
37753
|
+
overlay: true,
|
|
37754
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
37755
|
+
compute: (bars, inputs) => {
|
|
37756
|
+
const len = num3(inputs, "length", 20);
|
|
37757
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
37758
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37759
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37760
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37761
|
+
}
|
|
37762
|
+
};
|
|
37763
|
+
}
|
|
37764
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
37765
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
37766
|
+
var movingAverage = {
|
|
37767
|
+
type: "moving-average",
|
|
37768
|
+
title: "Moving Average",
|
|
37769
|
+
shortTitle: "MA",
|
|
37770
|
+
overlay: true,
|
|
37771
|
+
inputs: [
|
|
37772
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
37773
|
+
lengthInput(20),
|
|
37774
|
+
sourceInput(),
|
|
37775
|
+
offsetInput,
|
|
37776
|
+
colorInput()
|
|
37777
|
+
],
|
|
37778
|
+
compute: (bars, inputs) => {
|
|
37779
|
+
const len = num3(inputs, "length", 20);
|
|
37780
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37781
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
37782
|
+
let values;
|
|
37783
|
+
switch (kind) {
|
|
37784
|
+
case "EMA":
|
|
37785
|
+
values = ema(src, len);
|
|
37786
|
+
break;
|
|
37787
|
+
case "WMA":
|
|
37788
|
+
values = wma(src, len);
|
|
37789
|
+
break;
|
|
37790
|
+
case "RMA":
|
|
37791
|
+
values = rma(src, len);
|
|
37792
|
+
break;
|
|
37793
|
+
case "VWMA":
|
|
37794
|
+
values = vwma(src, volumes(bars), len);
|
|
37795
|
+
break;
|
|
37796
|
+
default:
|
|
37797
|
+
values = sma(src, len);
|
|
37798
|
+
}
|
|
37799
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37800
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37801
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37802
|
+
}
|
|
37803
|
+
};
|
|
37804
|
+
var smoothedMa = {
|
|
37805
|
+
type: "rma",
|
|
37806
|
+
title: "Smoothed Moving Average",
|
|
37807
|
+
shortTitle: "RMA",
|
|
37808
|
+
overlay: true,
|
|
37809
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37810
|
+
compute: (bars, inputs) => ({
|
|
37811
|
+
plots: [
|
|
37812
|
+
{
|
|
37813
|
+
key: "rma",
|
|
37814
|
+
title: "RMA",
|
|
37815
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37816
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37817
|
+
width: 2
|
|
37818
|
+
}
|
|
37819
|
+
]
|
|
37820
|
+
})
|
|
37821
|
+
};
|
|
37822
|
+
var zlema = {
|
|
37823
|
+
type: "zlema",
|
|
37824
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
37825
|
+
shortTitle: "ZLEMA",
|
|
37826
|
+
overlay: true,
|
|
37827
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37828
|
+
compute: (bars, inputs) => {
|
|
37829
|
+
const len = num3(inputs, "length", 14);
|
|
37830
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37831
|
+
const lag = Math.floor((len - 1) / 2);
|
|
37832
|
+
const lagged = shift(src, lag);
|
|
37833
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
37834
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37835
|
+
}
|
|
37836
|
+
};
|
|
37837
|
+
var vidya = {
|
|
37838
|
+
type: "vidya",
|
|
37839
|
+
title: "Variable Index Dynamic Average",
|
|
37840
|
+
shortTitle: "VIDYA",
|
|
37841
|
+
overlay: true,
|
|
37842
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
37843
|
+
compute: (bars, inputs) => {
|
|
37844
|
+
const len = num3(inputs, "length", 14);
|
|
37845
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
37846
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37847
|
+
const d = change(src);
|
|
37848
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
37849
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
37850
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
37851
|
+
let u = 0;
|
|
37852
|
+
let w = 0;
|
|
37853
|
+
let ok = true;
|
|
37854
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
37855
|
+
const x = d[k];
|
|
37856
|
+
if (!Number.isFinite(x)) {
|
|
37857
|
+
ok = false;
|
|
37858
|
+
break;
|
|
37859
|
+
}
|
|
37860
|
+
if (x > 0) u += x;
|
|
37861
|
+
else w -= x;
|
|
37862
|
+
}
|
|
37863
|
+
if (ok) {
|
|
37864
|
+
su[i] = u;
|
|
37865
|
+
sd[i] = w;
|
|
37866
|
+
}
|
|
37867
|
+
}
|
|
37868
|
+
const alpha = 2 / (len + 1);
|
|
37869
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
37870
|
+
let prev2 = Number.NaN;
|
|
37871
|
+
for (let i = 0; i < src.length; i++) {
|
|
37872
|
+
const x = src[i];
|
|
37873
|
+
const u = su[i];
|
|
37874
|
+
const w = sd[i];
|
|
37875
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
37876
|
+
const total = u + w;
|
|
37877
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
37878
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
37879
|
+
out[i] = prev2;
|
|
37880
|
+
}
|
|
37881
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37882
|
+
}
|
|
37883
|
+
};
|
|
37884
|
+
var maEnvelope = {
|
|
37885
|
+
type: "ma-envelope",
|
|
37886
|
+
title: "Moving Average Envelope",
|
|
37887
|
+
shortTitle: "MA Env",
|
|
37888
|
+
overlay: true,
|
|
37889
|
+
inputs: [
|
|
37890
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
37891
|
+
lengthInput(20),
|
|
37892
|
+
sourceInput(),
|
|
37893
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
37894
|
+
colorInput()
|
|
37895
|
+
],
|
|
37896
|
+
compute: (bars, inputs) => {
|
|
37897
|
+
const len = num3(inputs, "length", 20);
|
|
37898
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37899
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
37900
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
37901
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
37902
|
+
return {
|
|
37903
|
+
plots: [
|
|
37904
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
37905
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
37906
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
37907
|
+
],
|
|
37908
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
37909
|
+
};
|
|
37910
|
+
}
|
|
37911
|
+
};
|
|
37912
|
+
var linearRegression = {
|
|
37913
|
+
type: "linear-regression",
|
|
37914
|
+
title: "Linear Regression Curve",
|
|
37915
|
+
shortTitle: "LinReg",
|
|
37916
|
+
overlay: true,
|
|
37917
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37918
|
+
compute: (bars, inputs) => ({
|
|
37919
|
+
plots: [
|
|
37920
|
+
{
|
|
37921
|
+
key: "linreg",
|
|
37922
|
+
title: "LinReg",
|
|
37923
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37924
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37925
|
+
width: 2
|
|
37926
|
+
}
|
|
37927
|
+
]
|
|
37928
|
+
})
|
|
37929
|
+
};
|
|
37930
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
37931
|
+
|
|
37932
|
+
// src/core/native-indicators/classics/bands.ts
|
|
37933
|
+
function bollinger(bars, inputs) {
|
|
37934
|
+
const len = num3(inputs, "length", 20);
|
|
37935
|
+
const mult = num3(inputs, "mult", 2);
|
|
37936
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37937
|
+
const basis = sma(src, len);
|
|
37938
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
37939
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
37940
|
+
}
|
|
37941
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
37942
|
+
var bollingerBands = {
|
|
37943
|
+
type: "bollinger-bands",
|
|
37944
|
+
title: "Bollinger Bands",
|
|
37945
|
+
shortTitle: "BB",
|
|
37946
|
+
overlay: true,
|
|
37947
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
37948
|
+
compute: (bars, inputs) => {
|
|
37949
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
37950
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
37951
|
+
return {
|
|
37952
|
+
plots: [
|
|
37953
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
37954
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
37955
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
37956
|
+
],
|
|
37957
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
37958
|
+
};
|
|
37959
|
+
}
|
|
37960
|
+
};
|
|
37961
|
+
var percentB = {
|
|
37962
|
+
type: "percent-b",
|
|
37963
|
+
title: "Bollinger Bands %B",
|
|
37964
|
+
shortTitle: "%B",
|
|
37965
|
+
overlay: false,
|
|
37966
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
37967
|
+
compute: (bars, inputs) => {
|
|
37968
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
37969
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37970
|
+
const values = src.map((x, i) => {
|
|
37971
|
+
const u = upper[i];
|
|
37972
|
+
const l = lower[i];
|
|
37973
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
37974
|
+
});
|
|
37975
|
+
return {
|
|
37976
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
37977
|
+
levels: [
|
|
37978
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
37979
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
37980
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
37981
|
+
]
|
|
37982
|
+
};
|
|
37983
|
+
}
|
|
37984
|
+
};
|
|
37985
|
+
var bandwidth = {
|
|
37986
|
+
type: "bandwidth",
|
|
37987
|
+
title: "Bollinger Bands Width",
|
|
37988
|
+
shortTitle: "BandWidth",
|
|
37989
|
+
overlay: false,
|
|
37990
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
37991
|
+
compute: (bars, inputs) => {
|
|
37992
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
37993
|
+
const values = basis.map((b, i) => {
|
|
37994
|
+
const u = upper[i];
|
|
37995
|
+
const l = lower[i];
|
|
37996
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
37997
|
+
});
|
|
37998
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37999
|
+
}
|
|
38000
|
+
};
|
|
38001
|
+
var keltner = {
|
|
38002
|
+
type: "keltner-channels",
|
|
38003
|
+
title: "Keltner Channels",
|
|
38004
|
+
shortTitle: "KC",
|
|
38005
|
+
overlay: true,
|
|
38006
|
+
inputs: [
|
|
38007
|
+
lengthInput(20),
|
|
38008
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38009
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38010
|
+
sourceInput(),
|
|
38011
|
+
colorInput()
|
|
38012
|
+
],
|
|
38013
|
+
compute: (bars, inputs) => {
|
|
38014
|
+
const len = num3(inputs, "length", 20);
|
|
38015
|
+
const mult = num3(inputs, "mult", 2);
|
|
38016
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38017
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
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: zip(basis, range, (b, r) => b + r), color: ink },
|
|
38023
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
38024
|
+
],
|
|
38025
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38026
|
+
};
|
|
38027
|
+
}
|
|
38028
|
+
};
|
|
38029
|
+
var donchian = {
|
|
38030
|
+
type: "donchian-channels",
|
|
38031
|
+
title: "Donchian Channels",
|
|
38032
|
+
shortTitle: "DC",
|
|
38033
|
+
overlay: true,
|
|
38034
|
+
inputs: [lengthInput(20), colorInput()],
|
|
38035
|
+
compute: (bars, inputs) => {
|
|
38036
|
+
const len = num3(inputs, "length", 20);
|
|
38037
|
+
const upper = highest(highs(bars), len);
|
|
38038
|
+
const lower = lowest(lows(bars), len);
|
|
38039
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38040
|
+
return {
|
|
38041
|
+
plots: [
|
|
38042
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
38043
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38044
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38045
|
+
],
|
|
38046
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38047
|
+
};
|
|
38048
|
+
}
|
|
38049
|
+
};
|
|
38050
|
+
var chandelier = {
|
|
38051
|
+
type: "chandelier-exit",
|
|
38052
|
+
title: "Chandelier Exit",
|
|
38053
|
+
shortTitle: "CE",
|
|
38054
|
+
overlay: true,
|
|
38055
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
38056
|
+
compute: (bars, inputs) => {
|
|
38057
|
+
const len = num3(inputs, "length", 22);
|
|
38058
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
38059
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
38060
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
38061
|
+
return {
|
|
38062
|
+
plots: [
|
|
38063
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
38064
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
38065
|
+
]
|
|
38066
|
+
};
|
|
38067
|
+
}
|
|
38068
|
+
};
|
|
38069
|
+
var chandeKroll = {
|
|
38070
|
+
type: "chande-kroll-stop",
|
|
38071
|
+
title: "Chande Kroll Stop",
|
|
38072
|
+
shortTitle: "CKS",
|
|
38073
|
+
overlay: true,
|
|
38074
|
+
inputs: [
|
|
38075
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38076
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
38077
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
38078
|
+
],
|
|
38079
|
+
compute: (bars, inputs) => {
|
|
38080
|
+
const p = num3(inputs, "p", 10);
|
|
38081
|
+
const x = num3(inputs, "x", 1);
|
|
38082
|
+
const q = num3(inputs, "q", 9);
|
|
38083
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
38084
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
38085
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
38086
|
+
return {
|
|
38087
|
+
plots: [
|
|
38088
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
38089
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
38090
|
+
]
|
|
38091
|
+
};
|
|
38092
|
+
}
|
|
38093
|
+
};
|
|
38094
|
+
var supertrend = {
|
|
38095
|
+
type: "supertrend",
|
|
38096
|
+
title: "SuperTrend",
|
|
38097
|
+
shortTitle: "SuperTrend",
|
|
38098
|
+
overlay: true,
|
|
38099
|
+
inputs: [
|
|
38100
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38101
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
38102
|
+
],
|
|
38103
|
+
compute: (bars, inputs) => {
|
|
38104
|
+
const len = num3(inputs, "atrLength", 10);
|
|
38105
|
+
const mult = num3(inputs, "mult", 3);
|
|
38106
|
+
const range = atr(bars, len);
|
|
38107
|
+
const n = bars.length;
|
|
38108
|
+
const st = new Array(n).fill(Number.NaN);
|
|
38109
|
+
const dir = new Array(n).fill(1);
|
|
38110
|
+
let up = Number.NaN;
|
|
38111
|
+
let dn = Number.NaN;
|
|
38112
|
+
let trend = 1;
|
|
38113
|
+
for (let i = 0; i < n; i++) {
|
|
38114
|
+
const r = range[i];
|
|
38115
|
+
if (!Number.isFinite(r)) continue;
|
|
38116
|
+
const b = bars[i];
|
|
38117
|
+
const mid = (b.high + b.low) / 2;
|
|
38118
|
+
const basicUp = mid - mult * r;
|
|
38119
|
+
const basicDn = mid + mult * r;
|
|
38120
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
38121
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
38122
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
38123
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
38124
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
38125
|
+
dir[i] = trend;
|
|
38126
|
+
st[i] = trend === 1 ? up : dn;
|
|
38127
|
+
}
|
|
38128
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
38129
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
38130
|
+
}
|
|
38131
|
+
};
|
|
38132
|
+
var alligator = {
|
|
38133
|
+
type: "williams-alligator",
|
|
38134
|
+
title: "Williams Alligator",
|
|
38135
|
+
shortTitle: "Alligator",
|
|
38136
|
+
overlay: true,
|
|
38137
|
+
inputs: [
|
|
38138
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
38139
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
38140
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
38141
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
38142
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
38143
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
38144
|
+
],
|
|
38145
|
+
compute: (bars, inputs) => {
|
|
38146
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38147
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
38148
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
38149
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
38150
|
+
return {
|
|
38151
|
+
plots: [
|
|
38152
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
38153
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
38154
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
38155
|
+
]
|
|
38156
|
+
};
|
|
38157
|
+
}
|
|
38158
|
+
};
|
|
38159
|
+
var gator = {
|
|
38160
|
+
type: "gator-oscillator",
|
|
38161
|
+
title: "Gator Oscillator",
|
|
38162
|
+
shortTitle: "Gator Oscillator",
|
|
38163
|
+
overlay: false,
|
|
38164
|
+
inputs: [],
|
|
38165
|
+
compute: (bars) => {
|
|
38166
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38167
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
38168
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
38169
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
38170
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
38171
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
38172
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
38173
|
+
if (!Number.isFinite(x)) return null;
|
|
38174
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
38175
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
38176
|
+
});
|
|
38177
|
+
return {
|
|
38178
|
+
plots: [
|
|
38179
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
38180
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
38181
|
+
]
|
|
38182
|
+
};
|
|
38183
|
+
}
|
|
38184
|
+
};
|
|
38185
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
38186
|
+
|
|
38187
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
38188
|
+
function guideLevels(low, high, mid) {
|
|
38189
|
+
const levels = [
|
|
38190
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
38191
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
38192
|
+
];
|
|
38193
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
38194
|
+
return levels;
|
|
38195
|
+
}
|
|
38196
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
38197
|
+
function histColors(values) {
|
|
38198
|
+
return values.map((x, i) => {
|
|
38199
|
+
if (!Number.isFinite(x)) return null;
|
|
38200
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
38201
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
38202
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
38203
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
38204
|
+
});
|
|
38205
|
+
}
|
|
38206
|
+
var rsiSpec = {
|
|
38207
|
+
type: "rsi",
|
|
38208
|
+
title: "Relative Strength Index",
|
|
38209
|
+
shortTitle: "RSI",
|
|
38210
|
+
overlay: false,
|
|
38211
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38212
|
+
compute: (bars, inputs) => ({
|
|
38213
|
+
plots: [
|
|
38214
|
+
{
|
|
38215
|
+
key: "rsi",
|
|
38216
|
+
title: "RSI",
|
|
38217
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38218
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38219
|
+
width: 2
|
|
38220
|
+
}
|
|
38221
|
+
],
|
|
38222
|
+
levels: guideLevels(30, 70, 50)
|
|
38223
|
+
})
|
|
38224
|
+
};
|
|
38225
|
+
var stochastic = {
|
|
38226
|
+
type: "stochastic",
|
|
38227
|
+
title: "Stochastic",
|
|
38228
|
+
shortTitle: "Stoch",
|
|
38229
|
+
overlay: false,
|
|
38230
|
+
inputs: [
|
|
38231
|
+
lengthInput(14, "kLength", "%K length"),
|
|
38232
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
38233
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
38234
|
+
],
|
|
38235
|
+
compute: (bars, inputs) => {
|
|
38236
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
38237
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38238
|
+
return {
|
|
38239
|
+
plots: [
|
|
38240
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38241
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38242
|
+
],
|
|
38243
|
+
levels: guideLevels(20, 80)
|
|
38244
|
+
};
|
|
38245
|
+
}
|
|
38246
|
+
};
|
|
38247
|
+
var stochasticRsi = {
|
|
38248
|
+
type: "stochastic-rsi",
|
|
38249
|
+
title: "Stochastic RSI",
|
|
38250
|
+
shortTitle: "Stoch RSI",
|
|
38251
|
+
overlay: false,
|
|
38252
|
+
inputs: [
|
|
38253
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
38254
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
38255
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
38256
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
38257
|
+
sourceInput()
|
|
38258
|
+
],
|
|
38259
|
+
compute: (bars, inputs) => {
|
|
38260
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
38261
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
38262
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38263
|
+
return {
|
|
38264
|
+
plots: [
|
|
38265
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38266
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38267
|
+
],
|
|
38268
|
+
levels: guideLevels(20, 80)
|
|
38269
|
+
};
|
|
38270
|
+
}
|
|
38271
|
+
};
|
|
38272
|
+
var macd = {
|
|
38273
|
+
type: "macd",
|
|
38274
|
+
title: "MACD",
|
|
38275
|
+
overlay: false,
|
|
38276
|
+
inputs: [
|
|
38277
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38278
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38279
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38280
|
+
sourceInput()
|
|
38281
|
+
],
|
|
38282
|
+
compute: (bars, inputs) => {
|
|
38283
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38284
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
38285
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38286
|
+
const hist = sub(line, signal);
|
|
38287
|
+
return {
|
|
38288
|
+
plots: [
|
|
38289
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38290
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
38291
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38292
|
+
],
|
|
38293
|
+
levels: ZERO_LEVEL
|
|
38294
|
+
};
|
|
38295
|
+
}
|
|
38296
|
+
};
|
|
38297
|
+
var ppo = {
|
|
38298
|
+
type: "ppo",
|
|
38299
|
+
title: "Percentage Price Oscillator",
|
|
38300
|
+
shortTitle: "PPO",
|
|
38301
|
+
overlay: false,
|
|
38302
|
+
inputs: [
|
|
38303
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38304
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38305
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38306
|
+
sourceInput()
|
|
38307
|
+
],
|
|
38308
|
+
compute: (bars, inputs) => {
|
|
38309
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38310
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
38311
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
38312
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
38313
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38314
|
+
const hist = sub(line, signal);
|
|
38315
|
+
return {
|
|
38316
|
+
plots: [
|
|
38317
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38318
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
38319
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38320
|
+
],
|
|
38321
|
+
levels: ZERO_LEVEL
|
|
38322
|
+
};
|
|
38323
|
+
}
|
|
38324
|
+
};
|
|
38325
|
+
var awesome = {
|
|
38326
|
+
type: "awesome-oscillator",
|
|
38327
|
+
title: "Awesome Oscillator",
|
|
38328
|
+
shortTitle: "AO",
|
|
38329
|
+
overlay: false,
|
|
38330
|
+
inputs: [],
|
|
38331
|
+
compute: (bars) => {
|
|
38332
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38333
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
38334
|
+
const colors = ao.map((x, i) => {
|
|
38335
|
+
if (!Number.isFinite(x)) return null;
|
|
38336
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
38337
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
38338
|
+
});
|
|
38339
|
+
return {
|
|
38340
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
38341
|
+
levels: ZERO_LEVEL
|
|
38342
|
+
};
|
|
38343
|
+
}
|
|
38344
|
+
};
|
|
38345
|
+
var cci = {
|
|
38346
|
+
type: "commodity-channel-index",
|
|
38347
|
+
title: "Commodity Channel Index",
|
|
38348
|
+
shortTitle: "CCI",
|
|
38349
|
+
overlay: false,
|
|
38350
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
38351
|
+
compute: (bars, inputs) => {
|
|
38352
|
+
const len = num3(inputs, "length", 20);
|
|
38353
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
38354
|
+
const basis = sma(src, len);
|
|
38355
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
38356
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
38357
|
+
const mean = basis[i];
|
|
38358
|
+
if (!Number.isFinite(mean)) continue;
|
|
38359
|
+
let d = 0;
|
|
38360
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
38361
|
+
dev[i] = d / len;
|
|
38362
|
+
}
|
|
38363
|
+
const values = src.map((x, i) => {
|
|
38364
|
+
const b = basis[i];
|
|
38365
|
+
const d = dev[i];
|
|
38366
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
38367
|
+
});
|
|
38368
|
+
return {
|
|
38369
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38370
|
+
levels: guideLevels(-100, 100, 0)
|
|
38371
|
+
};
|
|
38372
|
+
}
|
|
38373
|
+
};
|
|
38374
|
+
var williamsR = {
|
|
38375
|
+
type: "williams-percent-r",
|
|
38376
|
+
title: "Williams %R",
|
|
38377
|
+
shortTitle: "%R",
|
|
38378
|
+
overlay: false,
|
|
38379
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38380
|
+
compute: (bars, inputs) => {
|
|
38381
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
38382
|
+
return {
|
|
38383
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38384
|
+
levels: guideLevels(-80, -20)
|
|
38385
|
+
};
|
|
38386
|
+
}
|
|
38387
|
+
};
|
|
38388
|
+
var cmo = {
|
|
38389
|
+
type: "chande-momentum-oscillator",
|
|
38390
|
+
title: "Chande Momentum Oscillator",
|
|
38391
|
+
shortTitle: "CMO",
|
|
38392
|
+
overlay: false,
|
|
38393
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38394
|
+
compute: (bars, inputs) => {
|
|
38395
|
+
const len = num3(inputs, "length", 9);
|
|
38396
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
38397
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
38398
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
38399
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
38400
|
+
return {
|
|
38401
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38402
|
+
levels: guideLevels(-50, 50, 0)
|
|
38403
|
+
};
|
|
38404
|
+
}
|
|
38405
|
+
};
|
|
38406
|
+
var connorsRsi = {
|
|
38407
|
+
type: "connors-rsi",
|
|
38408
|
+
title: "Connors RSI",
|
|
38409
|
+
shortTitle: "CRSI",
|
|
38410
|
+
overlay: false,
|
|
38411
|
+
inputs: [
|
|
38412
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
38413
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
38414
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
38415
|
+
colorInput()
|
|
38416
|
+
],
|
|
38417
|
+
compute: (bars, inputs) => {
|
|
38418
|
+
const c = closes(bars);
|
|
38419
|
+
const streak = new Array(c.length).fill(0);
|
|
38420
|
+
for (let i = 1; i < c.length; i++) {
|
|
38421
|
+
const d = c[i] - c[i - 1];
|
|
38422
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
38423
|
+
}
|
|
38424
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
38425
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
38426
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
38427
|
+
const values = c.map((_, i) => {
|
|
38428
|
+
const x = a[i];
|
|
38429
|
+
const y = b[i];
|
|
38430
|
+
const z = r[i];
|
|
38431
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
38432
|
+
});
|
|
38433
|
+
return {
|
|
38434
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38435
|
+
levels: guideLevels(20, 80, 50)
|
|
38436
|
+
};
|
|
38437
|
+
}
|
|
38438
|
+
};
|
|
38439
|
+
var fisher = {
|
|
38440
|
+
type: "fisher-transform",
|
|
38441
|
+
title: "Fisher Transform",
|
|
38442
|
+
shortTitle: "Fisher",
|
|
38443
|
+
overlay: false,
|
|
38444
|
+
inputs: [lengthInput(9)],
|
|
38445
|
+
compute: (bars, inputs) => {
|
|
38446
|
+
const len = num3(inputs, "length", 9);
|
|
38447
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38448
|
+
const hi = highest(hl2, len);
|
|
38449
|
+
const lo = lowest(hl2, len);
|
|
38450
|
+
const n = bars.length;
|
|
38451
|
+
const out = new Array(n).fill(Number.NaN);
|
|
38452
|
+
let value = 0;
|
|
38453
|
+
let fish = 0;
|
|
38454
|
+
for (let i = 0; i < n; i++) {
|
|
38455
|
+
const h = hi[i];
|
|
38456
|
+
const l = lo[i];
|
|
38457
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38458
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
38459
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
38460
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
38461
|
+
out[i] = fish;
|
|
38462
|
+
}
|
|
38463
|
+
return {
|
|
38464
|
+
plots: [
|
|
38465
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
38466
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
38467
|
+
],
|
|
38468
|
+
levels: ZERO_LEVEL
|
|
38469
|
+
};
|
|
38470
|
+
}
|
|
38471
|
+
};
|
|
38472
|
+
var trix = {
|
|
38473
|
+
type: "trix",
|
|
38474
|
+
title: "TRIX",
|
|
38475
|
+
overlay: false,
|
|
38476
|
+
inputs: [lengthInput(18), colorInput()],
|
|
38477
|
+
compute: (bars, inputs) => {
|
|
38478
|
+
const len = num3(inputs, "length", 18);
|
|
38479
|
+
const logClose = map(closes(bars), Math.log);
|
|
38480
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
38481
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
38482
|
+
return {
|
|
38483
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38484
|
+
levels: ZERO_LEVEL
|
|
38485
|
+
};
|
|
38486
|
+
}
|
|
38487
|
+
};
|
|
38488
|
+
function tsiLine(src, long, short) {
|
|
38489
|
+
const mom = change(src);
|
|
38490
|
+
const numer = ema(ema(mom, long), short);
|
|
38491
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
38492
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
38493
|
+
}
|
|
38494
|
+
var tsi = {
|
|
38495
|
+
type: "true-strength-index",
|
|
38496
|
+
title: "True Strength Index",
|
|
38497
|
+
shortTitle: "TSI",
|
|
38498
|
+
overlay: false,
|
|
38499
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
38500
|
+
compute: (bars, inputs) => {
|
|
38501
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
38502
|
+
return {
|
|
38503
|
+
plots: [
|
|
38504
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
38505
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
38506
|
+
],
|
|
38507
|
+
levels: ZERO_LEVEL
|
|
38508
|
+
};
|
|
38509
|
+
}
|
|
38510
|
+
};
|
|
38511
|
+
var smiErgodic = {
|
|
38512
|
+
type: "smi-ergodic",
|
|
38513
|
+
title: "SMI Ergodic Oscillator",
|
|
38514
|
+
shortTitle: "SMIE",
|
|
38515
|
+
overlay: false,
|
|
38516
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
38517
|
+
compute: (bars, inputs) => {
|
|
38518
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
38519
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
38520
|
+
const osc = sub(line, signal);
|
|
38521
|
+
return {
|
|
38522
|
+
plots: [
|
|
38523
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
38524
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
38525
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38526
|
+
],
|
|
38527
|
+
levels: ZERO_LEVEL
|
|
38528
|
+
};
|
|
38529
|
+
}
|
|
38530
|
+
};
|
|
38531
|
+
var stc = {
|
|
38532
|
+
type: "schaff-trend-cycle",
|
|
38533
|
+
title: "Schaff Trend Cycle",
|
|
38534
|
+
shortTitle: "Schaff Trend Cycle",
|
|
38535
|
+
overlay: false,
|
|
38536
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
38537
|
+
compute: (bars, inputs) => {
|
|
38538
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
38539
|
+
const src = closes(bars);
|
|
38540
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
38541
|
+
const stochOf = (v) => {
|
|
38542
|
+
const lo = lowest(v, len);
|
|
38543
|
+
const hi = highest(v, len);
|
|
38544
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38545
|
+
let prevRaw = Number.NaN;
|
|
38546
|
+
let smoothed = Number.NaN;
|
|
38547
|
+
for (let i = 0; i < v.length; i++) {
|
|
38548
|
+
const x = v[i];
|
|
38549
|
+
const l = lo[i];
|
|
38550
|
+
const h = hi[i];
|
|
38551
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
38552
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
38553
|
+
prevRaw = raw;
|
|
38554
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
38555
|
+
out[i] = smoothed;
|
|
38556
|
+
}
|
|
38557
|
+
return out;
|
|
38558
|
+
};
|
|
38559
|
+
const values = stochOf(stochOf(macdLine));
|
|
38560
|
+
return {
|
|
38561
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
38562
|
+
levels: guideLevels(25, 75)
|
|
38563
|
+
};
|
|
38564
|
+
}
|
|
38565
|
+
};
|
|
38566
|
+
var kst = {
|
|
38567
|
+
type: "know-sure-thing",
|
|
38568
|
+
title: "Know Sure Thing",
|
|
38569
|
+
shortTitle: "KST",
|
|
38570
|
+
overlay: false,
|
|
38571
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
38572
|
+
compute: (bars, inputs) => {
|
|
38573
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38574
|
+
const line = [
|
|
38575
|
+
sma(roc(src, 10), 10),
|
|
38576
|
+
sma(roc(src, 15), 10),
|
|
38577
|
+
sma(roc(src, 20), 10),
|
|
38578
|
+
sma(roc(src, 30), 15)
|
|
38579
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
38580
|
+
return {
|
|
38581
|
+
plots: [
|
|
38582
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
38583
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
38584
|
+
],
|
|
38585
|
+
levels: ZERO_LEVEL
|
|
38586
|
+
};
|
|
38587
|
+
}
|
|
38588
|
+
};
|
|
38589
|
+
var coppock = {
|
|
38590
|
+
type: "coppock-curve",
|
|
38591
|
+
title: "Coppock Curve",
|
|
38592
|
+
shortTitle: "Coppock",
|
|
38593
|
+
overlay: false,
|
|
38594
|
+
inputs: [
|
|
38595
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
38596
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
38597
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
38598
|
+
sourceInput(),
|
|
38599
|
+
colorInput()
|
|
38600
|
+
],
|
|
38601
|
+
compute: (bars, inputs) => {
|
|
38602
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38603
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
38604
|
+
return {
|
|
38605
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38606
|
+
levels: ZERO_LEVEL
|
|
38607
|
+
};
|
|
38608
|
+
}
|
|
38609
|
+
};
|
|
38610
|
+
var dpo = {
|
|
38611
|
+
type: "detrended-price-oscillator",
|
|
38612
|
+
title: "Detrended Price Oscillator",
|
|
38613
|
+
shortTitle: "DPO",
|
|
38614
|
+
overlay: false,
|
|
38615
|
+
inputs: [lengthInput(21), colorInput()],
|
|
38616
|
+
compute: (bars, inputs) => {
|
|
38617
|
+
const len = num3(inputs, "length", 21);
|
|
38618
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
38619
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
38620
|
+
return {
|
|
38621
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38622
|
+
levels: ZERO_LEVEL
|
|
38623
|
+
};
|
|
38624
|
+
}
|
|
38625
|
+
};
|
|
38626
|
+
var rocSpec = {
|
|
38627
|
+
type: "rate-of-change",
|
|
38628
|
+
title: "Rate of Change",
|
|
38629
|
+
shortTitle: "ROC",
|
|
38630
|
+
overlay: false,
|
|
38631
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38632
|
+
compute: (bars, inputs) => ({
|
|
38633
|
+
plots: [
|
|
38634
|
+
{
|
|
38635
|
+
key: "roc",
|
|
38636
|
+
title: "RoC",
|
|
38637
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
38638
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38639
|
+
width: 2
|
|
38640
|
+
}
|
|
38641
|
+
],
|
|
38642
|
+
levels: ZERO_LEVEL
|
|
38643
|
+
})
|
|
38644
|
+
};
|
|
38645
|
+
var ultimate = {
|
|
38646
|
+
type: "ultimate-oscillator",
|
|
38647
|
+
title: "Ultimate Oscillator",
|
|
38648
|
+
shortTitle: "UO",
|
|
38649
|
+
overlay: false,
|
|
38650
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
38651
|
+
compute: (bars, inputs) => {
|
|
38652
|
+
const n = bars.length;
|
|
38653
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
38654
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
38655
|
+
for (let i = 0; i < n; i++) {
|
|
38656
|
+
const b = bars[i];
|
|
38657
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
38658
|
+
const lo = Math.min(b.low, pc);
|
|
38659
|
+
const hi = Math.max(b.high, pc);
|
|
38660
|
+
bp[i] = b.close - lo;
|
|
38661
|
+
tr[i] = hi - lo;
|
|
38662
|
+
}
|
|
38663
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
38664
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
38665
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
38666
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
38667
|
+
const values = a7.map((x, i) => {
|
|
38668
|
+
const y = a14[i];
|
|
38669
|
+
const z = a28[i];
|
|
38670
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
38671
|
+
});
|
|
38672
|
+
return {
|
|
38673
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38674
|
+
levels: guideLevels(30, 70, 50)
|
|
38675
|
+
};
|
|
38676
|
+
}
|
|
38677
|
+
};
|
|
38678
|
+
var rvgi = {
|
|
38679
|
+
type: "relative-vigor-index",
|
|
38680
|
+
title: "Relative Vigor Index",
|
|
38681
|
+
shortTitle: "RVGI",
|
|
38682
|
+
overlay: false,
|
|
38683
|
+
inputs: [lengthInput(10)],
|
|
38684
|
+
compute: (bars, inputs) => {
|
|
38685
|
+
const len = num3(inputs, "length", 10);
|
|
38686
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
38687
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
38688
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
38689
|
+
return {
|
|
38690
|
+
plots: [
|
|
38691
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
38692
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
38693
|
+
],
|
|
38694
|
+
levels: ZERO_LEVEL
|
|
38695
|
+
};
|
|
38696
|
+
}
|
|
38697
|
+
};
|
|
38698
|
+
var relVolatility = {
|
|
38699
|
+
type: "relative-volatility-index",
|
|
38700
|
+
title: "Relative Volatility Index",
|
|
38701
|
+
shortTitle: "RVI",
|
|
38702
|
+
overlay: false,
|
|
38703
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
38704
|
+
compute: (bars, inputs) => {
|
|
38705
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
38706
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38707
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
38708
|
+
const d = change(src);
|
|
38709
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
38710
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
38711
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
38712
|
+
return {
|
|
38713
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38714
|
+
levels: guideLevels(20, 80, 50)
|
|
38715
|
+
};
|
|
38716
|
+
}
|
|
38717
|
+
};
|
|
38718
|
+
var bop = {
|
|
38719
|
+
type: "balance-of-power",
|
|
38720
|
+
title: "Balance of Power",
|
|
38721
|
+
shortTitle: "BOP",
|
|
38722
|
+
overlay: false,
|
|
38723
|
+
inputs: [colorInput()],
|
|
38724
|
+
compute: (bars, inputs) => {
|
|
38725
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
38726
|
+
return {
|
|
38727
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38728
|
+
levels: ZERO_LEVEL
|
|
38729
|
+
};
|
|
38730
|
+
}
|
|
38731
|
+
};
|
|
38732
|
+
var elderRay = {
|
|
38733
|
+
type: "elder-ray",
|
|
38734
|
+
title: "Elder Ray",
|
|
38735
|
+
overlay: false,
|
|
38736
|
+
inputs: [lengthInput(13)],
|
|
38737
|
+
compute: (bars, inputs) => {
|
|
38738
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
38739
|
+
return {
|
|
38740
|
+
plots: [
|
|
38741
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
38742
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
38743
|
+
],
|
|
38744
|
+
levels: ZERO_LEVEL
|
|
38745
|
+
};
|
|
38746
|
+
}
|
|
38747
|
+
};
|
|
38748
|
+
var ttmSqueeze = {
|
|
38749
|
+
type: "ttm-squeeze",
|
|
38750
|
+
title: "TTM Squeeze",
|
|
38751
|
+
overlay: false,
|
|
38752
|
+
inputs: [
|
|
38753
|
+
lengthInput(20),
|
|
38754
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38755
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
38756
|
+
],
|
|
38757
|
+
compute: (bars, inputs) => {
|
|
38758
|
+
const len = num3(inputs, "length", 20);
|
|
38759
|
+
const c = closes(bars);
|
|
38760
|
+
const basis = sma(c, len);
|
|
38761
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
38762
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
38763
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
38764
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
38765
|
+
const mom = linreg(sub(c, anchor), len);
|
|
38766
|
+
const squeezed = c.map((_, i) => {
|
|
38767
|
+
const d = dev[i];
|
|
38768
|
+
const r = kcRange[i];
|
|
38769
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
38770
|
+
});
|
|
38771
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
38772
|
+
return {
|
|
38773
|
+
plots: [
|
|
38774
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
38775
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
38776
|
+
]
|
|
38777
|
+
};
|
|
38778
|
+
}
|
|
38779
|
+
};
|
|
38780
|
+
var oscillatorSpecs = [
|
|
38781
|
+
rsiSpec,
|
|
38782
|
+
stochastic,
|
|
38783
|
+
stochasticRsi,
|
|
38784
|
+
macd,
|
|
38785
|
+
ppo,
|
|
38786
|
+
awesome,
|
|
38787
|
+
cci,
|
|
38788
|
+
williamsR,
|
|
38789
|
+
cmo,
|
|
38790
|
+
connorsRsi,
|
|
38791
|
+
fisher,
|
|
38792
|
+
trix,
|
|
38793
|
+
tsi,
|
|
38794
|
+
smiErgodic,
|
|
38795
|
+
stc,
|
|
38796
|
+
kst,
|
|
38797
|
+
coppock,
|
|
38798
|
+
dpo,
|
|
38799
|
+
rocSpec,
|
|
38800
|
+
ultimate,
|
|
38801
|
+
rvgi,
|
|
38802
|
+
relVolatility,
|
|
38803
|
+
bop,
|
|
38804
|
+
elderRay,
|
|
38805
|
+
ttmSqueeze
|
|
38806
|
+
];
|
|
38807
|
+
|
|
38808
|
+
// src/core/native-indicators/classics/trend.ts
|
|
38809
|
+
var aroon = {
|
|
38810
|
+
type: "aroon",
|
|
38811
|
+
title: "Aroon",
|
|
38812
|
+
overlay: false,
|
|
38813
|
+
inputs: [lengthInput(14)],
|
|
38814
|
+
compute: (bars, inputs) => {
|
|
38815
|
+
const len = num3(inputs, "length", 14);
|
|
38816
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38817
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38818
|
+
return {
|
|
38819
|
+
plots: [
|
|
38820
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
38821
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
38822
|
+
],
|
|
38823
|
+
levels: [
|
|
38824
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
38825
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
38826
|
+
]
|
|
38827
|
+
};
|
|
38828
|
+
}
|
|
38829
|
+
};
|
|
38830
|
+
var adx = {
|
|
38831
|
+
type: "average-directional-index",
|
|
38832
|
+
title: "Average Directional Index",
|
|
38833
|
+
shortTitle: "ADX",
|
|
38834
|
+
overlay: false,
|
|
38835
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
38836
|
+
compute: (bars, inputs) => {
|
|
38837
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
38838
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
38839
|
+
const n = bars.length;
|
|
38840
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
38841
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
38842
|
+
for (let i = 1; i < n; i++) {
|
|
38843
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
38844
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
38845
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
38846
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
38847
|
+
}
|
|
38848
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
38849
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38850
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38851
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
38852
|
+
return {
|
|
38853
|
+
plots: [
|
|
38854
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
38855
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
38856
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
38857
|
+
],
|
|
38858
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38859
|
+
};
|
|
38860
|
+
}
|
|
38861
|
+
};
|
|
38862
|
+
var vortex = {
|
|
38863
|
+
type: "vortex-indicator",
|
|
38864
|
+
title: "Vortex Indicator",
|
|
38865
|
+
shortTitle: "VI",
|
|
38866
|
+
overlay: false,
|
|
38867
|
+
inputs: [lengthInput(14)],
|
|
38868
|
+
compute: (bars, inputs) => {
|
|
38869
|
+
const len = num3(inputs, "length", 14);
|
|
38870
|
+
const n = bars.length;
|
|
38871
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
38872
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
38873
|
+
for (let i = 1; i < n; i++) {
|
|
38874
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
38875
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
38876
|
+
}
|
|
38877
|
+
const trSum = sum(trueRange(bars), len);
|
|
38878
|
+
return {
|
|
38879
|
+
plots: [
|
|
38880
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
38881
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
38882
|
+
],
|
|
38883
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38884
|
+
};
|
|
38885
|
+
}
|
|
38886
|
+
};
|
|
38887
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
38888
|
+
|
|
38889
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
38890
|
+
var atrSpec = {
|
|
38891
|
+
type: "average-true-range",
|
|
38892
|
+
title: "Average True Range",
|
|
38893
|
+
shortTitle: "ATR",
|
|
38894
|
+
overlay: false,
|
|
38895
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38896
|
+
compute: (bars, inputs) => ({
|
|
38897
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
38898
|
+
})
|
|
38899
|
+
};
|
|
38900
|
+
var historicalVolatility = {
|
|
38901
|
+
type: "historical-volatility",
|
|
38902
|
+
title: "Historical Volatility",
|
|
38903
|
+
shortTitle: "HV",
|
|
38904
|
+
overlay: false,
|
|
38905
|
+
inputs: [
|
|
38906
|
+
lengthInput(10),
|
|
38907
|
+
{ 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)" },
|
|
38908
|
+
colorInput()
|
|
38909
|
+
],
|
|
38910
|
+
compute: (bars, inputs) => {
|
|
38911
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
38912
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
38913
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38914
|
+
}
|
|
38915
|
+
};
|
|
38916
|
+
var chaikinVolatility = {
|
|
38917
|
+
type: "chaikin-volatility",
|
|
38918
|
+
title: "Chaikin Volatility",
|
|
38919
|
+
shortTitle: "CHV",
|
|
38920
|
+
overlay: false,
|
|
38921
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
38922
|
+
compute: (bars, inputs) => {
|
|
38923
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
38924
|
+
return {
|
|
38925
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38926
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
38927
|
+
};
|
|
38928
|
+
}
|
|
38929
|
+
};
|
|
38930
|
+
var massIndex = {
|
|
38931
|
+
type: "mass-index",
|
|
38932
|
+
title: "Mass Index",
|
|
38933
|
+
shortTitle: "MI",
|
|
38934
|
+
overlay: false,
|
|
38935
|
+
inputs: [lengthInput(25), colorInput()],
|
|
38936
|
+
compute: (bars, inputs) => {
|
|
38937
|
+
const range = sub(highs(bars), lows(bars));
|
|
38938
|
+
const single = ema(range, 9);
|
|
38939
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
38940
|
+
return {
|
|
38941
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38942
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38943
|
+
};
|
|
38944
|
+
}
|
|
38945
|
+
};
|
|
38946
|
+
var standardDeviation = {
|
|
38947
|
+
type: "standard-deviation",
|
|
38948
|
+
title: "Standard Deviation",
|
|
38949
|
+
shortTitle: "StdDev",
|
|
38950
|
+
overlay: false,
|
|
38951
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
38952
|
+
compute: (bars, inputs) => ({
|
|
38953
|
+
plots: [
|
|
38954
|
+
{
|
|
38955
|
+
key: "stdev",
|
|
38956
|
+
title: "StdDev",
|
|
38957
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
38958
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38959
|
+
width: 2
|
|
38960
|
+
}
|
|
38961
|
+
]
|
|
38962
|
+
})
|
|
38963
|
+
};
|
|
38964
|
+
var ulcerIndex = {
|
|
38965
|
+
type: "ulcer-index",
|
|
38966
|
+
title: "Ulcer Index",
|
|
38967
|
+
overlay: false,
|
|
38968
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38969
|
+
compute: (bars, inputs) => {
|
|
38970
|
+
const len = num3(inputs, "length", 14);
|
|
38971
|
+
const c = closes(bars);
|
|
38972
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
38973
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
38974
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38975
|
+
}
|
|
38976
|
+
};
|
|
38977
|
+
var choppiness = {
|
|
38978
|
+
type: "choppiness-index",
|
|
38979
|
+
title: "Choppiness Index",
|
|
38980
|
+
shortTitle: "CHOP",
|
|
38981
|
+
overlay: false,
|
|
38982
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38983
|
+
compute: (bars, inputs) => {
|
|
38984
|
+
const len = num3(inputs, "length", 14);
|
|
38985
|
+
const trSum = sum(trueRange(bars), len);
|
|
38986
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
38987
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
38988
|
+
return {
|
|
38989
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38990
|
+
levels: [
|
|
38991
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
38992
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
38993
|
+
]
|
|
38994
|
+
};
|
|
38995
|
+
}
|
|
38996
|
+
};
|
|
38997
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
38998
|
+
|
|
38999
|
+
// src/core/native-indicators/classics/volume.ts
|
|
39000
|
+
function moneyFlowVolume(bars) {
|
|
39001
|
+
return bars.map((b) => {
|
|
39002
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39003
|
+
if (b.high === b.low) return 0;
|
|
39004
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
39005
|
+
});
|
|
39006
|
+
}
|
|
39007
|
+
var obv = {
|
|
39008
|
+
type: "on-balance-volume",
|
|
39009
|
+
title: "On Balance Volume",
|
|
39010
|
+
shortTitle: "OBV",
|
|
39011
|
+
overlay: false,
|
|
39012
|
+
inputs: [colorInput()],
|
|
39013
|
+
compute: (bars, inputs) => {
|
|
39014
|
+
const vol = volumes(bars);
|
|
39015
|
+
const d = change(closes(bars));
|
|
39016
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
39017
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39018
|
+
}
|
|
39019
|
+
};
|
|
39020
|
+
var accDist = {
|
|
39021
|
+
type: "accumulation-distribution",
|
|
39022
|
+
title: "Accumulation / Distribution",
|
|
39023
|
+
shortTitle: "A/D",
|
|
39024
|
+
overlay: false,
|
|
39025
|
+
inputs: [colorInput()],
|
|
39026
|
+
compute: (bars, inputs) => ({
|
|
39027
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
39028
|
+
})
|
|
39029
|
+
};
|
|
39030
|
+
var cmf = {
|
|
39031
|
+
type: "chaikin-money-flow",
|
|
39032
|
+
title: "Chaikin Money Flow",
|
|
39033
|
+
shortTitle: "CMF",
|
|
39034
|
+
overlay: false,
|
|
39035
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39036
|
+
compute: (bars, inputs) => {
|
|
39037
|
+
const len = num3(inputs, "length", 20);
|
|
39038
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
39039
|
+
return {
|
|
39040
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39041
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39042
|
+
};
|
|
39043
|
+
}
|
|
39044
|
+
};
|
|
39045
|
+
var chaikinOscillator = {
|
|
39046
|
+
type: "chaikin-oscillator",
|
|
39047
|
+
title: "Chaikin Oscillator",
|
|
39048
|
+
shortTitle: "Chaikin Osc",
|
|
39049
|
+
overlay: false,
|
|
39050
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39051
|
+
compute: (bars, inputs) => {
|
|
39052
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
39053
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
39054
|
+
return {
|
|
39055
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39056
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39057
|
+
};
|
|
39058
|
+
}
|
|
39059
|
+
};
|
|
39060
|
+
var eom = {
|
|
39061
|
+
type: "ease-of-movement",
|
|
39062
|
+
title: "Ease of Movement",
|
|
39063
|
+
shortTitle: "EOM",
|
|
39064
|
+
overlay: false,
|
|
39065
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
39066
|
+
compute: (bars, inputs) => {
|
|
39067
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
39068
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39069
|
+
const move = change(hl2);
|
|
39070
|
+
const raw = bars.map((b, i) => {
|
|
39071
|
+
const m = move[i];
|
|
39072
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
39073
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
39074
|
+
});
|
|
39075
|
+
return {
|
|
39076
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39077
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39078
|
+
};
|
|
39079
|
+
}
|
|
39080
|
+
};
|
|
39081
|
+
var forceIndex = {
|
|
39082
|
+
type: "force-index",
|
|
39083
|
+
title: "Force Index",
|
|
39084
|
+
shortTitle: "FI",
|
|
39085
|
+
overlay: false,
|
|
39086
|
+
inputs: [lengthInput(13), colorInput()],
|
|
39087
|
+
compute: (bars, inputs) => {
|
|
39088
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
39089
|
+
return {
|
|
39090
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39091
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39092
|
+
};
|
|
39093
|
+
}
|
|
39094
|
+
};
|
|
39095
|
+
var klinger = {
|
|
39096
|
+
type: "klinger-oscillator",
|
|
39097
|
+
title: "Klinger Oscillator",
|
|
39098
|
+
shortTitle: "KVO",
|
|
39099
|
+
overlay: false,
|
|
39100
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39101
|
+
compute: (bars, inputs) => {
|
|
39102
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
39103
|
+
const trend = change(hlc3);
|
|
39104
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
39105
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
39106
|
+
return {
|
|
39107
|
+
plots: [
|
|
39108
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39109
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39110
|
+
],
|
|
39111
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39112
|
+
};
|
|
39113
|
+
}
|
|
39114
|
+
};
|
|
39115
|
+
var mfi = {
|
|
39116
|
+
type: "money-flow-index",
|
|
39117
|
+
title: "Money Flow Index",
|
|
39118
|
+
shortTitle: "MFI",
|
|
39119
|
+
overlay: false,
|
|
39120
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39121
|
+
compute: (bars, inputs) => {
|
|
39122
|
+
const len = num3(inputs, "length", 14);
|
|
39123
|
+
const tp = sourceValues(bars, "HLC3");
|
|
39124
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
39125
|
+
const d = change(tp);
|
|
39126
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
39127
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
39128
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
39129
|
+
return {
|
|
39130
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39131
|
+
levels: [
|
|
39132
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
39133
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
39134
|
+
]
|
|
39135
|
+
};
|
|
39136
|
+
}
|
|
39137
|
+
};
|
|
39138
|
+
function volumeIndex(bars, when) {
|
|
39139
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
39140
|
+
let index = 1e3;
|
|
39141
|
+
let started = false;
|
|
39142
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39143
|
+
const v = bars[i].volume;
|
|
39144
|
+
const pv = bars[i - 1].volume;
|
|
39145
|
+
const pc = bars[i - 1].close;
|
|
39146
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
39147
|
+
if (started) out[i] = index;
|
|
39148
|
+
continue;
|
|
39149
|
+
}
|
|
39150
|
+
started = true;
|
|
39151
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
39152
|
+
out[i] = index;
|
|
39153
|
+
}
|
|
39154
|
+
return out;
|
|
39155
|
+
}
|
|
39156
|
+
var nvi = {
|
|
39157
|
+
type: "negative-volume-index",
|
|
39158
|
+
title: "Negative Volume Index",
|
|
39159
|
+
shortTitle: "Negative Volume Index",
|
|
39160
|
+
overlay: false,
|
|
39161
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39162
|
+
compute: (bars, inputs) => {
|
|
39163
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
39164
|
+
return {
|
|
39165
|
+
plots: [
|
|
39166
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39167
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39168
|
+
]
|
|
39169
|
+
};
|
|
39170
|
+
}
|
|
39171
|
+
};
|
|
39172
|
+
var pvi = {
|
|
39173
|
+
type: "positive-volume-index",
|
|
39174
|
+
title: "Positive Volume Index",
|
|
39175
|
+
shortTitle: "Positive Volume Index",
|
|
39176
|
+
overlay: false,
|
|
39177
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39178
|
+
compute: (bars, inputs) => {
|
|
39179
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
39180
|
+
return {
|
|
39181
|
+
plots: [
|
|
39182
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39183
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39184
|
+
]
|
|
39185
|
+
};
|
|
39186
|
+
}
|
|
39187
|
+
};
|
|
39188
|
+
var pvt = {
|
|
39189
|
+
type: "price-volume-trend",
|
|
39190
|
+
title: "Price Volume Trend",
|
|
39191
|
+
shortTitle: "PVT",
|
|
39192
|
+
overlay: false,
|
|
39193
|
+
inputs: [colorInput()],
|
|
39194
|
+
compute: (bars, inputs) => {
|
|
39195
|
+
const c = closes(bars);
|
|
39196
|
+
const raw = bars.map((b, i) => {
|
|
39197
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39198
|
+
const pc = c[i - 1];
|
|
39199
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
39200
|
+
});
|
|
39201
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39202
|
+
}
|
|
39203
|
+
};
|
|
39204
|
+
var volumeOscillator = {
|
|
39205
|
+
type: "volume-oscillator",
|
|
39206
|
+
title: "Volume Oscillator",
|
|
39207
|
+
shortTitle: "Vol Osc",
|
|
39208
|
+
overlay: false,
|
|
39209
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39210
|
+
compute: (bars, inputs) => {
|
|
39211
|
+
const vol = volumes(bars);
|
|
39212
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
39213
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
39214
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39215
|
+
return {
|
|
39216
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39217
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39218
|
+
};
|
|
39219
|
+
}
|
|
39220
|
+
};
|
|
39221
|
+
var pvo = {
|
|
39222
|
+
type: "pvo",
|
|
39223
|
+
title: "Percentage Volume Oscillator",
|
|
39224
|
+
shortTitle: "PVO",
|
|
39225
|
+
overlay: false,
|
|
39226
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
39227
|
+
compute: (bars, inputs) => {
|
|
39228
|
+
const vol = volumes(bars);
|
|
39229
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
39230
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
39231
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39232
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39233
|
+
const hist = sub(line, signal);
|
|
39234
|
+
return {
|
|
39235
|
+
plots: [
|
|
39236
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
39237
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39238
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39239
|
+
],
|
|
39240
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39241
|
+
};
|
|
39242
|
+
}
|
|
39243
|
+
};
|
|
39244
|
+
var vfi = {
|
|
39245
|
+
type: "volume-flow-indicator",
|
|
39246
|
+
title: "Volume Flow Indicator",
|
|
39247
|
+
shortTitle: "Volume Flow Indicator",
|
|
39248
|
+
overlay: false,
|
|
39249
|
+
inputs: [
|
|
39250
|
+
lengthInput(130),
|
|
39251
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
39252
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
39253
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
39254
|
+
],
|
|
39255
|
+
compute: (bars, inputs) => {
|
|
39256
|
+
const len = num3(inputs, "length", 130);
|
|
39257
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
39258
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
39259
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
39260
|
+
const inter = change(typical);
|
|
39261
|
+
const vInter = stdev(inter, 30);
|
|
39262
|
+
const vol = volumes(bars);
|
|
39263
|
+
const vAve = sma(vol, len);
|
|
39264
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
39265
|
+
const raw = bars.map((b, i) => {
|
|
39266
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
39267
|
+
const v = vol[i];
|
|
39268
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39269
|
+
const m = mf[i];
|
|
39270
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
39271
|
+
const capped = Math.min(v, ave * volCoef);
|
|
39272
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
39273
|
+
});
|
|
39274
|
+
const values = bars.map((_, i) => {
|
|
39275
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39276
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
39277
|
+
});
|
|
39278
|
+
const flowSum = sum(raw, len);
|
|
39279
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
39280
|
+
return {
|
|
39281
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
39282
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39283
|
+
};
|
|
39284
|
+
}
|
|
39285
|
+
};
|
|
39286
|
+
var intradayIntensity = {
|
|
39287
|
+
type: "intraday-intensity",
|
|
39288
|
+
title: "Intraday Intensity",
|
|
39289
|
+
shortTitle: "Intraday Intensity",
|
|
39290
|
+
overlay: false,
|
|
39291
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39292
|
+
compute: (bars, inputs) => {
|
|
39293
|
+
const len = num3(inputs, "length", 21);
|
|
39294
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
39295
|
+
return {
|
|
39296
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39297
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39298
|
+
};
|
|
39299
|
+
}
|
|
39300
|
+
};
|
|
39301
|
+
var volumeSpecs = [
|
|
39302
|
+
obv,
|
|
39303
|
+
accDist,
|
|
39304
|
+
cmf,
|
|
39305
|
+
chaikinOscillator,
|
|
39306
|
+
eom,
|
|
39307
|
+
forceIndex,
|
|
39308
|
+
klinger,
|
|
39309
|
+
mfi,
|
|
39310
|
+
nvi,
|
|
39311
|
+
pvi,
|
|
39312
|
+
pvt,
|
|
39313
|
+
volumeOscillator,
|
|
39314
|
+
pvo,
|
|
39315
|
+
vfi,
|
|
39316
|
+
intradayIntensity
|
|
39317
|
+
];
|
|
39318
|
+
|
|
39319
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
39320
|
+
var DAY_MS2 = 864e5;
|
|
39321
|
+
function periodKey(anchor, time) {
|
|
39322
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
39323
|
+
if (anchor === "Month") {
|
|
39324
|
+
const d = new Date(time);
|
|
39325
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
39326
|
+
}
|
|
39327
|
+
return Math.floor(time / DAY_MS2);
|
|
39328
|
+
}
|
|
39329
|
+
var parabolicSar = {
|
|
39330
|
+
type: "parabolic-sar",
|
|
39331
|
+
title: "Parabolic SAR",
|
|
39332
|
+
shortTitle: "SAR",
|
|
39333
|
+
overlay: true,
|
|
39334
|
+
inputs: [
|
|
39335
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39336
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39337
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
39338
|
+
],
|
|
39339
|
+
compute: (bars, inputs) => {
|
|
39340
|
+
const start = num3(inputs, "start", 0.02);
|
|
39341
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
39342
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
39343
|
+
const n = bars.length;
|
|
39344
|
+
const values = new Array(n).fill(Number.NaN);
|
|
39345
|
+
const colors = new Array(n).fill(null);
|
|
39346
|
+
if (n >= 2) {
|
|
39347
|
+
let long = bars[1].close >= bars[0].close;
|
|
39348
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
39349
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
39350
|
+
let af = start;
|
|
39351
|
+
for (let i = 1; i < n; i++) {
|
|
39352
|
+
const b = bars[i];
|
|
39353
|
+
sar += af * (ep - sar);
|
|
39354
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
39355
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
39356
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
39357
|
+
if (reversed) {
|
|
39358
|
+
long = !long;
|
|
39359
|
+
sar = ep;
|
|
39360
|
+
ep = long ? b.high : b.low;
|
|
39361
|
+
af = start;
|
|
39362
|
+
} else if (long && b.high > ep) {
|
|
39363
|
+
ep = b.high;
|
|
39364
|
+
af = Math.min(max, af + inc);
|
|
39365
|
+
} else if (!long && b.low < ep) {
|
|
39366
|
+
ep = b.low;
|
|
39367
|
+
af = Math.min(max, af + inc);
|
|
39368
|
+
}
|
|
39369
|
+
values[i] = sar;
|
|
39370
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
39371
|
+
}
|
|
39372
|
+
}
|
|
39373
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
39374
|
+
}
|
|
39375
|
+
};
|
|
39376
|
+
var vwap = {
|
|
39377
|
+
type: "vwap",
|
|
39378
|
+
title: "Volume Weighted Average Price",
|
|
39379
|
+
shortTitle: "VWAP",
|
|
39380
|
+
overlay: true,
|
|
39381
|
+
inputs: [
|
|
39382
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
39383
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
39384
|
+
colorInput(INFO)
|
|
39385
|
+
],
|
|
39386
|
+
compute: (bars, inputs) => {
|
|
39387
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39388
|
+
const source = str3(inputs, "source", "HLC3");
|
|
39389
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
39390
|
+
let period = Number.NaN;
|
|
39391
|
+
let cumPV = 0;
|
|
39392
|
+
let cumV = 0;
|
|
39393
|
+
for (let i = 0; i < bars.length; i++) {
|
|
39394
|
+
const b = bars[i];
|
|
39395
|
+
const key = periodKey(anchor, b.time);
|
|
39396
|
+
if (key !== period) {
|
|
39397
|
+
period = key;
|
|
39398
|
+
cumPV = 0;
|
|
39399
|
+
cumV = 0;
|
|
39400
|
+
}
|
|
39401
|
+
const v = b.volume;
|
|
39402
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
39403
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
39404
|
+
cumPV += tp * v;
|
|
39405
|
+
cumV += v;
|
|
39406
|
+
}
|
|
39407
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
39408
|
+
}
|
|
39409
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
39410
|
+
}
|
|
39411
|
+
};
|
|
39412
|
+
var pivotPoints = {
|
|
39413
|
+
type: "pivot-points",
|
|
39414
|
+
title: "Pivot Points",
|
|
39415
|
+
shortTitle: "Pivots",
|
|
39416
|
+
overlay: true,
|
|
39417
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
39418
|
+
compute: (bars, inputs) => {
|
|
39419
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39420
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
39421
|
+
for (const b of bars) {
|
|
39422
|
+
const key = periodKey(anchor, b.time);
|
|
39423
|
+
const a = aggs.get(key);
|
|
39424
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
39425
|
+
else {
|
|
39426
|
+
a.high = Math.max(a.high, b.high);
|
|
39427
|
+
a.low = Math.min(a.low, b.low);
|
|
39428
|
+
a.close = b.close;
|
|
39429
|
+
}
|
|
39430
|
+
}
|
|
39431
|
+
const n = bars.length;
|
|
39432
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
39433
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
39434
|
+
for (let i = 0; i < n; i++) {
|
|
39435
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
39436
|
+
if (!prev2) continue;
|
|
39437
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
39438
|
+
levels.p[i] = p;
|
|
39439
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
39440
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
39441
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
39442
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
39443
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
39444
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
39445
|
+
}
|
|
39446
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
39447
|
+
return {
|
|
39448
|
+
plots: [
|
|
39449
|
+
plot("p", "P", WARNING),
|
|
39450
|
+
plot("r1", "R1", BEARISH),
|
|
39451
|
+
plot("s1", "S1", BULLISH),
|
|
39452
|
+
plot("r2", "R2", BEARISH),
|
|
39453
|
+
plot("s2", "S2", BULLISH),
|
|
39454
|
+
plot("r3", "R3", BEARISH),
|
|
39455
|
+
plot("s3", "S3", BULLISH)
|
|
39456
|
+
]
|
|
39457
|
+
};
|
|
39458
|
+
}
|
|
39459
|
+
};
|
|
39460
|
+
var fiftyTwoWeek = {
|
|
39461
|
+
type: "52-week-high-low",
|
|
39462
|
+
title: "52 Week High/Low",
|
|
39463
|
+
shortTitle: "52W H/L",
|
|
39464
|
+
overlay: true,
|
|
39465
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
39466
|
+
compute: (bars, inputs) => {
|
|
39467
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
39468
|
+
const n = bars.length;
|
|
39469
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
39470
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
39471
|
+
const maxIdx = [];
|
|
39472
|
+
const minIdx = [];
|
|
39473
|
+
let from = 0;
|
|
39474
|
+
for (let i = 0; i < n; i++) {
|
|
39475
|
+
const b = bars[i];
|
|
39476
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
39477
|
+
maxIdx.push(i);
|
|
39478
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
39479
|
+
minIdx.push(i);
|
|
39480
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
39481
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
39482
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
39483
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
39484
|
+
lo[i] = bars[minIdx[0]].low;
|
|
39485
|
+
}
|
|
39486
|
+
return {
|
|
39487
|
+
plots: [
|
|
39488
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
39489
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
39490
|
+
]
|
|
39491
|
+
};
|
|
39492
|
+
}
|
|
39493
|
+
};
|
|
39494
|
+
var zigzag = {
|
|
39495
|
+
type: "zigzag",
|
|
39496
|
+
title: "ZigZag",
|
|
39497
|
+
overlay: true,
|
|
39498
|
+
inputs: [
|
|
39499
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
39500
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39501
|
+
colorInput()
|
|
39502
|
+
],
|
|
39503
|
+
compute: (bars, inputs) => {
|
|
39504
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
39505
|
+
const depth = num3(inputs, "depth", 10);
|
|
39506
|
+
const pivots = [];
|
|
39507
|
+
if (bars.length > 1) {
|
|
39508
|
+
let up = bars[1].close >= bars[0].close;
|
|
39509
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
39510
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39511
|
+
const b = bars[i];
|
|
39512
|
+
if (up) {
|
|
39513
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
39514
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39515
|
+
pivots.push(ext);
|
|
39516
|
+
up = false;
|
|
39517
|
+
ext = { i, price: b.low };
|
|
39518
|
+
}
|
|
39519
|
+
} else if (b.low <= ext.price) {
|
|
39520
|
+
ext = { i, price: b.low };
|
|
39521
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39522
|
+
pivots.push(ext);
|
|
39523
|
+
up = true;
|
|
39524
|
+
ext = { i, price: b.high };
|
|
39525
|
+
}
|
|
39526
|
+
}
|
|
39527
|
+
pivots.push(ext);
|
|
39528
|
+
}
|
|
39529
|
+
return {
|
|
39530
|
+
plots: [],
|
|
39531
|
+
polylines: [
|
|
39532
|
+
{
|
|
39533
|
+
key: "zigzag",
|
|
39534
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
39535
|
+
curved: false,
|
|
39536
|
+
closed: false,
|
|
39537
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
39538
|
+
lineWidth: 2,
|
|
39539
|
+
lineStyle: "solid",
|
|
39540
|
+
arrowLeft: false,
|
|
39541
|
+
arrowRight: false
|
|
39542
|
+
}
|
|
39543
|
+
]
|
|
39544
|
+
};
|
|
39545
|
+
}
|
|
39546
|
+
};
|
|
39547
|
+
var williamsFractal = {
|
|
39548
|
+
type: "williams-fractal",
|
|
39549
|
+
title: "Williams Fractal",
|
|
39550
|
+
shortTitle: "Fractals",
|
|
39551
|
+
overlay: true,
|
|
39552
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
39553
|
+
compute: (bars, inputs) => {
|
|
39554
|
+
const p = num3(inputs, "periods", 2);
|
|
39555
|
+
const markers = [];
|
|
39556
|
+
const isExtreme = (i, pick, better) => {
|
|
39557
|
+
const v = pick(bars[i]);
|
|
39558
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
39559
|
+
if (k === i) continue;
|
|
39560
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
39561
|
+
}
|
|
39562
|
+
return true;
|
|
39563
|
+
};
|
|
39564
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
39565
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
39566
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
39567
|
+
}
|
|
39568
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
39569
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
39570
|
+
}
|
|
39571
|
+
}
|
|
39572
|
+
return { plots: [], markers };
|
|
39573
|
+
}
|
|
39574
|
+
};
|
|
39575
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
39576
|
+
|
|
39577
|
+
// src/core/native-indicators/classics/index.ts
|
|
39578
|
+
var classicSpecs = [
|
|
39579
|
+
...averageSpecs,
|
|
39580
|
+
...bandSpecs,
|
|
39581
|
+
...oscillatorSpecs,
|
|
39582
|
+
...trendSpecs,
|
|
39583
|
+
...volatilitySpecs,
|
|
39584
|
+
...volumeSpecs,
|
|
39585
|
+
...overlaySpecs
|
|
39586
|
+
];
|
|
39587
|
+
function registerClassicIndicators() {
|
|
39588
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
39589
|
+
}
|
|
39590
|
+
|
|
37259
39591
|
// src/Vela.ts
|
|
37260
39592
|
var Vela = class {
|
|
37261
39593
|
constructor(container, options = {}, deps = {}) {
|
|
37262
39594
|
registerBuiltinChartTypes();
|
|
37263
39595
|
registerVolume();
|
|
37264
39596
|
registerVpvr();
|
|
39597
|
+
registerClassicIndicators();
|
|
37265
39598
|
const element = resolveElement(container);
|
|
37266
39599
|
const theme = resolveTheme(options.theme);
|
|
37267
39600
|
let animZoom = true;
|
|
@@ -37336,25 +39669,28 @@ var Vela = class {
|
|
|
37336
39669
|
}
|
|
37337
39670
|
/**
|
|
37338
39671
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
37339
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
37340
|
-
* events)
|
|
37341
|
-
*
|
|
39672
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
39673
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
39674
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
39675
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
37342
39676
|
*/
|
|
37343
39677
|
addNativeIndicator(type, options) {
|
|
37344
39678
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
37345
39679
|
}
|
|
37346
39680
|
/**
|
|
37347
39681
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
37348
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
37349
|
-
*
|
|
37350
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
37351
|
-
*
|
|
39682
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
39683
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
39684
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
39685
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
39686
|
+
* provider (a type may need data the symbol lacks).
|
|
37352
39687
|
*/
|
|
37353
39688
|
availableNativeIndicators() {
|
|
37354
39689
|
return this.orchestrator.availableNativeIndicators();
|
|
37355
39690
|
}
|
|
37356
39691
|
/**
|
|
37357
|
-
* The native-indicator types PRESENT on the chart right now
|
|
39692
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
39693
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
37358
39694
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
37359
39695
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
37360
39696
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -38097,7 +40433,7 @@ var Statusline = class {
|
|
|
38097
40433
|
};
|
|
38098
40434
|
|
|
38099
40435
|
// src/widget/session-shading.ts
|
|
38100
|
-
var
|
|
40436
|
+
var DAY_MS3 = 864e5;
|
|
38101
40437
|
var MINUTE_MS = 6e4;
|
|
38102
40438
|
function parseWindow(text, allowOvernight = false) {
|
|
38103
40439
|
if (typeof text !== "string") return null;
|
|
@@ -38176,7 +40512,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38176
40512
|
const dtf = civilFormatter(spec.timezone);
|
|
38177
40513
|
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38178
40514
|
const seen = /* @__PURE__ */ new Set();
|
|
38179
|
-
for (let cursor = from -
|
|
40515
|
+
for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
|
|
38180
40516
|
const civil = civilParts(dtf, cursor);
|
|
38181
40517
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38182
40518
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
@@ -38201,7 +40537,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38201
40537
|
}
|
|
38202
40538
|
return { pre, post, extended: mergeAbutting(extended) };
|
|
38203
40539
|
}
|
|
38204
|
-
var COVER_PAD_MIN_MS = 2 *
|
|
40540
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS3;
|
|
38205
40541
|
var SessionShadingTracker = class {
|
|
38206
40542
|
constructor(onZones) {
|
|
38207
40543
|
this.onZones = onZones;
|
|
@@ -38225,7 +40561,7 @@ var SessionShadingTracker = class {
|
|
|
38225
40561
|
this.covered = null;
|
|
38226
40562
|
this.session = opts.session;
|
|
38227
40563
|
const tfMs = timeframeMs(opts.timeframe);
|
|
38228
|
-
this.intraday = Number.isFinite(tfMs) && tfMs <
|
|
40564
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
|
|
38229
40565
|
this.lastRange = opts.range;
|
|
38230
40566
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
38231
40567
|
if (my !== this.epoch) return;
|
|
@@ -38899,8 +41235,8 @@ var ChartCell = class {
|
|
|
38899
41235
|
/** The volume auto-add rides the cell's first candles (`load:end`); until then the
|
|
38900
41236
|
* registry can't show it and the dehydrated ledger reports the INTENT instead. */
|
|
38901
41237
|
this.volumeMayBePending = true;
|
|
38902
|
-
/** Sync mirror of the chart's
|
|
38903
|
-
*
|
|
41238
|
+
/** Sync mirror of the chart's native instances (id + type) — the removal handler
|
|
41239
|
+
* looks the removed id up here to learn which type an undo must re-add. */
|
|
38904
41240
|
this.presentNatives = [];
|
|
38905
41241
|
this.rangeBars = 0;
|
|
38906
41242
|
this.pendingRange = null;
|
|
@@ -39072,15 +41408,18 @@ var ChartCell = class {
|
|
|
39072
41408
|
redo: () => this.dropInstance(snapshot)
|
|
39073
41409
|
});
|
|
39074
41410
|
} else {
|
|
39075
|
-
const
|
|
39076
|
-
|
|
41411
|
+
const gone = this.presentNatives.find((n) => n.id === id2);
|
|
41412
|
+
if (gone) {
|
|
41413
|
+
const { type } = gone;
|
|
41414
|
+
let revived = null;
|
|
39077
41415
|
this.history.push({
|
|
39078
41416
|
undo: () => {
|
|
39079
|
-
this.inner?.addNativeIndicator(type);
|
|
41417
|
+
revived = this.inner?.addNativeIndicator(type) ?? null;
|
|
39080
41418
|
this.refreshNativeCatalog();
|
|
39081
41419
|
},
|
|
39082
41420
|
redo: () => {
|
|
39083
|
-
|
|
41421
|
+
revived?.remove();
|
|
41422
|
+
revived = null;
|
|
39084
41423
|
this.refreshNativeCatalog();
|
|
39085
41424
|
}
|
|
39086
41425
|
});
|
|
@@ -39385,9 +41724,9 @@ var ChartCell = class {
|
|
|
39385
41724
|
const live = this.inner?.renderer.get("priceStyle");
|
|
39386
41725
|
return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
|
|
39387
41726
|
}
|
|
39388
|
-
/** Manifest instances +
|
|
41727
|
+
/** Manifest instances + native instances — the topbar indicator count. */
|
|
39389
41728
|
get indicatorCount() {
|
|
39390
|
-
return this.instances.length + this.
|
|
41729
|
+
return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
|
|
39391
41730
|
}
|
|
39392
41731
|
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
39393
41732
|
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
@@ -39519,13 +41858,15 @@ var ChartCell = class {
|
|
|
39519
41858
|
if (!chart) return;
|
|
39520
41859
|
this.volumeIntent = led.natives.includes("volume");
|
|
39521
41860
|
this.history.silently(() => {
|
|
39522
|
-
const
|
|
39523
|
-
for (const type of led.natives)
|
|
39524
|
-
|
|
39525
|
-
|
|
39526
|
-
|
|
39527
|
-
if (
|
|
39528
|
-
|
|
41861
|
+
const owed = /* @__PURE__ */ new Map();
|
|
41862
|
+
for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
|
|
41863
|
+
for (const h of this.nativeHandles()) {
|
|
41864
|
+
const type = h.nativeType;
|
|
41865
|
+
const n = owed.get(type) ?? 0;
|
|
41866
|
+
if (n > 0) owed.set(type, n - 1);
|
|
41867
|
+
else h.remove();
|
|
41868
|
+
}
|
|
41869
|
+
for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
|
|
39529
41870
|
for (const it of [...this.instances]) this.dropInstance(it);
|
|
39530
41871
|
if (this.manifest.length > 0) {
|
|
39531
41872
|
for (const item of led.manifest) {
|
|
@@ -39543,34 +41884,42 @@ var ChartCell = class {
|
|
|
39543
41884
|
this.syncPresentNatives();
|
|
39544
41885
|
this.refreshNativeCatalog();
|
|
39545
41886
|
}
|
|
39546
|
-
/** The picker
|
|
41887
|
+
/** The supported natives in picker order: A→Z by title — registration order follows
|
|
41888
|
+
* the catalog's families, which is meaningless to the reader. This is the library
|
|
41889
|
+
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
41890
|
+
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
41891
|
+
supportedNatives() {
|
|
41892
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
|
|
41893
|
+
}
|
|
41894
|
+
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
41895
|
+
* then the manifest in the host's own order. */
|
|
39547
41896
|
libraryRows() {
|
|
39548
41897
|
return [
|
|
39549
|
-
...this.
|
|
41898
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
|
|
39550
41899
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
39551
41900
|
];
|
|
39552
41901
|
}
|
|
39553
|
-
/** The picker's on-chart rows:
|
|
41902
|
+
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
39554
41903
|
onChartRows() {
|
|
39555
41904
|
return [
|
|
39556
|
-
...this.
|
|
41905
|
+
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
39557
41906
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
39558
41907
|
];
|
|
39559
41908
|
}
|
|
39560
41909
|
/** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
|
|
39561
41910
|
addFromLibrary(index) {
|
|
39562
|
-
const natives = this.
|
|
41911
|
+
const natives = this.supportedNatives();
|
|
39563
41912
|
if (index < natives.length) this.addNative(natives[index].type);
|
|
39564
41913
|
else {
|
|
39565
41914
|
const entry = this.manifest[index - natives.length];
|
|
39566
41915
|
if (entry) this.addManifestInstance(entry);
|
|
39567
41916
|
}
|
|
39568
41917
|
}
|
|
39569
|
-
/** Remove by picker ON-CHART index (
|
|
41918
|
+
/** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
|
|
39570
41919
|
removeFromChart(index) {
|
|
39571
|
-
const
|
|
39572
|
-
if (index <
|
|
39573
|
-
else this.removeInstance(index -
|
|
41920
|
+
const natives = this.nativeHandles();
|
|
41921
|
+
if (index < natives.length) this.removeNative(natives[index]);
|
|
41922
|
+
else this.removeInstance(index - natives.length);
|
|
39574
41923
|
}
|
|
39575
41924
|
/**
|
|
39576
41925
|
* Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
|
|
@@ -39630,30 +41979,42 @@ var ChartCell = class {
|
|
|
39630
41979
|
it.handle = null;
|
|
39631
41980
|
this.deps.onIndicatorsChanged(this.id);
|
|
39632
41981
|
}
|
|
39633
|
-
/** Add a native indicator
|
|
41982
|
+
/** Add a native indicator. A multi-instance type gets a fresh instance every time; a
|
|
41983
|
+
* single-instance type already on the chart hands back its existing one — nothing
|
|
41984
|
+
* changed, so nothing enters the undo timeline. */
|
|
39634
41985
|
addNative(type) {
|
|
39635
|
-
this.inner
|
|
41986
|
+
const chart = this.inner;
|
|
41987
|
+
if (!chart) return;
|
|
41988
|
+
const before = new Set(chart.indicators().map((h) => h.id));
|
|
41989
|
+
let added = chart.addNativeIndicator(type);
|
|
39636
41990
|
this.syncPresentNatives();
|
|
39637
41991
|
this.refreshNativeCatalog();
|
|
41992
|
+
if (before.has(added.id)) return;
|
|
39638
41993
|
this.history.push({
|
|
39639
41994
|
undo: () => {
|
|
39640
|
-
|
|
41995
|
+
added?.remove();
|
|
41996
|
+
added = null;
|
|
39641
41997
|
this.refreshNativeCatalog();
|
|
39642
41998
|
},
|
|
39643
41999
|
redo: () => {
|
|
39644
|
-
this.inner?.addNativeIndicator(type);
|
|
42000
|
+
added = this.inner?.addNativeIndicator(type) ?? null;
|
|
39645
42001
|
this.refreshNativeCatalog();
|
|
39646
42002
|
}
|
|
39647
42003
|
});
|
|
39648
42004
|
}
|
|
39649
|
-
removeNative(
|
|
39650
|
-
|
|
42005
|
+
removeNative(handle) {
|
|
42006
|
+
handle.remove();
|
|
39651
42007
|
this.refreshNativeCatalog();
|
|
39652
42008
|
}
|
|
39653
|
-
/**
|
|
39654
|
-
*
|
|
42009
|
+
/** The chart's native instances, insertion order — the picker's on-chart rows and
|
|
42010
|
+
* the removal index space (script instances follow them). */
|
|
42011
|
+
nativeHandles() {
|
|
42012
|
+
return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
|
|
42013
|
+
}
|
|
42014
|
+
/** Sync mirror of the chart's native instances — the removal handler looks the
|
|
42015
|
+
* removed id up here (the registry has already forgotten it) to record its type. */
|
|
39655
42016
|
syncPresentNatives() {
|
|
39656
|
-
this.presentNatives = this.
|
|
42017
|
+
this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
|
|
39657
42018
|
}
|
|
39658
42019
|
/** Refresh the native catalog (supported/present flags) for this cell's market. */
|
|
39659
42020
|
refreshNativeCatalog() {
|