@luxalgo/vela 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/{DataProvider-XLdJB86x.d.ts → DataProvider-B-Jz59Rf.d.ts} +1 -1
- package/dist/{DataProvider-xH2VBcFT.d.cts → DataProvider-OfR3fXg9.d.cts} +1 -1
- package/dist/{chunk-6DPAYEGO.js → chunk-CAFCLMPF.js} +1 -0
- package/dist/chunk-FE6V2YAQ.js +1 -0
- package/dist/{chunk-32A7KHFM.js → chunk-IFJJPXSV.js} +8 -2
- package/dist/{chunk-7C7NWEXH.js → chunk-OSZDSH7F.js} +83 -59
- package/dist/{chunk-MWDNPGU3.js → chunk-T5Z5YUCF.js} +23 -16
- package/dist/{chunk-MWS4CUJL.js → chunk-XCBJU674.js} +2 -2
- package/dist/{chunk-J5QIR2Z3.js → chunk-YCD72KGK.js} +2543 -159
- package/dist/{contributions-x7iamlnd.d.cts → contributions-C3b0Sx5P.d.cts} +29 -13
- package/dist/{contributions-h5BzEiV2.d.ts → contributions-Swzz10fG.d.ts} +29 -13
- package/dist/index.cjs +2746 -355
- package/dist/index.d.cts +15 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -5
- package/dist/{options-bdSAtp7y.d.ts → options-CX1lSYWA.d.cts} +12 -2
- package/dist/{options-bdSAtp7y.d.cts → options-CX1lSYWA.d.ts} +12 -2
- package/dist/{plugin-2sJcf5mk.d.cts → plugin-BjnEE1zA.d.cts} +3 -3
- package/dist/{plugin-BLaNPh55.d.ts → plugin-mvddudqd.d.ts} +3 -3
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model--tH0VWCy.d.cts → statusline-model-D91cU2fF.d.cts} +3 -3
- package/dist/{statusline-model-CLpaO3Dl.d.ts → statusline-model-IURCya0H.d.ts} +3 -3
- package/dist/ui.cjs +23 -15
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2600 -209
- package/dist/vela.global.min.js +52 -55
- package/dist/widget.cjs +2663 -241
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +2663 -241
- package/dist/workspace.d.cts +25 -14
- package/dist/workspace.d.ts +25 -14
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- package/dist/chunk-H26BEHF4.js +0 -7
package/dist/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) {
|
|
@@ -1234,6 +1234,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
|
|
|
1234
1234
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
1235
1235
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
1236
1236
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
1237
|
+
registerIcon("info", S('<path d="M8 7.4v4.4"/><circle cx="8" cy="4.6" r="0.9" fill="currentColor" stroke="none"/>'));
|
|
1237
1238
|
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
1238
1239
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
1239
1240
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
@@ -1714,19 +1715,19 @@ var Surface = class _Surface {
|
|
|
1714
1715
|
}
|
|
1715
1716
|
setItems(items) {
|
|
1716
1717
|
this.items = items;
|
|
1717
|
-
for (const
|
|
1718
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1718
1719
|
this.subs.clear();
|
|
1719
1720
|
this.build();
|
|
1720
1721
|
const api = this.api;
|
|
1721
|
-
for (const
|
|
1722
|
-
api.setChild(
|
|
1723
|
-
|
|
1722
|
+
for (const sub2 of this.subs.values()) {
|
|
1723
|
+
api.setChild(sub2.handle.service);
|
|
1724
|
+
sub2.api.setParent(this.handle.service);
|
|
1724
1725
|
}
|
|
1725
1726
|
this.handle.flush();
|
|
1726
1727
|
if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
|
|
1727
1728
|
}
|
|
1728
1729
|
destroy() {
|
|
1729
|
-
for (const
|
|
1730
|
+
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1730
1731
|
this.subs.clear();
|
|
1731
1732
|
this.handle.stop();
|
|
1732
1733
|
this.positioner.remove();
|
|
@@ -1740,9 +1741,9 @@ var Surface = class _Surface {
|
|
|
1740
1741
|
for (const li of this.list.children) {
|
|
1741
1742
|
const id = li.dataset.veiId;
|
|
1742
1743
|
if (!id) continue;
|
|
1743
|
-
const
|
|
1744
|
-
if (
|
|
1745
|
-
vanilla.spreadProps(li, api.getTriggerItemProps(
|
|
1744
|
+
const sub2 = this.subs.get(id);
|
|
1745
|
+
if (sub2) {
|
|
1746
|
+
vanilla.spreadProps(li, api.getTriggerItemProps(sub2.api), this.mid);
|
|
1746
1747
|
continue;
|
|
1747
1748
|
}
|
|
1748
1749
|
const item = this.byId(id);
|
|
@@ -1819,7 +1820,7 @@ var Surface = class _Surface {
|
|
|
1819
1820
|
const arrow = iconEl("chevron-right", doc);
|
|
1820
1821
|
arrow.classList.add("vela-menu-arrow");
|
|
1821
1822
|
li.appendChild(arrow);
|
|
1822
|
-
const
|
|
1823
|
+
const sub2 = new _Surface(doc, {
|
|
1823
1824
|
host: this.host,
|
|
1824
1825
|
placement: "right-start",
|
|
1825
1826
|
onSelect: this.onSelect,
|
|
@@ -1827,8 +1828,8 @@ var Surface = class _Surface {
|
|
|
1827
1828
|
checkmarks: this.checkmarks,
|
|
1828
1829
|
id: `${this.mid}--${item.id}`
|
|
1829
1830
|
});
|
|
1830
|
-
|
|
1831
|
-
this.subs.set(item.id,
|
|
1831
|
+
sub2.setItems(item.submenu ?? []);
|
|
1832
|
+
this.subs.set(item.id, sub2);
|
|
1832
1833
|
}
|
|
1833
1834
|
this.list.appendChild(li);
|
|
1834
1835
|
}
|
|
@@ -3528,8 +3529,21 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3528
3529
|
};
|
|
3529
3530
|
const recentRow = document.createElement("div");
|
|
3530
3531
|
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
3532
|
+
const recentSwatches = document.createElement("div");
|
|
3533
|
+
recentSwatches.style.cssText = "display:contents;";
|
|
3534
|
+
const add = document.createElement("label");
|
|
3535
|
+
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;`;
|
|
3536
|
+
add.textContent = "+";
|
|
3537
|
+
const customInput = document.createElement("input");
|
|
3538
|
+
customInput.type = "color";
|
|
3539
|
+
customInput.value = curHex;
|
|
3540
|
+
customInput.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
3541
|
+
customInput.addEventListener("input", () => previewHex(customInput.value));
|
|
3542
|
+
customInput.addEventListener("change", () => pickHex(customInput.value, true));
|
|
3543
|
+
add.appendChild(customInput);
|
|
3544
|
+
recentRow.append(recentSwatches, add);
|
|
3531
3545
|
const renderRecents = () => {
|
|
3532
|
-
|
|
3546
|
+
recentSwatches.replaceChildren();
|
|
3533
3547
|
for (const c of recents) {
|
|
3534
3548
|
const sw = document.createElement("button");
|
|
3535
3549
|
sw.type = "button";
|
|
@@ -3538,18 +3552,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3538
3552
|
e.stopPropagation();
|
|
3539
3553
|
pickHex(c);
|
|
3540
3554
|
});
|
|
3541
|
-
|
|
3555
|
+
recentSwatches.appendChild(sw);
|
|
3542
3556
|
}
|
|
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
3557
|
};
|
|
3554
3558
|
const opLabel = document.createElement("div");
|
|
3555
3559
|
opLabel.textContent = "Opacity";
|
|
@@ -3589,14 +3593,18 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3589
3593
|
function emit() {
|
|
3590
3594
|
onChange(combineColor(curHex, curAlpha));
|
|
3591
3595
|
}
|
|
3592
|
-
function
|
|
3596
|
+
function previewHex(hex) {
|
|
3593
3597
|
curHex = splitColor(hex).hex6;
|
|
3594
|
-
addRecent(curHex);
|
|
3595
3598
|
paintSelection();
|
|
3596
3599
|
paintOpacity();
|
|
3597
|
-
if (custom) renderRecents();
|
|
3598
3600
|
emit();
|
|
3599
3601
|
}
|
|
3602
|
+
function pickHex(hex, custom = false) {
|
|
3603
|
+
previewHex(hex);
|
|
3604
|
+
addRecent(curHex);
|
|
3605
|
+
customInput.value = curHex;
|
|
3606
|
+
if (custom) renderRecents();
|
|
3607
|
+
}
|
|
3600
3608
|
renderRecents();
|
|
3601
3609
|
paintSelection();
|
|
3602
3610
|
paintOpacity();
|
|
@@ -5113,9 +5121,9 @@ function factoryResetConfig(factory) {
|
|
|
5113
5121
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
5114
5122
|
addRows(inst.rows);
|
|
5115
5123
|
}
|
|
5116
|
-
for (const
|
|
5117
|
-
if (
|
|
5118
|
-
addRows(
|
|
5124
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5125
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
5126
|
+
addRows(sub2.rows);
|
|
5119
5127
|
}
|
|
5120
5128
|
if (!section.instances) addRows(section.rows);
|
|
5121
5129
|
bag[t.id] = defaults2;
|
|
@@ -5184,7 +5192,8 @@ function mergeConfig(base, patch) {
|
|
|
5184
5192
|
labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
|
|
5185
5193
|
currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
|
|
5186
5194
|
priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
|
|
5187
|
-
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
|
|
5195
|
+
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
|
|
5196
|
+
animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
|
|
5188
5197
|
},
|
|
5189
5198
|
panes: {
|
|
5190
5199
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
@@ -14031,17 +14040,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
14031
14040
|
}
|
|
14032
14041
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
14033
14042
|
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
|
-
}
|
|
14043
|
+
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
14044
|
+
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
14045
|
+
scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
|
|
14046
|
+
scroller's content box, so the head lands one padding below the top and covers
|
|
14047
|
+
the first row. */
|
|
14048
|
+
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
14045
14049
|
.vela-sp-searchrow {
|
|
14046
14050
|
display: flex;
|
|
14047
14051
|
align-items: center;
|
|
@@ -14066,12 +14070,13 @@ var CSS8 = `
|
|
|
14066
14070
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
14067
14071
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
14068
14072
|
/* 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
|
|
14073
|
+
overflowing the body, and the result list stops capping itself \u2014 it fills the
|
|
14074
|
+
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
14075
|
+
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
14071
14076
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
14072
14077
|
[data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
|
|
14073
14078
|
[data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
|
|
14074
|
-
[data-layout='mobile'] .vela-sp-list { max-height: none; }
|
|
14079
|
+
[data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
|
|
14075
14080
|
.vela-sp-tab {
|
|
14076
14081
|
all: unset;
|
|
14077
14082
|
padding: 3px 10px;
|
|
@@ -14083,7 +14088,12 @@ var CSS8 = `
|
|
|
14083
14088
|
}
|
|
14084
14089
|
.vela-sp-tab:hover { color: var(--vela-fg); }
|
|
14085
14090
|
.vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
14086
|
-
.vela-sp-list {
|
|
14091
|
+
.vela-sp-list {
|
|
14092
|
+
min-height: 0;
|
|
14093
|
+
margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
|
|
14094
|
+
max-height: 46vh;
|
|
14095
|
+
overflow: auto;
|
|
14096
|
+
}
|
|
14087
14097
|
.vela-sp-list::-webkit-scrollbar { width: 8px; }
|
|
14088
14098
|
.vela-sp-list::-webkit-scrollbar-thumb {
|
|
14089
14099
|
background: var(--vela-scroll);
|
|
@@ -14176,8 +14186,8 @@ var SymbolPicker = class {
|
|
|
14176
14186
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14177
14187
|
this.tabs = doc.createElement("div");
|
|
14178
14188
|
this.tabs.className = "vela-sp-tabs";
|
|
14179
|
-
const
|
|
14180
|
-
|
|
14189
|
+
const head = doc.createElement("div");
|
|
14190
|
+
head.className = "vela-sp-head";
|
|
14181
14191
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14182
14192
|
const b = doc.createElement("button");
|
|
14183
14193
|
b.className = "vela-sp-tab";
|
|
@@ -14191,7 +14201,7 @@ var SymbolPicker = class {
|
|
|
14191
14201
|
});
|
|
14192
14202
|
this.tabs.appendChild(b);
|
|
14193
14203
|
}
|
|
14194
|
-
|
|
14204
|
+
head.append(searchRow, this.tabs);
|
|
14195
14205
|
this.list = doc.createElement("div");
|
|
14196
14206
|
this.list.className = "vela-sp-list";
|
|
14197
14207
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14204,7 +14214,8 @@ var SymbolPicker = class {
|
|
|
14204
14214
|
title: "Symbol Search",
|
|
14205
14215
|
host: opts.host,
|
|
14206
14216
|
closeOnInteractOutside: true,
|
|
14207
|
-
|
|
14217
|
+
flush: true,
|
|
14218
|
+
content: (body) => body.append(head, this.list),
|
|
14208
14219
|
onOpenChange: (open2) => {
|
|
14209
14220
|
if (open2) {
|
|
14210
14221
|
this.input.value = this.seed.toUpperCase();
|
|
@@ -15960,11 +15971,11 @@ var PriceScaleDrawer = class {
|
|
|
15960
15971
|
for (const item of items) {
|
|
15961
15972
|
if (item.id === "labels" && item.submenu) {
|
|
15962
15973
|
section("Labels");
|
|
15963
|
-
for (const
|
|
15964
|
-
row(
|
|
15965
|
-
checked:
|
|
15974
|
+
for (const sub2 of item.submenu) {
|
|
15975
|
+
row(sub2.label, {
|
|
15976
|
+
checked: sub2.checked,
|
|
15966
15977
|
onClick: () => {
|
|
15967
|
-
const feature =
|
|
15978
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
15968
15979
|
chart.renderer.set(feature, !this.flag(feature));
|
|
15969
15980
|
this.render();
|
|
15970
15981
|
}
|
|
@@ -15974,11 +15985,11 @@ var PriceScaleDrawer = class {
|
|
|
15974
15985
|
}
|
|
15975
15986
|
if (item.id === "levels" && item.submenu) {
|
|
15976
15987
|
section("Levels");
|
|
15977
|
-
for (const
|
|
15978
|
-
row(
|
|
15979
|
-
checked:
|
|
15988
|
+
for (const sub2 of item.submenu) {
|
|
15989
|
+
row(sub2.label, {
|
|
15990
|
+
checked: sub2.checked,
|
|
15980
15991
|
onClick: () => {
|
|
15981
|
-
const feature =
|
|
15992
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
15982
15993
|
chart.renderer.set(feature, !this.flag(feature));
|
|
15983
15994
|
this.render();
|
|
15984
15995
|
}
|
|
@@ -16552,8 +16563,8 @@ var DrawingController = class {
|
|
|
16552
16563
|
this.port?.setExternalGhost?.(doc);
|
|
16553
16564
|
}
|
|
16554
16565
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
16555
|
-
setToolShortcuts(
|
|
16556
|
-
this.port?.setToolShortcuts?.(
|
|
16566
|
+
setToolShortcuts(map2) {
|
|
16567
|
+
this.port?.setToolShortcuts?.(map2);
|
|
16557
16568
|
}
|
|
16558
16569
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
16559
16570
|
add(type, init = {}) {
|
|
@@ -17117,6 +17128,23 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
17117
17128
|
clear();
|
|
17118
17129
|
};
|
|
17119
17130
|
}
|
|
17131
|
+
function chromeHint(text, opts) {
|
|
17132
|
+
const px = opts.size ?? 16;
|
|
17133
|
+
const el = document.createElement("span");
|
|
17134
|
+
el.className = "vela-hint";
|
|
17135
|
+
el.setAttribute("role", "img");
|
|
17136
|
+
el.setAttribute("aria-label", text);
|
|
17137
|
+
el.innerHTML = iconAt("info", px);
|
|
17138
|
+
el.style.cssText = `flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:${px}px;height:${px}px;border-radius:50%;cursor:help;user-select:none;background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;`;
|
|
17139
|
+
const hover = (on) => {
|
|
17140
|
+
el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
|
|
17141
|
+
el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
|
|
17142
|
+
};
|
|
17143
|
+
el.addEventListener("pointerenter", () => hover(true));
|
|
17144
|
+
el.addEventListener("pointerleave", () => hover(false));
|
|
17145
|
+
const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
|
|
17146
|
+
return { el, dispose };
|
|
17147
|
+
}
|
|
17120
17148
|
|
|
17121
17149
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17122
17150
|
var TOOLBAR_WIDTH = 44;
|
|
@@ -17224,8 +17252,8 @@ var DrawingToolbar = class {
|
|
|
17224
17252
|
}
|
|
17225
17253
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
17226
17254
|
* right edge of the flyout rows, beside the favorite star. */
|
|
17227
|
-
setShortcuts(
|
|
17228
|
-
this.shortcuts = new Map(Object.entries(
|
|
17255
|
+
setShortcuts(map2) {
|
|
17256
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
17229
17257
|
}
|
|
17230
17258
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
17231
17259
|
paintStar(el, type) {
|
|
@@ -18122,6 +18150,24 @@ function localStorageAdapter(storageKey) {
|
|
|
18122
18150
|
};
|
|
18123
18151
|
}
|
|
18124
18152
|
|
|
18153
|
+
// src/core/options.ts
|
|
18154
|
+
var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
|
|
18155
|
+
var LIVE_BAR_EASE_DEFAULT_MS = 90;
|
|
18156
|
+
var LIVE_BAR_EASE_MAX_MS = 1e3;
|
|
18157
|
+
function resolveLiveBarEaseMs(value) {
|
|
18158
|
+
if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
|
|
18159
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
18160
|
+
return Math.min(value, LIVE_BAR_EASE_MAX_MS);
|
|
18161
|
+
}
|
|
18162
|
+
function resolveAnimations(animations) {
|
|
18163
|
+
if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
|
|
18164
|
+
return {
|
|
18165
|
+
animZoom: animations?.zoom ?? true,
|
|
18166
|
+
animPan: animations?.pan ?? true,
|
|
18167
|
+
animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
|
|
18168
|
+
};
|
|
18169
|
+
}
|
|
18170
|
+
|
|
18125
18171
|
// src/core/model/series.ts
|
|
18126
18172
|
function isLineLikeSeries(spec) {
|
|
18127
18173
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
@@ -18186,7 +18232,7 @@ var LiveSession = class {
|
|
|
18186
18232
|
|
|
18187
18233
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
18188
18234
|
var IndicatorHandleImpl = class {
|
|
18189
|
-
constructor(id, title, controller, source) {
|
|
18235
|
+
constructor(id, title, controller, source, nativeType) {
|
|
18190
18236
|
this.controller = controller;
|
|
18191
18237
|
this.schema = [];
|
|
18192
18238
|
this.propsSchema = [];
|
|
@@ -18195,6 +18241,7 @@ var IndicatorHandleImpl = class {
|
|
|
18195
18241
|
this.id = id;
|
|
18196
18242
|
this.title = title;
|
|
18197
18243
|
if (source !== void 0) this.source = source;
|
|
18244
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
18198
18245
|
}
|
|
18199
18246
|
get inputs() {
|
|
18200
18247
|
return this.schema;
|
|
@@ -18285,24 +18332,24 @@ function summarizeModel(model) {
|
|
|
18285
18332
|
}
|
|
18286
18333
|
function inspectModels(models) {
|
|
18287
18334
|
const indicators = models.map(summarizeModel);
|
|
18288
|
-
const
|
|
18335
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
18289
18336
|
return {
|
|
18290
18337
|
indicators,
|
|
18291
18338
|
totals: {
|
|
18292
18339
|
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:
|
|
18340
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
18341
|
+
fills: sum2((s) => s.fills),
|
|
18342
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
18343
|
+
priceLines: sum2((s) => s.priceLines),
|
|
18344
|
+
lines: sum2((s) => s.lines),
|
|
18345
|
+
boxes: sum2((s) => s.boxes),
|
|
18346
|
+
labels: sum2((s) => s.labels),
|
|
18347
|
+
polylines: sum2((s) => s.polylines),
|
|
18348
|
+
linefills: sum2((s) => s.linefills),
|
|
18349
|
+
tables: sum2((s) => s.tables),
|
|
18350
|
+
barColors: sum2((s) => s.barColors),
|
|
18351
|
+
trades: sum2((s) => s.trades),
|
|
18352
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
18306
18353
|
}
|
|
18307
18354
|
};
|
|
18308
18355
|
}
|
|
@@ -19371,16 +19418,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19371
19418
|
/**
|
|
19372
19419
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
19373
19420
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
19374
|
-
* INSTANCE per type
|
|
19375
|
-
*
|
|
19421
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
19422
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
19423
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
19424
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
19376
19425
|
*/
|
|
19377
19426
|
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
19427
|
const descriptor = getNativeIndicator(type);
|
|
19428
|
+
if (!descriptor?.multiInstance) {
|
|
19429
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19430
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
19431
|
+
}
|
|
19381
19432
|
const id = this.registry.nextId("native");
|
|
19382
19433
|
const title = descriptor?.title ?? type;
|
|
19383
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
19434
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
19384
19435
|
this.handles.set(id, handle);
|
|
19385
19436
|
if (!descriptor) {
|
|
19386
19437
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -19392,16 +19443,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19392
19443
|
void this.startNativeIndicator(id, handle);
|
|
19393
19444
|
return handle;
|
|
19394
19445
|
}
|
|
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). */
|
|
19446
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
19447
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
19402
19448
|
presentNativeIndicators() {
|
|
19403
19449
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
19404
19450
|
}
|
|
19451
|
+
/**
|
|
19452
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
19453
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
19454
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
19455
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
19456
|
+
* probe the provider for a required capability.
|
|
19457
|
+
*/
|
|
19405
19458
|
async availableNativeIndicators() {
|
|
19406
19459
|
const symbol = this.qualifiedSymbol();
|
|
19407
19460
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -19411,7 +19464,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19411
19464
|
title: d.title,
|
|
19412
19465
|
supported: await this.isNativeSupported(d, symbol),
|
|
19413
19466
|
present: present.has(d.type),
|
|
19414
|
-
beta: d.beta
|
|
19467
|
+
beta: d.beta,
|
|
19468
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
19415
19469
|
}))
|
|
19416
19470
|
);
|
|
19417
19471
|
}
|
|
@@ -20551,8 +20605,8 @@ var DrawingsControl = class {
|
|
|
20551
20605
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
20552
20606
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
20553
20607
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
20554
|
-
setToolShortcuts(
|
|
20555
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
20608
|
+
setToolShortcuts(map2) {
|
|
20609
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
20556
20610
|
return this;
|
|
20557
20611
|
}
|
|
20558
20612
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -21079,14 +21133,11 @@ var IndicatorInputsDialog = class {
|
|
|
21079
21133
|
buildTextField(id, current, emit) {
|
|
21080
21134
|
return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
|
|
21081
21135
|
}
|
|
21082
|
-
/**
|
|
21136
|
+
/** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
|
|
21083
21137
|
infoButton(tooltip2) {
|
|
21084
|
-
const
|
|
21085
|
-
|
|
21086
|
-
|
|
21087
|
-
b.className = "vela-ind-hint";
|
|
21088
|
-
b.style.cssText = "flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-weight:600;font-size:11px;font-family:inherit;line-height:1;cursor:help;user-select:none;";
|
|
21089
|
-
return b;
|
|
21138
|
+
const { el, dispose } = chromeHint(tooltip2, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
|
|
21139
|
+
this.dialogTips.push(dispose);
|
|
21140
|
+
return el;
|
|
21090
21141
|
}
|
|
21091
21142
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
21092
21143
|
select(id, options, current, emit) {
|
|
@@ -21523,8 +21574,6 @@ function ensureDialogStyles() {
|
|
|
21523
21574
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
21524
21575
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
21525
21576
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
21526
|
-
.vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
21527
|
-
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21528
21577
|
.vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
|
|
21529
21578
|
.vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
|
|
21530
21579
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
@@ -22033,8 +22082,8 @@ var InputsUI = class {
|
|
|
22033
22082
|
lg.style.top = `${top}px`;
|
|
22034
22083
|
}
|
|
22035
22084
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22036
|
-
setCollapsedPanes(
|
|
22037
|
-
this.paneCollapse =
|
|
22085
|
+
setCollapsedPanes(map2) {
|
|
22086
|
+
this.paneCollapse = map2;
|
|
22038
22087
|
}
|
|
22039
22088
|
/**
|
|
22040
22089
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -24043,11 +24092,11 @@ function mergeBarColors(indicators) {
|
|
|
24043
24092
|
}
|
|
24044
24093
|
}
|
|
24045
24094
|
if (!any) return EMPTY_BARCOLORS;
|
|
24046
|
-
const
|
|
24095
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24047
24096
|
for (const m of indicators.values()) {
|
|
24048
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24097
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24049
24098
|
}
|
|
24050
|
-
return
|
|
24099
|
+
return map2;
|
|
24051
24100
|
}
|
|
24052
24101
|
|
|
24053
24102
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24344,9 +24393,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
24344
24393
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24345
24394
|
return rightOffset + deltaX / barSpacing;
|
|
24346
24395
|
}
|
|
24347
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24396
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
24348
24397
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24349
|
-
if (
|
|
24398
|
+
if (shift2) return deltaY;
|
|
24350
24399
|
return null;
|
|
24351
24400
|
}
|
|
24352
24401
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -25968,11 +26017,11 @@ function mergeBarColors2(indicators) {
|
|
|
25968
26017
|
}
|
|
25969
26018
|
}
|
|
25970
26019
|
if (!any) return EMPTY_BARCOLORS2;
|
|
25971
|
-
const
|
|
26020
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
25972
26021
|
for (const m of indicators.values()) {
|
|
25973
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26022
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
25974
26023
|
}
|
|
25975
|
-
return
|
|
26024
|
+
return map2;
|
|
25976
26025
|
}
|
|
25977
26026
|
|
|
25978
26027
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27570,6 +27619,8 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
27570
27619
|
"symbol.style.baseline.fill-bottom",
|
|
27571
27620
|
"symbol.style.baseline.base-level",
|
|
27572
27621
|
"symbol.style.baseline.width",
|
|
27622
|
+
"symbol.animation",
|
|
27623
|
+
"symbol.animation.price-changes",
|
|
27573
27624
|
"symbol.timezone",
|
|
27574
27625
|
"scales",
|
|
27575
27626
|
"scales.price-scale",
|
|
@@ -27611,9 +27662,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
27611
27662
|
};
|
|
27612
27663
|
if (section.rows) addRows(section.rows);
|
|
27613
27664
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27614
|
-
for (const
|
|
27615
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27616
|
-
addRows(
|
|
27665
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27666
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27667
|
+
addRows(sub2.rows);
|
|
27617
27668
|
}
|
|
27618
27669
|
}
|
|
27619
27670
|
for (const hs of hostSections) {
|
|
@@ -27712,8 +27763,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
27712
27763
|
.vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
|
|
27713
27764
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
27714
27765
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
27715
|
-
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
|
|
27716
|
-
`;
|
|
27766
|
+
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
|
|
27717
27767
|
if (!existing) document.head.appendChild(st);
|
|
27718
27768
|
}
|
|
27719
27769
|
var SettingsDialog = class {
|
|
@@ -27734,6 +27784,8 @@ var SettingsDialog = class {
|
|
|
27734
27784
|
this.themeControl = null;
|
|
27735
27785
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
27736
27786
|
this.tabs = [];
|
|
27787
|
+
this.hintTips = [];
|
|
27788
|
+
// ⓘ tooltip disposers of the open build (see hint)
|
|
27737
27789
|
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
27738
27790
|
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
27739
27791
|
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
@@ -27922,6 +27974,13 @@ var SettingsDialog = class {
|
|
|
27922
27974
|
body.append(g);
|
|
27923
27975
|
}
|
|
27924
27976
|
showActive(config.series.style);
|
|
27977
|
+
body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
|
|
27978
|
+
body.append(sid(this.boolRow(
|
|
27979
|
+
"Animate price changes",
|
|
27980
|
+
config.priceScale.animateLastPrice,
|
|
27981
|
+
(v) => this.emit({ priceScale: { animateLastPrice: v } }),
|
|
27982
|
+
this.hint("Glide the live bar to each new price instead of snapping.")
|
|
27983
|
+
), "symbol.animation.price-changes"));
|
|
27925
27984
|
body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
|
|
27926
27985
|
body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
|
|
27927
27986
|
const renderHostSections = (placement) => {
|
|
@@ -28129,6 +28188,8 @@ var SettingsDialog = class {
|
|
|
28129
28188
|
this.ui = null;
|
|
28130
28189
|
this.root = null;
|
|
28131
28190
|
this.tabs = [];
|
|
28191
|
+
for (const dispose of this.hintTips) dispose();
|
|
28192
|
+
this.hintTips = [];
|
|
28132
28193
|
ui?.destroy();
|
|
28133
28194
|
}
|
|
28134
28195
|
destroy() {
|
|
@@ -28172,14 +28233,14 @@ var SettingsDialog = class {
|
|
|
28172
28233
|
};
|
|
28173
28234
|
if (section.rows) seed(section.rows);
|
|
28174
28235
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28175
|
-
for (const
|
|
28236
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28176
28237
|
for (const inst of section.instances ?? []) {
|
|
28177
28238
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28178
28239
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28179
28240
|
}
|
|
28180
|
-
for (const
|
|
28181
|
-
if (!
|
|
28182
|
-
bag[
|
|
28241
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28242
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28243
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28183
28244
|
}
|
|
28184
28245
|
const refreshers = [];
|
|
28185
28246
|
const put = (key, v) => {
|
|
@@ -28196,16 +28257,16 @@ var SettingsDialog = class {
|
|
|
28196
28257
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28197
28258
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28198
28259
|
}
|
|
28199
|
-
for (const
|
|
28200
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28201
|
-
const rows = filterHiddenRows(
|
|
28260
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28261
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28262
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28202
28263
|
if (rows.length === 0) continue;
|
|
28203
|
-
const subMarker = this.section(
|
|
28264
|
+
const subMarker = this.section(sub2.title);
|
|
28204
28265
|
subMarker.dataset.sdStyle = typeId;
|
|
28205
28266
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28206
28267
|
subMarker.dataset.sdSub = "1";
|
|
28207
28268
|
body.append(subMarker);
|
|
28208
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28269
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28209
28270
|
}
|
|
28210
28271
|
for (const r of refreshers) r();
|
|
28211
28272
|
}
|
|
@@ -28542,23 +28603,31 @@ var SettingsDialog = class {
|
|
|
28542
28603
|
}).el
|
|
28543
28604
|
});
|
|
28544
28605
|
}
|
|
28545
|
-
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28546
|
-
|
|
28547
|
-
|
|
28606
|
+
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28607
|
+
* `info` (see {@link hint}) rides after the label. */
|
|
28608
|
+
boolRow(label, value, onChange, info) {
|
|
28609
|
+
return this.toggleRow(label, value, onChange, [], void 0, info);
|
|
28548
28610
|
}
|
|
28549
28611
|
/** An enable row: checkbox + label in the label column, dependent controls in the
|
|
28550
28612
|
* shared control column; the control group dims and ignores input while the toggle
|
|
28551
28613
|
* is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
|
|
28552
|
-
toggleRow(label, value, onToggle, controls, get) {
|
|
28614
|
+
toggleRow(label, value, onToggle, controls, get, info) {
|
|
28553
28615
|
return fieldRow({
|
|
28554
28616
|
label,
|
|
28555
28617
|
labelSize: "sm",
|
|
28556
28618
|
bool: controls.length === 0,
|
|
28557
28619
|
toggle: { checked: value, onChange: onToggle, get },
|
|
28558
28620
|
control: controls,
|
|
28621
|
+
info,
|
|
28559
28622
|
className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
|
|
28560
28623
|
});
|
|
28561
28624
|
}
|
|
28625
|
+
/** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
|
|
28626
|
+
hint(text) {
|
|
28627
|
+
const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
|
|
28628
|
+
this.hintTips.push(dispose);
|
|
28629
|
+
return el;
|
|
28630
|
+
}
|
|
28562
28631
|
numberRow(label, value, min, max, step, onChange) {
|
|
28563
28632
|
return fieldRow({
|
|
28564
28633
|
label,
|
|
@@ -30300,11 +30369,11 @@ var DrawingInteraction = class {
|
|
|
30300
30369
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30301
30370
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30302
30371
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30303
|
-
down(x, y, mode = "off",
|
|
30372
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
30304
30373
|
const proj = this.deps.projector();
|
|
30305
30374
|
if (this.state.kind === "placing") {
|
|
30306
30375
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30307
|
-
const pivot =
|
|
30376
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30308
30377
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30309
30378
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30310
30379
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30323,7 +30392,7 @@ var DrawingInteraction = class {
|
|
|
30323
30392
|
}
|
|
30324
30393
|
const hit = this.hitAt(x, y);
|
|
30325
30394
|
if (hit) {
|
|
30326
|
-
if (
|
|
30395
|
+
if (shift2) {
|
|
30327
30396
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30328
30397
|
return;
|
|
30329
30398
|
}
|
|
@@ -30333,7 +30402,7 @@ var DrawingInteraction = class {
|
|
|
30333
30402
|
}
|
|
30334
30403
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30335
30404
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30336
|
-
move(x, y, mode = "off",
|
|
30405
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
30337
30406
|
if (this.state.kind === "idle") {
|
|
30338
30407
|
const tool = this.deps.activeTool();
|
|
30339
30408
|
const had = this.snapAt != null;
|
|
@@ -30347,7 +30416,7 @@ var DrawingInteraction = class {
|
|
|
30347
30416
|
this.captureFreehand(x, y);
|
|
30348
30417
|
return;
|
|
30349
30418
|
}
|
|
30350
|
-
const pivot =
|
|
30419
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30351
30420
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30352
30421
|
this.deps.changed();
|
|
30353
30422
|
return;
|
|
@@ -30360,7 +30429,7 @@ var DrawingInteraction = class {
|
|
|
30360
30429
|
if (this.state.handle >= 0) {
|
|
30361
30430
|
const a = d.anchors[this.state.handle];
|
|
30362
30431
|
if (a) {
|
|
30363
|
-
const pivot =
|
|
30432
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30364
30433
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30365
30434
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30366
30435
|
}
|
|
@@ -31058,21 +31127,21 @@ var DrawingSettingsPopup = class {
|
|
|
31058
31127
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31059
31128
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31060
31129
|
}
|
|
31061
|
-
const
|
|
31062
|
-
if (paths.has("vwap.midColor") &&
|
|
31130
|
+
const vwap2 = drawing.vwap;
|
|
31131
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31063
31132
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31064
31133
|
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,
|
|
31134
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31135
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31067
31136
|
bar.appendChild(
|
|
31068
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31137
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31069
31138
|
label: (v) => `${v}\u03C3`,
|
|
31070
31139
|
labelInTrigger: true
|
|
31071
31140
|
})
|
|
31072
31141
|
);
|
|
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,
|
|
31142
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31143
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31144
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31076
31145
|
}
|
|
31077
31146
|
if (paths.has("maxCurvature")) {
|
|
31078
31147
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32089,8 +32158,8 @@ var UserDrawingController = class {
|
|
|
32089
32158
|
this.toolbar.setFavorites(types);
|
|
32090
32159
|
}
|
|
32091
32160
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
32092
|
-
setToolShortcuts(
|
|
32093
|
-
this.toolbar.setShortcuts(
|
|
32161
|
+
setToolShortcuts(map2) {
|
|
32162
|
+
this.toolbar.setShortcuts(map2);
|
|
32094
32163
|
}
|
|
32095
32164
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
32096
32165
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -32208,7 +32277,7 @@ var UserDrawingController = class {
|
|
|
32208
32277
|
this.render();
|
|
32209
32278
|
}
|
|
32210
32279
|
}
|
|
32211
|
-
pointerDown(x, y, snap = "off",
|
|
32280
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
32212
32281
|
if (this.eraserMode) {
|
|
32213
32282
|
this.erasing = true;
|
|
32214
32283
|
this.deleteAt(x, y);
|
|
@@ -32228,9 +32297,9 @@ var UserDrawingController = class {
|
|
|
32228
32297
|
return;
|
|
32229
32298
|
}
|
|
32230
32299
|
}
|
|
32231
|
-
this.interaction.down(x, y, snap,
|
|
32300
|
+
this.interaction.down(x, y, snap, shift2);
|
|
32232
32301
|
}
|
|
32233
|
-
pointerMove(x, y, snap = "off",
|
|
32302
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
32234
32303
|
if (this.eraserMode) {
|
|
32235
32304
|
if (this.erasing) this.deleteAt(x, y);
|
|
32236
32305
|
return;
|
|
@@ -32241,7 +32310,7 @@ var UserDrawingController = class {
|
|
|
32241
32310
|
this.render();
|
|
32242
32311
|
return;
|
|
32243
32312
|
}
|
|
32244
|
-
this.interaction.move(x, y, snap,
|
|
32313
|
+
this.interaction.move(x, y, snap, shift2);
|
|
32245
32314
|
this.updateHover(x, y);
|
|
32246
32315
|
}
|
|
32247
32316
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -32918,14 +32987,14 @@ function layoutTable(ctx, t, args) {
|
|
|
32918
32987
|
}
|
|
32919
32988
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
32920
32989
|
if (box.cs > 1) {
|
|
32921
|
-
let
|
|
32922
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
32923
|
-
if (w2 >
|
|
32990
|
+
let sum2 = 0;
|
|
32991
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
32992
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
32924
32993
|
}
|
|
32925
32994
|
if (box.rs > 1) {
|
|
32926
|
-
let
|
|
32927
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
32928
|
-
if (h2 >
|
|
32995
|
+
let sum2 = 0;
|
|
32996
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
32997
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
32929
32998
|
}
|
|
32930
32999
|
}
|
|
32931
33000
|
let w = 0;
|
|
@@ -33512,24 +33581,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
33512
33581
|
drawSvgShapes(el, alpha);
|
|
33513
33582
|
continue;
|
|
33514
33583
|
}
|
|
33515
|
-
const
|
|
33584
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
33516
33585
|
let path = null;
|
|
33517
33586
|
if (tag === "path") {
|
|
33518
33587
|
const d = el.getAttribute("d");
|
|
33519
33588
|
if (d) path = new Path2D(d);
|
|
33520
33589
|
} else if (tag === "circle") {
|
|
33521
33590
|
path = new Path2D();
|
|
33522
|
-
path.arc(
|
|
33591
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
33523
33592
|
} else if (tag === "ellipse") {
|
|
33524
33593
|
path = new Path2D();
|
|
33525
|
-
path.ellipse(
|
|
33594
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
33526
33595
|
} else if (tag === "rect") {
|
|
33527
33596
|
path = new Path2D();
|
|
33528
|
-
path.rect(
|
|
33597
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
33529
33598
|
} else if (tag === "line") {
|
|
33530
33599
|
path = new Path2D();
|
|
33531
|
-
path.moveTo(
|
|
33532
|
-
path.lineTo(
|
|
33600
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
33601
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
33533
33602
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
33534
33603
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
33535
33604
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -33790,7 +33859,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
|
|
|
33790
33859
|
var ZOOM_TAU_MS = 70;
|
|
33791
33860
|
var SCALE_TAU_MS = 80;
|
|
33792
33861
|
var FLING_TAU_MS = 110;
|
|
33793
|
-
var LIVE_BAR_TAU_MS = 90;
|
|
33794
33862
|
var SCROLL_TO_TAU_MS = 130;
|
|
33795
33863
|
var FLING_STOP_PX = 0.02;
|
|
33796
33864
|
var PRICE_SCALE_K = 4e-3;
|
|
@@ -33861,9 +33929,13 @@ var NativeRenderer = class {
|
|
|
33861
33929
|
/** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
|
|
33862
33930
|
this.historyChordsEnabled = true;
|
|
33863
33931
|
this.liveRegion = null;
|
|
33864
|
-
// ── animation state (eased zoom + inertial pan) ──
|
|
33932
|
+
// ── animation state (eased zoom + inertial pan + live-bar glide) ──
|
|
33865
33933
|
this.animZoom = true;
|
|
33866
33934
|
this.animPan = true;
|
|
33935
|
+
this.animLiveBarMs = 0;
|
|
33936
|
+
// forming-bar OHLC glide time-constant; 0 = each tick snaps
|
|
33937
|
+
this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
|
|
33938
|
+
// the duration the settings-dialog on/off toggle restores (last non-zero value configured)
|
|
33867
33939
|
// Brand default candles.
|
|
33868
33940
|
this.candleUp = BULLISH;
|
|
33869
33941
|
this.candleDown = BEARISH;
|
|
@@ -33988,7 +34060,7 @@ var NativeRenderer = class {
|
|
|
33988
34060
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
33989
34061
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
33990
34062
|
this.name = "native";
|
|
33991
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
34063
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
33992
34064
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
33993
34065
|
* so moving onto the button doesn't count as leaving). */
|
|
33994
34066
|
this.onScrollProximityMove = (e) => {
|
|
@@ -34022,6 +34094,7 @@ var NativeRenderer = class {
|
|
|
34022
34094
|
this.backendMode = opts.nativeBackend;
|
|
34023
34095
|
this.animZoom = opts.animZoom;
|
|
34024
34096
|
this.animPan = opts.animPan;
|
|
34097
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
|
|
34025
34098
|
this.glowAmount = opts.glow;
|
|
34026
34099
|
this.candleUp = opts.upColor;
|
|
34027
34100
|
this.candleDown = opts.downColor;
|
|
@@ -34071,6 +34144,10 @@ var NativeRenderer = class {
|
|
|
34071
34144
|
case "animPan":
|
|
34072
34145
|
this.animPan = Boolean(value);
|
|
34073
34146
|
return;
|
|
34147
|
+
case "animLiveBar":
|
|
34148
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(value));
|
|
34149
|
+
return;
|
|
34150
|
+
// affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
|
|
34074
34151
|
case "intro": {
|
|
34075
34152
|
const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
|
|
34076
34153
|
this.introStyle = s;
|
|
@@ -34207,6 +34284,8 @@ var NativeRenderer = class {
|
|
|
34207
34284
|
return this.animZoom;
|
|
34208
34285
|
case "animPan":
|
|
34209
34286
|
return this.animPan;
|
|
34287
|
+
case "animLiveBar":
|
|
34288
|
+
return this.animLiveBarMs;
|
|
34210
34289
|
case "intro":
|
|
34211
34290
|
return this.introStyle;
|
|
34212
34291
|
case "zoomAnchor":
|
|
@@ -34375,7 +34454,8 @@ var NativeRenderer = class {
|
|
|
34375
34454
|
labelsVisible: this.scene.showAxisLabels,
|
|
34376
34455
|
currentPriceLine: this.scene.showPriceLine,
|
|
34377
34456
|
priceLabel: this.scene.showPriceLabel,
|
|
34378
|
-
countdown: this.scene.showCountdown
|
|
34457
|
+
countdown: this.scene.showCountdown,
|
|
34458
|
+
animateLastPrice: this.animLiveBarMs > 0
|
|
34379
34459
|
},
|
|
34380
34460
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
34381
34461
|
trades: {
|
|
@@ -34482,6 +34562,7 @@ var NativeRenderer = class {
|
|
|
34482
34562
|
this.scene.showPriceLabel = next.priceScale.priceLabel;
|
|
34483
34563
|
this.scene.showCountdown = next.priceScale.countdown;
|
|
34484
34564
|
this.syncCountdownTimer();
|
|
34565
|
+
this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
|
|
34485
34566
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
34486
34567
|
this.scene.tradeMarkers = {
|
|
34487
34568
|
visible: next.trades.visible,
|
|
@@ -34946,8 +35027,8 @@ var NativeRenderer = class {
|
|
|
34946
35027
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
34947
35028
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
34948
35029
|
drawingsSnapMode: () => this.snapMode,
|
|
34949
|
-
drawingsPointerDown: (x, y, snap,
|
|
34950
|
-
drawingsPointerMove: (x, y, snap,
|
|
35030
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
35031
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
34951
35032
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
34952
35033
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
34953
35034
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -35264,18 +35345,18 @@ var NativeRenderer = class {
|
|
|
35264
35345
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
35265
35346
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
35266
35347
|
const newHeadTime = this.bars[0].time;
|
|
35267
|
-
let
|
|
35348
|
+
let shift2 = 0;
|
|
35268
35349
|
if (newHeadTime !== prevHeadTime) {
|
|
35269
35350
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
35270
|
-
if (prepended > 0)
|
|
35351
|
+
if (prepended > 0) shift2 = prepended;
|
|
35271
35352
|
else {
|
|
35272
35353
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
35273
|
-
if (trimmed > 0)
|
|
35354
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
35274
35355
|
}
|
|
35275
35356
|
}
|
|
35276
|
-
if (
|
|
35277
|
-
this.zoomAnchorLogical +=
|
|
35278
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
35357
|
+
if (shift2 !== 0) {
|
|
35358
|
+
this.zoomAnchorLogical += shift2;
|
|
35359
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
35279
35360
|
}
|
|
35280
35361
|
}
|
|
35281
35362
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -35298,8 +35379,11 @@ var NativeRenderer = class {
|
|
|
35298
35379
|
const last = this.bars[n - 1];
|
|
35299
35380
|
if (last && bar.time === last.time) {
|
|
35300
35381
|
this.bars[n - 1] = bar;
|
|
35301
|
-
if (this.liveEaseTime !== bar.time)
|
|
35302
|
-
|
|
35382
|
+
if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
|
|
35383
|
+
this.syncLiveEase(bar);
|
|
35384
|
+
} else {
|
|
35385
|
+
this.animator.start();
|
|
35386
|
+
}
|
|
35303
35387
|
} else if (!last || bar.time > last.time) {
|
|
35304
35388
|
this.bars.push(bar);
|
|
35305
35389
|
this.syncLiveEase(bar);
|
|
@@ -35311,6 +35395,12 @@ var NativeRenderer = class {
|
|
|
35311
35395
|
this.scene.bars = this.bars;
|
|
35312
35396
|
this.scheduler.invalidate(4 /* Full */);
|
|
35313
35397
|
}
|
|
35398
|
+
/** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
|
|
35399
|
+
* what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
|
|
35400
|
+
setLiveBarEase(ms) {
|
|
35401
|
+
this.animLiveBarMs = ms;
|
|
35402
|
+
if (ms > 0) this.animLiveBarOnMs = ms;
|
|
35403
|
+
}
|
|
35314
35404
|
/** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
|
|
35315
35405
|
syncLiveEase(bar) {
|
|
35316
35406
|
this.liveEaseTime = bar.time;
|
|
@@ -35323,9 +35413,10 @@ var NativeRenderer = class {
|
|
|
35323
35413
|
const target = this.bars[this.bars.length - 1];
|
|
35324
35414
|
if (!target || this.liveEaseTime !== target.time) return false;
|
|
35325
35415
|
const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
|
|
35326
|
-
const
|
|
35327
|
-
const
|
|
35328
|
-
const
|
|
35416
|
+
const tau = this.animLiveBarMs;
|
|
35417
|
+
const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
|
|
35418
|
+
const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
|
|
35419
|
+
const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
|
|
35329
35420
|
const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
|
|
35330
35421
|
this.liveEaseHigh = active ? nh : target.high;
|
|
35331
35422
|
this.liveEaseLow = active ? nl : target.low;
|
|
@@ -36881,15 +36972,15 @@ var NativeRenderer = class {
|
|
|
36881
36972
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
36882
36973
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
36883
36974
|
collapsedMasterMap() {
|
|
36884
|
-
const
|
|
36975
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
36885
36976
|
for (const pane of this.scene.panes.values()) {
|
|
36886
36977
|
if (!pane.collapsed) continue;
|
|
36887
36978
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
36888
36979
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
36889
36980
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
36890
|
-
|
|
36981
|
+
map2.set(pane.id, master?.id ?? null);
|
|
36891
36982
|
}
|
|
36892
|
-
return
|
|
36983
|
+
return map2;
|
|
36893
36984
|
}
|
|
36894
36985
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
36895
36986
|
setToolbarGutter(px) {
|
|
@@ -37256,29 +37347,2335 @@ function registerVpvr() {
|
|
|
37256
37347
|
registerNativeIndicator(vpvrDescriptor);
|
|
37257
37348
|
}
|
|
37258
37349
|
|
|
37350
|
+
// src/core/model/identity.ts
|
|
37351
|
+
function stableSeriesId(parts) {
|
|
37352
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
37353
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
37354
|
+
}
|
|
37355
|
+
|
|
37356
|
+
// src/core/native-indicators/classics/define.ts
|
|
37357
|
+
function num3(inputs, key, fallback) {
|
|
37358
|
+
const v = inputs[key];
|
|
37359
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
37360
|
+
}
|
|
37361
|
+
function str3(inputs, key, fallback) {
|
|
37362
|
+
const v = inputs[key];
|
|
37363
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
37364
|
+
}
|
|
37365
|
+
function toPoints(bars, values, colors) {
|
|
37366
|
+
const pts = new Array(bars.length);
|
|
37367
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37368
|
+
const v = values[i];
|
|
37369
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
37370
|
+
const c = colors?.[i];
|
|
37371
|
+
if (c != null) p.color = c;
|
|
37372
|
+
pts[i] = p;
|
|
37373
|
+
}
|
|
37374
|
+
return pts;
|
|
37375
|
+
}
|
|
37376
|
+
var ClassicIndicator = class {
|
|
37377
|
+
constructor(spec) {
|
|
37378
|
+
this.spec = spec;
|
|
37379
|
+
this.inputs = {};
|
|
37380
|
+
}
|
|
37381
|
+
start(ctx, inputs) {
|
|
37382
|
+
this.ctx = ctx;
|
|
37383
|
+
this.inputs = inputs;
|
|
37384
|
+
this.recompute();
|
|
37385
|
+
}
|
|
37386
|
+
onBars() {
|
|
37387
|
+
this.recompute();
|
|
37388
|
+
}
|
|
37389
|
+
onViewport() {
|
|
37390
|
+
}
|
|
37391
|
+
setInputs(inputs) {
|
|
37392
|
+
this.inputs = inputs;
|
|
37393
|
+
this.recompute();
|
|
37394
|
+
}
|
|
37395
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
37396
|
+
suspend() {
|
|
37397
|
+
}
|
|
37398
|
+
resume() {
|
|
37399
|
+
this.recompute();
|
|
37400
|
+
}
|
|
37401
|
+
stop() {
|
|
37402
|
+
}
|
|
37403
|
+
recompute() {
|
|
37404
|
+
const bars = this.ctx.bars();
|
|
37405
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
37406
|
+
const instanceId = this.spec.type;
|
|
37407
|
+
const series = [];
|
|
37408
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
37409
|
+
out.plots.forEach((plot, ordinal) => {
|
|
37410
|
+
const kind = plot.kind ?? "line";
|
|
37411
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
37412
|
+
idsByKey.set(plot.key, id);
|
|
37413
|
+
series.push({
|
|
37414
|
+
id,
|
|
37415
|
+
title: plot.title,
|
|
37416
|
+
paneId: "",
|
|
37417
|
+
// stamped by the orchestrator
|
|
37418
|
+
kind,
|
|
37419
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
37420
|
+
style: {
|
|
37421
|
+
color: plot.color,
|
|
37422
|
+
width: plot.width ?? 1,
|
|
37423
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
37424
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
37425
|
+
},
|
|
37426
|
+
...plot.overlay ? { overlay: true } : {}
|
|
37427
|
+
});
|
|
37428
|
+
});
|
|
37429
|
+
if (out.markers && out.markers.length > 0) {
|
|
37430
|
+
series.push({
|
|
37431
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
37432
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
37433
|
+
paneId: "",
|
|
37434
|
+
kind: "markers",
|
|
37435
|
+
markers: out.markers
|
|
37436
|
+
});
|
|
37437
|
+
}
|
|
37438
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
37439
|
+
const from = idsByKey.get(band.from);
|
|
37440
|
+
const to = idsByKey.get(band.to);
|
|
37441
|
+
if (!from || !to) return [];
|
|
37442
|
+
return [
|
|
37443
|
+
{
|
|
37444
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
37445
|
+
paneId: "",
|
|
37446
|
+
fromSeriesId: from,
|
|
37447
|
+
toSeriesId: to,
|
|
37448
|
+
color: band.color
|
|
37449
|
+
}
|
|
37450
|
+
];
|
|
37451
|
+
});
|
|
37452
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
37453
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
37454
|
+
paneId: "",
|
|
37455
|
+
price: level.price,
|
|
37456
|
+
...level.color != null ? { color: level.color } : {},
|
|
37457
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
37458
|
+
width: 1,
|
|
37459
|
+
...level.title != null ? { title: level.title } : {}
|
|
37460
|
+
}));
|
|
37461
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
37462
|
+
const { key, ...rest } = poly;
|
|
37463
|
+
return {
|
|
37464
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
37465
|
+
paneId: "",
|
|
37466
|
+
...rest
|
|
37467
|
+
};
|
|
37468
|
+
});
|
|
37469
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
37470
|
+
this.ctx.setStatus("idle");
|
|
37471
|
+
}
|
|
37472
|
+
};
|
|
37473
|
+
function classicDescriptor(spec) {
|
|
37474
|
+
return {
|
|
37475
|
+
type: spec.type,
|
|
37476
|
+
title: spec.title,
|
|
37477
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
37478
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
37479
|
+
overlay: spec.overlay,
|
|
37480
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
37481
|
+
multiInstance: true,
|
|
37482
|
+
inputsSchema: () => spec.inputs,
|
|
37483
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
37484
|
+
create: () => new ClassicIndicator(spec)
|
|
37485
|
+
};
|
|
37486
|
+
}
|
|
37487
|
+
|
|
37488
|
+
// src/core/native-indicators/classics/math.ts
|
|
37489
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
37490
|
+
function sourceValues(bars, source) {
|
|
37491
|
+
const out = new Array(bars.length);
|
|
37492
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37493
|
+
const b = bars[i];
|
|
37494
|
+
switch (source) {
|
|
37495
|
+
case "Open":
|
|
37496
|
+
out[i] = b.open;
|
|
37497
|
+
break;
|
|
37498
|
+
case "High":
|
|
37499
|
+
out[i] = b.high;
|
|
37500
|
+
break;
|
|
37501
|
+
case "Low":
|
|
37502
|
+
out[i] = b.low;
|
|
37503
|
+
break;
|
|
37504
|
+
case "HL2":
|
|
37505
|
+
out[i] = (b.high + b.low) / 2;
|
|
37506
|
+
break;
|
|
37507
|
+
case "HLC3":
|
|
37508
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
37509
|
+
break;
|
|
37510
|
+
case "OHLC4":
|
|
37511
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
37512
|
+
break;
|
|
37513
|
+
case "HLCC4":
|
|
37514
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
37515
|
+
break;
|
|
37516
|
+
default:
|
|
37517
|
+
out[i] = b.close;
|
|
37518
|
+
}
|
|
37519
|
+
}
|
|
37520
|
+
return out;
|
|
37521
|
+
}
|
|
37522
|
+
function volumes(bars) {
|
|
37523
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
37524
|
+
}
|
|
37525
|
+
function highs(bars) {
|
|
37526
|
+
return bars.map((b) => b.high);
|
|
37527
|
+
}
|
|
37528
|
+
function lows(bars) {
|
|
37529
|
+
return bars.map((b) => b.low);
|
|
37530
|
+
}
|
|
37531
|
+
function closes(bars) {
|
|
37532
|
+
return bars.map((b) => b.close);
|
|
37533
|
+
}
|
|
37534
|
+
function opens(bars) {
|
|
37535
|
+
return bars.map((b) => b.open);
|
|
37536
|
+
}
|
|
37537
|
+
function zip(a, b, f) {
|
|
37538
|
+
const out = new Array(a.length);
|
|
37539
|
+
for (let i = 0; i < a.length; i++) {
|
|
37540
|
+
const x = a[i];
|
|
37541
|
+
const y = b[i];
|
|
37542
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
37543
|
+
}
|
|
37544
|
+
return out;
|
|
37545
|
+
}
|
|
37546
|
+
function map(a, f) {
|
|
37547
|
+
const out = new Array(a.length);
|
|
37548
|
+
for (let i = 0; i < a.length; i++) {
|
|
37549
|
+
const x = a[i];
|
|
37550
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
37551
|
+
}
|
|
37552
|
+
return out;
|
|
37553
|
+
}
|
|
37554
|
+
function sub(a, b) {
|
|
37555
|
+
return zip(a, b, (x, y) => x - y);
|
|
37556
|
+
}
|
|
37557
|
+
function windowed(v, len, fold) {
|
|
37558
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37559
|
+
if (len <= 0) return out;
|
|
37560
|
+
let run = 0;
|
|
37561
|
+
for (let i = 0; i < v.length; i++) {
|
|
37562
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
37563
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
37564
|
+
}
|
|
37565
|
+
return out;
|
|
37566
|
+
}
|
|
37567
|
+
function sum(v, len) {
|
|
37568
|
+
return windowed(v, len, (w, from, to) => {
|
|
37569
|
+
let s = 0;
|
|
37570
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37571
|
+
return s;
|
|
37572
|
+
});
|
|
37573
|
+
}
|
|
37574
|
+
function sma(v, len) {
|
|
37575
|
+
return windowed(v, len, (w, from, to) => {
|
|
37576
|
+
let s = 0;
|
|
37577
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37578
|
+
return s / len;
|
|
37579
|
+
});
|
|
37580
|
+
}
|
|
37581
|
+
function wma(v, len) {
|
|
37582
|
+
const norm = len * (len + 1) / 2;
|
|
37583
|
+
return windowed(v, len, (w, from, to) => {
|
|
37584
|
+
let s = 0;
|
|
37585
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
37586
|
+
return s / norm;
|
|
37587
|
+
});
|
|
37588
|
+
}
|
|
37589
|
+
function vwma(src, vol, len) {
|
|
37590
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
37591
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
37592
|
+
}
|
|
37593
|
+
function stdev(v, len) {
|
|
37594
|
+
return windowed(v, len, (w, from, to) => {
|
|
37595
|
+
let s = 0;
|
|
37596
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37597
|
+
const mean = s / len;
|
|
37598
|
+
let sq = 0;
|
|
37599
|
+
for (let k = from; k <= to; k++) {
|
|
37600
|
+
const d = w[k] - mean;
|
|
37601
|
+
sq += d * d;
|
|
37602
|
+
}
|
|
37603
|
+
return Math.sqrt(sq / len);
|
|
37604
|
+
});
|
|
37605
|
+
}
|
|
37606
|
+
function highest(v, len) {
|
|
37607
|
+
return windowed(v, len, (w, from, to) => {
|
|
37608
|
+
let m = -Infinity;
|
|
37609
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
37610
|
+
return m;
|
|
37611
|
+
});
|
|
37612
|
+
}
|
|
37613
|
+
function lowest(v, len) {
|
|
37614
|
+
return windowed(v, len, (w, from, to) => {
|
|
37615
|
+
let m = Infinity;
|
|
37616
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
37617
|
+
return m;
|
|
37618
|
+
});
|
|
37619
|
+
}
|
|
37620
|
+
function barsSinceHighest(v, len) {
|
|
37621
|
+
return windowed(v, len, (w, from, to) => {
|
|
37622
|
+
let m = -Infinity;
|
|
37623
|
+
let at = to;
|
|
37624
|
+
for (let k = from; k <= to; k++) {
|
|
37625
|
+
if (w[k] >= m) {
|
|
37626
|
+
m = w[k];
|
|
37627
|
+
at = k;
|
|
37628
|
+
}
|
|
37629
|
+
}
|
|
37630
|
+
return to - at;
|
|
37631
|
+
});
|
|
37632
|
+
}
|
|
37633
|
+
function barsSinceLowest(v, len) {
|
|
37634
|
+
return windowed(v, len, (w, from, to) => {
|
|
37635
|
+
let m = Infinity;
|
|
37636
|
+
let at = to;
|
|
37637
|
+
for (let k = from; k <= to; k++) {
|
|
37638
|
+
if (w[k] <= m) {
|
|
37639
|
+
m = w[k];
|
|
37640
|
+
at = k;
|
|
37641
|
+
}
|
|
37642
|
+
}
|
|
37643
|
+
return to - at;
|
|
37644
|
+
});
|
|
37645
|
+
}
|
|
37646
|
+
function recursive(v, len, alpha) {
|
|
37647
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37648
|
+
let run = 0;
|
|
37649
|
+
let prev2 = Number.NaN;
|
|
37650
|
+
for (let i = 0; i < v.length; i++) {
|
|
37651
|
+
const x = v[i];
|
|
37652
|
+
if (!Number.isFinite(x)) {
|
|
37653
|
+
run = 0;
|
|
37654
|
+
prev2 = Number.NaN;
|
|
37655
|
+
continue;
|
|
37656
|
+
}
|
|
37657
|
+
run += 1;
|
|
37658
|
+
if (Number.isFinite(prev2)) {
|
|
37659
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
37660
|
+
out[i] = prev2;
|
|
37661
|
+
} else if (run >= len) {
|
|
37662
|
+
let s = 0;
|
|
37663
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
37664
|
+
prev2 = s / len;
|
|
37665
|
+
out[i] = prev2;
|
|
37666
|
+
}
|
|
37667
|
+
}
|
|
37668
|
+
return out;
|
|
37669
|
+
}
|
|
37670
|
+
function ema(v, len) {
|
|
37671
|
+
return recursive(v, len, 2 / (len + 1));
|
|
37672
|
+
}
|
|
37673
|
+
function rma(v, len) {
|
|
37674
|
+
return recursive(v, len, 1 / len);
|
|
37675
|
+
}
|
|
37676
|
+
function change(v, n = 1) {
|
|
37677
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37678
|
+
for (let i = n; i < v.length; i++) {
|
|
37679
|
+
const a = v[i];
|
|
37680
|
+
const b = v[i - n];
|
|
37681
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
37682
|
+
}
|
|
37683
|
+
return out;
|
|
37684
|
+
}
|
|
37685
|
+
function roc(v, len) {
|
|
37686
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37687
|
+
for (let i = len; i < v.length; i++) {
|
|
37688
|
+
const a = v[i];
|
|
37689
|
+
const b = v[i - len];
|
|
37690
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
37691
|
+
}
|
|
37692
|
+
return out;
|
|
37693
|
+
}
|
|
37694
|
+
function shift(v, n) {
|
|
37695
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37696
|
+
for (let i = 0; i < v.length; i++) {
|
|
37697
|
+
const j = i - n;
|
|
37698
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
37699
|
+
}
|
|
37700
|
+
return out;
|
|
37701
|
+
}
|
|
37702
|
+
function trueRange(bars) {
|
|
37703
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
37704
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37705
|
+
const b = bars[i];
|
|
37706
|
+
if (i === 0) {
|
|
37707
|
+
out[i] = b.high - b.low;
|
|
37708
|
+
continue;
|
|
37709
|
+
}
|
|
37710
|
+
const pc = bars[i - 1].close;
|
|
37711
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
37712
|
+
}
|
|
37713
|
+
return out;
|
|
37714
|
+
}
|
|
37715
|
+
function atr(bars, len) {
|
|
37716
|
+
return rma(trueRange(bars), len);
|
|
37717
|
+
}
|
|
37718
|
+
function rsi(v, len) {
|
|
37719
|
+
const d = change(v);
|
|
37720
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
37721
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
37722
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
37723
|
+
}
|
|
37724
|
+
function stoch(v, hi, lo, len) {
|
|
37725
|
+
const hh = highest(hi, len);
|
|
37726
|
+
const ll = lowest(lo, len);
|
|
37727
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37728
|
+
for (let i = 0; i < v.length; i++) {
|
|
37729
|
+
const x = v[i];
|
|
37730
|
+
const h = hh[i];
|
|
37731
|
+
const l = ll[i];
|
|
37732
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
37733
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
37734
|
+
}
|
|
37735
|
+
return out;
|
|
37736
|
+
}
|
|
37737
|
+
function linreg(v, len) {
|
|
37738
|
+
const sx = (len - 1) * len / 2;
|
|
37739
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
37740
|
+
const denom = len * sxx - sx * sx;
|
|
37741
|
+
return windowed(v, len, (w, from, to) => {
|
|
37742
|
+
let sy = 0;
|
|
37743
|
+
let sxy = 0;
|
|
37744
|
+
for (let k = from; k <= to; k++) {
|
|
37745
|
+
const x = k - from;
|
|
37746
|
+
sy += w[k];
|
|
37747
|
+
sxy += x * w[k];
|
|
37748
|
+
}
|
|
37749
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
37750
|
+
const intercept = (sy - slope * sx) / len;
|
|
37751
|
+
return intercept + slope * (len - 1);
|
|
37752
|
+
});
|
|
37753
|
+
}
|
|
37754
|
+
function swma(v) {
|
|
37755
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37756
|
+
for (let i = 3; i < v.length; i++) {
|
|
37757
|
+
const a = v[i - 3];
|
|
37758
|
+
const b = v[i - 2];
|
|
37759
|
+
const c = v[i - 1];
|
|
37760
|
+
const d = v[i];
|
|
37761
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
37762
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
37763
|
+
}
|
|
37764
|
+
}
|
|
37765
|
+
return out;
|
|
37766
|
+
}
|
|
37767
|
+
function percentRank(v, len) {
|
|
37768
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37769
|
+
for (let i = len; i < v.length; i++) {
|
|
37770
|
+
const x = v[i];
|
|
37771
|
+
if (!Number.isFinite(x)) continue;
|
|
37772
|
+
let below = 0;
|
|
37773
|
+
let count = 0;
|
|
37774
|
+
for (let k = i - len; k < i; k++) {
|
|
37775
|
+
const y = v[k];
|
|
37776
|
+
if (!Number.isFinite(y)) {
|
|
37777
|
+
count = -1;
|
|
37778
|
+
break;
|
|
37779
|
+
}
|
|
37780
|
+
count += 1;
|
|
37781
|
+
if (y <= x) below += 1;
|
|
37782
|
+
}
|
|
37783
|
+
if (count === len) out[i] = 100 * below / len;
|
|
37784
|
+
}
|
|
37785
|
+
return out;
|
|
37786
|
+
}
|
|
37787
|
+
function cumSum(v) {
|
|
37788
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37789
|
+
let acc = 0;
|
|
37790
|
+
let seen = false;
|
|
37791
|
+
for (let i = 0; i < v.length; i++) {
|
|
37792
|
+
const x = v[i];
|
|
37793
|
+
if (Number.isFinite(x)) {
|
|
37794
|
+
acc += x;
|
|
37795
|
+
seen = true;
|
|
37796
|
+
}
|
|
37797
|
+
if (seen) out[i] = acc;
|
|
37798
|
+
}
|
|
37799
|
+
return out;
|
|
37800
|
+
}
|
|
37801
|
+
|
|
37802
|
+
// src/core/native-indicators/classics/shared.ts
|
|
37803
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
37804
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
37805
|
+
}
|
|
37806
|
+
function sourceInput(defval = "Close") {
|
|
37807
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
37808
|
+
}
|
|
37809
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
37810
|
+
return { key, title, type: "color", defval };
|
|
37811
|
+
}
|
|
37812
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
37813
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
37814
|
+
if (!m) return hex;
|
|
37815
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
37816
|
+
return `#${m[1]}${a}`;
|
|
37817
|
+
}
|
|
37818
|
+
|
|
37819
|
+
// src/core/native-indicators/classics/averages.ts
|
|
37820
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
37821
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
37822
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
37823
|
+
return {
|
|
37824
|
+
type,
|
|
37825
|
+
title,
|
|
37826
|
+
shortTitle,
|
|
37827
|
+
overlay: true,
|
|
37828
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
37829
|
+
compute: (bars, inputs) => {
|
|
37830
|
+
const len = num3(inputs, "length", 20);
|
|
37831
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
37832
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37833
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37834
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37835
|
+
}
|
|
37836
|
+
};
|
|
37837
|
+
}
|
|
37838
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
37839
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
37840
|
+
var movingAverage = {
|
|
37841
|
+
type: "moving-average",
|
|
37842
|
+
title: "Moving Average",
|
|
37843
|
+
shortTitle: "MA",
|
|
37844
|
+
overlay: true,
|
|
37845
|
+
inputs: [
|
|
37846
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
37847
|
+
lengthInput(20),
|
|
37848
|
+
sourceInput(),
|
|
37849
|
+
offsetInput,
|
|
37850
|
+
colorInput()
|
|
37851
|
+
],
|
|
37852
|
+
compute: (bars, inputs) => {
|
|
37853
|
+
const len = num3(inputs, "length", 20);
|
|
37854
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37855
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
37856
|
+
let values;
|
|
37857
|
+
switch (kind) {
|
|
37858
|
+
case "EMA":
|
|
37859
|
+
values = ema(src, len);
|
|
37860
|
+
break;
|
|
37861
|
+
case "WMA":
|
|
37862
|
+
values = wma(src, len);
|
|
37863
|
+
break;
|
|
37864
|
+
case "RMA":
|
|
37865
|
+
values = rma(src, len);
|
|
37866
|
+
break;
|
|
37867
|
+
case "VWMA":
|
|
37868
|
+
values = vwma(src, volumes(bars), len);
|
|
37869
|
+
break;
|
|
37870
|
+
default:
|
|
37871
|
+
values = sma(src, len);
|
|
37872
|
+
}
|
|
37873
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37874
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37875
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37876
|
+
}
|
|
37877
|
+
};
|
|
37878
|
+
var smoothedMa = {
|
|
37879
|
+
type: "rma",
|
|
37880
|
+
title: "Smoothed Moving Average",
|
|
37881
|
+
shortTitle: "RMA",
|
|
37882
|
+
overlay: true,
|
|
37883
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37884
|
+
compute: (bars, inputs) => ({
|
|
37885
|
+
plots: [
|
|
37886
|
+
{
|
|
37887
|
+
key: "rma",
|
|
37888
|
+
title: "RMA",
|
|
37889
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37890
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37891
|
+
width: 2
|
|
37892
|
+
}
|
|
37893
|
+
]
|
|
37894
|
+
})
|
|
37895
|
+
};
|
|
37896
|
+
var zlema = {
|
|
37897
|
+
type: "zlema",
|
|
37898
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
37899
|
+
shortTitle: "ZLEMA",
|
|
37900
|
+
overlay: true,
|
|
37901
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37902
|
+
compute: (bars, inputs) => {
|
|
37903
|
+
const len = num3(inputs, "length", 14);
|
|
37904
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37905
|
+
const lag = Math.floor((len - 1) / 2);
|
|
37906
|
+
const lagged = shift(src, lag);
|
|
37907
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
37908
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37909
|
+
}
|
|
37910
|
+
};
|
|
37911
|
+
var vidya = {
|
|
37912
|
+
type: "vidya",
|
|
37913
|
+
title: "Variable Index Dynamic Average",
|
|
37914
|
+
shortTitle: "VIDYA",
|
|
37915
|
+
overlay: true,
|
|
37916
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
37917
|
+
compute: (bars, inputs) => {
|
|
37918
|
+
const len = num3(inputs, "length", 14);
|
|
37919
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
37920
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37921
|
+
const d = change(src);
|
|
37922
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
37923
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
37924
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
37925
|
+
let u = 0;
|
|
37926
|
+
let w = 0;
|
|
37927
|
+
let ok = true;
|
|
37928
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
37929
|
+
const x = d[k];
|
|
37930
|
+
if (!Number.isFinite(x)) {
|
|
37931
|
+
ok = false;
|
|
37932
|
+
break;
|
|
37933
|
+
}
|
|
37934
|
+
if (x > 0) u += x;
|
|
37935
|
+
else w -= x;
|
|
37936
|
+
}
|
|
37937
|
+
if (ok) {
|
|
37938
|
+
su[i] = u;
|
|
37939
|
+
sd[i] = w;
|
|
37940
|
+
}
|
|
37941
|
+
}
|
|
37942
|
+
const alpha = 2 / (len + 1);
|
|
37943
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
37944
|
+
let prev2 = Number.NaN;
|
|
37945
|
+
for (let i = 0; i < src.length; i++) {
|
|
37946
|
+
const x = src[i];
|
|
37947
|
+
const u = su[i];
|
|
37948
|
+
const w = sd[i];
|
|
37949
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
37950
|
+
const total = u + w;
|
|
37951
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
37952
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
37953
|
+
out[i] = prev2;
|
|
37954
|
+
}
|
|
37955
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37956
|
+
}
|
|
37957
|
+
};
|
|
37958
|
+
var maEnvelope = {
|
|
37959
|
+
type: "ma-envelope",
|
|
37960
|
+
title: "Moving Average Envelope",
|
|
37961
|
+
shortTitle: "MA Env",
|
|
37962
|
+
overlay: true,
|
|
37963
|
+
inputs: [
|
|
37964
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
37965
|
+
lengthInput(20),
|
|
37966
|
+
sourceInput(),
|
|
37967
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
37968
|
+
colorInput()
|
|
37969
|
+
],
|
|
37970
|
+
compute: (bars, inputs) => {
|
|
37971
|
+
const len = num3(inputs, "length", 20);
|
|
37972
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37973
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
37974
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
37975
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
37976
|
+
return {
|
|
37977
|
+
plots: [
|
|
37978
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
37979
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
37980
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
37981
|
+
],
|
|
37982
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
37983
|
+
};
|
|
37984
|
+
}
|
|
37985
|
+
};
|
|
37986
|
+
var linearRegression = {
|
|
37987
|
+
type: "linear-regression",
|
|
37988
|
+
title: "Linear Regression Curve",
|
|
37989
|
+
shortTitle: "LinReg",
|
|
37990
|
+
overlay: true,
|
|
37991
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37992
|
+
compute: (bars, inputs) => ({
|
|
37993
|
+
plots: [
|
|
37994
|
+
{
|
|
37995
|
+
key: "linreg",
|
|
37996
|
+
title: "LinReg",
|
|
37997
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37998
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37999
|
+
width: 2
|
|
38000
|
+
}
|
|
38001
|
+
]
|
|
38002
|
+
})
|
|
38003
|
+
};
|
|
38004
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
38005
|
+
|
|
38006
|
+
// src/core/native-indicators/classics/bands.ts
|
|
38007
|
+
function bollinger(bars, inputs) {
|
|
38008
|
+
const len = num3(inputs, "length", 20);
|
|
38009
|
+
const mult = num3(inputs, "mult", 2);
|
|
38010
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38011
|
+
const basis = sma(src, len);
|
|
38012
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
38013
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
38014
|
+
}
|
|
38015
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
38016
|
+
var bollingerBands = {
|
|
38017
|
+
type: "bollinger-bands",
|
|
38018
|
+
title: "Bollinger Bands",
|
|
38019
|
+
shortTitle: "BB",
|
|
38020
|
+
overlay: true,
|
|
38021
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38022
|
+
compute: (bars, inputs) => {
|
|
38023
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
38024
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38025
|
+
return {
|
|
38026
|
+
plots: [
|
|
38027
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
38028
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38029
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38030
|
+
],
|
|
38031
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38032
|
+
};
|
|
38033
|
+
}
|
|
38034
|
+
};
|
|
38035
|
+
var percentB = {
|
|
38036
|
+
type: "percent-b",
|
|
38037
|
+
title: "Bollinger Bands %B",
|
|
38038
|
+
shortTitle: "%B",
|
|
38039
|
+
overlay: false,
|
|
38040
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38041
|
+
compute: (bars, inputs) => {
|
|
38042
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
38043
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38044
|
+
const values = src.map((x, i) => {
|
|
38045
|
+
const u = upper[i];
|
|
38046
|
+
const l = lower[i];
|
|
38047
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
38048
|
+
});
|
|
38049
|
+
return {
|
|
38050
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38051
|
+
levels: [
|
|
38052
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
38053
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
38054
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
38055
|
+
]
|
|
38056
|
+
};
|
|
38057
|
+
}
|
|
38058
|
+
};
|
|
38059
|
+
var bandwidth = {
|
|
38060
|
+
type: "bandwidth",
|
|
38061
|
+
title: "Bollinger Bands Width",
|
|
38062
|
+
shortTitle: "BandWidth",
|
|
38063
|
+
overlay: false,
|
|
38064
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38065
|
+
compute: (bars, inputs) => {
|
|
38066
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
38067
|
+
const values = basis.map((b, i) => {
|
|
38068
|
+
const u = upper[i];
|
|
38069
|
+
const l = lower[i];
|
|
38070
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
38071
|
+
});
|
|
38072
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38073
|
+
}
|
|
38074
|
+
};
|
|
38075
|
+
var keltner = {
|
|
38076
|
+
type: "keltner-channels",
|
|
38077
|
+
title: "Keltner Channels",
|
|
38078
|
+
shortTitle: "KC",
|
|
38079
|
+
overlay: true,
|
|
38080
|
+
inputs: [
|
|
38081
|
+
lengthInput(20),
|
|
38082
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38083
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38084
|
+
sourceInput(),
|
|
38085
|
+
colorInput()
|
|
38086
|
+
],
|
|
38087
|
+
compute: (bars, inputs) => {
|
|
38088
|
+
const len = num3(inputs, "length", 20);
|
|
38089
|
+
const mult = num3(inputs, "mult", 2);
|
|
38090
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38091
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
38092
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38093
|
+
return {
|
|
38094
|
+
plots: [
|
|
38095
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
38096
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
38097
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
38098
|
+
],
|
|
38099
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38100
|
+
};
|
|
38101
|
+
}
|
|
38102
|
+
};
|
|
38103
|
+
var donchian = {
|
|
38104
|
+
type: "donchian-channels",
|
|
38105
|
+
title: "Donchian Channels",
|
|
38106
|
+
shortTitle: "DC",
|
|
38107
|
+
overlay: true,
|
|
38108
|
+
inputs: [lengthInput(20), colorInput()],
|
|
38109
|
+
compute: (bars, inputs) => {
|
|
38110
|
+
const len = num3(inputs, "length", 20);
|
|
38111
|
+
const upper = highest(highs(bars), len);
|
|
38112
|
+
const lower = lowest(lows(bars), len);
|
|
38113
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38114
|
+
return {
|
|
38115
|
+
plots: [
|
|
38116
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
38117
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38118
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38119
|
+
],
|
|
38120
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38121
|
+
};
|
|
38122
|
+
}
|
|
38123
|
+
};
|
|
38124
|
+
var chandelier = {
|
|
38125
|
+
type: "chandelier-exit",
|
|
38126
|
+
title: "Chandelier Exit",
|
|
38127
|
+
shortTitle: "CE",
|
|
38128
|
+
overlay: true,
|
|
38129
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
38130
|
+
compute: (bars, inputs) => {
|
|
38131
|
+
const len = num3(inputs, "length", 22);
|
|
38132
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
38133
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
38134
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
38135
|
+
return {
|
|
38136
|
+
plots: [
|
|
38137
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
38138
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
38139
|
+
]
|
|
38140
|
+
};
|
|
38141
|
+
}
|
|
38142
|
+
};
|
|
38143
|
+
var chandeKroll = {
|
|
38144
|
+
type: "chande-kroll-stop",
|
|
38145
|
+
title: "Chande Kroll Stop",
|
|
38146
|
+
shortTitle: "CKS",
|
|
38147
|
+
overlay: true,
|
|
38148
|
+
inputs: [
|
|
38149
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38150
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
38151
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
38152
|
+
],
|
|
38153
|
+
compute: (bars, inputs) => {
|
|
38154
|
+
const p = num3(inputs, "p", 10);
|
|
38155
|
+
const x = num3(inputs, "x", 1);
|
|
38156
|
+
const q = num3(inputs, "q", 9);
|
|
38157
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
38158
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
38159
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
38160
|
+
return {
|
|
38161
|
+
plots: [
|
|
38162
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
38163
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
38164
|
+
]
|
|
38165
|
+
};
|
|
38166
|
+
}
|
|
38167
|
+
};
|
|
38168
|
+
var supertrend = {
|
|
38169
|
+
type: "supertrend",
|
|
38170
|
+
title: "SuperTrend",
|
|
38171
|
+
shortTitle: "SuperTrend",
|
|
38172
|
+
overlay: true,
|
|
38173
|
+
inputs: [
|
|
38174
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38175
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
38176
|
+
],
|
|
38177
|
+
compute: (bars, inputs) => {
|
|
38178
|
+
const len = num3(inputs, "atrLength", 10);
|
|
38179
|
+
const mult = num3(inputs, "mult", 3);
|
|
38180
|
+
const range = atr(bars, len);
|
|
38181
|
+
const n = bars.length;
|
|
38182
|
+
const st = new Array(n).fill(Number.NaN);
|
|
38183
|
+
const dir = new Array(n).fill(1);
|
|
38184
|
+
let up = Number.NaN;
|
|
38185
|
+
let dn = Number.NaN;
|
|
38186
|
+
let trend = 1;
|
|
38187
|
+
for (let i = 0; i < n; i++) {
|
|
38188
|
+
const r = range[i];
|
|
38189
|
+
if (!Number.isFinite(r)) continue;
|
|
38190
|
+
const b = bars[i];
|
|
38191
|
+
const mid = (b.high + b.low) / 2;
|
|
38192
|
+
const basicUp = mid - mult * r;
|
|
38193
|
+
const basicDn = mid + mult * r;
|
|
38194
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
38195
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
38196
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
38197
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
38198
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
38199
|
+
dir[i] = trend;
|
|
38200
|
+
st[i] = trend === 1 ? up : dn;
|
|
38201
|
+
}
|
|
38202
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
38203
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
38204
|
+
}
|
|
38205
|
+
};
|
|
38206
|
+
var alligator = {
|
|
38207
|
+
type: "williams-alligator",
|
|
38208
|
+
title: "Williams Alligator",
|
|
38209
|
+
shortTitle: "Alligator",
|
|
38210
|
+
overlay: true,
|
|
38211
|
+
inputs: [
|
|
38212
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
38213
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
38214
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
38215
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
38216
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
38217
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
38218
|
+
],
|
|
38219
|
+
compute: (bars, inputs) => {
|
|
38220
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38221
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
38222
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
38223
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
38224
|
+
return {
|
|
38225
|
+
plots: [
|
|
38226
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
38227
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
38228
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
38229
|
+
]
|
|
38230
|
+
};
|
|
38231
|
+
}
|
|
38232
|
+
};
|
|
38233
|
+
var gator = {
|
|
38234
|
+
type: "gator-oscillator",
|
|
38235
|
+
title: "Gator Oscillator",
|
|
38236
|
+
shortTitle: "Gator Oscillator",
|
|
38237
|
+
overlay: false,
|
|
38238
|
+
inputs: [],
|
|
38239
|
+
compute: (bars) => {
|
|
38240
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38241
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
38242
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
38243
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
38244
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
38245
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
38246
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
38247
|
+
if (!Number.isFinite(x)) return null;
|
|
38248
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
38249
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
38250
|
+
});
|
|
38251
|
+
return {
|
|
38252
|
+
plots: [
|
|
38253
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
38254
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
38255
|
+
]
|
|
38256
|
+
};
|
|
38257
|
+
}
|
|
38258
|
+
};
|
|
38259
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
38260
|
+
|
|
38261
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
38262
|
+
function guideLevels(low, high, mid) {
|
|
38263
|
+
const levels = [
|
|
38264
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
38265
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
38266
|
+
];
|
|
38267
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
38268
|
+
return levels;
|
|
38269
|
+
}
|
|
38270
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
38271
|
+
function histColors(values) {
|
|
38272
|
+
return values.map((x, i) => {
|
|
38273
|
+
if (!Number.isFinite(x)) return null;
|
|
38274
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
38275
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
38276
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
38277
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
38278
|
+
});
|
|
38279
|
+
}
|
|
38280
|
+
var rsiSpec = {
|
|
38281
|
+
type: "rsi",
|
|
38282
|
+
title: "Relative Strength Index",
|
|
38283
|
+
shortTitle: "RSI",
|
|
38284
|
+
overlay: false,
|
|
38285
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38286
|
+
compute: (bars, inputs) => ({
|
|
38287
|
+
plots: [
|
|
38288
|
+
{
|
|
38289
|
+
key: "rsi",
|
|
38290
|
+
title: "RSI",
|
|
38291
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38292
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38293
|
+
width: 2
|
|
38294
|
+
}
|
|
38295
|
+
],
|
|
38296
|
+
levels: guideLevels(30, 70, 50)
|
|
38297
|
+
})
|
|
38298
|
+
};
|
|
38299
|
+
var stochastic = {
|
|
38300
|
+
type: "stochastic",
|
|
38301
|
+
title: "Stochastic",
|
|
38302
|
+
shortTitle: "Stoch",
|
|
38303
|
+
overlay: false,
|
|
38304
|
+
inputs: [
|
|
38305
|
+
lengthInput(14, "kLength", "%K length"),
|
|
38306
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
38307
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
38308
|
+
],
|
|
38309
|
+
compute: (bars, inputs) => {
|
|
38310
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
38311
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38312
|
+
return {
|
|
38313
|
+
plots: [
|
|
38314
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38315
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38316
|
+
],
|
|
38317
|
+
levels: guideLevels(20, 80)
|
|
38318
|
+
};
|
|
38319
|
+
}
|
|
38320
|
+
};
|
|
38321
|
+
var stochasticRsi = {
|
|
38322
|
+
type: "stochastic-rsi",
|
|
38323
|
+
title: "Stochastic RSI",
|
|
38324
|
+
shortTitle: "Stoch RSI",
|
|
38325
|
+
overlay: false,
|
|
38326
|
+
inputs: [
|
|
38327
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
38328
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
38329
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
38330
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
38331
|
+
sourceInput()
|
|
38332
|
+
],
|
|
38333
|
+
compute: (bars, inputs) => {
|
|
38334
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
38335
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
38336
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38337
|
+
return {
|
|
38338
|
+
plots: [
|
|
38339
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38340
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38341
|
+
],
|
|
38342
|
+
levels: guideLevels(20, 80)
|
|
38343
|
+
};
|
|
38344
|
+
}
|
|
38345
|
+
};
|
|
38346
|
+
var macd = {
|
|
38347
|
+
type: "macd",
|
|
38348
|
+
title: "MACD",
|
|
38349
|
+
overlay: false,
|
|
38350
|
+
inputs: [
|
|
38351
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38352
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38353
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38354
|
+
sourceInput()
|
|
38355
|
+
],
|
|
38356
|
+
compute: (bars, inputs) => {
|
|
38357
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38358
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
38359
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38360
|
+
const hist = sub(line, signal);
|
|
38361
|
+
return {
|
|
38362
|
+
plots: [
|
|
38363
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38364
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
38365
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38366
|
+
],
|
|
38367
|
+
levels: ZERO_LEVEL
|
|
38368
|
+
};
|
|
38369
|
+
}
|
|
38370
|
+
};
|
|
38371
|
+
var ppo = {
|
|
38372
|
+
type: "ppo",
|
|
38373
|
+
title: "Percentage Price Oscillator",
|
|
38374
|
+
shortTitle: "PPO",
|
|
38375
|
+
overlay: false,
|
|
38376
|
+
inputs: [
|
|
38377
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38378
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38379
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38380
|
+
sourceInput()
|
|
38381
|
+
],
|
|
38382
|
+
compute: (bars, inputs) => {
|
|
38383
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38384
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
38385
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
38386
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
38387
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38388
|
+
const hist = sub(line, signal);
|
|
38389
|
+
return {
|
|
38390
|
+
plots: [
|
|
38391
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38392
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
38393
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38394
|
+
],
|
|
38395
|
+
levels: ZERO_LEVEL
|
|
38396
|
+
};
|
|
38397
|
+
}
|
|
38398
|
+
};
|
|
38399
|
+
var awesome = {
|
|
38400
|
+
type: "awesome-oscillator",
|
|
38401
|
+
title: "Awesome Oscillator",
|
|
38402
|
+
shortTitle: "AO",
|
|
38403
|
+
overlay: false,
|
|
38404
|
+
inputs: [],
|
|
38405
|
+
compute: (bars) => {
|
|
38406
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38407
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
38408
|
+
const colors = ao.map((x, i) => {
|
|
38409
|
+
if (!Number.isFinite(x)) return null;
|
|
38410
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
38411
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
38412
|
+
});
|
|
38413
|
+
return {
|
|
38414
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
38415
|
+
levels: ZERO_LEVEL
|
|
38416
|
+
};
|
|
38417
|
+
}
|
|
38418
|
+
};
|
|
38419
|
+
var cci = {
|
|
38420
|
+
type: "commodity-channel-index",
|
|
38421
|
+
title: "Commodity Channel Index",
|
|
38422
|
+
shortTitle: "CCI",
|
|
38423
|
+
overlay: false,
|
|
38424
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
38425
|
+
compute: (bars, inputs) => {
|
|
38426
|
+
const len = num3(inputs, "length", 20);
|
|
38427
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
38428
|
+
const basis = sma(src, len);
|
|
38429
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
38430
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
38431
|
+
const mean = basis[i];
|
|
38432
|
+
if (!Number.isFinite(mean)) continue;
|
|
38433
|
+
let d = 0;
|
|
38434
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
38435
|
+
dev[i] = d / len;
|
|
38436
|
+
}
|
|
38437
|
+
const values = src.map((x, i) => {
|
|
38438
|
+
const b = basis[i];
|
|
38439
|
+
const d = dev[i];
|
|
38440
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
38441
|
+
});
|
|
38442
|
+
return {
|
|
38443
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38444
|
+
levels: guideLevels(-100, 100, 0)
|
|
38445
|
+
};
|
|
38446
|
+
}
|
|
38447
|
+
};
|
|
38448
|
+
var williamsR = {
|
|
38449
|
+
type: "williams-percent-r",
|
|
38450
|
+
title: "Williams %R",
|
|
38451
|
+
shortTitle: "%R",
|
|
38452
|
+
overlay: false,
|
|
38453
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38454
|
+
compute: (bars, inputs) => {
|
|
38455
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
38456
|
+
return {
|
|
38457
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38458
|
+
levels: guideLevels(-80, -20)
|
|
38459
|
+
};
|
|
38460
|
+
}
|
|
38461
|
+
};
|
|
38462
|
+
var cmo = {
|
|
38463
|
+
type: "chande-momentum-oscillator",
|
|
38464
|
+
title: "Chande Momentum Oscillator",
|
|
38465
|
+
shortTitle: "CMO",
|
|
38466
|
+
overlay: false,
|
|
38467
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38468
|
+
compute: (bars, inputs) => {
|
|
38469
|
+
const len = num3(inputs, "length", 9);
|
|
38470
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
38471
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
38472
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
38473
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
38474
|
+
return {
|
|
38475
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38476
|
+
levels: guideLevels(-50, 50, 0)
|
|
38477
|
+
};
|
|
38478
|
+
}
|
|
38479
|
+
};
|
|
38480
|
+
var connorsRsi = {
|
|
38481
|
+
type: "connors-rsi",
|
|
38482
|
+
title: "Connors RSI",
|
|
38483
|
+
shortTitle: "CRSI",
|
|
38484
|
+
overlay: false,
|
|
38485
|
+
inputs: [
|
|
38486
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
38487
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
38488
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
38489
|
+
colorInput()
|
|
38490
|
+
],
|
|
38491
|
+
compute: (bars, inputs) => {
|
|
38492
|
+
const c = closes(bars);
|
|
38493
|
+
const streak = new Array(c.length).fill(0);
|
|
38494
|
+
for (let i = 1; i < c.length; i++) {
|
|
38495
|
+
const d = c[i] - c[i - 1];
|
|
38496
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
38497
|
+
}
|
|
38498
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
38499
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
38500
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
38501
|
+
const values = c.map((_, i) => {
|
|
38502
|
+
const x = a[i];
|
|
38503
|
+
const y = b[i];
|
|
38504
|
+
const z = r[i];
|
|
38505
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
38506
|
+
});
|
|
38507
|
+
return {
|
|
38508
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38509
|
+
levels: guideLevels(20, 80, 50)
|
|
38510
|
+
};
|
|
38511
|
+
}
|
|
38512
|
+
};
|
|
38513
|
+
var fisher = {
|
|
38514
|
+
type: "fisher-transform",
|
|
38515
|
+
title: "Fisher Transform",
|
|
38516
|
+
shortTitle: "Fisher",
|
|
38517
|
+
overlay: false,
|
|
38518
|
+
inputs: [lengthInput(9)],
|
|
38519
|
+
compute: (bars, inputs) => {
|
|
38520
|
+
const len = num3(inputs, "length", 9);
|
|
38521
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38522
|
+
const hi = highest(hl2, len);
|
|
38523
|
+
const lo = lowest(hl2, len);
|
|
38524
|
+
const n = bars.length;
|
|
38525
|
+
const out = new Array(n).fill(Number.NaN);
|
|
38526
|
+
let value = 0;
|
|
38527
|
+
let fish = 0;
|
|
38528
|
+
for (let i = 0; i < n; i++) {
|
|
38529
|
+
const h = hi[i];
|
|
38530
|
+
const l = lo[i];
|
|
38531
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38532
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
38533
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
38534
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
38535
|
+
out[i] = fish;
|
|
38536
|
+
}
|
|
38537
|
+
return {
|
|
38538
|
+
plots: [
|
|
38539
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
38540
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
38541
|
+
],
|
|
38542
|
+
levels: ZERO_LEVEL
|
|
38543
|
+
};
|
|
38544
|
+
}
|
|
38545
|
+
};
|
|
38546
|
+
var trix = {
|
|
38547
|
+
type: "trix",
|
|
38548
|
+
title: "TRIX",
|
|
38549
|
+
overlay: false,
|
|
38550
|
+
inputs: [lengthInput(18), colorInput()],
|
|
38551
|
+
compute: (bars, inputs) => {
|
|
38552
|
+
const len = num3(inputs, "length", 18);
|
|
38553
|
+
const logClose = map(closes(bars), Math.log);
|
|
38554
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
38555
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
38556
|
+
return {
|
|
38557
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38558
|
+
levels: ZERO_LEVEL
|
|
38559
|
+
};
|
|
38560
|
+
}
|
|
38561
|
+
};
|
|
38562
|
+
function tsiLine(src, long, short) {
|
|
38563
|
+
const mom = change(src);
|
|
38564
|
+
const numer = ema(ema(mom, long), short);
|
|
38565
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
38566
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
38567
|
+
}
|
|
38568
|
+
var tsi = {
|
|
38569
|
+
type: "true-strength-index",
|
|
38570
|
+
title: "True Strength Index",
|
|
38571
|
+
shortTitle: "TSI",
|
|
38572
|
+
overlay: false,
|
|
38573
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
38574
|
+
compute: (bars, inputs) => {
|
|
38575
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
38576
|
+
return {
|
|
38577
|
+
plots: [
|
|
38578
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
38579
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
38580
|
+
],
|
|
38581
|
+
levels: ZERO_LEVEL
|
|
38582
|
+
};
|
|
38583
|
+
}
|
|
38584
|
+
};
|
|
38585
|
+
var smiErgodic = {
|
|
38586
|
+
type: "smi-ergodic",
|
|
38587
|
+
title: "SMI Ergodic Oscillator",
|
|
38588
|
+
shortTitle: "SMIE",
|
|
38589
|
+
overlay: false,
|
|
38590
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
38591
|
+
compute: (bars, inputs) => {
|
|
38592
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
38593
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
38594
|
+
const osc = sub(line, signal);
|
|
38595
|
+
return {
|
|
38596
|
+
plots: [
|
|
38597
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
38598
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
38599
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38600
|
+
],
|
|
38601
|
+
levels: ZERO_LEVEL
|
|
38602
|
+
};
|
|
38603
|
+
}
|
|
38604
|
+
};
|
|
38605
|
+
var stc = {
|
|
38606
|
+
type: "schaff-trend-cycle",
|
|
38607
|
+
title: "Schaff Trend Cycle",
|
|
38608
|
+
shortTitle: "Schaff Trend Cycle",
|
|
38609
|
+
overlay: false,
|
|
38610
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
38611
|
+
compute: (bars, inputs) => {
|
|
38612
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
38613
|
+
const src = closes(bars);
|
|
38614
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
38615
|
+
const stochOf = (v) => {
|
|
38616
|
+
const lo = lowest(v, len);
|
|
38617
|
+
const hi = highest(v, len);
|
|
38618
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38619
|
+
let prevRaw = Number.NaN;
|
|
38620
|
+
let smoothed = Number.NaN;
|
|
38621
|
+
for (let i = 0; i < v.length; i++) {
|
|
38622
|
+
const x = v[i];
|
|
38623
|
+
const l = lo[i];
|
|
38624
|
+
const h = hi[i];
|
|
38625
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
38626
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
38627
|
+
prevRaw = raw;
|
|
38628
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
38629
|
+
out[i] = smoothed;
|
|
38630
|
+
}
|
|
38631
|
+
return out;
|
|
38632
|
+
};
|
|
38633
|
+
const values = stochOf(stochOf(macdLine));
|
|
38634
|
+
return {
|
|
38635
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
38636
|
+
levels: guideLevels(25, 75)
|
|
38637
|
+
};
|
|
38638
|
+
}
|
|
38639
|
+
};
|
|
38640
|
+
var kst = {
|
|
38641
|
+
type: "know-sure-thing",
|
|
38642
|
+
title: "Know Sure Thing",
|
|
38643
|
+
shortTitle: "KST",
|
|
38644
|
+
overlay: false,
|
|
38645
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
38646
|
+
compute: (bars, inputs) => {
|
|
38647
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38648
|
+
const line = [
|
|
38649
|
+
sma(roc(src, 10), 10),
|
|
38650
|
+
sma(roc(src, 15), 10),
|
|
38651
|
+
sma(roc(src, 20), 10),
|
|
38652
|
+
sma(roc(src, 30), 15)
|
|
38653
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
38654
|
+
return {
|
|
38655
|
+
plots: [
|
|
38656
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
38657
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
38658
|
+
],
|
|
38659
|
+
levels: ZERO_LEVEL
|
|
38660
|
+
};
|
|
38661
|
+
}
|
|
38662
|
+
};
|
|
38663
|
+
var coppock = {
|
|
38664
|
+
type: "coppock-curve",
|
|
38665
|
+
title: "Coppock Curve",
|
|
38666
|
+
shortTitle: "Coppock",
|
|
38667
|
+
overlay: false,
|
|
38668
|
+
inputs: [
|
|
38669
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
38670
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
38671
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
38672
|
+
sourceInput(),
|
|
38673
|
+
colorInput()
|
|
38674
|
+
],
|
|
38675
|
+
compute: (bars, inputs) => {
|
|
38676
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38677
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
38678
|
+
return {
|
|
38679
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38680
|
+
levels: ZERO_LEVEL
|
|
38681
|
+
};
|
|
38682
|
+
}
|
|
38683
|
+
};
|
|
38684
|
+
var dpo = {
|
|
38685
|
+
type: "detrended-price-oscillator",
|
|
38686
|
+
title: "Detrended Price Oscillator",
|
|
38687
|
+
shortTitle: "DPO",
|
|
38688
|
+
overlay: false,
|
|
38689
|
+
inputs: [lengthInput(21), colorInput()],
|
|
38690
|
+
compute: (bars, inputs) => {
|
|
38691
|
+
const len = num3(inputs, "length", 21);
|
|
38692
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
38693
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
38694
|
+
return {
|
|
38695
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38696
|
+
levels: ZERO_LEVEL
|
|
38697
|
+
};
|
|
38698
|
+
}
|
|
38699
|
+
};
|
|
38700
|
+
var rocSpec = {
|
|
38701
|
+
type: "rate-of-change",
|
|
38702
|
+
title: "Rate of Change",
|
|
38703
|
+
shortTitle: "ROC",
|
|
38704
|
+
overlay: false,
|
|
38705
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38706
|
+
compute: (bars, inputs) => ({
|
|
38707
|
+
plots: [
|
|
38708
|
+
{
|
|
38709
|
+
key: "roc",
|
|
38710
|
+
title: "RoC",
|
|
38711
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
38712
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38713
|
+
width: 2
|
|
38714
|
+
}
|
|
38715
|
+
],
|
|
38716
|
+
levels: ZERO_LEVEL
|
|
38717
|
+
})
|
|
38718
|
+
};
|
|
38719
|
+
var ultimate = {
|
|
38720
|
+
type: "ultimate-oscillator",
|
|
38721
|
+
title: "Ultimate Oscillator",
|
|
38722
|
+
shortTitle: "UO",
|
|
38723
|
+
overlay: false,
|
|
38724
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
38725
|
+
compute: (bars, inputs) => {
|
|
38726
|
+
const n = bars.length;
|
|
38727
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
38728
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
38729
|
+
for (let i = 0; i < n; i++) {
|
|
38730
|
+
const b = bars[i];
|
|
38731
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
38732
|
+
const lo = Math.min(b.low, pc);
|
|
38733
|
+
const hi = Math.max(b.high, pc);
|
|
38734
|
+
bp[i] = b.close - lo;
|
|
38735
|
+
tr[i] = hi - lo;
|
|
38736
|
+
}
|
|
38737
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
38738
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
38739
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
38740
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
38741
|
+
const values = a7.map((x, i) => {
|
|
38742
|
+
const y = a14[i];
|
|
38743
|
+
const z = a28[i];
|
|
38744
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
38745
|
+
});
|
|
38746
|
+
return {
|
|
38747
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38748
|
+
levels: guideLevels(30, 70, 50)
|
|
38749
|
+
};
|
|
38750
|
+
}
|
|
38751
|
+
};
|
|
38752
|
+
var rvgi = {
|
|
38753
|
+
type: "relative-vigor-index",
|
|
38754
|
+
title: "Relative Vigor Index",
|
|
38755
|
+
shortTitle: "RVGI",
|
|
38756
|
+
overlay: false,
|
|
38757
|
+
inputs: [lengthInput(10)],
|
|
38758
|
+
compute: (bars, inputs) => {
|
|
38759
|
+
const len = num3(inputs, "length", 10);
|
|
38760
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
38761
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
38762
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
38763
|
+
return {
|
|
38764
|
+
plots: [
|
|
38765
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
38766
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
38767
|
+
],
|
|
38768
|
+
levels: ZERO_LEVEL
|
|
38769
|
+
};
|
|
38770
|
+
}
|
|
38771
|
+
};
|
|
38772
|
+
var relVolatility = {
|
|
38773
|
+
type: "relative-volatility-index",
|
|
38774
|
+
title: "Relative Volatility Index",
|
|
38775
|
+
shortTitle: "RVI",
|
|
38776
|
+
overlay: false,
|
|
38777
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
38778
|
+
compute: (bars, inputs) => {
|
|
38779
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
38780
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38781
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
38782
|
+
const d = change(src);
|
|
38783
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
38784
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
38785
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
38786
|
+
return {
|
|
38787
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38788
|
+
levels: guideLevels(20, 80, 50)
|
|
38789
|
+
};
|
|
38790
|
+
}
|
|
38791
|
+
};
|
|
38792
|
+
var bop = {
|
|
38793
|
+
type: "balance-of-power",
|
|
38794
|
+
title: "Balance of Power",
|
|
38795
|
+
shortTitle: "BOP",
|
|
38796
|
+
overlay: false,
|
|
38797
|
+
inputs: [colorInput()],
|
|
38798
|
+
compute: (bars, inputs) => {
|
|
38799
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
38800
|
+
return {
|
|
38801
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38802
|
+
levels: ZERO_LEVEL
|
|
38803
|
+
};
|
|
38804
|
+
}
|
|
38805
|
+
};
|
|
38806
|
+
var elderRay = {
|
|
38807
|
+
type: "elder-ray",
|
|
38808
|
+
title: "Elder Ray",
|
|
38809
|
+
overlay: false,
|
|
38810
|
+
inputs: [lengthInput(13)],
|
|
38811
|
+
compute: (bars, inputs) => {
|
|
38812
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
38813
|
+
return {
|
|
38814
|
+
plots: [
|
|
38815
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
38816
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
38817
|
+
],
|
|
38818
|
+
levels: ZERO_LEVEL
|
|
38819
|
+
};
|
|
38820
|
+
}
|
|
38821
|
+
};
|
|
38822
|
+
var ttmSqueeze = {
|
|
38823
|
+
type: "ttm-squeeze",
|
|
38824
|
+
title: "TTM Squeeze",
|
|
38825
|
+
overlay: false,
|
|
38826
|
+
inputs: [
|
|
38827
|
+
lengthInput(20),
|
|
38828
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38829
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
38830
|
+
],
|
|
38831
|
+
compute: (bars, inputs) => {
|
|
38832
|
+
const len = num3(inputs, "length", 20);
|
|
38833
|
+
const c = closes(bars);
|
|
38834
|
+
const basis = sma(c, len);
|
|
38835
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
38836
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
38837
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
38838
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
38839
|
+
const mom = linreg(sub(c, anchor), len);
|
|
38840
|
+
const squeezed = c.map((_, i) => {
|
|
38841
|
+
const d = dev[i];
|
|
38842
|
+
const r = kcRange[i];
|
|
38843
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
38844
|
+
});
|
|
38845
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
38846
|
+
return {
|
|
38847
|
+
plots: [
|
|
38848
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
38849
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
38850
|
+
]
|
|
38851
|
+
};
|
|
38852
|
+
}
|
|
38853
|
+
};
|
|
38854
|
+
var oscillatorSpecs = [
|
|
38855
|
+
rsiSpec,
|
|
38856
|
+
stochastic,
|
|
38857
|
+
stochasticRsi,
|
|
38858
|
+
macd,
|
|
38859
|
+
ppo,
|
|
38860
|
+
awesome,
|
|
38861
|
+
cci,
|
|
38862
|
+
williamsR,
|
|
38863
|
+
cmo,
|
|
38864
|
+
connorsRsi,
|
|
38865
|
+
fisher,
|
|
38866
|
+
trix,
|
|
38867
|
+
tsi,
|
|
38868
|
+
smiErgodic,
|
|
38869
|
+
stc,
|
|
38870
|
+
kst,
|
|
38871
|
+
coppock,
|
|
38872
|
+
dpo,
|
|
38873
|
+
rocSpec,
|
|
38874
|
+
ultimate,
|
|
38875
|
+
rvgi,
|
|
38876
|
+
relVolatility,
|
|
38877
|
+
bop,
|
|
38878
|
+
elderRay,
|
|
38879
|
+
ttmSqueeze
|
|
38880
|
+
];
|
|
38881
|
+
|
|
38882
|
+
// src/core/native-indicators/classics/trend.ts
|
|
38883
|
+
var aroon = {
|
|
38884
|
+
type: "aroon",
|
|
38885
|
+
title: "Aroon",
|
|
38886
|
+
overlay: false,
|
|
38887
|
+
inputs: [lengthInput(14)],
|
|
38888
|
+
compute: (bars, inputs) => {
|
|
38889
|
+
const len = num3(inputs, "length", 14);
|
|
38890
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38891
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38892
|
+
return {
|
|
38893
|
+
plots: [
|
|
38894
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
38895
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
38896
|
+
],
|
|
38897
|
+
levels: [
|
|
38898
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
38899
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
38900
|
+
]
|
|
38901
|
+
};
|
|
38902
|
+
}
|
|
38903
|
+
};
|
|
38904
|
+
var adx = {
|
|
38905
|
+
type: "average-directional-index",
|
|
38906
|
+
title: "Average Directional Index",
|
|
38907
|
+
shortTitle: "ADX",
|
|
38908
|
+
overlay: false,
|
|
38909
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
38910
|
+
compute: (bars, inputs) => {
|
|
38911
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
38912
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
38913
|
+
const n = bars.length;
|
|
38914
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
38915
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
38916
|
+
for (let i = 1; i < n; i++) {
|
|
38917
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
38918
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
38919
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
38920
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
38921
|
+
}
|
|
38922
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
38923
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38924
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38925
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
38926
|
+
return {
|
|
38927
|
+
plots: [
|
|
38928
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
38929
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
38930
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
38931
|
+
],
|
|
38932
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38933
|
+
};
|
|
38934
|
+
}
|
|
38935
|
+
};
|
|
38936
|
+
var vortex = {
|
|
38937
|
+
type: "vortex-indicator",
|
|
38938
|
+
title: "Vortex Indicator",
|
|
38939
|
+
shortTitle: "VI",
|
|
38940
|
+
overlay: false,
|
|
38941
|
+
inputs: [lengthInput(14)],
|
|
38942
|
+
compute: (bars, inputs) => {
|
|
38943
|
+
const len = num3(inputs, "length", 14);
|
|
38944
|
+
const n = bars.length;
|
|
38945
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
38946
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
38947
|
+
for (let i = 1; i < n; i++) {
|
|
38948
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
38949
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
38950
|
+
}
|
|
38951
|
+
const trSum = sum(trueRange(bars), len);
|
|
38952
|
+
return {
|
|
38953
|
+
plots: [
|
|
38954
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
38955
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
38956
|
+
],
|
|
38957
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
38958
|
+
};
|
|
38959
|
+
}
|
|
38960
|
+
};
|
|
38961
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
38962
|
+
|
|
38963
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
38964
|
+
var atrSpec = {
|
|
38965
|
+
type: "average-true-range",
|
|
38966
|
+
title: "Average True Range",
|
|
38967
|
+
shortTitle: "ATR",
|
|
38968
|
+
overlay: false,
|
|
38969
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38970
|
+
compute: (bars, inputs) => ({
|
|
38971
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
38972
|
+
})
|
|
38973
|
+
};
|
|
38974
|
+
var historicalVolatility = {
|
|
38975
|
+
type: "historical-volatility",
|
|
38976
|
+
title: "Historical Volatility",
|
|
38977
|
+
shortTitle: "HV",
|
|
38978
|
+
overlay: false,
|
|
38979
|
+
inputs: [
|
|
38980
|
+
lengthInput(10),
|
|
38981
|
+
{ 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)" },
|
|
38982
|
+
colorInput()
|
|
38983
|
+
],
|
|
38984
|
+
compute: (bars, inputs) => {
|
|
38985
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
38986
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
38987
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38988
|
+
}
|
|
38989
|
+
};
|
|
38990
|
+
var chaikinVolatility = {
|
|
38991
|
+
type: "chaikin-volatility",
|
|
38992
|
+
title: "Chaikin Volatility",
|
|
38993
|
+
shortTitle: "CHV",
|
|
38994
|
+
overlay: false,
|
|
38995
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
38996
|
+
compute: (bars, inputs) => {
|
|
38997
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
38998
|
+
return {
|
|
38999
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39000
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39001
|
+
};
|
|
39002
|
+
}
|
|
39003
|
+
};
|
|
39004
|
+
var massIndex = {
|
|
39005
|
+
type: "mass-index",
|
|
39006
|
+
title: "Mass Index",
|
|
39007
|
+
shortTitle: "MI",
|
|
39008
|
+
overlay: false,
|
|
39009
|
+
inputs: [lengthInput(25), colorInput()],
|
|
39010
|
+
compute: (bars, inputs) => {
|
|
39011
|
+
const range = sub(highs(bars), lows(bars));
|
|
39012
|
+
const single = ema(range, 9);
|
|
39013
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39014
|
+
return {
|
|
39015
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39016
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
39017
|
+
};
|
|
39018
|
+
}
|
|
39019
|
+
};
|
|
39020
|
+
var standardDeviation = {
|
|
39021
|
+
type: "standard-deviation",
|
|
39022
|
+
title: "Standard Deviation",
|
|
39023
|
+
shortTitle: "StdDev",
|
|
39024
|
+
overlay: false,
|
|
39025
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
39026
|
+
compute: (bars, inputs) => ({
|
|
39027
|
+
plots: [
|
|
39028
|
+
{
|
|
39029
|
+
key: "stdev",
|
|
39030
|
+
title: "StdDev",
|
|
39031
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
39032
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39033
|
+
width: 2
|
|
39034
|
+
}
|
|
39035
|
+
]
|
|
39036
|
+
})
|
|
39037
|
+
};
|
|
39038
|
+
var ulcerIndex = {
|
|
39039
|
+
type: "ulcer-index",
|
|
39040
|
+
title: "Ulcer Index",
|
|
39041
|
+
overlay: false,
|
|
39042
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39043
|
+
compute: (bars, inputs) => {
|
|
39044
|
+
const len = num3(inputs, "length", 14);
|
|
39045
|
+
const c = closes(bars);
|
|
39046
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
39047
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
39048
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39049
|
+
}
|
|
39050
|
+
};
|
|
39051
|
+
var choppiness = {
|
|
39052
|
+
type: "choppiness-index",
|
|
39053
|
+
title: "Choppiness Index",
|
|
39054
|
+
shortTitle: "CHOP",
|
|
39055
|
+
overlay: false,
|
|
39056
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39057
|
+
compute: (bars, inputs) => {
|
|
39058
|
+
const len = num3(inputs, "length", 14);
|
|
39059
|
+
const trSum = sum(trueRange(bars), len);
|
|
39060
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
39061
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
39062
|
+
return {
|
|
39063
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39064
|
+
levels: [
|
|
39065
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
39066
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
39067
|
+
]
|
|
39068
|
+
};
|
|
39069
|
+
}
|
|
39070
|
+
};
|
|
39071
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
39072
|
+
|
|
39073
|
+
// src/core/native-indicators/classics/volume.ts
|
|
39074
|
+
function moneyFlowVolume(bars) {
|
|
39075
|
+
return bars.map((b) => {
|
|
39076
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39077
|
+
if (b.high === b.low) return 0;
|
|
39078
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
39079
|
+
});
|
|
39080
|
+
}
|
|
39081
|
+
var obv = {
|
|
39082
|
+
type: "on-balance-volume",
|
|
39083
|
+
title: "On Balance Volume",
|
|
39084
|
+
shortTitle: "OBV",
|
|
39085
|
+
overlay: false,
|
|
39086
|
+
inputs: [colorInput()],
|
|
39087
|
+
compute: (bars, inputs) => {
|
|
39088
|
+
const vol = volumes(bars);
|
|
39089
|
+
const d = change(closes(bars));
|
|
39090
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
39091
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39092
|
+
}
|
|
39093
|
+
};
|
|
39094
|
+
var accDist = {
|
|
39095
|
+
type: "accumulation-distribution",
|
|
39096
|
+
title: "Accumulation / Distribution",
|
|
39097
|
+
shortTitle: "A/D",
|
|
39098
|
+
overlay: false,
|
|
39099
|
+
inputs: [colorInput()],
|
|
39100
|
+
compute: (bars, inputs) => ({
|
|
39101
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
39102
|
+
})
|
|
39103
|
+
};
|
|
39104
|
+
var cmf = {
|
|
39105
|
+
type: "chaikin-money-flow",
|
|
39106
|
+
title: "Chaikin Money Flow",
|
|
39107
|
+
shortTitle: "CMF",
|
|
39108
|
+
overlay: false,
|
|
39109
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39110
|
+
compute: (bars, inputs) => {
|
|
39111
|
+
const len = num3(inputs, "length", 20);
|
|
39112
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
39113
|
+
return {
|
|
39114
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39115
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39116
|
+
};
|
|
39117
|
+
}
|
|
39118
|
+
};
|
|
39119
|
+
var chaikinOscillator = {
|
|
39120
|
+
type: "chaikin-oscillator",
|
|
39121
|
+
title: "Chaikin Oscillator",
|
|
39122
|
+
shortTitle: "Chaikin Osc",
|
|
39123
|
+
overlay: false,
|
|
39124
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39125
|
+
compute: (bars, inputs) => {
|
|
39126
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
39127
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
39128
|
+
return {
|
|
39129
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39130
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39131
|
+
};
|
|
39132
|
+
}
|
|
39133
|
+
};
|
|
39134
|
+
var eom = {
|
|
39135
|
+
type: "ease-of-movement",
|
|
39136
|
+
title: "Ease of Movement",
|
|
39137
|
+
shortTitle: "EOM",
|
|
39138
|
+
overlay: false,
|
|
39139
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
39140
|
+
compute: (bars, inputs) => {
|
|
39141
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
39142
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39143
|
+
const move = change(hl2);
|
|
39144
|
+
const raw = bars.map((b, i) => {
|
|
39145
|
+
const m = move[i];
|
|
39146
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
39147
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
39148
|
+
});
|
|
39149
|
+
return {
|
|
39150
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39151
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39152
|
+
};
|
|
39153
|
+
}
|
|
39154
|
+
};
|
|
39155
|
+
var forceIndex = {
|
|
39156
|
+
type: "force-index",
|
|
39157
|
+
title: "Force Index",
|
|
39158
|
+
shortTitle: "FI",
|
|
39159
|
+
overlay: false,
|
|
39160
|
+
inputs: [lengthInput(13), colorInput()],
|
|
39161
|
+
compute: (bars, inputs) => {
|
|
39162
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
39163
|
+
return {
|
|
39164
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39165
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39166
|
+
};
|
|
39167
|
+
}
|
|
39168
|
+
};
|
|
39169
|
+
var klinger = {
|
|
39170
|
+
type: "klinger-oscillator",
|
|
39171
|
+
title: "Klinger Oscillator",
|
|
39172
|
+
shortTitle: "KVO",
|
|
39173
|
+
overlay: false,
|
|
39174
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39175
|
+
compute: (bars, inputs) => {
|
|
39176
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
39177
|
+
const trend = change(hlc3);
|
|
39178
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
39179
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
39180
|
+
return {
|
|
39181
|
+
plots: [
|
|
39182
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39183
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39184
|
+
],
|
|
39185
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39186
|
+
};
|
|
39187
|
+
}
|
|
39188
|
+
};
|
|
39189
|
+
var mfi = {
|
|
39190
|
+
type: "money-flow-index",
|
|
39191
|
+
title: "Money Flow Index",
|
|
39192
|
+
shortTitle: "MFI",
|
|
39193
|
+
overlay: false,
|
|
39194
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39195
|
+
compute: (bars, inputs) => {
|
|
39196
|
+
const len = num3(inputs, "length", 14);
|
|
39197
|
+
const tp = sourceValues(bars, "HLC3");
|
|
39198
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
39199
|
+
const d = change(tp);
|
|
39200
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
39201
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
39202
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
39203
|
+
return {
|
|
39204
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39205
|
+
levels: [
|
|
39206
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
39207
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
39208
|
+
]
|
|
39209
|
+
};
|
|
39210
|
+
}
|
|
39211
|
+
};
|
|
39212
|
+
function volumeIndex(bars, when) {
|
|
39213
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
39214
|
+
let index = 1e3;
|
|
39215
|
+
let started = false;
|
|
39216
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39217
|
+
const v = bars[i].volume;
|
|
39218
|
+
const pv = bars[i - 1].volume;
|
|
39219
|
+
const pc = bars[i - 1].close;
|
|
39220
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
39221
|
+
if (started) out[i] = index;
|
|
39222
|
+
continue;
|
|
39223
|
+
}
|
|
39224
|
+
started = true;
|
|
39225
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
39226
|
+
out[i] = index;
|
|
39227
|
+
}
|
|
39228
|
+
return out;
|
|
39229
|
+
}
|
|
39230
|
+
var nvi = {
|
|
39231
|
+
type: "negative-volume-index",
|
|
39232
|
+
title: "Negative Volume Index",
|
|
39233
|
+
shortTitle: "Negative Volume Index",
|
|
39234
|
+
overlay: false,
|
|
39235
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39236
|
+
compute: (bars, inputs) => {
|
|
39237
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
39238
|
+
return {
|
|
39239
|
+
plots: [
|
|
39240
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39241
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39242
|
+
]
|
|
39243
|
+
};
|
|
39244
|
+
}
|
|
39245
|
+
};
|
|
39246
|
+
var pvi = {
|
|
39247
|
+
type: "positive-volume-index",
|
|
39248
|
+
title: "Positive Volume Index",
|
|
39249
|
+
shortTitle: "Positive Volume Index",
|
|
39250
|
+
overlay: false,
|
|
39251
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39252
|
+
compute: (bars, inputs) => {
|
|
39253
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
39254
|
+
return {
|
|
39255
|
+
plots: [
|
|
39256
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39257
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39258
|
+
]
|
|
39259
|
+
};
|
|
39260
|
+
}
|
|
39261
|
+
};
|
|
39262
|
+
var pvt = {
|
|
39263
|
+
type: "price-volume-trend",
|
|
39264
|
+
title: "Price Volume Trend",
|
|
39265
|
+
shortTitle: "PVT",
|
|
39266
|
+
overlay: false,
|
|
39267
|
+
inputs: [colorInput()],
|
|
39268
|
+
compute: (bars, inputs) => {
|
|
39269
|
+
const c = closes(bars);
|
|
39270
|
+
const raw = bars.map((b, i) => {
|
|
39271
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39272
|
+
const pc = c[i - 1];
|
|
39273
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
39274
|
+
});
|
|
39275
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39276
|
+
}
|
|
39277
|
+
};
|
|
39278
|
+
var volumeOscillator = {
|
|
39279
|
+
type: "volume-oscillator",
|
|
39280
|
+
title: "Volume Oscillator",
|
|
39281
|
+
shortTitle: "Vol Osc",
|
|
39282
|
+
overlay: false,
|
|
39283
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39284
|
+
compute: (bars, inputs) => {
|
|
39285
|
+
const vol = volumes(bars);
|
|
39286
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
39287
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
39288
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39289
|
+
return {
|
|
39290
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39291
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39292
|
+
};
|
|
39293
|
+
}
|
|
39294
|
+
};
|
|
39295
|
+
var pvo = {
|
|
39296
|
+
type: "pvo",
|
|
39297
|
+
title: "Percentage Volume Oscillator",
|
|
39298
|
+
shortTitle: "PVO",
|
|
39299
|
+
overlay: false,
|
|
39300
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
39301
|
+
compute: (bars, inputs) => {
|
|
39302
|
+
const vol = volumes(bars);
|
|
39303
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
39304
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
39305
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39306
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39307
|
+
const hist = sub(line, signal);
|
|
39308
|
+
return {
|
|
39309
|
+
plots: [
|
|
39310
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
39311
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39312
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39313
|
+
],
|
|
39314
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39315
|
+
};
|
|
39316
|
+
}
|
|
39317
|
+
};
|
|
39318
|
+
var vfi = {
|
|
39319
|
+
type: "volume-flow-indicator",
|
|
39320
|
+
title: "Volume Flow Indicator",
|
|
39321
|
+
shortTitle: "Volume Flow Indicator",
|
|
39322
|
+
overlay: false,
|
|
39323
|
+
inputs: [
|
|
39324
|
+
lengthInput(130),
|
|
39325
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
39326
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
39327
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
39328
|
+
],
|
|
39329
|
+
compute: (bars, inputs) => {
|
|
39330
|
+
const len = num3(inputs, "length", 130);
|
|
39331
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
39332
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
39333
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
39334
|
+
const inter = change(typical);
|
|
39335
|
+
const vInter = stdev(inter, 30);
|
|
39336
|
+
const vol = volumes(bars);
|
|
39337
|
+
const vAve = sma(vol, len);
|
|
39338
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
39339
|
+
const raw = bars.map((b, i) => {
|
|
39340
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
39341
|
+
const v = vol[i];
|
|
39342
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39343
|
+
const m = mf[i];
|
|
39344
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
39345
|
+
const capped = Math.min(v, ave * volCoef);
|
|
39346
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
39347
|
+
});
|
|
39348
|
+
const values = bars.map((_, i) => {
|
|
39349
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39350
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
39351
|
+
});
|
|
39352
|
+
const flowSum = sum(raw, len);
|
|
39353
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
39354
|
+
return {
|
|
39355
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
39356
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39357
|
+
};
|
|
39358
|
+
}
|
|
39359
|
+
};
|
|
39360
|
+
var intradayIntensity = {
|
|
39361
|
+
type: "intraday-intensity",
|
|
39362
|
+
title: "Intraday Intensity",
|
|
39363
|
+
shortTitle: "Intraday Intensity",
|
|
39364
|
+
overlay: false,
|
|
39365
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39366
|
+
compute: (bars, inputs) => {
|
|
39367
|
+
const len = num3(inputs, "length", 21);
|
|
39368
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
39369
|
+
return {
|
|
39370
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39371
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39372
|
+
};
|
|
39373
|
+
}
|
|
39374
|
+
};
|
|
39375
|
+
var volumeSpecs = [
|
|
39376
|
+
obv,
|
|
39377
|
+
accDist,
|
|
39378
|
+
cmf,
|
|
39379
|
+
chaikinOscillator,
|
|
39380
|
+
eom,
|
|
39381
|
+
forceIndex,
|
|
39382
|
+
klinger,
|
|
39383
|
+
mfi,
|
|
39384
|
+
nvi,
|
|
39385
|
+
pvi,
|
|
39386
|
+
pvt,
|
|
39387
|
+
volumeOscillator,
|
|
39388
|
+
pvo,
|
|
39389
|
+
vfi,
|
|
39390
|
+
intradayIntensity
|
|
39391
|
+
];
|
|
39392
|
+
|
|
39393
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
39394
|
+
var DAY_MS2 = 864e5;
|
|
39395
|
+
function periodKey(anchor, time) {
|
|
39396
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
39397
|
+
if (anchor === "Month") {
|
|
39398
|
+
const d = new Date(time);
|
|
39399
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
39400
|
+
}
|
|
39401
|
+
return Math.floor(time / DAY_MS2);
|
|
39402
|
+
}
|
|
39403
|
+
var parabolicSar = {
|
|
39404
|
+
type: "parabolic-sar",
|
|
39405
|
+
title: "Parabolic SAR",
|
|
39406
|
+
shortTitle: "SAR",
|
|
39407
|
+
overlay: true,
|
|
39408
|
+
inputs: [
|
|
39409
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39410
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39411
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
39412
|
+
],
|
|
39413
|
+
compute: (bars, inputs) => {
|
|
39414
|
+
const start = num3(inputs, "start", 0.02);
|
|
39415
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
39416
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
39417
|
+
const n = bars.length;
|
|
39418
|
+
const values = new Array(n).fill(Number.NaN);
|
|
39419
|
+
const colors = new Array(n).fill(null);
|
|
39420
|
+
if (n >= 2) {
|
|
39421
|
+
let long = bars[1].close >= bars[0].close;
|
|
39422
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
39423
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
39424
|
+
let af = start;
|
|
39425
|
+
for (let i = 1; i < n; i++) {
|
|
39426
|
+
const b = bars[i];
|
|
39427
|
+
sar += af * (ep - sar);
|
|
39428
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
39429
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
39430
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
39431
|
+
if (reversed) {
|
|
39432
|
+
long = !long;
|
|
39433
|
+
sar = ep;
|
|
39434
|
+
ep = long ? b.high : b.low;
|
|
39435
|
+
af = start;
|
|
39436
|
+
} else if (long && b.high > ep) {
|
|
39437
|
+
ep = b.high;
|
|
39438
|
+
af = Math.min(max, af + inc);
|
|
39439
|
+
} else if (!long && b.low < ep) {
|
|
39440
|
+
ep = b.low;
|
|
39441
|
+
af = Math.min(max, af + inc);
|
|
39442
|
+
}
|
|
39443
|
+
values[i] = sar;
|
|
39444
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
39445
|
+
}
|
|
39446
|
+
}
|
|
39447
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
39448
|
+
}
|
|
39449
|
+
};
|
|
39450
|
+
var vwap = {
|
|
39451
|
+
type: "vwap",
|
|
39452
|
+
title: "Volume Weighted Average Price",
|
|
39453
|
+
shortTitle: "VWAP",
|
|
39454
|
+
overlay: true,
|
|
39455
|
+
inputs: [
|
|
39456
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
39457
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
39458
|
+
colorInput(INFO)
|
|
39459
|
+
],
|
|
39460
|
+
compute: (bars, inputs) => {
|
|
39461
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39462
|
+
const source = str3(inputs, "source", "HLC3");
|
|
39463
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
39464
|
+
let period = Number.NaN;
|
|
39465
|
+
let cumPV = 0;
|
|
39466
|
+
let cumV = 0;
|
|
39467
|
+
for (let i = 0; i < bars.length; i++) {
|
|
39468
|
+
const b = bars[i];
|
|
39469
|
+
const key = periodKey(anchor, b.time);
|
|
39470
|
+
if (key !== period) {
|
|
39471
|
+
period = key;
|
|
39472
|
+
cumPV = 0;
|
|
39473
|
+
cumV = 0;
|
|
39474
|
+
}
|
|
39475
|
+
const v = b.volume;
|
|
39476
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
39477
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
39478
|
+
cumPV += tp * v;
|
|
39479
|
+
cumV += v;
|
|
39480
|
+
}
|
|
39481
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
39482
|
+
}
|
|
39483
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
39484
|
+
}
|
|
39485
|
+
};
|
|
39486
|
+
var pivotPoints = {
|
|
39487
|
+
type: "pivot-points",
|
|
39488
|
+
title: "Pivot Points",
|
|
39489
|
+
shortTitle: "Pivots",
|
|
39490
|
+
overlay: true,
|
|
39491
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
39492
|
+
compute: (bars, inputs) => {
|
|
39493
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39494
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
39495
|
+
for (const b of bars) {
|
|
39496
|
+
const key = periodKey(anchor, b.time);
|
|
39497
|
+
const a = aggs.get(key);
|
|
39498
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
39499
|
+
else {
|
|
39500
|
+
a.high = Math.max(a.high, b.high);
|
|
39501
|
+
a.low = Math.min(a.low, b.low);
|
|
39502
|
+
a.close = b.close;
|
|
39503
|
+
}
|
|
39504
|
+
}
|
|
39505
|
+
const n = bars.length;
|
|
39506
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
39507
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
39508
|
+
for (let i = 0; i < n; i++) {
|
|
39509
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
39510
|
+
if (!prev2) continue;
|
|
39511
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
39512
|
+
levels.p[i] = p;
|
|
39513
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
39514
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
39515
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
39516
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
39517
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
39518
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
39519
|
+
}
|
|
39520
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
39521
|
+
return {
|
|
39522
|
+
plots: [
|
|
39523
|
+
plot("p", "P", WARNING),
|
|
39524
|
+
plot("r1", "R1", BEARISH),
|
|
39525
|
+
plot("s1", "S1", BULLISH),
|
|
39526
|
+
plot("r2", "R2", BEARISH),
|
|
39527
|
+
plot("s2", "S2", BULLISH),
|
|
39528
|
+
plot("r3", "R3", BEARISH),
|
|
39529
|
+
plot("s3", "S3", BULLISH)
|
|
39530
|
+
]
|
|
39531
|
+
};
|
|
39532
|
+
}
|
|
39533
|
+
};
|
|
39534
|
+
var fiftyTwoWeek = {
|
|
39535
|
+
type: "52-week-high-low",
|
|
39536
|
+
title: "52 Week High/Low",
|
|
39537
|
+
shortTitle: "52W H/L",
|
|
39538
|
+
overlay: true,
|
|
39539
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
39540
|
+
compute: (bars, inputs) => {
|
|
39541
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
39542
|
+
const n = bars.length;
|
|
39543
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
39544
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
39545
|
+
const maxIdx = [];
|
|
39546
|
+
const minIdx = [];
|
|
39547
|
+
let from = 0;
|
|
39548
|
+
for (let i = 0; i < n; i++) {
|
|
39549
|
+
const b = bars[i];
|
|
39550
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
39551
|
+
maxIdx.push(i);
|
|
39552
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
39553
|
+
minIdx.push(i);
|
|
39554
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
39555
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
39556
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
39557
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
39558
|
+
lo[i] = bars[minIdx[0]].low;
|
|
39559
|
+
}
|
|
39560
|
+
return {
|
|
39561
|
+
plots: [
|
|
39562
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
39563
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
39564
|
+
]
|
|
39565
|
+
};
|
|
39566
|
+
}
|
|
39567
|
+
};
|
|
39568
|
+
var zigzag = {
|
|
39569
|
+
type: "zigzag",
|
|
39570
|
+
title: "ZigZag",
|
|
39571
|
+
overlay: true,
|
|
39572
|
+
inputs: [
|
|
39573
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
39574
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39575
|
+
colorInput()
|
|
39576
|
+
],
|
|
39577
|
+
compute: (bars, inputs) => {
|
|
39578
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
39579
|
+
const depth = num3(inputs, "depth", 10);
|
|
39580
|
+
const pivots = [];
|
|
39581
|
+
if (bars.length > 1) {
|
|
39582
|
+
let up = bars[1].close >= bars[0].close;
|
|
39583
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
39584
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39585
|
+
const b = bars[i];
|
|
39586
|
+
if (up) {
|
|
39587
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
39588
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39589
|
+
pivots.push(ext);
|
|
39590
|
+
up = false;
|
|
39591
|
+
ext = { i, price: b.low };
|
|
39592
|
+
}
|
|
39593
|
+
} else if (b.low <= ext.price) {
|
|
39594
|
+
ext = { i, price: b.low };
|
|
39595
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39596
|
+
pivots.push(ext);
|
|
39597
|
+
up = true;
|
|
39598
|
+
ext = { i, price: b.high };
|
|
39599
|
+
}
|
|
39600
|
+
}
|
|
39601
|
+
pivots.push(ext);
|
|
39602
|
+
}
|
|
39603
|
+
return {
|
|
39604
|
+
plots: [],
|
|
39605
|
+
polylines: [
|
|
39606
|
+
{
|
|
39607
|
+
key: "zigzag",
|
|
39608
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
39609
|
+
curved: false,
|
|
39610
|
+
closed: false,
|
|
39611
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
39612
|
+
lineWidth: 2,
|
|
39613
|
+
lineStyle: "solid",
|
|
39614
|
+
arrowLeft: false,
|
|
39615
|
+
arrowRight: false
|
|
39616
|
+
}
|
|
39617
|
+
]
|
|
39618
|
+
};
|
|
39619
|
+
}
|
|
39620
|
+
};
|
|
39621
|
+
var williamsFractal = {
|
|
39622
|
+
type: "williams-fractal",
|
|
39623
|
+
title: "Williams Fractal",
|
|
39624
|
+
shortTitle: "Fractals",
|
|
39625
|
+
overlay: true,
|
|
39626
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
39627
|
+
compute: (bars, inputs) => {
|
|
39628
|
+
const p = num3(inputs, "periods", 2);
|
|
39629
|
+
const markers = [];
|
|
39630
|
+
const isExtreme = (i, pick, better) => {
|
|
39631
|
+
const v = pick(bars[i]);
|
|
39632
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
39633
|
+
if (k === i) continue;
|
|
39634
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
39635
|
+
}
|
|
39636
|
+
return true;
|
|
39637
|
+
};
|
|
39638
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
39639
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
39640
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
39641
|
+
}
|
|
39642
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
39643
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
39644
|
+
}
|
|
39645
|
+
}
|
|
39646
|
+
return { plots: [], markers };
|
|
39647
|
+
}
|
|
39648
|
+
};
|
|
39649
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
39650
|
+
|
|
39651
|
+
// src/core/native-indicators/classics/index.ts
|
|
39652
|
+
var classicSpecs = [
|
|
39653
|
+
...averageSpecs,
|
|
39654
|
+
...bandSpecs,
|
|
39655
|
+
...oscillatorSpecs,
|
|
39656
|
+
...trendSpecs,
|
|
39657
|
+
...volatilitySpecs,
|
|
39658
|
+
...volumeSpecs,
|
|
39659
|
+
...overlaySpecs
|
|
39660
|
+
];
|
|
39661
|
+
function registerClassicIndicators() {
|
|
39662
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
39663
|
+
}
|
|
39664
|
+
|
|
37259
39665
|
// src/Vela.ts
|
|
37260
39666
|
var Vela = class {
|
|
37261
39667
|
constructor(container, options = {}, deps = {}) {
|
|
37262
39668
|
registerBuiltinChartTypes();
|
|
37263
39669
|
registerVolume();
|
|
37264
39670
|
registerVpvr();
|
|
39671
|
+
registerClassicIndicators();
|
|
37265
39672
|
const element = resolveElement(container);
|
|
37266
39673
|
const theme = resolveTheme(options.theme);
|
|
37267
|
-
let animZoom = true;
|
|
37268
|
-
let animPan = true;
|
|
37269
|
-
if (typeof options.animations === "boolean") {
|
|
37270
|
-
animZoom = options.animations;
|
|
37271
|
-
animPan = options.animations;
|
|
37272
|
-
} else if (options.animations) {
|
|
37273
|
-
animZoom = options.animations.zoom ?? true;
|
|
37274
|
-
animPan = options.animations.pan ?? true;
|
|
37275
|
-
}
|
|
37276
39674
|
const display = {
|
|
37277
39675
|
currentPriceLine: options.currentPriceLine ?? true,
|
|
37278
39676
|
logScale: options.logScale ?? false,
|
|
37279
39677
|
nativeBackend: options.nativeBackend ?? "auto",
|
|
37280
|
-
|
|
37281
|
-
animPan,
|
|
39678
|
+
...resolveAnimations(options.animations),
|
|
37282
39679
|
glow: options.glow ?? 0,
|
|
37283
39680
|
upColor: options.upColor ?? BULLISH,
|
|
37284
39681
|
downColor: options.downColor ?? BEARISH,
|
|
@@ -37336,25 +39733,28 @@ var Vela = class {
|
|
|
37336
39733
|
}
|
|
37337
39734
|
/**
|
|
37338
39735
|
* 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
|
-
*
|
|
39736
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
39737
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
39738
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
39739
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
37342
39740
|
*/
|
|
37343
39741
|
addNativeIndicator(type, options) {
|
|
37344
39742
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
37345
39743
|
}
|
|
37346
39744
|
/**
|
|
37347
39745
|
* 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
|
-
*
|
|
39746
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
39747
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
39748
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
39749
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
39750
|
+
* provider (a type may need data the symbol lacks).
|
|
37352
39751
|
*/
|
|
37353
39752
|
availableNativeIndicators() {
|
|
37354
39753
|
return this.orchestrator.availableNativeIndicators();
|
|
37355
39754
|
}
|
|
37356
39755
|
/**
|
|
37357
|
-
* The native-indicator types PRESENT on the chart right now
|
|
39756
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
39757
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
37358
39758
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
37359
39759
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
37360
39760
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -37590,9 +39990,6 @@ function resolveElement(container) {
|
|
|
37590
39990
|
return element;
|
|
37591
39991
|
}
|
|
37592
39992
|
|
|
37593
|
-
// src/core/options.ts
|
|
37594
|
-
var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
|
|
37595
|
-
|
|
37596
39993
|
// src/widget/statusline-model.ts
|
|
37597
39994
|
function segmentVisibility(parts, chartHidden = false) {
|
|
37598
39995
|
return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
|
|
@@ -38097,7 +40494,7 @@ var Statusline = class {
|
|
|
38097
40494
|
};
|
|
38098
40495
|
|
|
38099
40496
|
// src/widget/session-shading.ts
|
|
38100
|
-
var
|
|
40497
|
+
var DAY_MS3 = 864e5;
|
|
38101
40498
|
var MINUTE_MS = 6e4;
|
|
38102
40499
|
function parseWindow(text, allowOvernight = false) {
|
|
38103
40500
|
if (typeof text !== "string") return null;
|
|
@@ -38176,7 +40573,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38176
40573
|
const dtf = civilFormatter(spec.timezone);
|
|
38177
40574
|
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38178
40575
|
const seen = /* @__PURE__ */ new Set();
|
|
38179
|
-
for (let cursor = from -
|
|
40576
|
+
for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
|
|
38180
40577
|
const civil = civilParts(dtf, cursor);
|
|
38181
40578
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38182
40579
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
@@ -38201,7 +40598,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38201
40598
|
}
|
|
38202
40599
|
return { pre, post, extended: mergeAbutting(extended) };
|
|
38203
40600
|
}
|
|
38204
|
-
var COVER_PAD_MIN_MS = 2 *
|
|
40601
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS3;
|
|
38205
40602
|
var SessionShadingTracker = class {
|
|
38206
40603
|
constructor(onZones) {
|
|
38207
40604
|
this.onZones = onZones;
|
|
@@ -38225,7 +40622,7 @@ var SessionShadingTracker = class {
|
|
|
38225
40622
|
this.covered = null;
|
|
38226
40623
|
this.session = opts.session;
|
|
38227
40624
|
const tfMs = timeframeMs(opts.timeframe);
|
|
38228
|
-
this.intraday = Number.isFinite(tfMs) && tfMs <
|
|
40625
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
|
|
38229
40626
|
this.lastRange = opts.range;
|
|
38230
40627
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
38231
40628
|
if (my !== this.epoch) return;
|
|
@@ -38899,8 +41296,8 @@ var ChartCell = class {
|
|
|
38899
41296
|
/** The volume auto-add rides the cell's first candles (`load:end`); until then the
|
|
38900
41297
|
* registry can't show it and the dehydrated ledger reports the INTENT instead. */
|
|
38901
41298
|
this.volumeMayBePending = true;
|
|
38902
|
-
/** Sync mirror of the chart's
|
|
38903
|
-
*
|
|
41299
|
+
/** Sync mirror of the chart's native instances (id + type) — the removal handler
|
|
41300
|
+
* looks the removed id up here to learn which type an undo must re-add. */
|
|
38904
41301
|
this.presentNatives = [];
|
|
38905
41302
|
this.rangeBars = 0;
|
|
38906
41303
|
this.pendingRange = null;
|
|
@@ -39072,15 +41469,18 @@ var ChartCell = class {
|
|
|
39072
41469
|
redo: () => this.dropInstance(snapshot)
|
|
39073
41470
|
});
|
|
39074
41471
|
} else {
|
|
39075
|
-
const
|
|
39076
|
-
|
|
41472
|
+
const gone = this.presentNatives.find((n) => n.id === id2);
|
|
41473
|
+
if (gone) {
|
|
41474
|
+
const { type } = gone;
|
|
41475
|
+
let revived = null;
|
|
39077
41476
|
this.history.push({
|
|
39078
41477
|
undo: () => {
|
|
39079
|
-
this.inner?.addNativeIndicator(type);
|
|
41478
|
+
revived = this.inner?.addNativeIndicator(type) ?? null;
|
|
39080
41479
|
this.refreshNativeCatalog();
|
|
39081
41480
|
},
|
|
39082
41481
|
redo: () => {
|
|
39083
|
-
|
|
41482
|
+
revived?.remove();
|
|
41483
|
+
revived = null;
|
|
39084
41484
|
this.refreshNativeCatalog();
|
|
39085
41485
|
}
|
|
39086
41486
|
});
|
|
@@ -39385,9 +41785,9 @@ var ChartCell = class {
|
|
|
39385
41785
|
const live = this.inner?.renderer.get("priceStyle");
|
|
39386
41786
|
return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
|
|
39387
41787
|
}
|
|
39388
|
-
/** Manifest instances +
|
|
41788
|
+
/** Manifest instances + native instances — the topbar indicator count. */
|
|
39389
41789
|
get indicatorCount() {
|
|
39390
|
-
return this.instances.length + this.
|
|
41790
|
+
return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
|
|
39391
41791
|
}
|
|
39392
41792
|
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
39393
41793
|
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
@@ -39519,13 +41919,15 @@ var ChartCell = class {
|
|
|
39519
41919
|
if (!chart) return;
|
|
39520
41920
|
this.volumeIntent = led.natives.includes("volume");
|
|
39521
41921
|
this.history.silently(() => {
|
|
39522
|
-
const
|
|
39523
|
-
for (const type of led.natives)
|
|
39524
|
-
|
|
39525
|
-
|
|
39526
|
-
|
|
39527
|
-
if (
|
|
39528
|
-
|
|
41922
|
+
const owed = /* @__PURE__ */ new Map();
|
|
41923
|
+
for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
|
|
41924
|
+
for (const h of this.nativeHandles()) {
|
|
41925
|
+
const type = h.nativeType;
|
|
41926
|
+
const n = owed.get(type) ?? 0;
|
|
41927
|
+
if (n > 0) owed.set(type, n - 1);
|
|
41928
|
+
else h.remove();
|
|
41929
|
+
}
|
|
41930
|
+
for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
|
|
39529
41931
|
for (const it of [...this.instances]) this.dropInstance(it);
|
|
39530
41932
|
if (this.manifest.length > 0) {
|
|
39531
41933
|
for (const item of led.manifest) {
|
|
@@ -39543,34 +41945,42 @@ var ChartCell = class {
|
|
|
39543
41945
|
this.syncPresentNatives();
|
|
39544
41946
|
this.refreshNativeCatalog();
|
|
39545
41947
|
}
|
|
39546
|
-
/** The picker
|
|
41948
|
+
/** The supported natives in picker order: A→Z by title — registration order follows
|
|
41949
|
+
* the catalog's families, which is meaningless to the reader. This is the library
|
|
41950
|
+
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
41951
|
+
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
41952
|
+
supportedNatives() {
|
|
41953
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
|
|
41954
|
+
}
|
|
41955
|
+
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
41956
|
+
* then the manifest in the host's own order. */
|
|
39547
41957
|
libraryRows() {
|
|
39548
41958
|
return [
|
|
39549
|
-
...this.
|
|
41959
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
|
|
39550
41960
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
39551
41961
|
];
|
|
39552
41962
|
}
|
|
39553
|
-
/** The picker's on-chart rows:
|
|
41963
|
+
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
39554
41964
|
onChartRows() {
|
|
39555
41965
|
return [
|
|
39556
|
-
...this.
|
|
41966
|
+
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
39557
41967
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
39558
41968
|
];
|
|
39559
41969
|
}
|
|
39560
41970
|
/** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
|
|
39561
41971
|
addFromLibrary(index) {
|
|
39562
|
-
const natives = this.
|
|
41972
|
+
const natives = this.supportedNatives();
|
|
39563
41973
|
if (index < natives.length) this.addNative(natives[index].type);
|
|
39564
41974
|
else {
|
|
39565
41975
|
const entry = this.manifest[index - natives.length];
|
|
39566
41976
|
if (entry) this.addManifestInstance(entry);
|
|
39567
41977
|
}
|
|
39568
41978
|
}
|
|
39569
|
-
/** Remove by picker ON-CHART index (
|
|
41979
|
+
/** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
|
|
39570
41980
|
removeFromChart(index) {
|
|
39571
|
-
const
|
|
39572
|
-
if (index <
|
|
39573
|
-
else this.removeInstance(index -
|
|
41981
|
+
const natives = this.nativeHandles();
|
|
41982
|
+
if (index < natives.length) this.removeNative(natives[index]);
|
|
41983
|
+
else this.removeInstance(index - natives.length);
|
|
39574
41984
|
}
|
|
39575
41985
|
/**
|
|
39576
41986
|
* Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
|
|
@@ -39630,30 +42040,42 @@ var ChartCell = class {
|
|
|
39630
42040
|
it.handle = null;
|
|
39631
42041
|
this.deps.onIndicatorsChanged(this.id);
|
|
39632
42042
|
}
|
|
39633
|
-
/** Add a native indicator
|
|
42043
|
+
/** Add a native indicator. A multi-instance type gets a fresh instance every time; a
|
|
42044
|
+
* single-instance type already on the chart hands back its existing one — nothing
|
|
42045
|
+
* changed, so nothing enters the undo timeline. */
|
|
39634
42046
|
addNative(type) {
|
|
39635
|
-
this.inner
|
|
42047
|
+
const chart = this.inner;
|
|
42048
|
+
if (!chart) return;
|
|
42049
|
+
const before = new Set(chart.indicators().map((h) => h.id));
|
|
42050
|
+
let added = chart.addNativeIndicator(type);
|
|
39636
42051
|
this.syncPresentNatives();
|
|
39637
42052
|
this.refreshNativeCatalog();
|
|
42053
|
+
if (before.has(added.id)) return;
|
|
39638
42054
|
this.history.push({
|
|
39639
42055
|
undo: () => {
|
|
39640
|
-
|
|
42056
|
+
added?.remove();
|
|
42057
|
+
added = null;
|
|
39641
42058
|
this.refreshNativeCatalog();
|
|
39642
42059
|
},
|
|
39643
42060
|
redo: () => {
|
|
39644
|
-
this.inner?.addNativeIndicator(type);
|
|
42061
|
+
added = this.inner?.addNativeIndicator(type) ?? null;
|
|
39645
42062
|
this.refreshNativeCatalog();
|
|
39646
42063
|
}
|
|
39647
42064
|
});
|
|
39648
42065
|
}
|
|
39649
|
-
removeNative(
|
|
39650
|
-
|
|
42066
|
+
removeNative(handle) {
|
|
42067
|
+
handle.remove();
|
|
39651
42068
|
this.refreshNativeCatalog();
|
|
39652
42069
|
}
|
|
39653
|
-
/**
|
|
39654
|
-
*
|
|
42070
|
+
/** The chart's native instances, insertion order — the picker's on-chart rows and
|
|
42071
|
+
* the removal index space (script instances follow them). */
|
|
42072
|
+
nativeHandles() {
|
|
42073
|
+
return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
|
|
42074
|
+
}
|
|
42075
|
+
/** Sync mirror of the chart's native instances — the removal handler looks the
|
|
42076
|
+
* removed id up here (the registry has already forgotten it) to record its type. */
|
|
39655
42077
|
syncPresentNatives() {
|
|
39656
|
-
this.presentNatives = this.
|
|
42078
|
+
this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
|
|
39657
42079
|
}
|
|
39658
42080
|
/** Refresh the native catalog (supported/present flags) for this cell's market. */
|
|
39659
42081
|
refreshNativeCatalog() {
|