@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/widget.cjs
CHANGED
|
@@ -667,7 +667,7 @@ function attachForming(closed, forming) {
|
|
|
667
667
|
|
|
668
668
|
// src/data/MultiProviderFeed.ts
|
|
669
669
|
function intervalMs(timeframe) {
|
|
670
|
-
const
|
|
670
|
+
const map2 = {
|
|
671
671
|
"1m": 6e4,
|
|
672
672
|
"3m": 18e4,
|
|
673
673
|
"5m": 3e5,
|
|
@@ -689,7 +689,7 @@ function intervalMs(timeframe) {
|
|
|
689
689
|
W: 6048e5,
|
|
690
690
|
M: 2592e6
|
|
691
691
|
};
|
|
692
|
-
return
|
|
692
|
+
return map2[timeframe] ?? 36e5;
|
|
693
693
|
}
|
|
694
694
|
var MultiProviderFeed = class {
|
|
695
695
|
constructor(store = sharedBarStore) {
|
|
@@ -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();
|
|
@@ -5167,9 +5175,9 @@ function factoryResetConfig(factory) {
|
|
|
5167
5175
|
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
5168
5176
|
addRows(inst.rows);
|
|
5169
5177
|
}
|
|
5170
|
-
for (const
|
|
5171
|
-
if (
|
|
5172
|
-
addRows(
|
|
5178
|
+
for (const sub2 of section.subsections ?? []) {
|
|
5179
|
+
if (sub2.enableKey) defaults2[sub2.enableKey] = false;
|
|
5180
|
+
addRows(sub2.rows);
|
|
5173
5181
|
}
|
|
5174
5182
|
if (!section.instances) addRows(section.rows);
|
|
5175
5183
|
bag[t.id] = defaults2;
|
|
@@ -5238,7 +5246,8 @@ function mergeConfig(base, patch) {
|
|
|
5238
5246
|
labelsVisible: isBool(ps.labelsVisible) ? ps.labelsVisible : base.priceScale.labelsVisible,
|
|
5239
5247
|
currentPriceLine: isBool(ps.currentPriceLine) ? ps.currentPriceLine : base.priceScale.currentPriceLine,
|
|
5240
5248
|
priceLabel: isBool(ps.priceLabel) ? ps.priceLabel : base.priceScale.priceLabel,
|
|
5241
|
-
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown
|
|
5249
|
+
countdown: isBool(ps.countdown) ? ps.countdown : base.priceScale.countdown,
|
|
5250
|
+
animateLastPrice: isBool(ps.animateLastPrice) ? ps.animateLastPrice : base.priceScale.animateLastPrice
|
|
5242
5251
|
},
|
|
5243
5252
|
panes: {
|
|
5244
5253
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
@@ -14098,17 +14107,12 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
14098
14107
|
}
|
|
14099
14108
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
14100
14109
|
var CSS8 = `
|
|
14101
|
-
/*
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
z-index: 1;
|
|
14108
|
-
background: var(--vela-surface);
|
|
14109
|
-
margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
|
|
14110
|
-
padding: var(--vela-space-4) var(--vela-space-4) 0;
|
|
14111
|
-
}
|
|
14110
|
+
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
14111
|
+
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
14112
|
+
scrolling padded body is not an option \u2014 browsers pin sticky boxes to the
|
|
14113
|
+
scroller's content box, so the head lands one padding below the top and covers
|
|
14114
|
+
the first row. */
|
|
14115
|
+
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
14112
14116
|
.vela-sp-searchrow {
|
|
14113
14117
|
display: flex;
|
|
14114
14118
|
align-items: center;
|
|
@@ -14133,12 +14137,13 @@ var CSS8 = `
|
|
|
14133
14137
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
14134
14138
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
14135
14139
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
14136
|
-
overflowing the body, and the result list stops capping itself \u2014
|
|
14137
|
-
the
|
|
14140
|
+
overflowing the body, and the result list stops capping itself \u2014 it fills the
|
|
14141
|
+
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
14142
|
+
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
14138
14143
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
14139
14144
|
[data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
|
|
14140
14145
|
[data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
|
|
14141
|
-
[data-layout='mobile'] .vela-sp-list { max-height: none; }
|
|
14146
|
+
[data-layout='mobile'] .vela-sp-list { flex: 1 1 auto; max-height: none; margin-bottom: 0; }
|
|
14142
14147
|
.vela-sp-tab {
|
|
14143
14148
|
all: unset;
|
|
14144
14149
|
padding: 3px 10px;
|
|
@@ -14150,7 +14155,12 @@ var CSS8 = `
|
|
|
14150
14155
|
}
|
|
14151
14156
|
.vela-sp-tab:hover { color: var(--vela-fg); }
|
|
14152
14157
|
.vela-sp-tab[data-active] { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
14153
|
-
.vela-sp-list {
|
|
14158
|
+
.vela-sp-list {
|
|
14159
|
+
min-height: 0;
|
|
14160
|
+
margin: var(--vela-space-2) var(--vela-space-4) var(--vela-space-4);
|
|
14161
|
+
max-height: 46vh;
|
|
14162
|
+
overflow: auto;
|
|
14163
|
+
}
|
|
14154
14164
|
.vela-sp-list::-webkit-scrollbar { width: 8px; }
|
|
14155
14165
|
.vela-sp-list::-webkit-scrollbar-thumb {
|
|
14156
14166
|
background: var(--vela-scroll);
|
|
@@ -14243,8 +14253,8 @@ var SymbolPicker = class {
|
|
|
14243
14253
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14244
14254
|
this.tabs = doc.createElement("div");
|
|
14245
14255
|
this.tabs.className = "vela-sp-tabs";
|
|
14246
|
-
const
|
|
14247
|
-
|
|
14256
|
+
const head = doc.createElement("div");
|
|
14257
|
+
head.className = "vela-sp-head";
|
|
14248
14258
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14249
14259
|
const b = doc.createElement("button");
|
|
14250
14260
|
b.className = "vela-sp-tab";
|
|
@@ -14258,7 +14268,7 @@ var SymbolPicker = class {
|
|
|
14258
14268
|
});
|
|
14259
14269
|
this.tabs.appendChild(b);
|
|
14260
14270
|
}
|
|
14261
|
-
|
|
14271
|
+
head.append(searchRow, this.tabs);
|
|
14262
14272
|
this.list = doc.createElement("div");
|
|
14263
14273
|
this.list.className = "vela-sp-list";
|
|
14264
14274
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14271,7 +14281,8 @@ var SymbolPicker = class {
|
|
|
14271
14281
|
title: "Symbol Search",
|
|
14272
14282
|
host: opts.host,
|
|
14273
14283
|
closeOnInteractOutside: true,
|
|
14274
|
-
|
|
14284
|
+
flush: true,
|
|
14285
|
+
content: (body) => body.append(head, this.list),
|
|
14275
14286
|
onOpenChange: (open2) => {
|
|
14276
14287
|
if (open2) {
|
|
14277
14288
|
this.input.value = this.seed.toUpperCase();
|
|
@@ -16042,11 +16053,11 @@ var PriceScaleDrawer = class {
|
|
|
16042
16053
|
for (const item of items) {
|
|
16043
16054
|
if (item.id === "labels" && item.submenu) {
|
|
16044
16055
|
section("Labels");
|
|
16045
|
-
for (const
|
|
16046
|
-
row(
|
|
16047
|
-
checked:
|
|
16056
|
+
for (const sub2 of item.submenu) {
|
|
16057
|
+
row(sub2.label, {
|
|
16058
|
+
checked: sub2.checked,
|
|
16048
16059
|
onClick: () => {
|
|
16049
|
-
const feature =
|
|
16060
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
16050
16061
|
chart.renderer.set(feature, !this.flag(feature));
|
|
16051
16062
|
this.render();
|
|
16052
16063
|
}
|
|
@@ -16056,11 +16067,11 @@ var PriceScaleDrawer = class {
|
|
|
16056
16067
|
}
|
|
16057
16068
|
if (item.id === "levels" && item.submenu) {
|
|
16058
16069
|
section("Levels");
|
|
16059
|
-
for (const
|
|
16060
|
-
row(
|
|
16061
|
-
checked:
|
|
16070
|
+
for (const sub2 of item.submenu) {
|
|
16071
|
+
row(sub2.label, {
|
|
16072
|
+
checked: sub2.checked,
|
|
16062
16073
|
onClick: () => {
|
|
16063
|
-
const feature =
|
|
16074
|
+
const feature = sub2.id.slice("toggle:".length);
|
|
16064
16075
|
chart.renderer.set(feature, !this.flag(feature));
|
|
16065
16076
|
this.render();
|
|
16066
16077
|
}
|
|
@@ -16634,8 +16645,8 @@ var DrawingController = class {
|
|
|
16634
16645
|
this.port?.setExternalGhost?.(doc);
|
|
16635
16646
|
}
|
|
16636
16647
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
16637
|
-
setToolShortcuts(
|
|
16638
|
-
this.port?.setToolShortcuts?.(
|
|
16648
|
+
setToolShortcuts(map2) {
|
|
16649
|
+
this.port?.setToolShortcuts?.(map2);
|
|
16639
16650
|
}
|
|
16640
16651
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
16641
16652
|
add(type, init = {}) {
|
|
@@ -17199,6 +17210,23 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
17199
17210
|
clear();
|
|
17200
17211
|
};
|
|
17201
17212
|
}
|
|
17213
|
+
function chromeHint(text, opts) {
|
|
17214
|
+
const px = opts.size ?? 16;
|
|
17215
|
+
const el = document.createElement("span");
|
|
17216
|
+
el.className = "vela-hint";
|
|
17217
|
+
el.setAttribute("role", "img");
|
|
17218
|
+
el.setAttribute("aria-label", text);
|
|
17219
|
+
el.innerHTML = iconAt("info", px);
|
|
17220
|
+
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;`;
|
|
17221
|
+
const hover = (on) => {
|
|
17222
|
+
el.style.background = on ? "var(--vela-active)" : "var(--vela-hover)";
|
|
17223
|
+
el.style.color = on ? "var(--vela-fg-bright)" : "var(--vela-fg-muted)";
|
|
17224
|
+
};
|
|
17225
|
+
el.addEventListener("pointerenter", () => hover(true));
|
|
17226
|
+
el.addEventListener("pointerleave", () => hover(false));
|
|
17227
|
+
const dispose = attachChromeTooltip(el, { host: opts.host, theme: opts.theme, text: () => text, wrap: true, delayMs: 250 });
|
|
17228
|
+
return { el, dispose };
|
|
17229
|
+
}
|
|
17202
17230
|
|
|
17203
17231
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17204
17232
|
var TOOLBAR_WIDTH = 44;
|
|
@@ -17306,8 +17334,8 @@ var DrawingToolbar = class {
|
|
|
17306
17334
|
}
|
|
17307
17335
|
/** Per-tool shortcut hints (pre-formatted display strings, e.g. `'Alt+T'`) shown at the
|
|
17308
17336
|
* right edge of the flyout rows, beside the favorite star. */
|
|
17309
|
-
setShortcuts(
|
|
17310
|
-
this.shortcuts = new Map(Object.entries(
|
|
17337
|
+
setShortcuts(map2) {
|
|
17338
|
+
this.shortcuts = new Map(Object.entries(map2));
|
|
17311
17339
|
}
|
|
17312
17340
|
/** Paint one star for the current favorite state (filled + gold, or outline). */
|
|
17313
17341
|
paintStar(el, type) {
|
|
@@ -18190,6 +18218,24 @@ function localStorageAdapter(storageKey) {
|
|
|
18190
18218
|
};
|
|
18191
18219
|
}
|
|
18192
18220
|
|
|
18221
|
+
// src/core/options.ts
|
|
18222
|
+
var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
|
|
18223
|
+
var LIVE_BAR_EASE_DEFAULT_MS = 90;
|
|
18224
|
+
var LIVE_BAR_EASE_MAX_MS = 1e3;
|
|
18225
|
+
function resolveLiveBarEaseMs(value) {
|
|
18226
|
+
if (value === true) return LIVE_BAR_EASE_DEFAULT_MS;
|
|
18227
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
18228
|
+
return Math.min(value, LIVE_BAR_EASE_MAX_MS);
|
|
18229
|
+
}
|
|
18230
|
+
function resolveAnimations(animations) {
|
|
18231
|
+
if (typeof animations === "boolean") return { animZoom: animations, animPan: animations, animLiveBar: 0 };
|
|
18232
|
+
return {
|
|
18233
|
+
animZoom: animations?.zoom ?? true,
|
|
18234
|
+
animPan: animations?.pan ?? true,
|
|
18235
|
+
animLiveBar: resolveLiveBarEaseMs(animations?.liveBar)
|
|
18236
|
+
};
|
|
18237
|
+
}
|
|
18238
|
+
|
|
18193
18239
|
// src/core/model/series.ts
|
|
18194
18240
|
function isLineLikeSeries(spec) {
|
|
18195
18241
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
@@ -18254,7 +18300,7 @@ var LiveSession = class {
|
|
|
18254
18300
|
|
|
18255
18301
|
// src/core/engine/IndicatorHandleImpl.ts
|
|
18256
18302
|
var IndicatorHandleImpl = class {
|
|
18257
|
-
constructor(id, title, controller, source) {
|
|
18303
|
+
constructor(id, title, controller, source, nativeType) {
|
|
18258
18304
|
this.controller = controller;
|
|
18259
18305
|
this.schema = [];
|
|
18260
18306
|
this.propsSchema = [];
|
|
@@ -18263,6 +18309,7 @@ var IndicatorHandleImpl = class {
|
|
|
18263
18309
|
this.id = id;
|
|
18264
18310
|
this.title = title;
|
|
18265
18311
|
if (source !== void 0) this.source = source;
|
|
18312
|
+
if (nativeType !== void 0) this.nativeType = nativeType;
|
|
18266
18313
|
}
|
|
18267
18314
|
get inputs() {
|
|
18268
18315
|
return this.schema;
|
|
@@ -18353,24 +18400,24 @@ function summarizeModel(model) {
|
|
|
18353
18400
|
}
|
|
18354
18401
|
function inspectModels(models) {
|
|
18355
18402
|
const indicators = models.map(summarizeModel);
|
|
18356
|
-
const
|
|
18403
|
+
const sum2 = (pick) => indicators.reduce((n, s) => n + pick(s), 0);
|
|
18357
18404
|
return {
|
|
18358
18405
|
indicators,
|
|
18359
18406
|
totals: {
|
|
18360
18407
|
panes: new Set(indicators.map((s) => s.paneId ?? "price")).size,
|
|
18361
|
-
series:
|
|
18362
|
-
fills:
|
|
18363
|
-
backgrounds:
|
|
18364
|
-
priceLines:
|
|
18365
|
-
lines:
|
|
18366
|
-
boxes:
|
|
18367
|
-
labels:
|
|
18368
|
-
polylines:
|
|
18369
|
-
linefills:
|
|
18370
|
-
tables:
|
|
18371
|
-
barColors:
|
|
18372
|
-
trades:
|
|
18373
|
-
forcedOverlay:
|
|
18408
|
+
series: sum2((s) => Object.values(s.series).reduce((a, b) => a + b, 0)),
|
|
18409
|
+
fills: sum2((s) => s.fills),
|
|
18410
|
+
backgrounds: sum2((s) => s.backgrounds),
|
|
18411
|
+
priceLines: sum2((s) => s.priceLines),
|
|
18412
|
+
lines: sum2((s) => s.lines),
|
|
18413
|
+
boxes: sum2((s) => s.boxes),
|
|
18414
|
+
labels: sum2((s) => s.labels),
|
|
18415
|
+
polylines: sum2((s) => s.polylines),
|
|
18416
|
+
linefills: sum2((s) => s.linefills),
|
|
18417
|
+
tables: sum2((s) => s.tables),
|
|
18418
|
+
barColors: sum2((s) => s.barColors),
|
|
18419
|
+
trades: sum2((s) => s.trades),
|
|
18420
|
+
forcedOverlay: sum2((s) => s.forcedOverlay)
|
|
18374
18421
|
}
|
|
18375
18422
|
};
|
|
18376
18423
|
}
|
|
@@ -19439,16 +19486,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19439
19486
|
/**
|
|
19440
19487
|
* Add a NATIVE (core-computed, no Pine engine) indicator by registered type — a first-class
|
|
19441
19488
|
* indicator that shares the registry, handle, legend, settings, and lifecycle events. SINGLE
|
|
19442
|
-
* INSTANCE per type
|
|
19443
|
-
*
|
|
19489
|
+
* INSTANCE per type unless the descriptor opts into `multiInstance`: a second add of a
|
|
19490
|
+
* single-instance type returns the existing handle; a multi-instance type gets a fresh
|
|
19491
|
+
* instance every time. Unknown type ⇒ a fail-soft handle that never mounts (mirrors
|
|
19492
|
+
* addIndicator). Built-in single-instance types: `'volume'`, `'vpvr'`.
|
|
19444
19493
|
*/
|
|
19445
19494
|
addNativeIndicator(type, options = {}) {
|
|
19446
|
-
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19447
|
-
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this);
|
|
19448
19495
|
const descriptor = getNativeIndicator(type);
|
|
19496
|
+
if (!descriptor?.multiInstance) {
|
|
19497
|
+
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
19498
|
+
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
19499
|
+
}
|
|
19449
19500
|
const id = this.registry.nextId("native");
|
|
19450
19501
|
const title = descriptor?.title ?? type;
|
|
19451
|
-
const handle = new IndicatorHandleImpl(id, title, this);
|
|
19502
|
+
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
19452
19503
|
this.handles.set(id, handle);
|
|
19453
19504
|
if (!descriptor) {
|
|
19454
19505
|
console.warn(`[vela] addNativeIndicator("${type}") \u2014 no native indicator registered for this type.`);
|
|
@@ -19460,16 +19511,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19460
19511
|
void this.startNativeIndicator(id, handle);
|
|
19461
19512
|
return handle;
|
|
19462
19513
|
}
|
|
19463
|
-
/**
|
|
19464
|
-
*
|
|
19465
|
-
* entry's `supported` (applies to the current symbol) and `present` (already added — a second
|
|
19466
|
-
* add is a no-op). Powers a host "add native indicator" menu that can gate + de-duplicate. Async
|
|
19467
|
-
* because a descriptor's `isSupported` may probe the provider for a required capability.
|
|
19468
|
-
*/
|
|
19469
|
-
/** The native types present on the chart — SYNC (registry state; no support probe). */
|
|
19514
|
+
/** The native types present on the chart, one entry per INSTANCE in registry order (a
|
|
19515
|
+
* multi-instance type repeats) — SYNC (registry state; no support probe). */
|
|
19470
19516
|
presentNativeIndicators() {
|
|
19471
19517
|
return this.registry.all().map((r) => r.native?.type).filter((t) => !!t);
|
|
19472
19518
|
}
|
|
19519
|
+
/**
|
|
19520
|
+
* The catalog of registered native-indicator types with their live state on THIS chart: each
|
|
19521
|
+
* entry's `supported` (applies to the current symbol) and `present` (at least one instance
|
|
19522
|
+
* added — a second add of a single-instance type is a no-op). Powers a host "add native
|
|
19523
|
+
* indicator" menu that can gate + de-duplicate. Async because a descriptor's `isSupported` may
|
|
19524
|
+
* probe the provider for a required capability.
|
|
19525
|
+
*/
|
|
19473
19526
|
async availableNativeIndicators() {
|
|
19474
19527
|
const symbol = this.qualifiedSymbol();
|
|
19475
19528
|
const present = new Set(this.presentNativeIndicators());
|
|
@@ -19479,7 +19532,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19479
19532
|
title: d.title,
|
|
19480
19533
|
supported: await this.isNativeSupported(d, symbol),
|
|
19481
19534
|
present: present.has(d.type),
|
|
19482
|
-
beta: d.beta
|
|
19535
|
+
beta: d.beta,
|
|
19536
|
+
...d.multiInstance ? { multiInstance: true } : {}
|
|
19483
19537
|
}))
|
|
19484
19538
|
);
|
|
19485
19539
|
}
|
|
@@ -20619,8 +20673,8 @@ var DrawingsControl = class {
|
|
|
20619
20673
|
/** Show per-tool shortcut hints in the toolbar flyouts. Values are PRE-FORMATTED
|
|
20620
20674
|
* display strings (e.g. `'Alt+T'`, `'⌥T'`) — the host owns the keymap and the
|
|
20621
20675
|
* platform formatting, so hints always match the host's actual bindings. */
|
|
20622
|
-
setToolShortcuts(
|
|
20623
|
-
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(
|
|
20676
|
+
setToolShortcuts(map2) {
|
|
20677
|
+
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map2);
|
|
20624
20678
|
return this;
|
|
20625
20679
|
}
|
|
20626
20680
|
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
@@ -21147,14 +21201,11 @@ var IndicatorInputsDialog = class {
|
|
|
21147
21201
|
buildTextField(id, current, emit) {
|
|
21148
21202
|
return buildFieldControl({ kind: "text", id, value: current, onChange: emit }).el;
|
|
21149
21203
|
}
|
|
21150
|
-
/**
|
|
21204
|
+
/** The shared ⓘ hint carrying an input's `tooltip` (see {@link chromeHint}). */
|
|
21151
21205
|
infoButton(tooltip2) {
|
|
21152
|
-
const
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
b.className = "vela-ind-hint";
|
|
21156
|
-
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;";
|
|
21157
|
-
return b;
|
|
21206
|
+
const { el, dispose } = chromeHint(tooltip2, { host: this.host.container, theme: () => this.host.theme(), size: 18 });
|
|
21207
|
+
this.dialogTips.push(dispose);
|
|
21208
|
+
return el;
|
|
21158
21209
|
}
|
|
21159
21210
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
21160
21211
|
select(id, options, current, emit) {
|
|
@@ -21591,8 +21642,6 @@ function ensureDialogStyles() {
|
|
|
21591
21642
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
21592
21643
|
.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;}
|
|
21593
21644
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
21594
|
-
.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;}
|
|
21595
|
-
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21596
21645
|
.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;}
|
|
21597
21646
|
.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;}
|
|
21598
21647
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
@@ -22101,8 +22150,8 @@ var InputsUI = class {
|
|
|
22101
22150
|
lg.style.top = `${top}px`;
|
|
22102
22151
|
}
|
|
22103
22152
|
/** Set which panes are collapsed and, for each, the master indicator to keep in its strip. */
|
|
22104
|
-
setCollapsedPanes(
|
|
22105
|
-
this.paneCollapse =
|
|
22153
|
+
setCollapsedPanes(map2) {
|
|
22154
|
+
this.paneCollapse = map2;
|
|
22106
22155
|
}
|
|
22107
22156
|
/**
|
|
22108
22157
|
* Keep the fold toggle in step with the indicator count: with 2+ indicators anywhere
|
|
@@ -24111,11 +24160,11 @@ function mergeBarColors(indicators) {
|
|
|
24111
24160
|
}
|
|
24112
24161
|
}
|
|
24113
24162
|
if (!any) return EMPTY_BARCOLORS;
|
|
24114
|
-
const
|
|
24163
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
24115
24164
|
for (const m of indicators.values()) {
|
|
24116
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
24165
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
24117
24166
|
}
|
|
24118
|
-
return
|
|
24167
|
+
return map2;
|
|
24119
24168
|
}
|
|
24120
24169
|
|
|
24121
24170
|
// src/renderers/native/core/ViewportState.ts
|
|
@@ -24412,9 +24461,9 @@ function isHorizontalWheel(deltaX, deltaY) {
|
|
|
24412
24461
|
function wheelPanRightOffset(rightOffset, deltaX, barSpacing) {
|
|
24413
24462
|
return rightOffset + deltaX / barSpacing;
|
|
24414
24463
|
}
|
|
24415
|
-
function wheelPanDelta(deltaX, deltaY,
|
|
24464
|
+
function wheelPanDelta(deltaX, deltaY, shift2) {
|
|
24416
24465
|
if (isHorizontalWheel(deltaX, deltaY)) return deltaX;
|
|
24417
|
-
if (
|
|
24466
|
+
if (shift2) return deltaY;
|
|
24418
24467
|
return null;
|
|
24419
24468
|
}
|
|
24420
24469
|
function effectiveSnapMode(momentaryOverride, sticky) {
|
|
@@ -26036,11 +26085,11 @@ function mergeBarColors2(indicators) {
|
|
|
26036
26085
|
}
|
|
26037
26086
|
}
|
|
26038
26087
|
if (!any) return EMPTY_BARCOLORS2;
|
|
26039
|
-
const
|
|
26088
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
26040
26089
|
for (const m of indicators.values()) {
|
|
26041
|
-
if (m.barColors) for (const bc of m.barColors)
|
|
26090
|
+
if (m.barColors) for (const bc of m.barColors) map2.set(bc.time, bc.color);
|
|
26042
26091
|
}
|
|
26043
|
-
return
|
|
26092
|
+
return map2;
|
|
26044
26093
|
}
|
|
26045
26094
|
|
|
26046
26095
|
// src/renderers/shared/drawing-geometry.ts
|
|
@@ -27638,6 +27687,8 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
27638
27687
|
"symbol.style.baseline.fill-bottom",
|
|
27639
27688
|
"symbol.style.baseline.base-level",
|
|
27640
27689
|
"symbol.style.baseline.width",
|
|
27690
|
+
"symbol.animation",
|
|
27691
|
+
"symbol.animation.price-changes",
|
|
27641
27692
|
"symbol.timezone",
|
|
27642
27693
|
"scales",
|
|
27643
27694
|
"scales.price-scale",
|
|
@@ -27679,9 +27730,9 @@ function settingsIdCatalog(hostSections) {
|
|
|
27679
27730
|
};
|
|
27680
27731
|
if (section.rows) addRows(section.rows);
|
|
27681
27732
|
for (const inst of section.instances ?? []) addRows(inst.rows);
|
|
27682
|
-
for (const
|
|
27683
|
-
ids.add(`${scope}.${settingsIdSlug(
|
|
27684
|
-
addRows(
|
|
27733
|
+
for (const sub2 of section.subsections ?? []) {
|
|
27734
|
+
ids.add(`${scope}.${settingsIdSlug(sub2.title)}`);
|
|
27735
|
+
addRows(sub2.rows);
|
|
27685
27736
|
}
|
|
27686
27737
|
}
|
|
27687
27738
|
for (const hs of hostSections) {
|
|
@@ -27780,8 +27831,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
27780
27831
|
.vela-sd-mobile .vela-select-trigger,.vela-sd-mobile .vela-num input,.vela-sd-mobile .vela-width-field{height:34px;}
|
|
27781
27832
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
27782
27833
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
27783
|
-
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}
|
|
27784
|
-
`;
|
|
27834
|
+
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span,.vela-sd-mobile .vela-field-label{white-space:normal !important;}`;
|
|
27785
27835
|
if (!existing) document.head.appendChild(st);
|
|
27786
27836
|
}
|
|
27787
27837
|
var SettingsDialog = class {
|
|
@@ -27802,6 +27852,8 @@ var SettingsDialog = class {
|
|
|
27802
27852
|
this.themeControl = null;
|
|
27803
27853
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
27804
27854
|
this.tabs = [];
|
|
27855
|
+
this.hintTips = [];
|
|
27856
|
+
// ⓘ tooltip disposers of the open build (see hint)
|
|
27805
27857
|
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
27806
27858
|
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
27807
27859
|
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
@@ -27990,6 +28042,13 @@ var SettingsDialog = class {
|
|
|
27990
28042
|
body.append(g);
|
|
27991
28043
|
}
|
|
27992
28044
|
showActive(config.series.style);
|
|
28045
|
+
body.append(sid(this.sectionTitle("Animation"), "symbol.animation"));
|
|
28046
|
+
body.append(sid(this.boolRow(
|
|
28047
|
+
"Animate price changes",
|
|
28048
|
+
config.priceScale.animateLastPrice,
|
|
28049
|
+
(v) => this.emit({ priceScale: { animateLastPrice: v } }),
|
|
28050
|
+
this.hint("Glide the live bar to each new price instead of snapping.")
|
|
28051
|
+
), "symbol.animation.price-changes"));
|
|
27993
28052
|
body.append(sid(this.sectionTitle("Time zone"), "symbol.timezone"));
|
|
27994
28053
|
body.append(sid(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })), "symbol.timezone"));
|
|
27995
28054
|
const renderHostSections = (placement) => {
|
|
@@ -28197,6 +28256,8 @@ var SettingsDialog = class {
|
|
|
28197
28256
|
this.ui = null;
|
|
28198
28257
|
this.root = null;
|
|
28199
28258
|
this.tabs = [];
|
|
28259
|
+
for (const dispose of this.hintTips) dispose();
|
|
28260
|
+
this.hintTips = [];
|
|
28200
28261
|
ui?.destroy();
|
|
28201
28262
|
}
|
|
28202
28263
|
destroy() {
|
|
@@ -28240,14 +28301,14 @@ var SettingsDialog = class {
|
|
|
28240
28301
|
};
|
|
28241
28302
|
if (section.rows) seed(section.rows);
|
|
28242
28303
|
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
28243
|
-
for (const
|
|
28304
|
+
for (const sub2 of section.subsections ?? []) seed(sub2.rows);
|
|
28244
28305
|
for (const inst of section.instances ?? []) {
|
|
28245
28306
|
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
28246
28307
|
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
28247
28308
|
}
|
|
28248
|
-
for (const
|
|
28249
|
-
if (!
|
|
28250
|
-
bag[
|
|
28309
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28310
|
+
if (!sub2.enableKey || sub2.enableKey in bag) continue;
|
|
28311
|
+
bag[sub2.enableKey] = values[sub2.enableKey] === true;
|
|
28251
28312
|
}
|
|
28252
28313
|
const refreshers = [];
|
|
28253
28314
|
const put = (key, v) => {
|
|
@@ -28264,16 +28325,16 @@ var SettingsDialog = class {
|
|
|
28264
28325
|
if (section.layout === "grouped") body.append(this.groupedRows(`${typeId}/rows`, rows, bag, put, refreshers));
|
|
28265
28326
|
else this.flatTypeRows(rows, bag, put, refreshers, body);
|
|
28266
28327
|
}
|
|
28267
|
-
for (const
|
|
28268
|
-
if (settingsIdHidden(`${scope}.${settingsIdSlug(
|
|
28269
|
-
const rows = filterHiddenRows(
|
|
28328
|
+
for (const sub2 of section.subsections ?? []) {
|
|
28329
|
+
if (settingsIdHidden(`${scope}.${settingsIdSlug(sub2.title)}`, this.hiddenSettings)) continue;
|
|
28330
|
+
const rows = filterHiddenRows(sub2.rows, scope, this.hiddenSettings);
|
|
28270
28331
|
if (rows.length === 0) continue;
|
|
28271
|
-
const subMarker = this.section(
|
|
28332
|
+
const subMarker = this.section(sub2.title);
|
|
28272
28333
|
subMarker.dataset.sdStyle = typeId;
|
|
28273
28334
|
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
28274
28335
|
subMarker.dataset.sdSub = "1";
|
|
28275
28336
|
body.append(subMarker);
|
|
28276
|
-
body.append(this.groupedRows(`${typeId}/${
|
|
28337
|
+
body.append(this.groupedRows(`${typeId}/${sub2.title}`, rows, bag, put, refreshers, sub2.enableKey));
|
|
28277
28338
|
}
|
|
28278
28339
|
for (const r of refreshers) r();
|
|
28279
28340
|
}
|
|
@@ -28610,23 +28671,31 @@ var SettingsDialog = class {
|
|
|
28610
28671
|
}).el
|
|
28611
28672
|
});
|
|
28612
28673
|
}
|
|
28613
|
-
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28614
|
-
|
|
28615
|
-
|
|
28674
|
+
/** A toggle row: the checkbox sits to the LEFT of its label (never in the control area).
|
|
28675
|
+
* `info` (see {@link hint}) rides after the label. */
|
|
28676
|
+
boolRow(label, value, onChange, info) {
|
|
28677
|
+
return this.toggleRow(label, value, onChange, [], void 0, info);
|
|
28616
28678
|
}
|
|
28617
28679
|
/** An enable row: checkbox + label in the label column, dependent controls in the
|
|
28618
28680
|
* shared control column; the control group dims and ignores input while the toggle
|
|
28619
28681
|
* is off. With `get`, the row re-reads its state on a 'vela-sync' event. */
|
|
28620
|
-
toggleRow(label, value, onToggle, controls, get) {
|
|
28682
|
+
toggleRow(label, value, onToggle, controls, get, info) {
|
|
28621
28683
|
return fieldRow({
|
|
28622
28684
|
label,
|
|
28623
28685
|
labelSize: "sm",
|
|
28624
28686
|
bool: controls.length === 0,
|
|
28625
28687
|
toggle: { checked: value, onChange: onToggle, get },
|
|
28626
28688
|
control: controls,
|
|
28689
|
+
info,
|
|
28627
28690
|
className: controls.length === 0 ? "vela-sd-bool" : "vela-sd-row"
|
|
28628
28691
|
});
|
|
28629
28692
|
}
|
|
28693
|
+
/** The shared ⓘ hint after a row label (see {@link chromeHint}). Disposed with the dialog. */
|
|
28694
|
+
hint(text) {
|
|
28695
|
+
const { el, dispose } = chromeHint(text, { host: this.container, theme: () => this.theme });
|
|
28696
|
+
this.hintTips.push(dispose);
|
|
28697
|
+
return el;
|
|
28698
|
+
}
|
|
28630
28699
|
numberRow(label, value, min, max, step, onChange) {
|
|
28631
28700
|
return fieldRow({
|
|
28632
28701
|
label,
|
|
@@ -30368,11 +30437,11 @@ var DrawingInteraction = class {
|
|
|
30368
30437
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
30369
30438
|
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
30370
30439
|
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
30371
|
-
down(x, y, mode = "off",
|
|
30440
|
+
down(x, y, mode = "off", shift2 = false) {
|
|
30372
30441
|
const proj = this.deps.projector();
|
|
30373
30442
|
if (this.state.kind === "placing") {
|
|
30374
30443
|
if (this.state.draft.placementMode() !== "click") return;
|
|
30375
|
-
const pivot =
|
|
30444
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30376
30445
|
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30377
30446
|
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
30378
30447
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
@@ -30391,7 +30460,7 @@ var DrawingInteraction = class {
|
|
|
30391
30460
|
}
|
|
30392
30461
|
const hit = this.hitAt(x, y);
|
|
30393
30462
|
if (hit) {
|
|
30394
|
-
if (
|
|
30463
|
+
if (shift2) {
|
|
30395
30464
|
this.deps.emit({ kind: "select", ids: [hit.id], additive: true });
|
|
30396
30465
|
return;
|
|
30397
30466
|
}
|
|
@@ -30401,7 +30470,7 @@ var DrawingInteraction = class {
|
|
|
30401
30470
|
}
|
|
30402
30471
|
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
30403
30472
|
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
30404
|
-
move(x, y, mode = "off",
|
|
30473
|
+
move(x, y, mode = "off", shift2 = false) {
|
|
30405
30474
|
if (this.state.kind === "idle") {
|
|
30406
30475
|
const tool = this.deps.activeTool();
|
|
30407
30476
|
const had = this.snapAt != null;
|
|
@@ -30415,7 +30484,7 @@ var DrawingInteraction = class {
|
|
|
30415
30484
|
this.captureFreehand(x, y);
|
|
30416
30485
|
return;
|
|
30417
30486
|
}
|
|
30418
|
-
const pivot =
|
|
30487
|
+
const pivot = shift2 ? this.placingPivot(this.state.draft) : null;
|
|
30419
30488
|
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
30420
30489
|
this.deps.changed();
|
|
30421
30490
|
return;
|
|
@@ -30428,7 +30497,7 @@ var DrawingInteraction = class {
|
|
|
30428
30497
|
if (this.state.handle >= 0) {
|
|
30429
30498
|
const a = d.anchors[this.state.handle];
|
|
30430
30499
|
if (a) {
|
|
30431
|
-
const pivot =
|
|
30500
|
+
const pivot = shift2 && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
30432
30501
|
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
30433
30502
|
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
30434
30503
|
}
|
|
@@ -31126,21 +31195,21 @@ var DrawingSettingsPopup = class {
|
|
|
31126
31195
|
bar.appendChild(this.colorButton("Lower fill", BUCKET_ICON, reg.lowerFill, (v) => actions.patch({ "reg.lowerFill": v })));
|
|
31127
31196
|
bar.appendChild(this.toggle("Show R\xB2", R2_ICON, reg.showR2, (v) => actions.patch({ "reg.showR2": v })));
|
|
31128
31197
|
}
|
|
31129
|
-
const
|
|
31130
|
-
if (paths.has("vwap.midColor") &&
|
|
31198
|
+
const vwap2 = drawing.vwap;
|
|
31199
|
+
if (paths.has("vwap.midColor") && vwap2) {
|
|
31131
31200
|
const styles = LINE_STYLE_OPTIONS.map((o) => o.value);
|
|
31132
31201
|
const MULTS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5];
|
|
31133
|
-
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON,
|
|
31134
|
-
bar.appendChild(this.dropdown("VWAP style", styles,
|
|
31202
|
+
bar.appendChild(this.colorButton("VWAP color", BRUSH_ICON, vwap2.midColor, (v) => actions.patch({ "vwap.midColor": v })));
|
|
31203
|
+
bar.appendChild(this.dropdown("VWAP style", styles, vwap2.midStyle, (s) => lineIcon(2, s), (v) => actions.patch({ "vwap.midStyle": v }), { label: styleLabel2 }));
|
|
31135
31204
|
bar.appendChild(
|
|
31136
|
-
this.dropdown("Band multiplier", MULTS,
|
|
31205
|
+
this.dropdown("Band multiplier", MULTS, vwap2.multiplier, () => BANDS_ICON, (v) => actions.patch({ "vwap.multiplier": v }), {
|
|
31137
31206
|
label: (v) => `${v}\u03C3`,
|
|
31138
31207
|
labelInTrigger: true
|
|
31139
31208
|
})
|
|
31140
31209
|
);
|
|
31141
|
-
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON,
|
|
31142
|
-
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON,
|
|
31143
|
-
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON,
|
|
31210
|
+
bar.appendChild(this.colorButton("Upper band color", BRUSH_ICON, vwap2.upperColor, (v) => actions.patch({ "vwap.upperColor": v })));
|
|
31211
|
+
bar.appendChild(this.colorButton("Lower band color", BRUSH_ICON, vwap2.lowerColor, (v) => actions.patch({ "vwap.lowerColor": v })));
|
|
31212
|
+
bar.appendChild(this.colorButton("Band fill", BUCKET_ICON, vwap2.bandFill, (v) => actions.patch({ "vwap.bandFill": v })));
|
|
31144
31213
|
}
|
|
31145
31214
|
if (paths.has("maxCurvature")) {
|
|
31146
31215
|
const cur = drawing.maxCurvature ?? 24;
|
|
@@ -32157,8 +32226,8 @@ var UserDrawingController = class {
|
|
|
32157
32226
|
this.toolbar.setFavorites(types);
|
|
32158
32227
|
}
|
|
32159
32228
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
32160
|
-
setToolShortcuts(
|
|
32161
|
-
this.toolbar.setShortcuts(
|
|
32229
|
+
setToolShortcuts(map2) {
|
|
32230
|
+
this.toolbar.setShortcuts(map2);
|
|
32162
32231
|
}
|
|
32163
32232
|
/** Core push: set the sticky magnet mode. Applies to the renderer + reflects on the
|
|
32164
32233
|
* in-chart toolbar WITHOUT notifying back (the caller already holds the value). */
|
|
@@ -32276,7 +32345,7 @@ var UserDrawingController = class {
|
|
|
32276
32345
|
this.render();
|
|
32277
32346
|
}
|
|
32278
32347
|
}
|
|
32279
|
-
pointerDown(x, y, snap = "off",
|
|
32348
|
+
pointerDown(x, y, snap = "off", shift2 = false) {
|
|
32280
32349
|
if (this.eraserMode) {
|
|
32281
32350
|
this.erasing = true;
|
|
32282
32351
|
this.deleteAt(x, y);
|
|
@@ -32296,9 +32365,9 @@ var UserDrawingController = class {
|
|
|
32296
32365
|
return;
|
|
32297
32366
|
}
|
|
32298
32367
|
}
|
|
32299
|
-
this.interaction.down(x, y, snap,
|
|
32368
|
+
this.interaction.down(x, y, snap, shift2);
|
|
32300
32369
|
}
|
|
32301
|
-
pointerMove(x, y, snap = "off",
|
|
32370
|
+
pointerMove(x, y, snap = "off", shift2 = false) {
|
|
32302
32371
|
if (this.eraserMode) {
|
|
32303
32372
|
if (this.erasing) this.deleteAt(x, y);
|
|
32304
32373
|
return;
|
|
@@ -32309,7 +32378,7 @@ var UserDrawingController = class {
|
|
|
32309
32378
|
this.render();
|
|
32310
32379
|
return;
|
|
32311
32380
|
}
|
|
32312
|
-
this.interaction.move(x, y, snap,
|
|
32381
|
+
this.interaction.move(x, y, snap, shift2);
|
|
32313
32382
|
this.updateHover(x, y);
|
|
32314
32383
|
}
|
|
32315
32384
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -32986,14 +33055,14 @@ function layoutTable(ctx, t, args) {
|
|
|
32986
33055
|
}
|
|
32987
33056
|
for (const { box, w: w2, h: h2 } of spanning) {
|
|
32988
33057
|
if (box.cs > 1) {
|
|
32989
|
-
let
|
|
32990
|
-
for (let c = box.c; c < box.c + box.cs; c += 1)
|
|
32991
|
-
if (w2 >
|
|
33058
|
+
let sum2 = 0;
|
|
33059
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum2 += colW[c];
|
|
33060
|
+
if (w2 > sum2) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum2) / box.cs;
|
|
32992
33061
|
}
|
|
32993
33062
|
if (box.rs > 1) {
|
|
32994
|
-
let
|
|
32995
|
-
for (let r = box.r; r < box.r + box.rs; r += 1)
|
|
32996
|
-
if (h2 >
|
|
33063
|
+
let sum2 = 0;
|
|
33064
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum2 += rowH[r];
|
|
33065
|
+
if (h2 > sum2) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum2) / box.rs;
|
|
32997
33066
|
}
|
|
32998
33067
|
}
|
|
32999
33068
|
let w = 0;
|
|
@@ -33580,24 +33649,24 @@ function rasterizeOverlay(ctx, root, frame) {
|
|
|
33580
33649
|
drawSvgShapes(el, alpha);
|
|
33581
33650
|
continue;
|
|
33582
33651
|
}
|
|
33583
|
-
const
|
|
33652
|
+
const num4 = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
|
|
33584
33653
|
let path = null;
|
|
33585
33654
|
if (tag === "path") {
|
|
33586
33655
|
const d = el.getAttribute("d");
|
|
33587
33656
|
if (d) path = new Path2D(d);
|
|
33588
33657
|
} else if (tag === "circle") {
|
|
33589
33658
|
path = new Path2D();
|
|
33590
|
-
path.arc(
|
|
33659
|
+
path.arc(num4("cx"), num4("cy"), num4("r"), 0, Math.PI * 2);
|
|
33591
33660
|
} else if (tag === "ellipse") {
|
|
33592
33661
|
path = new Path2D();
|
|
33593
|
-
path.ellipse(
|
|
33662
|
+
path.ellipse(num4("cx"), num4("cy"), num4("rx"), num4("ry"), 0, 0, Math.PI * 2);
|
|
33594
33663
|
} else if (tag === "rect") {
|
|
33595
33664
|
path = new Path2D();
|
|
33596
|
-
path.rect(
|
|
33665
|
+
path.rect(num4("x"), num4("y"), num4("width"), num4("height"));
|
|
33597
33666
|
} else if (tag === "line") {
|
|
33598
33667
|
path = new Path2D();
|
|
33599
|
-
path.moveTo(
|
|
33600
|
-
path.lineTo(
|
|
33668
|
+
path.moveTo(num4("x1"), num4("y1"));
|
|
33669
|
+
path.lineTo(num4("x2"), num4("y2"));
|
|
33601
33670
|
} else if (tag === "polyline" || tag === "polygon") {
|
|
33602
33671
|
const pts = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
|
|
33603
33672
|
if (pts.length < 4 || pts.some(Number.isNaN)) continue;
|
|
@@ -33858,7 +33927,6 @@ var ZOOM_OUT_MARGIN_BARS = 6;
|
|
|
33858
33927
|
var ZOOM_TAU_MS = 70;
|
|
33859
33928
|
var SCALE_TAU_MS = 80;
|
|
33860
33929
|
var FLING_TAU_MS = 110;
|
|
33861
|
-
var LIVE_BAR_TAU_MS = 90;
|
|
33862
33930
|
var SCROLL_TO_TAU_MS = 130;
|
|
33863
33931
|
var FLING_STOP_PX = 0.02;
|
|
33864
33932
|
var PRICE_SCALE_K = 4e-3;
|
|
@@ -33929,9 +33997,13 @@ var NativeRenderer = class {
|
|
|
33929
33997
|
/** Drawings layer self-serves Ctrl+Z/Y (see the `historyChords` feature). */
|
|
33930
33998
|
this.historyChordsEnabled = true;
|
|
33931
33999
|
this.liveRegion = null;
|
|
33932
|
-
// ── animation state (eased zoom + inertial pan) ──
|
|
34000
|
+
// ── animation state (eased zoom + inertial pan + live-bar glide) ──
|
|
33933
34001
|
this.animZoom = true;
|
|
33934
34002
|
this.animPan = true;
|
|
34003
|
+
this.animLiveBarMs = 0;
|
|
34004
|
+
// forming-bar OHLC glide time-constant; 0 = each tick snaps
|
|
34005
|
+
this.animLiveBarOnMs = LIVE_BAR_EASE_DEFAULT_MS;
|
|
34006
|
+
// the duration the settings-dialog on/off toggle restores (last non-zero value configured)
|
|
33935
34007
|
// Brand default candles.
|
|
33936
34008
|
this.candleUp = BULLISH;
|
|
33937
34009
|
this.candleDown = BEARISH;
|
|
@@ -34056,7 +34128,7 @@ var NativeRenderer = class {
|
|
|
34056
34128
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
34057
34129
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
34058
34130
|
this.name = "native";
|
|
34059
|
-
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"];
|
|
34131
|
+
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"];
|
|
34060
34132
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
34061
34133
|
* so moving onto the button doesn't count as leaving). */
|
|
34062
34134
|
this.onScrollProximityMove = (e) => {
|
|
@@ -34090,6 +34162,7 @@ var NativeRenderer = class {
|
|
|
34090
34162
|
this.backendMode = opts.nativeBackend;
|
|
34091
34163
|
this.animZoom = opts.animZoom;
|
|
34092
34164
|
this.animPan = opts.animPan;
|
|
34165
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(opts.animLiveBar));
|
|
34093
34166
|
this.glowAmount = opts.glow;
|
|
34094
34167
|
this.candleUp = opts.upColor;
|
|
34095
34168
|
this.candleDown = opts.downColor;
|
|
@@ -34139,6 +34212,10 @@ var NativeRenderer = class {
|
|
|
34139
34212
|
case "animPan":
|
|
34140
34213
|
this.animPan = Boolean(value);
|
|
34141
34214
|
return;
|
|
34215
|
+
case "animLiveBar":
|
|
34216
|
+
this.setLiveBarEase(resolveLiveBarEaseMs(value));
|
|
34217
|
+
return;
|
|
34218
|
+
// affects the next tick only; a glide in flight finishes at the new rate (or snaps at 0)
|
|
34142
34219
|
case "intro": {
|
|
34143
34220
|
const s = value === false || value === "none" || value === "off" || value == null ? "" : String(value);
|
|
34144
34221
|
this.introStyle = s;
|
|
@@ -34275,6 +34352,8 @@ var NativeRenderer = class {
|
|
|
34275
34352
|
return this.animZoom;
|
|
34276
34353
|
case "animPan":
|
|
34277
34354
|
return this.animPan;
|
|
34355
|
+
case "animLiveBar":
|
|
34356
|
+
return this.animLiveBarMs;
|
|
34278
34357
|
case "intro":
|
|
34279
34358
|
return this.introStyle;
|
|
34280
34359
|
case "zoomAnchor":
|
|
@@ -34443,7 +34522,8 @@ var NativeRenderer = class {
|
|
|
34443
34522
|
labelsVisible: this.scene.showAxisLabels,
|
|
34444
34523
|
currentPriceLine: this.scene.showPriceLine,
|
|
34445
34524
|
priceLabel: this.scene.showPriceLabel,
|
|
34446
|
-
countdown: this.scene.showCountdown
|
|
34525
|
+
countdown: this.scene.showCountdown,
|
|
34526
|
+
animateLastPrice: this.animLiveBarMs > 0
|
|
34447
34527
|
},
|
|
34448
34528
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
34449
34529
|
trades: {
|
|
@@ -34550,6 +34630,7 @@ var NativeRenderer = class {
|
|
|
34550
34630
|
this.scene.showPriceLabel = next.priceScale.priceLabel;
|
|
34551
34631
|
this.scene.showCountdown = next.priceScale.countdown;
|
|
34552
34632
|
this.syncCountdownTimer();
|
|
34633
|
+
this.animLiveBarMs = next.priceScale.animateLastPrice ? this.animLiveBarOnMs : 0;
|
|
34553
34634
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
34554
34635
|
this.scene.tradeMarkers = {
|
|
34555
34636
|
visible: next.trades.visible,
|
|
@@ -35014,8 +35095,8 @@ var NativeRenderer = class {
|
|
|
35014
35095
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
35015
35096
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
35016
35097
|
drawingsSnapMode: () => this.snapMode,
|
|
35017
|
-
drawingsPointerDown: (x, y, snap,
|
|
35018
|
-
drawingsPointerMove: (x, y, snap,
|
|
35098
|
+
drawingsPointerDown: (x, y, snap, shift2) => this.userDrawings?.pointerDown(x, y, snap, shift2),
|
|
35099
|
+
drawingsPointerMove: (x, y, snap, shift2) => this.userDrawings?.pointerMove(x, y, snap, shift2),
|
|
35019
35100
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
35020
35101
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
35021
35102
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -35332,18 +35413,18 @@ var NativeRenderer = class {
|
|
|
35332
35413
|
for (const model of this.scene.indicators.values()) this.refreshAnchorOffset(model);
|
|
35333
35414
|
if (opts?.preserveView === true && prevHeadTime != null && this.bars.length > 0) {
|
|
35334
35415
|
const newHeadTime = this.bars[0].time;
|
|
35335
|
-
let
|
|
35416
|
+
let shift2 = 0;
|
|
35336
35417
|
if (newHeadTime !== prevHeadTime) {
|
|
35337
35418
|
const prepended = this.bars.findIndex((b) => b.time === prevHeadTime);
|
|
35338
|
-
if (prepended > 0)
|
|
35419
|
+
if (prepended > 0) shift2 = prepended;
|
|
35339
35420
|
else {
|
|
35340
35421
|
const trimmed = prevBars.findIndex((b) => b.time === newHeadTime);
|
|
35341
|
-
if (trimmed > 0)
|
|
35422
|
+
if (trimmed > 0) shift2 = -trimmed;
|
|
35342
35423
|
}
|
|
35343
35424
|
}
|
|
35344
|
-
if (
|
|
35345
|
-
this.zoomAnchorLogical +=
|
|
35346
|
-
if (this.hoverLogical != null) this.hoverLogical +=
|
|
35425
|
+
if (shift2 !== 0) {
|
|
35426
|
+
this.zoomAnchorLogical += shift2;
|
|
35427
|
+
if (this.hoverLogical != null) this.hoverLogical += shift2;
|
|
35347
35428
|
}
|
|
35348
35429
|
}
|
|
35349
35430
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
@@ -35366,8 +35447,11 @@ var NativeRenderer = class {
|
|
|
35366
35447
|
const last = this.bars[n - 1];
|
|
35367
35448
|
if (last && bar.time === last.time) {
|
|
35368
35449
|
this.bars[n - 1] = bar;
|
|
35369
|
-
if (this.liveEaseTime !== bar.time)
|
|
35370
|
-
|
|
35450
|
+
if (this.animLiveBarMs <= 0 || this.liveEaseTime !== bar.time) {
|
|
35451
|
+
this.syncLiveEase(bar);
|
|
35452
|
+
} else {
|
|
35453
|
+
this.animator.start();
|
|
35454
|
+
}
|
|
35371
35455
|
} else if (!last || bar.time > last.time) {
|
|
35372
35456
|
this.bars.push(bar);
|
|
35373
35457
|
this.syncLiveEase(bar);
|
|
@@ -35379,6 +35463,12 @@ var NativeRenderer = class {
|
|
|
35379
35463
|
this.scene.bars = this.bars;
|
|
35380
35464
|
this.scheduler.invalidate(4 /* Full */);
|
|
35381
35465
|
}
|
|
35466
|
+
/** Set the live-bar glide duration (0 = off). A non-zero value is also remembered as
|
|
35467
|
+
* what the config's on/off toggle (`priceScale.animateLastPrice`) switches back on to. */
|
|
35468
|
+
setLiveBarEase(ms) {
|
|
35469
|
+
this.animLiveBarMs = ms;
|
|
35470
|
+
if (ms > 0) this.animLiveBarOnMs = ms;
|
|
35471
|
+
}
|
|
35382
35472
|
/** Snap the eased forming-bar state to `bar` — no glide (a fresh bar or the first tick of one). */
|
|
35383
35473
|
syncLiveEase(bar) {
|
|
35384
35474
|
this.liveEaseTime = bar.time;
|
|
@@ -35391,9 +35481,10 @@ var NativeRenderer = class {
|
|
|
35391
35481
|
const target = this.bars[this.bars.length - 1];
|
|
35392
35482
|
if (!target || this.liveEaseTime !== target.time) return false;
|
|
35393
35483
|
const eps = Math.max(1e-9, Math.abs(target.close) * 1e-6);
|
|
35394
|
-
const
|
|
35395
|
-
const
|
|
35396
|
-
const
|
|
35484
|
+
const tau = this.animLiveBarMs;
|
|
35485
|
+
const nh = easeToward(this.liveEaseHigh, target.high, dtMs, tau);
|
|
35486
|
+
const nl = easeToward(this.liveEaseLow, target.low, dtMs, tau);
|
|
35487
|
+
const nc = easeToward(this.liveEaseClose, target.close, dtMs, tau);
|
|
35397
35488
|
const active = Math.abs(nh - target.high) > eps || Math.abs(nl - target.low) > eps || Math.abs(nc - target.close) > eps;
|
|
35398
35489
|
this.liveEaseHigh = active ? nh : target.high;
|
|
35399
35490
|
this.liveEaseLow = active ? nl : target.low;
|
|
@@ -36949,15 +37040,15 @@ var NativeRenderer = class {
|
|
|
36949
37040
|
/** For each collapsed pane, the master (master-scale) indicator id whose legend row stays in the
|
|
36950
37041
|
* strip — merged own-scale indicators are hidden while collapsed. */
|
|
36951
37042
|
collapsedMasterMap() {
|
|
36952
|
-
const
|
|
37043
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
36953
37044
|
for (const pane of this.scene.panes.values()) {
|
|
36954
37045
|
if (!pane.collapsed) continue;
|
|
36955
37046
|
const models = this.scene.orderedIndicatorsForPane(pane.id);
|
|
36956
37047
|
const merged = new Set(this.scene.ownScaleIndicatorsForPane(pane.id).map((m) => m.id));
|
|
36957
37048
|
const master = models.find((m) => !merged.has(m.id)) ?? models[0];
|
|
36958
|
-
|
|
37049
|
+
map2.set(pane.id, master?.id ?? null);
|
|
36959
37050
|
}
|
|
36960
|
-
return
|
|
37051
|
+
return map2;
|
|
36961
37052
|
}
|
|
36962
37053
|
/** Reserve `px` of left gutter for the docked drawings toolbar (0 releases it) + re-lay-out the plot. */
|
|
36963
37054
|
setToolbarGutter(px) {
|
|
@@ -37324,29 +37415,2335 @@ function registerVpvr() {
|
|
|
37324
37415
|
registerNativeIndicator(vpvrDescriptor);
|
|
37325
37416
|
}
|
|
37326
37417
|
|
|
37418
|
+
// src/core/model/identity.ts
|
|
37419
|
+
function stableSeriesId(parts) {
|
|
37420
|
+
const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
|
|
37421
|
+
return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
|
|
37422
|
+
}
|
|
37423
|
+
|
|
37424
|
+
// src/core/native-indicators/classics/define.ts
|
|
37425
|
+
function num3(inputs, key, fallback) {
|
|
37426
|
+
const v = inputs[key];
|
|
37427
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
37428
|
+
}
|
|
37429
|
+
function str3(inputs, key, fallback) {
|
|
37430
|
+
const v = inputs[key];
|
|
37431
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
37432
|
+
}
|
|
37433
|
+
function toPoints(bars, values, colors) {
|
|
37434
|
+
const pts = new Array(bars.length);
|
|
37435
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37436
|
+
const v = values[i];
|
|
37437
|
+
const p = { time: bars[i].time, value: v != null && Number.isFinite(v) ? v : null };
|
|
37438
|
+
const c = colors?.[i];
|
|
37439
|
+
if (c != null) p.color = c;
|
|
37440
|
+
pts[i] = p;
|
|
37441
|
+
}
|
|
37442
|
+
return pts;
|
|
37443
|
+
}
|
|
37444
|
+
var ClassicIndicator = class {
|
|
37445
|
+
constructor(spec) {
|
|
37446
|
+
this.spec = spec;
|
|
37447
|
+
this.inputs = {};
|
|
37448
|
+
}
|
|
37449
|
+
start(ctx, inputs) {
|
|
37450
|
+
this.ctx = ctx;
|
|
37451
|
+
this.inputs = inputs;
|
|
37452
|
+
this.recompute();
|
|
37453
|
+
}
|
|
37454
|
+
onBars() {
|
|
37455
|
+
this.recompute();
|
|
37456
|
+
}
|
|
37457
|
+
onViewport() {
|
|
37458
|
+
}
|
|
37459
|
+
setInputs(inputs) {
|
|
37460
|
+
this.inputs = inputs;
|
|
37461
|
+
this.recompute();
|
|
37462
|
+
}
|
|
37463
|
+
/** Nothing to free — the compute reads the live bar accessor on demand. */
|
|
37464
|
+
suspend() {
|
|
37465
|
+
}
|
|
37466
|
+
resume() {
|
|
37467
|
+
this.recompute();
|
|
37468
|
+
}
|
|
37469
|
+
stop() {
|
|
37470
|
+
}
|
|
37471
|
+
recompute() {
|
|
37472
|
+
const bars = this.ctx.bars();
|
|
37473
|
+
const out = this.spec.compute(bars, this.inputs);
|
|
37474
|
+
const instanceId = this.spec.type;
|
|
37475
|
+
const series = [];
|
|
37476
|
+
const idsByKey = /* @__PURE__ */ new Map();
|
|
37477
|
+
out.plots.forEach((plot, ordinal) => {
|
|
37478
|
+
const kind = plot.kind ?? "line";
|
|
37479
|
+
const id = stableSeriesId({ instanceId, kind, title: plot.title, ordinal });
|
|
37480
|
+
idsByKey.set(plot.key, id);
|
|
37481
|
+
series.push({
|
|
37482
|
+
id,
|
|
37483
|
+
title: plot.title,
|
|
37484
|
+
paneId: "",
|
|
37485
|
+
// stamped by the orchestrator
|
|
37486
|
+
kind,
|
|
37487
|
+
points: toPoints(bars, plot.values, plot.colors),
|
|
37488
|
+
style: {
|
|
37489
|
+
color: plot.color,
|
|
37490
|
+
width: plot.width ?? 1,
|
|
37491
|
+
lineStyle: plot.lineStyle ?? "solid",
|
|
37492
|
+
...plot.base != null ? { base: plot.base } : {}
|
|
37493
|
+
},
|
|
37494
|
+
...plot.overlay ? { overlay: true } : {}
|
|
37495
|
+
});
|
|
37496
|
+
});
|
|
37497
|
+
if (out.markers && out.markers.length > 0) {
|
|
37498
|
+
series.push({
|
|
37499
|
+
id: stableSeriesId({ instanceId, kind: "markers", title: "markers", ordinal: 0 }),
|
|
37500
|
+
title: this.spec.shortTitle ?? this.spec.title,
|
|
37501
|
+
paneId: "",
|
|
37502
|
+
kind: "markers",
|
|
37503
|
+
markers: out.markers
|
|
37504
|
+
});
|
|
37505
|
+
}
|
|
37506
|
+
const fills = (out.bands ?? []).flatMap((band, ordinal) => {
|
|
37507
|
+
const from = idsByKey.get(band.from);
|
|
37508
|
+
const to = idsByKey.get(band.to);
|
|
37509
|
+
if (!from || !to) return [];
|
|
37510
|
+
return [
|
|
37511
|
+
{
|
|
37512
|
+
id: stableSeriesId({ instanceId, kind: "fill", title: band.key, ordinal }),
|
|
37513
|
+
paneId: "",
|
|
37514
|
+
fromSeriesId: from,
|
|
37515
|
+
toSeriesId: to,
|
|
37516
|
+
color: band.color
|
|
37517
|
+
}
|
|
37518
|
+
];
|
|
37519
|
+
});
|
|
37520
|
+
const priceLines = (out.levels ?? []).map((level, ordinal) => ({
|
|
37521
|
+
id: stableSeriesId({ instanceId, kind: "hline", title: level.key, ordinal }),
|
|
37522
|
+
paneId: "",
|
|
37523
|
+
price: level.price,
|
|
37524
|
+
...level.color != null ? { color: level.color } : {},
|
|
37525
|
+
lineStyle: level.lineStyle ?? "dashed",
|
|
37526
|
+
width: 1,
|
|
37527
|
+
...level.title != null ? { title: level.title } : {}
|
|
37528
|
+
}));
|
|
37529
|
+
const polylines = (out.polylines ?? []).map((poly, ordinal) => {
|
|
37530
|
+
const { key, ...rest } = poly;
|
|
37531
|
+
return {
|
|
37532
|
+
id: stableSeriesId({ instanceId, kind: "polyline", title: key, ordinal }),
|
|
37533
|
+
paneId: "",
|
|
37534
|
+
...rest
|
|
37535
|
+
};
|
|
37536
|
+
});
|
|
37537
|
+
this.ctx.emit({ series, fills, priceLines, polylines });
|
|
37538
|
+
this.ctx.setStatus("idle");
|
|
37539
|
+
}
|
|
37540
|
+
};
|
|
37541
|
+
function classicDescriptor(spec) {
|
|
37542
|
+
return {
|
|
37543
|
+
type: spec.type,
|
|
37544
|
+
title: spec.title,
|
|
37545
|
+
...spec.shortTitle != null ? { shortTitle: spec.shortTitle } : {},
|
|
37546
|
+
paneHint: spec.overlay ? "price" : "new",
|
|
37547
|
+
overlay: spec.overlay,
|
|
37548
|
+
// Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
|
|
37549
|
+
multiInstance: true,
|
|
37550
|
+
inputsSchema: () => spec.inputs,
|
|
37551
|
+
defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
|
|
37552
|
+
create: () => new ClassicIndicator(spec)
|
|
37553
|
+
};
|
|
37554
|
+
}
|
|
37555
|
+
|
|
37556
|
+
// src/core/native-indicators/classics/math.ts
|
|
37557
|
+
var SOURCES2 = ["Close", "Open", "High", "Low", "HL2", "HLC3", "OHLC4", "HLCC4"];
|
|
37558
|
+
function sourceValues(bars, source) {
|
|
37559
|
+
const out = new Array(bars.length);
|
|
37560
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37561
|
+
const b = bars[i];
|
|
37562
|
+
switch (source) {
|
|
37563
|
+
case "Open":
|
|
37564
|
+
out[i] = b.open;
|
|
37565
|
+
break;
|
|
37566
|
+
case "High":
|
|
37567
|
+
out[i] = b.high;
|
|
37568
|
+
break;
|
|
37569
|
+
case "Low":
|
|
37570
|
+
out[i] = b.low;
|
|
37571
|
+
break;
|
|
37572
|
+
case "HL2":
|
|
37573
|
+
out[i] = (b.high + b.low) / 2;
|
|
37574
|
+
break;
|
|
37575
|
+
case "HLC3":
|
|
37576
|
+
out[i] = (b.high + b.low + b.close) / 3;
|
|
37577
|
+
break;
|
|
37578
|
+
case "OHLC4":
|
|
37579
|
+
out[i] = (b.open + b.high + b.low + b.close) / 4;
|
|
37580
|
+
break;
|
|
37581
|
+
case "HLCC4":
|
|
37582
|
+
out[i] = (b.high + b.low + b.close + b.close) / 4;
|
|
37583
|
+
break;
|
|
37584
|
+
default:
|
|
37585
|
+
out[i] = b.close;
|
|
37586
|
+
}
|
|
37587
|
+
}
|
|
37588
|
+
return out;
|
|
37589
|
+
}
|
|
37590
|
+
function volumes(bars) {
|
|
37591
|
+
return bars.map((b) => b.volume != null && Number.isFinite(b.volume) ? b.volume : Number.NaN);
|
|
37592
|
+
}
|
|
37593
|
+
function highs(bars) {
|
|
37594
|
+
return bars.map((b) => b.high);
|
|
37595
|
+
}
|
|
37596
|
+
function lows(bars) {
|
|
37597
|
+
return bars.map((b) => b.low);
|
|
37598
|
+
}
|
|
37599
|
+
function closes(bars) {
|
|
37600
|
+
return bars.map((b) => b.close);
|
|
37601
|
+
}
|
|
37602
|
+
function opens(bars) {
|
|
37603
|
+
return bars.map((b) => b.open);
|
|
37604
|
+
}
|
|
37605
|
+
function zip(a, b, f) {
|
|
37606
|
+
const out = new Array(a.length);
|
|
37607
|
+
for (let i = 0; i < a.length; i++) {
|
|
37608
|
+
const x = a[i];
|
|
37609
|
+
const y = b[i];
|
|
37610
|
+
out[i] = Number.isFinite(x) && Number.isFinite(y) ? f(x, y) : Number.NaN;
|
|
37611
|
+
}
|
|
37612
|
+
return out;
|
|
37613
|
+
}
|
|
37614
|
+
function map(a, f) {
|
|
37615
|
+
const out = new Array(a.length);
|
|
37616
|
+
for (let i = 0; i < a.length; i++) {
|
|
37617
|
+
const x = a[i];
|
|
37618
|
+
out[i] = Number.isFinite(x) ? f(x) : Number.NaN;
|
|
37619
|
+
}
|
|
37620
|
+
return out;
|
|
37621
|
+
}
|
|
37622
|
+
function sub(a, b) {
|
|
37623
|
+
return zip(a, b, (x, y) => x - y);
|
|
37624
|
+
}
|
|
37625
|
+
function windowed(v, len, fold) {
|
|
37626
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37627
|
+
if (len <= 0) return out;
|
|
37628
|
+
let run = 0;
|
|
37629
|
+
for (let i = 0; i < v.length; i++) {
|
|
37630
|
+
run = Number.isFinite(v[i]) ? run + 1 : 0;
|
|
37631
|
+
if (run >= len) out[i] = fold(v, i - len + 1, i);
|
|
37632
|
+
}
|
|
37633
|
+
return out;
|
|
37634
|
+
}
|
|
37635
|
+
function sum(v, len) {
|
|
37636
|
+
return windowed(v, len, (w, from, to) => {
|
|
37637
|
+
let s = 0;
|
|
37638
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37639
|
+
return s;
|
|
37640
|
+
});
|
|
37641
|
+
}
|
|
37642
|
+
function sma(v, len) {
|
|
37643
|
+
return windowed(v, len, (w, from, to) => {
|
|
37644
|
+
let s = 0;
|
|
37645
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37646
|
+
return s / len;
|
|
37647
|
+
});
|
|
37648
|
+
}
|
|
37649
|
+
function wma(v, len) {
|
|
37650
|
+
const norm = len * (len + 1) / 2;
|
|
37651
|
+
return windowed(v, len, (w, from, to) => {
|
|
37652
|
+
let s = 0;
|
|
37653
|
+
for (let k = from; k <= to; k++) s += w[k] * (k - from + 1);
|
|
37654
|
+
return s / norm;
|
|
37655
|
+
});
|
|
37656
|
+
}
|
|
37657
|
+
function vwma(src, vol, len) {
|
|
37658
|
+
const pv = zip(src, vol, (p, q) => p * q);
|
|
37659
|
+
return zip(sum(pv, len), sum(vol, len), (a, b) => b > 0 ? a / b : Number.NaN);
|
|
37660
|
+
}
|
|
37661
|
+
function stdev(v, len) {
|
|
37662
|
+
return windowed(v, len, (w, from, to) => {
|
|
37663
|
+
let s = 0;
|
|
37664
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
37665
|
+
const mean = s / len;
|
|
37666
|
+
let sq = 0;
|
|
37667
|
+
for (let k = from; k <= to; k++) {
|
|
37668
|
+
const d = w[k] - mean;
|
|
37669
|
+
sq += d * d;
|
|
37670
|
+
}
|
|
37671
|
+
return Math.sqrt(sq / len);
|
|
37672
|
+
});
|
|
37673
|
+
}
|
|
37674
|
+
function highest(v, len) {
|
|
37675
|
+
return windowed(v, len, (w, from, to) => {
|
|
37676
|
+
let m = -Infinity;
|
|
37677
|
+
for (let k = from; k <= to; k++) if (w[k] > m) m = w[k];
|
|
37678
|
+
return m;
|
|
37679
|
+
});
|
|
37680
|
+
}
|
|
37681
|
+
function lowest(v, len) {
|
|
37682
|
+
return windowed(v, len, (w, from, to) => {
|
|
37683
|
+
let m = Infinity;
|
|
37684
|
+
for (let k = from; k <= to; k++) if (w[k] < m) m = w[k];
|
|
37685
|
+
return m;
|
|
37686
|
+
});
|
|
37687
|
+
}
|
|
37688
|
+
function barsSinceHighest(v, len) {
|
|
37689
|
+
return windowed(v, len, (w, from, to) => {
|
|
37690
|
+
let m = -Infinity;
|
|
37691
|
+
let at = to;
|
|
37692
|
+
for (let k = from; k <= to; k++) {
|
|
37693
|
+
if (w[k] >= m) {
|
|
37694
|
+
m = w[k];
|
|
37695
|
+
at = k;
|
|
37696
|
+
}
|
|
37697
|
+
}
|
|
37698
|
+
return to - at;
|
|
37699
|
+
});
|
|
37700
|
+
}
|
|
37701
|
+
function barsSinceLowest(v, len) {
|
|
37702
|
+
return windowed(v, len, (w, from, to) => {
|
|
37703
|
+
let m = Infinity;
|
|
37704
|
+
let at = to;
|
|
37705
|
+
for (let k = from; k <= to; k++) {
|
|
37706
|
+
if (w[k] <= m) {
|
|
37707
|
+
m = w[k];
|
|
37708
|
+
at = k;
|
|
37709
|
+
}
|
|
37710
|
+
}
|
|
37711
|
+
return to - at;
|
|
37712
|
+
});
|
|
37713
|
+
}
|
|
37714
|
+
function recursive(v, len, alpha) {
|
|
37715
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37716
|
+
let run = 0;
|
|
37717
|
+
let prev2 = Number.NaN;
|
|
37718
|
+
for (let i = 0; i < v.length; i++) {
|
|
37719
|
+
const x = v[i];
|
|
37720
|
+
if (!Number.isFinite(x)) {
|
|
37721
|
+
run = 0;
|
|
37722
|
+
prev2 = Number.NaN;
|
|
37723
|
+
continue;
|
|
37724
|
+
}
|
|
37725
|
+
run += 1;
|
|
37726
|
+
if (Number.isFinite(prev2)) {
|
|
37727
|
+
prev2 = alpha * x + (1 - alpha) * prev2;
|
|
37728
|
+
out[i] = prev2;
|
|
37729
|
+
} else if (run >= len) {
|
|
37730
|
+
let s = 0;
|
|
37731
|
+
for (let k = i - len + 1; k <= i; k++) s += v[k];
|
|
37732
|
+
prev2 = s / len;
|
|
37733
|
+
out[i] = prev2;
|
|
37734
|
+
}
|
|
37735
|
+
}
|
|
37736
|
+
return out;
|
|
37737
|
+
}
|
|
37738
|
+
function ema(v, len) {
|
|
37739
|
+
return recursive(v, len, 2 / (len + 1));
|
|
37740
|
+
}
|
|
37741
|
+
function rma(v, len) {
|
|
37742
|
+
return recursive(v, len, 1 / len);
|
|
37743
|
+
}
|
|
37744
|
+
function change(v, n = 1) {
|
|
37745
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37746
|
+
for (let i = n; i < v.length; i++) {
|
|
37747
|
+
const a = v[i];
|
|
37748
|
+
const b = v[i - n];
|
|
37749
|
+
if (Number.isFinite(a) && Number.isFinite(b)) out[i] = a - b;
|
|
37750
|
+
}
|
|
37751
|
+
return out;
|
|
37752
|
+
}
|
|
37753
|
+
function roc(v, len) {
|
|
37754
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37755
|
+
for (let i = len; i < v.length; i++) {
|
|
37756
|
+
const a = v[i];
|
|
37757
|
+
const b = v[i - len];
|
|
37758
|
+
if (Number.isFinite(a) && Number.isFinite(b) && b !== 0) out[i] = 100 * (a - b) / b;
|
|
37759
|
+
}
|
|
37760
|
+
return out;
|
|
37761
|
+
}
|
|
37762
|
+
function shift(v, n) {
|
|
37763
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37764
|
+
for (let i = 0; i < v.length; i++) {
|
|
37765
|
+
const j = i - n;
|
|
37766
|
+
if (j >= 0 && j < v.length) out[i] = v[j];
|
|
37767
|
+
}
|
|
37768
|
+
return out;
|
|
37769
|
+
}
|
|
37770
|
+
function trueRange(bars) {
|
|
37771
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
37772
|
+
for (let i = 0; i < bars.length; i++) {
|
|
37773
|
+
const b = bars[i];
|
|
37774
|
+
if (i === 0) {
|
|
37775
|
+
out[i] = b.high - b.low;
|
|
37776
|
+
continue;
|
|
37777
|
+
}
|
|
37778
|
+
const pc = bars[i - 1].close;
|
|
37779
|
+
out[i] = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc));
|
|
37780
|
+
}
|
|
37781
|
+
return out;
|
|
37782
|
+
}
|
|
37783
|
+
function atr(bars, len) {
|
|
37784
|
+
return rma(trueRange(bars), len);
|
|
37785
|
+
}
|
|
37786
|
+
function rsi(v, len) {
|
|
37787
|
+
const d = change(v);
|
|
37788
|
+
const up = map(d, (x) => Math.max(x, 0));
|
|
37789
|
+
const dn = map(d, (x) => Math.max(-x, 0));
|
|
37790
|
+
return zip(rma(up, len), rma(dn, len), (u, w) => w === 0 ? 100 : 100 - 100 / (1 + u / w));
|
|
37791
|
+
}
|
|
37792
|
+
function stoch(v, hi, lo, len) {
|
|
37793
|
+
const hh = highest(hi, len);
|
|
37794
|
+
const ll = lowest(lo, len);
|
|
37795
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37796
|
+
for (let i = 0; i < v.length; i++) {
|
|
37797
|
+
const x = v[i];
|
|
37798
|
+
const h = hh[i];
|
|
37799
|
+
const l = ll[i];
|
|
37800
|
+
if (!Number.isFinite(x) || !Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
37801
|
+
out[i] = h === l ? 50 : 100 * (x - l) / (h - l);
|
|
37802
|
+
}
|
|
37803
|
+
return out;
|
|
37804
|
+
}
|
|
37805
|
+
function linreg(v, len) {
|
|
37806
|
+
const sx = (len - 1) * len / 2;
|
|
37807
|
+
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
37808
|
+
const denom = len * sxx - sx * sx;
|
|
37809
|
+
return windowed(v, len, (w, from, to) => {
|
|
37810
|
+
let sy = 0;
|
|
37811
|
+
let sxy = 0;
|
|
37812
|
+
for (let k = from; k <= to; k++) {
|
|
37813
|
+
const x = k - from;
|
|
37814
|
+
sy += w[k];
|
|
37815
|
+
sxy += x * w[k];
|
|
37816
|
+
}
|
|
37817
|
+
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
37818
|
+
const intercept = (sy - slope * sx) / len;
|
|
37819
|
+
return intercept + slope * (len - 1);
|
|
37820
|
+
});
|
|
37821
|
+
}
|
|
37822
|
+
function swma(v) {
|
|
37823
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37824
|
+
for (let i = 3; i < v.length; i++) {
|
|
37825
|
+
const a = v[i - 3];
|
|
37826
|
+
const b = v[i - 2];
|
|
37827
|
+
const c = v[i - 1];
|
|
37828
|
+
const d = v[i];
|
|
37829
|
+
if (Number.isFinite(a) && Number.isFinite(b) && Number.isFinite(c) && Number.isFinite(d)) {
|
|
37830
|
+
out[i] = (a + 2 * b + 2 * c + d) / 6;
|
|
37831
|
+
}
|
|
37832
|
+
}
|
|
37833
|
+
return out;
|
|
37834
|
+
}
|
|
37835
|
+
function percentRank(v, len) {
|
|
37836
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37837
|
+
for (let i = len; i < v.length; i++) {
|
|
37838
|
+
const x = v[i];
|
|
37839
|
+
if (!Number.isFinite(x)) continue;
|
|
37840
|
+
let below = 0;
|
|
37841
|
+
let count = 0;
|
|
37842
|
+
for (let k = i - len; k < i; k++) {
|
|
37843
|
+
const y = v[k];
|
|
37844
|
+
if (!Number.isFinite(y)) {
|
|
37845
|
+
count = -1;
|
|
37846
|
+
break;
|
|
37847
|
+
}
|
|
37848
|
+
count += 1;
|
|
37849
|
+
if (y <= x) below += 1;
|
|
37850
|
+
}
|
|
37851
|
+
if (count === len) out[i] = 100 * below / len;
|
|
37852
|
+
}
|
|
37853
|
+
return out;
|
|
37854
|
+
}
|
|
37855
|
+
function cumSum(v) {
|
|
37856
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
37857
|
+
let acc = 0;
|
|
37858
|
+
let seen = false;
|
|
37859
|
+
for (let i = 0; i < v.length; i++) {
|
|
37860
|
+
const x = v[i];
|
|
37861
|
+
if (Number.isFinite(x)) {
|
|
37862
|
+
acc += x;
|
|
37863
|
+
seen = true;
|
|
37864
|
+
}
|
|
37865
|
+
if (seen) out[i] = acc;
|
|
37866
|
+
}
|
|
37867
|
+
return out;
|
|
37868
|
+
}
|
|
37869
|
+
|
|
37870
|
+
// src/core/native-indicators/classics/shared.ts
|
|
37871
|
+
function lengthInput(defval, key = "length", title = "Length", max = 5e3) {
|
|
37872
|
+
return { key, title, type: "int", defval, min: 1, max, step: 1 };
|
|
37873
|
+
}
|
|
37874
|
+
function sourceInput(defval = "Close") {
|
|
37875
|
+
return { key: "source", title: "Source", type: "string", defval, options: SOURCES2 };
|
|
37876
|
+
}
|
|
37877
|
+
function colorInput(defval = SERIES_LINE, title = "Color", key = "color") {
|
|
37878
|
+
return { key, title, type: "color", defval };
|
|
37879
|
+
}
|
|
37880
|
+
function withAlpha2(hex, alpha = 0.08) {
|
|
37881
|
+
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
37882
|
+
if (!m) return hex;
|
|
37883
|
+
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
37884
|
+
return `#${m[1]}${a}`;
|
|
37885
|
+
}
|
|
37886
|
+
|
|
37887
|
+
// src/core/native-indicators/classics/averages.ts
|
|
37888
|
+
var MA_KINDS = ["SMA", "EMA", "WMA", "RMA", "VWMA"];
|
|
37889
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1 };
|
|
37890
|
+
function fixedAverage(type, title, shortTitle, smooth) {
|
|
37891
|
+
return {
|
|
37892
|
+
type,
|
|
37893
|
+
title,
|
|
37894
|
+
shortTitle,
|
|
37895
|
+
overlay: true,
|
|
37896
|
+
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
37897
|
+
compute: (bars, inputs) => {
|
|
37898
|
+
const len = num3(inputs, "length", 20);
|
|
37899
|
+
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
37900
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37901
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37902
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37903
|
+
}
|
|
37904
|
+
};
|
|
37905
|
+
}
|
|
37906
|
+
var simpleMa = fixedAverage("sma", "Simple Moving Average", "SMA", sma);
|
|
37907
|
+
var exponentialMa = fixedAverage("ema", "Exponential Moving Average", "EMA", ema);
|
|
37908
|
+
var movingAverage = {
|
|
37909
|
+
type: "moving-average",
|
|
37910
|
+
title: "Moving Average",
|
|
37911
|
+
shortTitle: "MA",
|
|
37912
|
+
overlay: true,
|
|
37913
|
+
inputs: [
|
|
37914
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: MA_KINDS },
|
|
37915
|
+
lengthInput(20),
|
|
37916
|
+
sourceInput(),
|
|
37917
|
+
offsetInput,
|
|
37918
|
+
colorInput()
|
|
37919
|
+
],
|
|
37920
|
+
compute: (bars, inputs) => {
|
|
37921
|
+
const len = num3(inputs, "length", 20);
|
|
37922
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37923
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
37924
|
+
let values;
|
|
37925
|
+
switch (kind) {
|
|
37926
|
+
case "EMA":
|
|
37927
|
+
values = ema(src, len);
|
|
37928
|
+
break;
|
|
37929
|
+
case "WMA":
|
|
37930
|
+
values = wma(src, len);
|
|
37931
|
+
break;
|
|
37932
|
+
case "RMA":
|
|
37933
|
+
values = rma(src, len);
|
|
37934
|
+
break;
|
|
37935
|
+
case "VWMA":
|
|
37936
|
+
values = vwma(src, volumes(bars), len);
|
|
37937
|
+
break;
|
|
37938
|
+
default:
|
|
37939
|
+
values = sma(src, len);
|
|
37940
|
+
}
|
|
37941
|
+
const offset = Math.trunc(num3(inputs, "offset", 0));
|
|
37942
|
+
if (offset !== 0) values = shift(values, offset);
|
|
37943
|
+
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37944
|
+
}
|
|
37945
|
+
};
|
|
37946
|
+
var smoothedMa = {
|
|
37947
|
+
type: "rma",
|
|
37948
|
+
title: "Smoothed Moving Average",
|
|
37949
|
+
shortTitle: "RMA",
|
|
37950
|
+
overlay: true,
|
|
37951
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37952
|
+
compute: (bars, inputs) => ({
|
|
37953
|
+
plots: [
|
|
37954
|
+
{
|
|
37955
|
+
key: "rma",
|
|
37956
|
+
title: "RMA",
|
|
37957
|
+
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
37958
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
37959
|
+
width: 2
|
|
37960
|
+
}
|
|
37961
|
+
]
|
|
37962
|
+
})
|
|
37963
|
+
};
|
|
37964
|
+
var zlema = {
|
|
37965
|
+
type: "zlema",
|
|
37966
|
+
title: "Zero-Lag Exponential Moving Average",
|
|
37967
|
+
shortTitle: "ZLEMA",
|
|
37968
|
+
overlay: true,
|
|
37969
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
37970
|
+
compute: (bars, inputs) => {
|
|
37971
|
+
const len = num3(inputs, "length", 14);
|
|
37972
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37973
|
+
const lag = Math.floor((len - 1) / 2);
|
|
37974
|
+
const lagged = shift(src, lag);
|
|
37975
|
+
const delagged = zip(src, lagged, (a, b) => 2 * a - b);
|
|
37976
|
+
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
37977
|
+
}
|
|
37978
|
+
};
|
|
37979
|
+
var vidya = {
|
|
37980
|
+
type: "vidya",
|
|
37981
|
+
title: "Variable Index Dynamic Average",
|
|
37982
|
+
shortTitle: "VIDYA",
|
|
37983
|
+
overlay: true,
|
|
37984
|
+
inputs: [lengthInput(14), { key: "cmoLength", title: "Momentum length", type: "int", defval: 9, min: 1, max: 500, step: 1 }, sourceInput(), colorInput()],
|
|
37985
|
+
compute: (bars, inputs) => {
|
|
37986
|
+
const len = num3(inputs, "length", 14);
|
|
37987
|
+
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
37988
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
37989
|
+
const d = change(src);
|
|
37990
|
+
const su = new Array(src.length).fill(Number.NaN);
|
|
37991
|
+
const sd = new Array(src.length).fill(Number.NaN);
|
|
37992
|
+
for (let i = cmoLen; i < src.length; i++) {
|
|
37993
|
+
let u = 0;
|
|
37994
|
+
let w = 0;
|
|
37995
|
+
let ok = true;
|
|
37996
|
+
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
37997
|
+
const x = d[k];
|
|
37998
|
+
if (!Number.isFinite(x)) {
|
|
37999
|
+
ok = false;
|
|
38000
|
+
break;
|
|
38001
|
+
}
|
|
38002
|
+
if (x > 0) u += x;
|
|
38003
|
+
else w -= x;
|
|
38004
|
+
}
|
|
38005
|
+
if (ok) {
|
|
38006
|
+
su[i] = u;
|
|
38007
|
+
sd[i] = w;
|
|
38008
|
+
}
|
|
38009
|
+
}
|
|
38010
|
+
const alpha = 2 / (len + 1);
|
|
38011
|
+
const out = new Array(src.length).fill(Number.NaN);
|
|
38012
|
+
let prev2 = Number.NaN;
|
|
38013
|
+
for (let i = 0; i < src.length; i++) {
|
|
38014
|
+
const x = src[i];
|
|
38015
|
+
const u = su[i];
|
|
38016
|
+
const w = sd[i];
|
|
38017
|
+
if (!Number.isFinite(x) || !Number.isFinite(u) || !Number.isFinite(w)) continue;
|
|
38018
|
+
const total = u + w;
|
|
38019
|
+
const k = total === 0 ? 0 : Math.abs((u - w) / total);
|
|
38020
|
+
prev2 = Number.isFinite(prev2) ? alpha * k * x + (1 - alpha * k) * prev2 : x;
|
|
38021
|
+
out[i] = prev2;
|
|
38022
|
+
}
|
|
38023
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38024
|
+
}
|
|
38025
|
+
};
|
|
38026
|
+
var maEnvelope = {
|
|
38027
|
+
type: "ma-envelope",
|
|
38028
|
+
title: "Moving Average Envelope",
|
|
38029
|
+
shortTitle: "MA Env",
|
|
38030
|
+
overlay: true,
|
|
38031
|
+
inputs: [
|
|
38032
|
+
{ key: "maType", title: "Type", type: "string", defval: "SMA", options: ["SMA", "EMA"] },
|
|
38033
|
+
lengthInput(20),
|
|
38034
|
+
sourceInput(),
|
|
38035
|
+
{ key: "percent", title: "Percent", type: "float", defval: 2.5, min: 0.01, max: 50, step: 0.1 },
|
|
38036
|
+
colorInput()
|
|
38037
|
+
],
|
|
38038
|
+
compute: (bars, inputs) => {
|
|
38039
|
+
const len = num3(inputs, "length", 20);
|
|
38040
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38041
|
+
const basis = str3(inputs, "maType", "SMA") === "EMA" ? ema(src, len) : sma(src, len);
|
|
38042
|
+
const k = num3(inputs, "percent", 2.5) / 100;
|
|
38043
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38044
|
+
return {
|
|
38045
|
+
plots: [
|
|
38046
|
+
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
38047
|
+
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
38048
|
+
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
38049
|
+
],
|
|
38050
|
+
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38051
|
+
};
|
|
38052
|
+
}
|
|
38053
|
+
};
|
|
38054
|
+
var linearRegression = {
|
|
38055
|
+
type: "linear-regression",
|
|
38056
|
+
title: "Linear Regression Curve",
|
|
38057
|
+
shortTitle: "LinReg",
|
|
38058
|
+
overlay: true,
|
|
38059
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38060
|
+
compute: (bars, inputs) => ({
|
|
38061
|
+
plots: [
|
|
38062
|
+
{
|
|
38063
|
+
key: "linreg",
|
|
38064
|
+
title: "LinReg",
|
|
38065
|
+
values: linreg(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38066
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38067
|
+
width: 2
|
|
38068
|
+
}
|
|
38069
|
+
]
|
|
38070
|
+
})
|
|
38071
|
+
};
|
|
38072
|
+
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
38073
|
+
|
|
38074
|
+
// src/core/native-indicators/classics/bands.ts
|
|
38075
|
+
function bollinger(bars, inputs) {
|
|
38076
|
+
const len = num3(inputs, "length", 20);
|
|
38077
|
+
const mult = num3(inputs, "mult", 2);
|
|
38078
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38079
|
+
const basis = sma(src, len);
|
|
38080
|
+
const dev = map(stdev(src, len), (x) => x * mult);
|
|
38081
|
+
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
38082
|
+
}
|
|
38083
|
+
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
38084
|
+
var bollingerBands = {
|
|
38085
|
+
type: "bollinger-bands",
|
|
38086
|
+
title: "Bollinger Bands",
|
|
38087
|
+
shortTitle: "BB",
|
|
38088
|
+
overlay: true,
|
|
38089
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38090
|
+
compute: (bars, inputs) => {
|
|
38091
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
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: upper, color: ink },
|
|
38097
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38098
|
+
],
|
|
38099
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38100
|
+
};
|
|
38101
|
+
}
|
|
38102
|
+
};
|
|
38103
|
+
var percentB = {
|
|
38104
|
+
type: "percent-b",
|
|
38105
|
+
title: "Bollinger Bands %B",
|
|
38106
|
+
shortTitle: "%B",
|
|
38107
|
+
overlay: false,
|
|
38108
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38109
|
+
compute: (bars, inputs) => {
|
|
38110
|
+
const { upper, lower } = bollinger(bars, inputs);
|
|
38111
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38112
|
+
const values = src.map((x, i) => {
|
|
38113
|
+
const u = upper[i];
|
|
38114
|
+
const l = lower[i];
|
|
38115
|
+
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
38116
|
+
});
|
|
38117
|
+
return {
|
|
38118
|
+
plots: [{ key: "pb", title: "%B", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38119
|
+
levels: [
|
|
38120
|
+
{ key: "upper", price: 1, color: NEUTRAL },
|
|
38121
|
+
{ key: "middle", price: 0.5, color: NEUTRAL, lineStyle: "dotted" },
|
|
38122
|
+
{ key: "lower", price: 0, color: NEUTRAL }
|
|
38123
|
+
]
|
|
38124
|
+
};
|
|
38125
|
+
}
|
|
38126
|
+
};
|
|
38127
|
+
var bandwidth = {
|
|
38128
|
+
type: "bandwidth",
|
|
38129
|
+
title: "Bollinger Bands Width",
|
|
38130
|
+
shortTitle: "BandWidth",
|
|
38131
|
+
overlay: false,
|
|
38132
|
+
inputs: [...BB_INPUTS, colorInput()],
|
|
38133
|
+
compute: (bars, inputs) => {
|
|
38134
|
+
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
38135
|
+
const values = basis.map((b, i) => {
|
|
38136
|
+
const u = upper[i];
|
|
38137
|
+
const l = lower[i];
|
|
38138
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b * 100 : Number.NaN;
|
|
38139
|
+
});
|
|
38140
|
+
return { plots: [{ key: "bbw", title: "BBW", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
38141
|
+
}
|
|
38142
|
+
};
|
|
38143
|
+
var keltner = {
|
|
38144
|
+
type: "keltner-channels",
|
|
38145
|
+
title: "Keltner Channels",
|
|
38146
|
+
shortTitle: "KC",
|
|
38147
|
+
overlay: true,
|
|
38148
|
+
inputs: [
|
|
38149
|
+
lengthInput(20),
|
|
38150
|
+
{ key: "mult", title: "Multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38151
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38152
|
+
sourceInput(),
|
|
38153
|
+
colorInput()
|
|
38154
|
+
],
|
|
38155
|
+
compute: (bars, inputs) => {
|
|
38156
|
+
const len = num3(inputs, "length", 20);
|
|
38157
|
+
const mult = num3(inputs, "mult", 2);
|
|
38158
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
38159
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * mult);
|
|
38160
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38161
|
+
return {
|
|
38162
|
+
plots: [
|
|
38163
|
+
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
38164
|
+
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
38165
|
+
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
38166
|
+
],
|
|
38167
|
+
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38168
|
+
};
|
|
38169
|
+
}
|
|
38170
|
+
};
|
|
38171
|
+
var donchian = {
|
|
38172
|
+
type: "donchian-channels",
|
|
38173
|
+
title: "Donchian Channels",
|
|
38174
|
+
shortTitle: "DC",
|
|
38175
|
+
overlay: true,
|
|
38176
|
+
inputs: [lengthInput(20), colorInput()],
|
|
38177
|
+
compute: (bars, inputs) => {
|
|
38178
|
+
const len = num3(inputs, "length", 20);
|
|
38179
|
+
const upper = highest(highs(bars), len);
|
|
38180
|
+
const lower = lowest(lows(bars), len);
|
|
38181
|
+
const ink = str3(inputs, "color", SERIES_LINE);
|
|
38182
|
+
return {
|
|
38183
|
+
plots: [
|
|
38184
|
+
{ key: "basis", title: "Basis", values: zip(upper, lower, (u, l) => (u + l) / 2), color: WARNING },
|
|
38185
|
+
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
38186
|
+
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
38187
|
+
],
|
|
38188
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
38189
|
+
};
|
|
38190
|
+
}
|
|
38191
|
+
};
|
|
38192
|
+
var chandelier = {
|
|
38193
|
+
type: "chandelier-exit",
|
|
38194
|
+
title: "Chandelier Exit",
|
|
38195
|
+
shortTitle: "CE",
|
|
38196
|
+
overlay: true,
|
|
38197
|
+
inputs: [lengthInput(22), { key: "mult", title: "ATR multiplier", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }],
|
|
38198
|
+
compute: (bars, inputs) => {
|
|
38199
|
+
const len = num3(inputs, "length", 22);
|
|
38200
|
+
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
38201
|
+
const long = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
38202
|
+
const short = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
38203
|
+
return {
|
|
38204
|
+
plots: [
|
|
38205
|
+
{ key: "long", title: "Long stop", values: long, color: BULLISH },
|
|
38206
|
+
{ key: "short", title: "Short stop", values: short, color: BEARISH }
|
|
38207
|
+
]
|
|
38208
|
+
};
|
|
38209
|
+
}
|
|
38210
|
+
};
|
|
38211
|
+
var chandeKroll = {
|
|
38212
|
+
type: "chande-kroll-stop",
|
|
38213
|
+
title: "Chande Kroll Stop",
|
|
38214
|
+
shortTitle: "CKS",
|
|
38215
|
+
overlay: true,
|
|
38216
|
+
inputs: [
|
|
38217
|
+
{ key: "p", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38218
|
+
{ key: "x", title: "ATR multiplier", type: "float", defval: 1, min: 0.1, max: 50, step: 0.1 },
|
|
38219
|
+
{ key: "q", title: "Stop length", type: "int", defval: 9, min: 1, max: 500, step: 1 }
|
|
38220
|
+
],
|
|
38221
|
+
compute: (bars, inputs) => {
|
|
38222
|
+
const p = num3(inputs, "p", 10);
|
|
38223
|
+
const x = num3(inputs, "x", 1);
|
|
38224
|
+
const q = num3(inputs, "q", 9);
|
|
38225
|
+
const range = map(atr(bars, p), (v) => v * x);
|
|
38226
|
+
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
38227
|
+
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
38228
|
+
return {
|
|
38229
|
+
plots: [
|
|
38230
|
+
{ key: "stopShort", title: "Stop short", values: highest(firstHigh, q), color: BEARISH },
|
|
38231
|
+
{ key: "stopLong", title: "Stop long", values: lowest(firstLow, q), color: BULLISH }
|
|
38232
|
+
]
|
|
38233
|
+
};
|
|
38234
|
+
}
|
|
38235
|
+
};
|
|
38236
|
+
var supertrend = {
|
|
38237
|
+
type: "supertrend",
|
|
38238
|
+
title: "SuperTrend",
|
|
38239
|
+
shortTitle: "SuperTrend",
|
|
38240
|
+
overlay: true,
|
|
38241
|
+
inputs: [
|
|
38242
|
+
{ key: "atrLength", title: "ATR length", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
38243
|
+
{ key: "mult", title: "Factor", type: "float", defval: 3, min: 0.1, max: 50, step: 0.1 }
|
|
38244
|
+
],
|
|
38245
|
+
compute: (bars, inputs) => {
|
|
38246
|
+
const len = num3(inputs, "atrLength", 10);
|
|
38247
|
+
const mult = num3(inputs, "mult", 3);
|
|
38248
|
+
const range = atr(bars, len);
|
|
38249
|
+
const n = bars.length;
|
|
38250
|
+
const st = new Array(n).fill(Number.NaN);
|
|
38251
|
+
const dir = new Array(n).fill(1);
|
|
38252
|
+
let up = Number.NaN;
|
|
38253
|
+
let dn = Number.NaN;
|
|
38254
|
+
let trend = 1;
|
|
38255
|
+
for (let i = 0; i < n; i++) {
|
|
38256
|
+
const r = range[i];
|
|
38257
|
+
if (!Number.isFinite(r)) continue;
|
|
38258
|
+
const b = bars[i];
|
|
38259
|
+
const mid = (b.high + b.low) / 2;
|
|
38260
|
+
const basicUp = mid - mult * r;
|
|
38261
|
+
const basicDn = mid + mult * r;
|
|
38262
|
+
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
38263
|
+
up = Number.isFinite(up) && prevClose > up ? Math.max(basicUp, up) : basicUp;
|
|
38264
|
+
dn = Number.isFinite(dn) && prevClose < dn ? Math.min(basicDn, dn) : basicDn;
|
|
38265
|
+
if (trend === 1 && b.close < up) trend = -1;
|
|
38266
|
+
else if (trend === -1 && b.close > dn) trend = 1;
|
|
38267
|
+
dir[i] = trend;
|
|
38268
|
+
st[i] = trend === 1 ? up : dn;
|
|
38269
|
+
}
|
|
38270
|
+
const colors = dir.map((d, i) => Number.isFinite(st[i]) ? d === 1 ? BULLISH : BEARISH : null);
|
|
38271
|
+
return { plots: [{ key: "st", title: "SuperTrend", values: st, color: BULLISH, width: 2, colors }] };
|
|
38272
|
+
}
|
|
38273
|
+
};
|
|
38274
|
+
var alligator = {
|
|
38275
|
+
type: "williams-alligator",
|
|
38276
|
+
title: "Williams Alligator",
|
|
38277
|
+
shortTitle: "Alligator",
|
|
38278
|
+
overlay: true,
|
|
38279
|
+
inputs: [
|
|
38280
|
+
{ key: "jawLength", title: "Jaw length", type: "int", defval: 13, min: 1, max: 500, step: 1 },
|
|
38281
|
+
{ key: "teethLength", title: "Teeth length", type: "int", defval: 8, min: 1, max: 500, step: 1 },
|
|
38282
|
+
{ key: "lipsLength", title: "Lips length", type: "int", defval: 5, min: 1, max: 500, step: 1 },
|
|
38283
|
+
{ key: "jawOffset", title: "Jaw offset", type: "int", defval: 8, min: 0, max: 100, step: 1 },
|
|
38284
|
+
{ key: "teethOffset", title: "Teeth offset", type: "int", defval: 5, min: 0, max: 100, step: 1 },
|
|
38285
|
+
{ key: "lipsOffset", title: "Lips offset", type: "int", defval: 3, min: 0, max: 100, step: 1 }
|
|
38286
|
+
],
|
|
38287
|
+
compute: (bars, inputs) => {
|
|
38288
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38289
|
+
const jaw = shift(rma(hl2, num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
38290
|
+
const teeth = shift(rma(hl2, num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
38291
|
+
const lips = shift(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
38292
|
+
return {
|
|
38293
|
+
plots: [
|
|
38294
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: SERIES_LINE },
|
|
38295
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: BEARISH },
|
|
38296
|
+
{ key: "lips", title: "Lips", values: lips, color: BULLISH }
|
|
38297
|
+
]
|
|
38298
|
+
};
|
|
38299
|
+
}
|
|
38300
|
+
};
|
|
38301
|
+
var gator = {
|
|
38302
|
+
type: "gator-oscillator",
|
|
38303
|
+
title: "Gator Oscillator",
|
|
38304
|
+
shortTitle: "Gator Oscillator",
|
|
38305
|
+
overlay: false,
|
|
38306
|
+
inputs: [],
|
|
38307
|
+
compute: (bars) => {
|
|
38308
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38309
|
+
const jaw = shift(rma(hl2, 13), 8);
|
|
38310
|
+
const teeth = shift(rma(hl2, 8), 5);
|
|
38311
|
+
const lips = shift(rma(hl2, 5), 3);
|
|
38312
|
+
const upper = map(sub(jaw, teeth), Math.abs);
|
|
38313
|
+
const lower = map(sub(teeth, lips), (x) => -Math.abs(x));
|
|
38314
|
+
const colorByExpansion = (v) => v.map((x, i) => {
|
|
38315
|
+
if (!Number.isFinite(x)) return null;
|
|
38316
|
+
const prev2 = i > 0 ? v[i - 1] : Number.NaN;
|
|
38317
|
+
return Number.isFinite(prev2) && Math.abs(x) > Math.abs(prev2) ? BULLISH : BEARISH;
|
|
38318
|
+
});
|
|
38319
|
+
return {
|
|
38320
|
+
plots: [
|
|
38321
|
+
{ key: "upper", title: "Upper", values: upper, kind: "histogram", color: BULLISH, colors: colorByExpansion(upper), base: 0 },
|
|
38322
|
+
{ key: "lower", title: "Lower", values: lower, kind: "histogram", color: BEARISH, colors: colorByExpansion(lower), base: 0 }
|
|
38323
|
+
]
|
|
38324
|
+
};
|
|
38325
|
+
}
|
|
38326
|
+
};
|
|
38327
|
+
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
38328
|
+
|
|
38329
|
+
// src/core/native-indicators/classics/oscillators.ts
|
|
38330
|
+
function guideLevels(low, high, mid) {
|
|
38331
|
+
const levels = [
|
|
38332
|
+
{ key: "upper", price: high, color: NEUTRAL },
|
|
38333
|
+
{ key: "lower", price: low, color: NEUTRAL }
|
|
38334
|
+
];
|
|
38335
|
+
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
38336
|
+
return levels;
|
|
38337
|
+
}
|
|
38338
|
+
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
38339
|
+
function histColors(values) {
|
|
38340
|
+
return values.map((x, i) => {
|
|
38341
|
+
if (!Number.isFinite(x)) return null;
|
|
38342
|
+
const prev2 = i > 0 ? values[i - 1] : Number.NaN;
|
|
38343
|
+
const rising = Number.isFinite(prev2) ? x >= prev2 : true;
|
|
38344
|
+
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
38345
|
+
return rising ? `${BEARISH}80` : BEARISH;
|
|
38346
|
+
});
|
|
38347
|
+
}
|
|
38348
|
+
var rsiSpec = {
|
|
38349
|
+
type: "rsi",
|
|
38350
|
+
title: "Relative Strength Index",
|
|
38351
|
+
shortTitle: "RSI",
|
|
38352
|
+
overlay: false,
|
|
38353
|
+
inputs: [lengthInput(14), sourceInput(), colorInput()],
|
|
38354
|
+
compute: (bars, inputs) => ({
|
|
38355
|
+
plots: [
|
|
38356
|
+
{
|
|
38357
|
+
key: "rsi",
|
|
38358
|
+
title: "RSI",
|
|
38359
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
38360
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38361
|
+
width: 2
|
|
38362
|
+
}
|
|
38363
|
+
],
|
|
38364
|
+
levels: guideLevels(30, 70, 50)
|
|
38365
|
+
})
|
|
38366
|
+
};
|
|
38367
|
+
var stochastic = {
|
|
38368
|
+
type: "stochastic",
|
|
38369
|
+
title: "Stochastic",
|
|
38370
|
+
shortTitle: "Stoch",
|
|
38371
|
+
overlay: false,
|
|
38372
|
+
inputs: [
|
|
38373
|
+
lengthInput(14, "kLength", "%K length"),
|
|
38374
|
+
lengthInput(1, "kSmoothing", "%K smoothing", 500),
|
|
38375
|
+
lengthInput(3, "dLength", "%D smoothing", 500)
|
|
38376
|
+
],
|
|
38377
|
+
compute: (bars, inputs) => {
|
|
38378
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 1));
|
|
38379
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38380
|
+
return {
|
|
38381
|
+
plots: [
|
|
38382
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38383
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38384
|
+
],
|
|
38385
|
+
levels: guideLevels(20, 80)
|
|
38386
|
+
};
|
|
38387
|
+
}
|
|
38388
|
+
};
|
|
38389
|
+
var stochasticRsi = {
|
|
38390
|
+
type: "stochastic-rsi",
|
|
38391
|
+
title: "Stochastic RSI",
|
|
38392
|
+
shortTitle: "Stoch RSI",
|
|
38393
|
+
overlay: false,
|
|
38394
|
+
inputs: [
|
|
38395
|
+
lengthInput(14, "rsiLength", "RSI length"),
|
|
38396
|
+
lengthInput(14, "stochLength", "Stochastic length"),
|
|
38397
|
+
lengthInput(3, "kSmoothing", "%K smoothing", 500),
|
|
38398
|
+
lengthInput(3, "dLength", "%D smoothing", 500),
|
|
38399
|
+
sourceInput()
|
|
38400
|
+
],
|
|
38401
|
+
compute: (bars, inputs) => {
|
|
38402
|
+
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
38403
|
+
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
38404
|
+
const d = sma(k, num3(inputs, "dLength", 3));
|
|
38405
|
+
return {
|
|
38406
|
+
plots: [
|
|
38407
|
+
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
38408
|
+
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
38409
|
+
],
|
|
38410
|
+
levels: guideLevels(20, 80)
|
|
38411
|
+
};
|
|
38412
|
+
}
|
|
38413
|
+
};
|
|
38414
|
+
var macd = {
|
|
38415
|
+
type: "macd",
|
|
38416
|
+
title: "MACD",
|
|
38417
|
+
overlay: false,
|
|
38418
|
+
inputs: [
|
|
38419
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38420
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38421
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38422
|
+
sourceInput()
|
|
38423
|
+
],
|
|
38424
|
+
compute: (bars, inputs) => {
|
|
38425
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38426
|
+
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
38427
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38428
|
+
const hist = sub(line, signal);
|
|
38429
|
+
return {
|
|
38430
|
+
plots: [
|
|
38431
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38432
|
+
{ key: "macd", title: "MACD", values: line, color: SERIES_LINE, width: 2 },
|
|
38433
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38434
|
+
],
|
|
38435
|
+
levels: ZERO_LEVEL
|
|
38436
|
+
};
|
|
38437
|
+
}
|
|
38438
|
+
};
|
|
38439
|
+
var ppo = {
|
|
38440
|
+
type: "ppo",
|
|
38441
|
+
title: "Percentage Price Oscillator",
|
|
38442
|
+
shortTitle: "PPO",
|
|
38443
|
+
overlay: false,
|
|
38444
|
+
inputs: [
|
|
38445
|
+
lengthInput(12, "fastLength", "Fast length"),
|
|
38446
|
+
lengthInput(26, "slowLength", "Slow length"),
|
|
38447
|
+
lengthInput(9, "signalLength", "Signal smoothing", 500),
|
|
38448
|
+
sourceInput()
|
|
38449
|
+
],
|
|
38450
|
+
compute: (bars, inputs) => {
|
|
38451
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38452
|
+
const fast = ema(src, num3(inputs, "fastLength", 12));
|
|
38453
|
+
const slow = ema(src, num3(inputs, "slowLength", 26));
|
|
38454
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
38455
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
38456
|
+
const hist = sub(line, signal);
|
|
38457
|
+
return {
|
|
38458
|
+
plots: [
|
|
38459
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: histColors(hist), base: 0 },
|
|
38460
|
+
{ key: "ppo", title: "PPO", values: line, color: SERIES_LINE, width: 2 },
|
|
38461
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38462
|
+
],
|
|
38463
|
+
levels: ZERO_LEVEL
|
|
38464
|
+
};
|
|
38465
|
+
}
|
|
38466
|
+
};
|
|
38467
|
+
var awesome = {
|
|
38468
|
+
type: "awesome-oscillator",
|
|
38469
|
+
title: "Awesome Oscillator",
|
|
38470
|
+
shortTitle: "AO",
|
|
38471
|
+
overlay: false,
|
|
38472
|
+
inputs: [],
|
|
38473
|
+
compute: (bars) => {
|
|
38474
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38475
|
+
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
38476
|
+
const colors = ao.map((x, i) => {
|
|
38477
|
+
if (!Number.isFinite(x)) return null;
|
|
38478
|
+
const prev2 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
38479
|
+
return Number.isFinite(prev2) && x < prev2 ? BEARISH : BULLISH;
|
|
38480
|
+
});
|
|
38481
|
+
return {
|
|
38482
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "histogram", color: BULLISH, colors, base: 0 }],
|
|
38483
|
+
levels: ZERO_LEVEL
|
|
38484
|
+
};
|
|
38485
|
+
}
|
|
38486
|
+
};
|
|
38487
|
+
var cci = {
|
|
38488
|
+
type: "commodity-channel-index",
|
|
38489
|
+
title: "Commodity Channel Index",
|
|
38490
|
+
shortTitle: "CCI",
|
|
38491
|
+
overlay: false,
|
|
38492
|
+
inputs: [lengthInput(20), sourceInput("HLC3"), colorInput()],
|
|
38493
|
+
compute: (bars, inputs) => {
|
|
38494
|
+
const len = num3(inputs, "length", 20);
|
|
38495
|
+
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
38496
|
+
const basis = sma(src, len);
|
|
38497
|
+
const dev = new Array(src.length).fill(Number.NaN);
|
|
38498
|
+
for (let i = len - 1; i < src.length; i++) {
|
|
38499
|
+
const mean = basis[i];
|
|
38500
|
+
if (!Number.isFinite(mean)) continue;
|
|
38501
|
+
let d = 0;
|
|
38502
|
+
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
38503
|
+
dev[i] = d / len;
|
|
38504
|
+
}
|
|
38505
|
+
const values = src.map((x, i) => {
|
|
38506
|
+
const b = basis[i];
|
|
38507
|
+
const d = dev[i];
|
|
38508
|
+
return Number.isFinite(b) && Number.isFinite(d) && d !== 0 ? (x - b) / (0.015 * d) : Number.NaN;
|
|
38509
|
+
});
|
|
38510
|
+
return {
|
|
38511
|
+
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38512
|
+
levels: guideLevels(-100, 100, 0)
|
|
38513
|
+
};
|
|
38514
|
+
}
|
|
38515
|
+
};
|
|
38516
|
+
var williamsR = {
|
|
38517
|
+
type: "williams-percent-r",
|
|
38518
|
+
title: "Williams %R",
|
|
38519
|
+
shortTitle: "%R",
|
|
38520
|
+
overlay: false,
|
|
38521
|
+
inputs: [lengthInput(14), colorInput()],
|
|
38522
|
+
compute: (bars, inputs) => {
|
|
38523
|
+
const values = map(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "length", 14)), (x) => x - 100);
|
|
38524
|
+
return {
|
|
38525
|
+
plots: [{ key: "wr", title: "%R", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38526
|
+
levels: guideLevels(-80, -20)
|
|
38527
|
+
};
|
|
38528
|
+
}
|
|
38529
|
+
};
|
|
38530
|
+
var cmo = {
|
|
38531
|
+
type: "chande-momentum-oscillator",
|
|
38532
|
+
title: "Chande Momentum Oscillator",
|
|
38533
|
+
shortTitle: "CMO",
|
|
38534
|
+
overlay: false,
|
|
38535
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38536
|
+
compute: (bars, inputs) => {
|
|
38537
|
+
const len = num3(inputs, "length", 9);
|
|
38538
|
+
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
38539
|
+
const su = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
38540
|
+
const sd = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
38541
|
+
const values = zip(su, sd, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
38542
|
+
return {
|
|
38543
|
+
plots: [{ key: "cmo", title: "CMO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38544
|
+
levels: guideLevels(-50, 50, 0)
|
|
38545
|
+
};
|
|
38546
|
+
}
|
|
38547
|
+
};
|
|
38548
|
+
var connorsRsi = {
|
|
38549
|
+
type: "connors-rsi",
|
|
38550
|
+
title: "Connors RSI",
|
|
38551
|
+
shortTitle: "CRSI",
|
|
38552
|
+
overlay: false,
|
|
38553
|
+
inputs: [
|
|
38554
|
+
lengthInput(3, "rsiLength", "RSI length"),
|
|
38555
|
+
lengthInput(2, "streakLength", "Streak RSI length"),
|
|
38556
|
+
lengthInput(100, "rankLength", "Percent-rank length"),
|
|
38557
|
+
colorInput()
|
|
38558
|
+
],
|
|
38559
|
+
compute: (bars, inputs) => {
|
|
38560
|
+
const c = closes(bars);
|
|
38561
|
+
const streak = new Array(c.length).fill(0);
|
|
38562
|
+
for (let i = 1; i < c.length; i++) {
|
|
38563
|
+
const d = c[i] - c[i - 1];
|
|
38564
|
+
streak[i] = d > 0 ? Math.max(streak[i - 1], 0) + 1 : d < 0 ? Math.min(streak[i - 1], 0) - 1 : 0;
|
|
38565
|
+
}
|
|
38566
|
+
const a = rsi(c, num3(inputs, "rsiLength", 3));
|
|
38567
|
+
const b = rsi(streak, num3(inputs, "streakLength", 2));
|
|
38568
|
+
const r = percentRank(roc(c, 1), num3(inputs, "rankLength", 100));
|
|
38569
|
+
const values = c.map((_, i) => {
|
|
38570
|
+
const x = a[i];
|
|
38571
|
+
const y = b[i];
|
|
38572
|
+
const z = r[i];
|
|
38573
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
38574
|
+
});
|
|
38575
|
+
return {
|
|
38576
|
+
plots: [{ key: "crsi", title: "CRSI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38577
|
+
levels: guideLevels(20, 80, 50)
|
|
38578
|
+
};
|
|
38579
|
+
}
|
|
38580
|
+
};
|
|
38581
|
+
var fisher = {
|
|
38582
|
+
type: "fisher-transform",
|
|
38583
|
+
title: "Fisher Transform",
|
|
38584
|
+
shortTitle: "Fisher",
|
|
38585
|
+
overlay: false,
|
|
38586
|
+
inputs: [lengthInput(9)],
|
|
38587
|
+
compute: (bars, inputs) => {
|
|
38588
|
+
const len = num3(inputs, "length", 9);
|
|
38589
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
38590
|
+
const hi = highest(hl2, len);
|
|
38591
|
+
const lo = lowest(hl2, len);
|
|
38592
|
+
const n = bars.length;
|
|
38593
|
+
const out = new Array(n).fill(Number.NaN);
|
|
38594
|
+
let value = 0;
|
|
38595
|
+
let fish = 0;
|
|
38596
|
+
for (let i = 0; i < n; i++) {
|
|
38597
|
+
const h = hi[i];
|
|
38598
|
+
const l = lo[i];
|
|
38599
|
+
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
38600
|
+
const norm = h === l ? 0 : (hl2[i] - l) / (h - l) - 0.5;
|
|
38601
|
+
value = Math.max(-0.999, Math.min(0.999, 0.66 * norm + 0.67 * value));
|
|
38602
|
+
fish = 0.5 * Math.log((1 + value) / (1 - value)) + 0.5 * fish;
|
|
38603
|
+
out[i] = fish;
|
|
38604
|
+
}
|
|
38605
|
+
return {
|
|
38606
|
+
plots: [
|
|
38607
|
+
{ key: "fisher", title: "Fisher", values: out, color: SERIES_LINE, width: 2 },
|
|
38608
|
+
{ key: "trigger", title: "Trigger", values: shift(out, 1), color: WARNING }
|
|
38609
|
+
],
|
|
38610
|
+
levels: ZERO_LEVEL
|
|
38611
|
+
};
|
|
38612
|
+
}
|
|
38613
|
+
};
|
|
38614
|
+
var trix = {
|
|
38615
|
+
type: "trix",
|
|
38616
|
+
title: "TRIX",
|
|
38617
|
+
overlay: false,
|
|
38618
|
+
inputs: [lengthInput(18), colorInput()],
|
|
38619
|
+
compute: (bars, inputs) => {
|
|
38620
|
+
const len = num3(inputs, "length", 18);
|
|
38621
|
+
const logClose = map(closes(bars), Math.log);
|
|
38622
|
+
const smooth = ema(ema(ema(logClose, len), len), len);
|
|
38623
|
+
const values = map(change(smooth), (x) => x * 1e4);
|
|
38624
|
+
return {
|
|
38625
|
+
plots: [{ key: "trix", title: "TRIX", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38626
|
+
levels: ZERO_LEVEL
|
|
38627
|
+
};
|
|
38628
|
+
}
|
|
38629
|
+
};
|
|
38630
|
+
function tsiLine(src, long, short) {
|
|
38631
|
+
const mom = change(src);
|
|
38632
|
+
const numer = ema(ema(mom, long), short);
|
|
38633
|
+
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
38634
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : 100 * a / b);
|
|
38635
|
+
}
|
|
38636
|
+
var tsi = {
|
|
38637
|
+
type: "true-strength-index",
|
|
38638
|
+
title: "True Strength Index",
|
|
38639
|
+
shortTitle: "TSI",
|
|
38640
|
+
overlay: false,
|
|
38641
|
+
inputs: [lengthInput(25, "longLength", "Long length"), lengthInput(13, "shortLength", "Short length"), lengthInput(13, "signalLength", "Signal length")],
|
|
38642
|
+
compute: (bars, inputs) => {
|
|
38643
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13));
|
|
38644
|
+
return {
|
|
38645
|
+
plots: [
|
|
38646
|
+
{ key: "tsi", title: "TSI", values: line, color: SERIES_LINE, width: 2 },
|
|
38647
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
38648
|
+
],
|
|
38649
|
+
levels: ZERO_LEVEL
|
|
38650
|
+
};
|
|
38651
|
+
}
|
|
38652
|
+
};
|
|
38653
|
+
var smiErgodic = {
|
|
38654
|
+
type: "smi-ergodic",
|
|
38655
|
+
title: "SMI Ergodic Oscillator",
|
|
38656
|
+
shortTitle: "SMIE",
|
|
38657
|
+
overlay: false,
|
|
38658
|
+
inputs: [lengthInput(5, "shortLength", "Short length"), lengthInput(20, "longLength", "Long length"), lengthInput(5, "signalLength", "Signal length")],
|
|
38659
|
+
compute: (bars, inputs) => {
|
|
38660
|
+
const line = tsiLine(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
38661
|
+
const signal = ema(line, num3(inputs, "signalLength", 5));
|
|
38662
|
+
const osc = sub(line, signal);
|
|
38663
|
+
return {
|
|
38664
|
+
plots: [
|
|
38665
|
+
{ key: "osc", title: "Oscillator", values: osc, kind: "histogram", color: BULLISH, colors: histColors(osc), base: 0 },
|
|
38666
|
+
{ key: "smi", title: "SMI", values: line, color: SERIES_LINE, width: 2 },
|
|
38667
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
38668
|
+
],
|
|
38669
|
+
levels: ZERO_LEVEL
|
|
38670
|
+
};
|
|
38671
|
+
}
|
|
38672
|
+
};
|
|
38673
|
+
var stc = {
|
|
38674
|
+
type: "schaff-trend-cycle",
|
|
38675
|
+
title: "Schaff Trend Cycle",
|
|
38676
|
+
shortTitle: "Schaff Trend Cycle",
|
|
38677
|
+
overlay: false,
|
|
38678
|
+
inputs: [lengthInput(10, "cycleLength", "Cycle length"), lengthInput(23, "fastLength", "Fast length"), lengthInput(50, "slowLength", "Slow length")],
|
|
38679
|
+
compute: (bars, inputs) => {
|
|
38680
|
+
const len = num3(inputs, "cycleLength", 10);
|
|
38681
|
+
const src = closes(bars);
|
|
38682
|
+
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
38683
|
+
const stochOf = (v) => {
|
|
38684
|
+
const lo = lowest(v, len);
|
|
38685
|
+
const hi = highest(v, len);
|
|
38686
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
38687
|
+
let prevRaw = Number.NaN;
|
|
38688
|
+
let smoothed = Number.NaN;
|
|
38689
|
+
for (let i = 0; i < v.length; i++) {
|
|
38690
|
+
const x = v[i];
|
|
38691
|
+
const l = lo[i];
|
|
38692
|
+
const h = hi[i];
|
|
38693
|
+
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
38694
|
+
const raw = h > l ? (x - l) / (h - l) * 100 : Number.isFinite(prevRaw) ? prevRaw : 50;
|
|
38695
|
+
prevRaw = raw;
|
|
38696
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + 0.5 * (raw - smoothed) : raw;
|
|
38697
|
+
out[i] = smoothed;
|
|
38698
|
+
}
|
|
38699
|
+
return out;
|
|
38700
|
+
};
|
|
38701
|
+
const values = stochOf(stochOf(macdLine));
|
|
38702
|
+
return {
|
|
38703
|
+
plots: [{ key: "stc", title: "STC", values, color: SERIES_LINE, width: 2 }],
|
|
38704
|
+
levels: guideLevels(25, 75)
|
|
38705
|
+
};
|
|
38706
|
+
}
|
|
38707
|
+
};
|
|
38708
|
+
var kst = {
|
|
38709
|
+
type: "know-sure-thing",
|
|
38710
|
+
title: "Know Sure Thing",
|
|
38711
|
+
shortTitle: "KST",
|
|
38712
|
+
overlay: false,
|
|
38713
|
+
inputs: [sourceInput(), lengthInput(9, "signalLength", "Signal length")],
|
|
38714
|
+
compute: (bars, inputs) => {
|
|
38715
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38716
|
+
const line = [
|
|
38717
|
+
sma(roc(src, 10), 10),
|
|
38718
|
+
sma(roc(src, 15), 10),
|
|
38719
|
+
sma(roc(src, 20), 10),
|
|
38720
|
+
sma(roc(src, 30), 15)
|
|
38721
|
+
].reduce((acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)), new Array(src.length).fill(0));
|
|
38722
|
+
return {
|
|
38723
|
+
plots: [
|
|
38724
|
+
{ key: "kst", title: "KST", values: line, color: SERIES_LINE, width: 2 },
|
|
38725
|
+
{ key: "signal", title: "Signal", values: sma(line, num3(inputs, "signalLength", 9)), color: WARNING }
|
|
38726
|
+
],
|
|
38727
|
+
levels: ZERO_LEVEL
|
|
38728
|
+
};
|
|
38729
|
+
}
|
|
38730
|
+
};
|
|
38731
|
+
var coppock = {
|
|
38732
|
+
type: "coppock-curve",
|
|
38733
|
+
title: "Coppock Curve",
|
|
38734
|
+
shortTitle: "Coppock",
|
|
38735
|
+
overlay: false,
|
|
38736
|
+
inputs: [
|
|
38737
|
+
lengthInput(10, "wmaLength", "WMA length"),
|
|
38738
|
+
lengthInput(14, "longRoc", "Long RoC length"),
|
|
38739
|
+
lengthInput(11, "shortRoc", "Short RoC length"),
|
|
38740
|
+
sourceInput(),
|
|
38741
|
+
colorInput()
|
|
38742
|
+
],
|
|
38743
|
+
compute: (bars, inputs) => {
|
|
38744
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38745
|
+
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
38746
|
+
return {
|
|
38747
|
+
plots: [{ key: "coppock", title: "Coppock", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38748
|
+
levels: ZERO_LEVEL
|
|
38749
|
+
};
|
|
38750
|
+
}
|
|
38751
|
+
};
|
|
38752
|
+
var dpo = {
|
|
38753
|
+
type: "detrended-price-oscillator",
|
|
38754
|
+
title: "Detrended Price Oscillator",
|
|
38755
|
+
shortTitle: "DPO",
|
|
38756
|
+
overlay: false,
|
|
38757
|
+
inputs: [lengthInput(21), colorInput()],
|
|
38758
|
+
compute: (bars, inputs) => {
|
|
38759
|
+
const len = num3(inputs, "length", 21);
|
|
38760
|
+
const barsback = Math.floor(len / 2) + 1;
|
|
38761
|
+
const values = sub(closes(bars), shift(sma(closes(bars), len), barsback));
|
|
38762
|
+
return {
|
|
38763
|
+
plots: [{ key: "dpo", title: "DPO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38764
|
+
levels: ZERO_LEVEL
|
|
38765
|
+
};
|
|
38766
|
+
}
|
|
38767
|
+
};
|
|
38768
|
+
var rocSpec = {
|
|
38769
|
+
type: "rate-of-change",
|
|
38770
|
+
title: "Rate of Change",
|
|
38771
|
+
shortTitle: "ROC",
|
|
38772
|
+
overlay: false,
|
|
38773
|
+
inputs: [lengthInput(9), sourceInput(), colorInput()],
|
|
38774
|
+
compute: (bars, inputs) => ({
|
|
38775
|
+
plots: [
|
|
38776
|
+
{
|
|
38777
|
+
key: "roc",
|
|
38778
|
+
title: "RoC",
|
|
38779
|
+
values: roc(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 9)),
|
|
38780
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
38781
|
+
width: 2
|
|
38782
|
+
}
|
|
38783
|
+
],
|
|
38784
|
+
levels: ZERO_LEVEL
|
|
38785
|
+
})
|
|
38786
|
+
};
|
|
38787
|
+
var ultimate = {
|
|
38788
|
+
type: "ultimate-oscillator",
|
|
38789
|
+
title: "Ultimate Oscillator",
|
|
38790
|
+
shortTitle: "UO",
|
|
38791
|
+
overlay: false,
|
|
38792
|
+
inputs: [lengthInput(7, "fastLength", "Fast length"), lengthInput(14, "middleLength", "Middle length"), lengthInput(28, "slowLength", "Slow length"), colorInput()],
|
|
38793
|
+
compute: (bars, inputs) => {
|
|
38794
|
+
const n = bars.length;
|
|
38795
|
+
const bp = new Array(n).fill(Number.NaN);
|
|
38796
|
+
const tr = new Array(n).fill(Number.NaN);
|
|
38797
|
+
for (let i = 0; i < n; i++) {
|
|
38798
|
+
const b = bars[i];
|
|
38799
|
+
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
38800
|
+
const lo = Math.min(b.low, pc);
|
|
38801
|
+
const hi = Math.max(b.high, pc);
|
|
38802
|
+
bp[i] = b.close - lo;
|
|
38803
|
+
tr[i] = hi - lo;
|
|
38804
|
+
}
|
|
38805
|
+
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
38806
|
+
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
38807
|
+
const a14 = avg(num3(inputs, "middleLength", 14));
|
|
38808
|
+
const a28 = avg(num3(inputs, "slowLength", 28));
|
|
38809
|
+
const values = a7.map((x, i) => {
|
|
38810
|
+
const y = a14[i];
|
|
38811
|
+
const z = a28[i];
|
|
38812
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
38813
|
+
});
|
|
38814
|
+
return {
|
|
38815
|
+
plots: [{ key: "uo", title: "UO", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38816
|
+
levels: guideLevels(30, 70, 50)
|
|
38817
|
+
};
|
|
38818
|
+
}
|
|
38819
|
+
};
|
|
38820
|
+
var rvgi = {
|
|
38821
|
+
type: "relative-vigor-index",
|
|
38822
|
+
title: "Relative Vigor Index",
|
|
38823
|
+
shortTitle: "RVGI",
|
|
38824
|
+
overlay: false,
|
|
38825
|
+
inputs: [lengthInput(10)],
|
|
38826
|
+
compute: (bars, inputs) => {
|
|
38827
|
+
const len = num3(inputs, "length", 10);
|
|
38828
|
+
const co = swma(sub(closes(bars), opens(bars)));
|
|
38829
|
+
const hl = swma(sub(highs(bars), lows(bars)));
|
|
38830
|
+
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
38831
|
+
return {
|
|
38832
|
+
plots: [
|
|
38833
|
+
{ key: "rvgi", title: "RVGI", values: line, color: SERIES_LINE, width: 2 },
|
|
38834
|
+
{ key: "signal", title: "Signal", values: swma(line), color: WARNING }
|
|
38835
|
+
],
|
|
38836
|
+
levels: ZERO_LEVEL
|
|
38837
|
+
};
|
|
38838
|
+
}
|
|
38839
|
+
};
|
|
38840
|
+
var relVolatility = {
|
|
38841
|
+
type: "relative-volatility-index",
|
|
38842
|
+
title: "Relative Volatility Index",
|
|
38843
|
+
shortTitle: "RVI",
|
|
38844
|
+
overlay: false,
|
|
38845
|
+
inputs: [lengthInput(10, "stdevLength", "StdDev length"), lengthInput(14, "smoothLength", "Smoothing length"), sourceInput(), colorInput()],
|
|
38846
|
+
compute: (bars, inputs) => {
|
|
38847
|
+
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
38848
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
38849
|
+
const dev = stdev(src, num3(inputs, "stdevLength", 10));
|
|
38850
|
+
const d = change(src);
|
|
38851
|
+
const up = rma(zip(dev, d, (s, x) => x > 0 ? s : 0), smoothLen);
|
|
38852
|
+
const dn = rma(zip(dev, d, (s, x) => x <= 0 ? s : 0), smoothLen);
|
|
38853
|
+
const values = zip(up, dn, (u, w) => u + w === 0 ? 50 : 100 * u / (u + w));
|
|
38854
|
+
return {
|
|
38855
|
+
plots: [{ key: "rvi", title: "RVI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38856
|
+
levels: guideLevels(20, 80, 50)
|
|
38857
|
+
};
|
|
38858
|
+
}
|
|
38859
|
+
};
|
|
38860
|
+
var bop = {
|
|
38861
|
+
type: "balance-of-power",
|
|
38862
|
+
title: "Balance of Power",
|
|
38863
|
+
shortTitle: "BOP",
|
|
38864
|
+
overlay: false,
|
|
38865
|
+
inputs: [colorInput()],
|
|
38866
|
+
compute: (bars, inputs) => {
|
|
38867
|
+
const values = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
38868
|
+
return {
|
|
38869
|
+
plots: [{ key: "bop", title: "BOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
38870
|
+
levels: ZERO_LEVEL
|
|
38871
|
+
};
|
|
38872
|
+
}
|
|
38873
|
+
};
|
|
38874
|
+
var elderRay = {
|
|
38875
|
+
type: "elder-ray",
|
|
38876
|
+
title: "Elder Ray",
|
|
38877
|
+
overlay: false,
|
|
38878
|
+
inputs: [lengthInput(13)],
|
|
38879
|
+
compute: (bars, inputs) => {
|
|
38880
|
+
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
38881
|
+
return {
|
|
38882
|
+
plots: [
|
|
38883
|
+
{ key: "bull", title: "Bull power", values: sub(highs(bars), basis), kind: "histogram", color: BULLISH, base: 0 },
|
|
38884
|
+
{ key: "bear", title: "Bear power", values: sub(lows(bars), basis), kind: "histogram", color: BEARISH, base: 0 }
|
|
38885
|
+
],
|
|
38886
|
+
levels: ZERO_LEVEL
|
|
38887
|
+
};
|
|
38888
|
+
}
|
|
38889
|
+
};
|
|
38890
|
+
var ttmSqueeze = {
|
|
38891
|
+
type: "ttm-squeeze",
|
|
38892
|
+
title: "TTM Squeeze",
|
|
38893
|
+
overlay: false,
|
|
38894
|
+
inputs: [
|
|
38895
|
+
lengthInput(20),
|
|
38896
|
+
{ key: "bbMult", title: "Bollinger multiplier", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 },
|
|
38897
|
+
{ key: "kcMult", title: "Keltner multiplier", type: "float", defval: 1.5, min: 0.1, max: 50, step: 0.1 }
|
|
38898
|
+
],
|
|
38899
|
+
compute: (bars, inputs) => {
|
|
38900
|
+
const len = num3(inputs, "length", 20);
|
|
38901
|
+
const c = closes(bars);
|
|
38902
|
+
const basis = sma(c, len);
|
|
38903
|
+
const dev = map(stdev(c, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
38904
|
+
const kcRange = map(rma(trueRange(bars), len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
38905
|
+
const donchianMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
38906
|
+
const anchor = zip(donchianMid, basis, (d, b) => (d + b) / 2);
|
|
38907
|
+
const mom = linreg(sub(c, anchor), len);
|
|
38908
|
+
const squeezed = c.map((_, i) => {
|
|
38909
|
+
const d = dev[i];
|
|
38910
|
+
const r = kcRange[i];
|
|
38911
|
+
return Number.isFinite(d) && Number.isFinite(r) ? d < r : false;
|
|
38912
|
+
});
|
|
38913
|
+
const stateColors = squeezed.map((s, i) => Number.isFinite(mom[i]) ? s ? BEARISH : BULLISH : null);
|
|
38914
|
+
return {
|
|
38915
|
+
plots: [
|
|
38916
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "histogram", color: BULLISH, colors: histColors(mom), base: 0 },
|
|
38917
|
+
{ key: "squeeze", title: "Squeeze", values: mom.map((x) => Number.isFinite(x) ? 0 : Number.NaN), kind: "cross", color: INFO, colors: stateColors }
|
|
38918
|
+
]
|
|
38919
|
+
};
|
|
38920
|
+
}
|
|
38921
|
+
};
|
|
38922
|
+
var oscillatorSpecs = [
|
|
38923
|
+
rsiSpec,
|
|
38924
|
+
stochastic,
|
|
38925
|
+
stochasticRsi,
|
|
38926
|
+
macd,
|
|
38927
|
+
ppo,
|
|
38928
|
+
awesome,
|
|
38929
|
+
cci,
|
|
38930
|
+
williamsR,
|
|
38931
|
+
cmo,
|
|
38932
|
+
connorsRsi,
|
|
38933
|
+
fisher,
|
|
38934
|
+
trix,
|
|
38935
|
+
tsi,
|
|
38936
|
+
smiErgodic,
|
|
38937
|
+
stc,
|
|
38938
|
+
kst,
|
|
38939
|
+
coppock,
|
|
38940
|
+
dpo,
|
|
38941
|
+
rocSpec,
|
|
38942
|
+
ultimate,
|
|
38943
|
+
rvgi,
|
|
38944
|
+
relVolatility,
|
|
38945
|
+
bop,
|
|
38946
|
+
elderRay,
|
|
38947
|
+
ttmSqueeze
|
|
38948
|
+
];
|
|
38949
|
+
|
|
38950
|
+
// src/core/native-indicators/classics/trend.ts
|
|
38951
|
+
var aroon = {
|
|
38952
|
+
type: "aroon",
|
|
38953
|
+
title: "Aroon",
|
|
38954
|
+
overlay: false,
|
|
38955
|
+
inputs: [lengthInput(14)],
|
|
38956
|
+
compute: (bars, inputs) => {
|
|
38957
|
+
const len = num3(inputs, "length", 14);
|
|
38958
|
+
const up = map(barsSinceHighest(highs(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38959
|
+
const down = map(barsSinceLowest(lows(bars), len + 1), (x) => 100 * (len - x) / len);
|
|
38960
|
+
return {
|
|
38961
|
+
plots: [
|
|
38962
|
+
{ key: "up", title: "Aroon up", values: up, color: BULLISH, width: 2 },
|
|
38963
|
+
{ key: "down", title: "Aroon down", values: down, color: BEARISH, width: 2 }
|
|
38964
|
+
],
|
|
38965
|
+
levels: [
|
|
38966
|
+
{ key: "upper", price: 100, color: NEUTRAL, lineStyle: "dotted" },
|
|
38967
|
+
{ key: "lower", price: 0, color: NEUTRAL, lineStyle: "dotted" }
|
|
38968
|
+
]
|
|
38969
|
+
};
|
|
38970
|
+
}
|
|
38971
|
+
};
|
|
38972
|
+
var adx = {
|
|
38973
|
+
type: "average-directional-index",
|
|
38974
|
+
title: "Average Directional Index",
|
|
38975
|
+
shortTitle: "ADX",
|
|
38976
|
+
overlay: false,
|
|
38977
|
+
inputs: [lengthInput(14, "diLength", "DI length"), lengthInput(14, "adxLength", "ADX smoothing")],
|
|
38978
|
+
compute: (bars, inputs) => {
|
|
38979
|
+
const diLen = num3(inputs, "diLength", 14);
|
|
38980
|
+
const adxLen = num3(inputs, "adxLength", 14);
|
|
38981
|
+
const n = bars.length;
|
|
38982
|
+
const plusDm = new Array(n).fill(Number.NaN);
|
|
38983
|
+
const minusDm = new Array(n).fill(Number.NaN);
|
|
38984
|
+
for (let i = 1; i < n; i++) {
|
|
38985
|
+
const up = bars[i].high - bars[i - 1].high;
|
|
38986
|
+
const dn = bars[i - 1].low - bars[i].low;
|
|
38987
|
+
plusDm[i] = up > dn && up > 0 ? up : 0;
|
|
38988
|
+
minusDm[i] = dn > up && dn > 0 ? dn : 0;
|
|
38989
|
+
}
|
|
38990
|
+
const atrLine = rma(trueRange(bars), diLen);
|
|
38991
|
+
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38992
|
+
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
38993
|
+
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
38994
|
+
return {
|
|
38995
|
+
plots: [
|
|
38996
|
+
{ key: "adx", title: "ADX", values: rma(dx, adxLen), color: SERIES_LINE, width: 2 },
|
|
38997
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: BULLISH },
|
|
38998
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: BEARISH }
|
|
38999
|
+
],
|
|
39000
|
+
levels: [{ key: "threshold", price: 25, color: NEUTRAL, lineStyle: "dotted" }]
|
|
39001
|
+
};
|
|
39002
|
+
}
|
|
39003
|
+
};
|
|
39004
|
+
var vortex = {
|
|
39005
|
+
type: "vortex-indicator",
|
|
39006
|
+
title: "Vortex Indicator",
|
|
39007
|
+
shortTitle: "VI",
|
|
39008
|
+
overlay: false,
|
|
39009
|
+
inputs: [lengthInput(14)],
|
|
39010
|
+
compute: (bars, inputs) => {
|
|
39011
|
+
const len = num3(inputs, "length", 14);
|
|
39012
|
+
const n = bars.length;
|
|
39013
|
+
const vmPlus = new Array(n).fill(Number.NaN);
|
|
39014
|
+
const vmMinus = new Array(n).fill(Number.NaN);
|
|
39015
|
+
for (let i = 1; i < n; i++) {
|
|
39016
|
+
vmPlus[i] = Math.abs(bars[i].high - bars[i - 1].low);
|
|
39017
|
+
vmMinus[i] = Math.abs(bars[i].low - bars[i - 1].high);
|
|
39018
|
+
}
|
|
39019
|
+
const trSum = sum(trueRange(bars), len);
|
|
39020
|
+
return {
|
|
39021
|
+
plots: [
|
|
39022
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BULLISH, width: 2 },
|
|
39023
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: BEARISH, width: 2 }
|
|
39024
|
+
],
|
|
39025
|
+
levels: [{ key: "one", price: 1, color: NEUTRAL, lineStyle: "dotted" }]
|
|
39026
|
+
};
|
|
39027
|
+
}
|
|
39028
|
+
};
|
|
39029
|
+
var trendSpecs = [aroon, adx, vortex];
|
|
39030
|
+
|
|
39031
|
+
// src/core/native-indicators/classics/volatility.ts
|
|
39032
|
+
var atrSpec = {
|
|
39033
|
+
type: "average-true-range",
|
|
39034
|
+
title: "Average True Range",
|
|
39035
|
+
shortTitle: "ATR",
|
|
39036
|
+
overlay: false,
|
|
39037
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39038
|
+
compute: (bars, inputs) => ({
|
|
39039
|
+
plots: [{ key: "atr", title: "ATR", values: atr(bars, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
39040
|
+
})
|
|
39041
|
+
};
|
|
39042
|
+
var historicalVolatility = {
|
|
39043
|
+
type: "historical-volatility",
|
|
39044
|
+
title: "Historical Volatility",
|
|
39045
|
+
shortTitle: "HV",
|
|
39046
|
+
overlay: false,
|
|
39047
|
+
inputs: [
|
|
39048
|
+
lengthInput(10),
|
|
39049
|
+
{ 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)" },
|
|
39050
|
+
colorInput()
|
|
39051
|
+
],
|
|
39052
|
+
compute: (bars, inputs) => {
|
|
39053
|
+
const logReturns = map(change(map(closes(bars), Math.log)), (x) => x);
|
|
39054
|
+
const values = map(stdev(logReturns, num3(inputs, "length", 10)), (x) => x * Math.sqrt(num3(inputs, "annual", 365)) * 100);
|
|
39055
|
+
return { plots: [{ key: "hv", title: "HV", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39056
|
+
}
|
|
39057
|
+
};
|
|
39058
|
+
var chaikinVolatility = {
|
|
39059
|
+
type: "chaikin-volatility",
|
|
39060
|
+
title: "Chaikin Volatility",
|
|
39061
|
+
shortTitle: "CHV",
|
|
39062
|
+
overlay: false,
|
|
39063
|
+
inputs: [lengthInput(10, "emaLength", "EMA length"), lengthInput(10, "rocLength", "RoC length"), colorInput()],
|
|
39064
|
+
compute: (bars, inputs) => {
|
|
39065
|
+
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
39066
|
+
return {
|
|
39067
|
+
plots: [{ key: "cv", title: "Chaikin Vol", values: roc(range, num3(inputs, "rocLength", 10)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39068
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39069
|
+
};
|
|
39070
|
+
}
|
|
39071
|
+
};
|
|
39072
|
+
var massIndex = {
|
|
39073
|
+
type: "mass-index",
|
|
39074
|
+
title: "Mass Index",
|
|
39075
|
+
shortTitle: "MI",
|
|
39076
|
+
overlay: false,
|
|
39077
|
+
inputs: [lengthInput(25), colorInput()],
|
|
39078
|
+
compute: (bars, inputs) => {
|
|
39079
|
+
const range = sub(highs(bars), lows(bars));
|
|
39080
|
+
const single = ema(range, 9);
|
|
39081
|
+
const ratio = zip(single, ema(single, 9), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
39082
|
+
return {
|
|
39083
|
+
plots: [{ key: "mi", title: "Mass Index", values: sum(ratio, num3(inputs, "length", 25)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39084
|
+
levels: [{ key: "reversal", price: 27, color: NEUTRAL, lineStyle: "dotted" }]
|
|
39085
|
+
};
|
|
39086
|
+
}
|
|
39087
|
+
};
|
|
39088
|
+
var standardDeviation = {
|
|
39089
|
+
type: "standard-deviation",
|
|
39090
|
+
title: "Standard Deviation",
|
|
39091
|
+
shortTitle: "StdDev",
|
|
39092
|
+
overlay: false,
|
|
39093
|
+
inputs: [lengthInput(20), sourceInput(), colorInput()],
|
|
39094
|
+
compute: (bars, inputs) => ({
|
|
39095
|
+
plots: [
|
|
39096
|
+
{
|
|
39097
|
+
key: "stdev",
|
|
39098
|
+
title: "StdDev",
|
|
39099
|
+
values: stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20)),
|
|
39100
|
+
color: str3(inputs, "color", SERIES_LINE),
|
|
39101
|
+
width: 2
|
|
39102
|
+
}
|
|
39103
|
+
]
|
|
39104
|
+
})
|
|
39105
|
+
};
|
|
39106
|
+
var ulcerIndex = {
|
|
39107
|
+
type: "ulcer-index",
|
|
39108
|
+
title: "Ulcer Index",
|
|
39109
|
+
overlay: false,
|
|
39110
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39111
|
+
compute: (bars, inputs) => {
|
|
39112
|
+
const len = num3(inputs, "length", 14);
|
|
39113
|
+
const c = closes(bars);
|
|
39114
|
+
const drawdown = zip(c, highest(c, len), (x, h) => h === 0 ? 0 : 100 * (x - h) / h);
|
|
39115
|
+
const values = map(sma(map(drawdown, (d) => d * d), len), Math.sqrt);
|
|
39116
|
+
return { plots: [{ key: "ui", title: "Ulcer", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39117
|
+
}
|
|
39118
|
+
};
|
|
39119
|
+
var choppiness = {
|
|
39120
|
+
type: "choppiness-index",
|
|
39121
|
+
title: "Choppiness Index",
|
|
39122
|
+
shortTitle: "CHOP",
|
|
39123
|
+
overlay: false,
|
|
39124
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39125
|
+
compute: (bars, inputs) => {
|
|
39126
|
+
const len = num3(inputs, "length", 14);
|
|
39127
|
+
const trSum = sum(trueRange(bars), len);
|
|
39128
|
+
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
39129
|
+
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
39130
|
+
return {
|
|
39131
|
+
plots: [{ key: "chop", title: "CHOP", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39132
|
+
levels: [
|
|
39133
|
+
{ key: "upper", price: 61.8, color: NEUTRAL },
|
|
39134
|
+
{ key: "lower", price: 38.2, color: NEUTRAL }
|
|
39135
|
+
]
|
|
39136
|
+
};
|
|
39137
|
+
}
|
|
39138
|
+
};
|
|
39139
|
+
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
39140
|
+
|
|
39141
|
+
// src/core/native-indicators/classics/volume.ts
|
|
39142
|
+
function moneyFlowVolume(bars) {
|
|
39143
|
+
return bars.map((b) => {
|
|
39144
|
+
if (b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39145
|
+
if (b.high === b.low) return 0;
|
|
39146
|
+
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
39147
|
+
});
|
|
39148
|
+
}
|
|
39149
|
+
var obv = {
|
|
39150
|
+
type: "on-balance-volume",
|
|
39151
|
+
title: "On Balance Volume",
|
|
39152
|
+
shortTitle: "OBV",
|
|
39153
|
+
overlay: false,
|
|
39154
|
+
inputs: [colorInput()],
|
|
39155
|
+
compute: (bars, inputs) => {
|
|
39156
|
+
const vol = volumes(bars);
|
|
39157
|
+
const d = change(closes(bars));
|
|
39158
|
+
const signed3 = zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0);
|
|
39159
|
+
return { plots: [{ key: "obv", title: "OBV", values: cumSum(signed3), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39160
|
+
}
|
|
39161
|
+
};
|
|
39162
|
+
var accDist = {
|
|
39163
|
+
type: "accumulation-distribution",
|
|
39164
|
+
title: "Accumulation / Distribution",
|
|
39165
|
+
shortTitle: "A/D",
|
|
39166
|
+
overlay: false,
|
|
39167
|
+
inputs: [colorInput()],
|
|
39168
|
+
compute: (bars, inputs) => ({
|
|
39169
|
+
plots: [{ key: "ad", title: "A/D", values: cumSum(moneyFlowVolume(bars)), color: str3(inputs, "color", SERIES_LINE), width: 2 }]
|
|
39170
|
+
})
|
|
39171
|
+
};
|
|
39172
|
+
var cmf = {
|
|
39173
|
+
type: "chaikin-money-flow",
|
|
39174
|
+
title: "Chaikin Money Flow",
|
|
39175
|
+
shortTitle: "CMF",
|
|
39176
|
+
overlay: false,
|
|
39177
|
+
inputs: [lengthInput(20), colorInput()],
|
|
39178
|
+
compute: (bars, inputs) => {
|
|
39179
|
+
const len = num3(inputs, "length", 20);
|
|
39180
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
39181
|
+
return {
|
|
39182
|
+
plots: [{ key: "cmf", title: "CMF", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39183
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39184
|
+
};
|
|
39185
|
+
}
|
|
39186
|
+
};
|
|
39187
|
+
var chaikinOscillator = {
|
|
39188
|
+
type: "chaikin-oscillator",
|
|
39189
|
+
title: "Chaikin Oscillator",
|
|
39190
|
+
shortTitle: "Chaikin Osc",
|
|
39191
|
+
overlay: false,
|
|
39192
|
+
inputs: [lengthInput(3, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39193
|
+
compute: (bars, inputs) => {
|
|
39194
|
+
const ad = cumSum(moneyFlowVolume(bars));
|
|
39195
|
+
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
39196
|
+
return {
|
|
39197
|
+
plots: [{ key: "co", title: "Chaikin Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39198
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39199
|
+
};
|
|
39200
|
+
}
|
|
39201
|
+
};
|
|
39202
|
+
var eom = {
|
|
39203
|
+
type: "ease-of-movement",
|
|
39204
|
+
title: "Ease of Movement",
|
|
39205
|
+
shortTitle: "EOM",
|
|
39206
|
+
overlay: false,
|
|
39207
|
+
inputs: [lengthInput(14), { key: "divisor", title: "Divisor", type: "int", defval: 1e4, min: 1, max: 1e9, step: 1 }, colorInput()],
|
|
39208
|
+
compute: (bars, inputs) => {
|
|
39209
|
+
const div = num3(inputs, "divisor", 1e4);
|
|
39210
|
+
const hl2 = sourceValues(bars, "HL2");
|
|
39211
|
+
const move = change(hl2);
|
|
39212
|
+
const raw = bars.map((b, i) => {
|
|
39213
|
+
const m = move[i];
|
|
39214
|
+
if (b.volume == null || !Number.isFinite(b.volume) || b.volume === 0 || !Number.isFinite(m)) return Number.NaN;
|
|
39215
|
+
return div * m * (b.high - b.low) / b.volume;
|
|
39216
|
+
});
|
|
39217
|
+
return {
|
|
39218
|
+
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39219
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39220
|
+
};
|
|
39221
|
+
}
|
|
39222
|
+
};
|
|
39223
|
+
var forceIndex = {
|
|
39224
|
+
type: "force-index",
|
|
39225
|
+
title: "Force Index",
|
|
39226
|
+
shortTitle: "FI",
|
|
39227
|
+
overlay: false,
|
|
39228
|
+
inputs: [lengthInput(13), colorInput()],
|
|
39229
|
+
compute: (bars, inputs) => {
|
|
39230
|
+
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
39231
|
+
return {
|
|
39232
|
+
plots: [{ key: "force", title: "Force", values: ema(raw, num3(inputs, "length", 13)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39233
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39234
|
+
};
|
|
39235
|
+
}
|
|
39236
|
+
};
|
|
39237
|
+
var klinger = {
|
|
39238
|
+
type: "klinger-oscillator",
|
|
39239
|
+
title: "Klinger Oscillator",
|
|
39240
|
+
shortTitle: "KVO",
|
|
39241
|
+
overlay: false,
|
|
39242
|
+
inputs: [lengthInput(34, "fastLength", "Fast length"), lengthInput(55, "slowLength", "Slow length"), lengthInput(13, "signalLength", "Signal length")],
|
|
39243
|
+
compute: (bars, inputs) => {
|
|
39244
|
+
const hlc3 = sourceValues(bars, "HLC3");
|
|
39245
|
+
const trend = change(hlc3);
|
|
39246
|
+
const signedVolume = zip(volumes(bars), trend, (v, t) => t >= 0 ? v : -v);
|
|
39247
|
+
const line = sub(ema(signedVolume, num3(inputs, "fastLength", 34)), ema(signedVolume, num3(inputs, "slowLength", 55)));
|
|
39248
|
+
return {
|
|
39249
|
+
plots: [
|
|
39250
|
+
{ key: "kvo", title: "KVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39251
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: WARNING }
|
|
39252
|
+
],
|
|
39253
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39254
|
+
};
|
|
39255
|
+
}
|
|
39256
|
+
};
|
|
39257
|
+
var mfi = {
|
|
39258
|
+
type: "money-flow-index",
|
|
39259
|
+
title: "Money Flow Index",
|
|
39260
|
+
shortTitle: "MFI",
|
|
39261
|
+
overlay: false,
|
|
39262
|
+
inputs: [lengthInput(14), colorInput()],
|
|
39263
|
+
compute: (bars, inputs) => {
|
|
39264
|
+
const len = num3(inputs, "length", 14);
|
|
39265
|
+
const tp = sourceValues(bars, "HLC3");
|
|
39266
|
+
const rawFlow = zip(tp, volumes(bars), (p, v) => p * v);
|
|
39267
|
+
const d = change(tp);
|
|
39268
|
+
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
39269
|
+
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
39270
|
+
const values = zip(pos, neg, (p, n) => n === 0 ? 100 : 100 - 100 / (1 + p / n));
|
|
39271
|
+
return {
|
|
39272
|
+
plots: [{ key: "mfi", title: "MFI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39273
|
+
levels: [
|
|
39274
|
+
{ key: "upper", price: 80, color: NEUTRAL },
|
|
39275
|
+
{ key: "lower", price: 20, color: NEUTRAL }
|
|
39276
|
+
]
|
|
39277
|
+
};
|
|
39278
|
+
}
|
|
39279
|
+
};
|
|
39280
|
+
function volumeIndex(bars, when) {
|
|
39281
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
39282
|
+
let index = 1e3;
|
|
39283
|
+
let started = false;
|
|
39284
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39285
|
+
const v = bars[i].volume;
|
|
39286
|
+
const pv = bars[i - 1].volume;
|
|
39287
|
+
const pc = bars[i - 1].close;
|
|
39288
|
+
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
39289
|
+
if (started) out[i] = index;
|
|
39290
|
+
continue;
|
|
39291
|
+
}
|
|
39292
|
+
started = true;
|
|
39293
|
+
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
39294
|
+
out[i] = index;
|
|
39295
|
+
}
|
|
39296
|
+
return out;
|
|
39297
|
+
}
|
|
39298
|
+
var nvi = {
|
|
39299
|
+
type: "negative-volume-index",
|
|
39300
|
+
title: "Negative Volume Index",
|
|
39301
|
+
shortTitle: "Negative Volume Index",
|
|
39302
|
+
overlay: false,
|
|
39303
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39304
|
+
compute: (bars, inputs) => {
|
|
39305
|
+
const line = volumeIndex(bars, (v, pv) => v < pv);
|
|
39306
|
+
return {
|
|
39307
|
+
plots: [
|
|
39308
|
+
{ key: "nvi", title: "NVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39309
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39310
|
+
]
|
|
39311
|
+
};
|
|
39312
|
+
}
|
|
39313
|
+
};
|
|
39314
|
+
var pvi = {
|
|
39315
|
+
type: "positive-volume-index",
|
|
39316
|
+
title: "Positive Volume Index",
|
|
39317
|
+
shortTitle: "Positive Volume Index",
|
|
39318
|
+
overlay: false,
|
|
39319
|
+
inputs: [lengthInput(255, "signalLength", "Signal length"), colorInput()],
|
|
39320
|
+
compute: (bars, inputs) => {
|
|
39321
|
+
const line = volumeIndex(bars, (v, pv) => v > pv);
|
|
39322
|
+
return {
|
|
39323
|
+
plots: [
|
|
39324
|
+
{ key: "pvi", title: "PVI", values: line, color: str3(inputs, "color", SERIES_LINE), width: 2 },
|
|
39325
|
+
{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 255)), color: WARNING }
|
|
39326
|
+
]
|
|
39327
|
+
};
|
|
39328
|
+
}
|
|
39329
|
+
};
|
|
39330
|
+
var pvt = {
|
|
39331
|
+
type: "price-volume-trend",
|
|
39332
|
+
title: "Price Volume Trend",
|
|
39333
|
+
shortTitle: "PVT",
|
|
39334
|
+
overlay: false,
|
|
39335
|
+
inputs: [colorInput()],
|
|
39336
|
+
compute: (bars, inputs) => {
|
|
39337
|
+
const c = closes(bars);
|
|
39338
|
+
const raw = bars.map((b, i) => {
|
|
39339
|
+
if (i === 0 || b.volume == null || !Number.isFinite(b.volume)) return Number.NaN;
|
|
39340
|
+
const pc = c[i - 1];
|
|
39341
|
+
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
39342
|
+
});
|
|
39343
|
+
return { plots: [{ key: "pvt", title: "PVT", values: cumSum(raw), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
39344
|
+
}
|
|
39345
|
+
};
|
|
39346
|
+
var volumeOscillator = {
|
|
39347
|
+
type: "volume-oscillator",
|
|
39348
|
+
title: "Volume Oscillator",
|
|
39349
|
+
shortTitle: "Vol Osc",
|
|
39350
|
+
overlay: false,
|
|
39351
|
+
inputs: [lengthInput(5, "fastLength", "Fast length"), lengthInput(10, "slowLength", "Slow length"), colorInput()],
|
|
39352
|
+
compute: (bars, inputs) => {
|
|
39353
|
+
const vol = volumes(bars);
|
|
39354
|
+
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
39355
|
+
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
39356
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39357
|
+
return {
|
|
39358
|
+
plots: [{ key: "vo", title: "Volume Osc", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39359
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39360
|
+
};
|
|
39361
|
+
}
|
|
39362
|
+
};
|
|
39363
|
+
var pvo = {
|
|
39364
|
+
type: "pvo",
|
|
39365
|
+
title: "Percentage Volume Oscillator",
|
|
39366
|
+
shortTitle: "PVO",
|
|
39367
|
+
overlay: false,
|
|
39368
|
+
inputs: [lengthInput(12, "fastLength", "Fast length"), lengthInput(26, "slowLength", "Slow length"), lengthInput(9, "signalLength", "Signal length")],
|
|
39369
|
+
compute: (bars, inputs) => {
|
|
39370
|
+
const vol = volumes(bars);
|
|
39371
|
+
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
39372
|
+
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
39373
|
+
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : 100 * (f - s) / s);
|
|
39374
|
+
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
39375
|
+
const hist = sub(line, signal);
|
|
39376
|
+
return {
|
|
39377
|
+
plots: [
|
|
39378
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "histogram", color: BULLISH, colors: hist.map((x) => Number.isFinite(x) ? x >= 0 ? BULLISH : BEARISH : null), base: 0 },
|
|
39379
|
+
{ key: "pvo", title: "PVO", values: line, color: SERIES_LINE, width: 2 },
|
|
39380
|
+
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
39381
|
+
],
|
|
39382
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39383
|
+
};
|
|
39384
|
+
}
|
|
39385
|
+
};
|
|
39386
|
+
var vfi = {
|
|
39387
|
+
type: "volume-flow-indicator",
|
|
39388
|
+
title: "Volume Flow Indicator",
|
|
39389
|
+
shortTitle: "Volume Flow Indicator",
|
|
39390
|
+
overlay: false,
|
|
39391
|
+
inputs: [
|
|
39392
|
+
lengthInput(130),
|
|
39393
|
+
{ key: "coef", title: "Cutoff coefficient", type: "float", defval: 0.2, min: 0.01, max: 10, step: 0.01 },
|
|
39394
|
+
{ key: "volCoef", title: "Volume cap", type: "float", defval: 2.5, min: 0.1, max: 50, step: 0.1 },
|
|
39395
|
+
lengthInput(3, "smoothLength", "Smoothing length")
|
|
39396
|
+
],
|
|
39397
|
+
compute: (bars, inputs) => {
|
|
39398
|
+
const len = num3(inputs, "length", 130);
|
|
39399
|
+
const coef = num3(inputs, "coef", 0.2);
|
|
39400
|
+
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
39401
|
+
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
39402
|
+
const inter = change(typical);
|
|
39403
|
+
const vInter = stdev(inter, 30);
|
|
39404
|
+
const vol = volumes(bars);
|
|
39405
|
+
const vAve = sma(vol, len);
|
|
39406
|
+
const mf = change(sourceValues(bars, "HLC3"));
|
|
39407
|
+
const raw = bars.map((b, i) => {
|
|
39408
|
+
const cutoff = Number.isFinite(vInter[i]) ? coef * vInter[i] * b.close : Number.NaN;
|
|
39409
|
+
const v = vol[i];
|
|
39410
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39411
|
+
const m = mf[i];
|
|
39412
|
+
if (!Number.isFinite(cutoff) || !Number.isFinite(v) || !Number.isFinite(ave) || !Number.isFinite(m)) return Number.NaN;
|
|
39413
|
+
const capped = Math.min(v, ave * volCoef);
|
|
39414
|
+
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
39415
|
+
});
|
|
39416
|
+
const values = bars.map((_, i) => {
|
|
39417
|
+
const ave = i > 0 ? vAve[i - 1] : Number.NaN;
|
|
39418
|
+
return Number.isFinite(ave) && ave !== 0 ? ave : Number.NaN;
|
|
39419
|
+
});
|
|
39420
|
+
const flowSum = sum(raw, len);
|
|
39421
|
+
const line = zip(flowSum, values, (f, a) => f / a);
|
|
39422
|
+
return {
|
|
39423
|
+
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
39424
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39425
|
+
};
|
|
39426
|
+
}
|
|
39427
|
+
};
|
|
39428
|
+
var intradayIntensity = {
|
|
39429
|
+
type: "intraday-intensity",
|
|
39430
|
+
title: "Intraday Intensity",
|
|
39431
|
+
shortTitle: "Intraday Intensity",
|
|
39432
|
+
overlay: false,
|
|
39433
|
+
inputs: [lengthInput(21), colorInput()],
|
|
39434
|
+
compute: (bars, inputs) => {
|
|
39435
|
+
const len = num3(inputs, "length", 21);
|
|
39436
|
+
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : 100 * m / v);
|
|
39437
|
+
return {
|
|
39438
|
+
plots: [{ key: "ii", title: "II%", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
39439
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
39440
|
+
};
|
|
39441
|
+
}
|
|
39442
|
+
};
|
|
39443
|
+
var volumeSpecs = [
|
|
39444
|
+
obv,
|
|
39445
|
+
accDist,
|
|
39446
|
+
cmf,
|
|
39447
|
+
chaikinOscillator,
|
|
39448
|
+
eom,
|
|
39449
|
+
forceIndex,
|
|
39450
|
+
klinger,
|
|
39451
|
+
mfi,
|
|
39452
|
+
nvi,
|
|
39453
|
+
pvi,
|
|
39454
|
+
pvt,
|
|
39455
|
+
volumeOscillator,
|
|
39456
|
+
pvo,
|
|
39457
|
+
vfi,
|
|
39458
|
+
intradayIntensity
|
|
39459
|
+
];
|
|
39460
|
+
|
|
39461
|
+
// src/core/native-indicators/classics/overlays.ts
|
|
39462
|
+
var DAY_MS2 = 864e5;
|
|
39463
|
+
function periodKey(anchor, time) {
|
|
39464
|
+
if (anchor === "Week") return Math.floor((Math.floor(time / DAY_MS2) + 3) / 7);
|
|
39465
|
+
if (anchor === "Month") {
|
|
39466
|
+
const d = new Date(time);
|
|
39467
|
+
return d.getUTCFullYear() * 12 + d.getUTCMonth();
|
|
39468
|
+
}
|
|
39469
|
+
return Math.floor(time / DAY_MS2);
|
|
39470
|
+
}
|
|
39471
|
+
var parabolicSar = {
|
|
39472
|
+
type: "parabolic-sar",
|
|
39473
|
+
title: "Parabolic SAR",
|
|
39474
|
+
shortTitle: "SAR",
|
|
39475
|
+
overlay: true,
|
|
39476
|
+
inputs: [
|
|
39477
|
+
{ key: "start", title: "Start", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39478
|
+
{ key: "increment", title: "Increment", type: "float", defval: 0.02, min: 1e-3, max: 1, step: 1e-3 },
|
|
39479
|
+
{ key: "maximum", title: "Max value", type: "float", defval: 0.2, min: 0.01, max: 1, step: 0.01 }
|
|
39480
|
+
],
|
|
39481
|
+
compute: (bars, inputs) => {
|
|
39482
|
+
const start = num3(inputs, "start", 0.02);
|
|
39483
|
+
const inc = num3(inputs, "increment", 0.02);
|
|
39484
|
+
const max = num3(inputs, "maximum", 0.2);
|
|
39485
|
+
const n = bars.length;
|
|
39486
|
+
const values = new Array(n).fill(Number.NaN);
|
|
39487
|
+
const colors = new Array(n).fill(null);
|
|
39488
|
+
if (n >= 2) {
|
|
39489
|
+
let long = bars[1].close >= bars[0].close;
|
|
39490
|
+
let sar = long ? bars[0].low : bars[0].high;
|
|
39491
|
+
let ep = long ? bars[0].high : bars[0].low;
|
|
39492
|
+
let af = start;
|
|
39493
|
+
for (let i = 1; i < n; i++) {
|
|
39494
|
+
const b = bars[i];
|
|
39495
|
+
sar += af * (ep - sar);
|
|
39496
|
+
if (long) sar = Math.min(sar, bars[i - 1].low, i >= 2 ? bars[i - 2].low : bars[i - 1].low);
|
|
39497
|
+
else sar = Math.max(sar, bars[i - 1].high, i >= 2 ? bars[i - 2].high : bars[i - 1].high);
|
|
39498
|
+
const reversed = long ? b.low < sar : b.high > sar;
|
|
39499
|
+
if (reversed) {
|
|
39500
|
+
long = !long;
|
|
39501
|
+
sar = ep;
|
|
39502
|
+
ep = long ? b.high : b.low;
|
|
39503
|
+
af = start;
|
|
39504
|
+
} else if (long && b.high > ep) {
|
|
39505
|
+
ep = b.high;
|
|
39506
|
+
af = Math.min(max, af + inc);
|
|
39507
|
+
} else if (!long && b.low < ep) {
|
|
39508
|
+
ep = b.low;
|
|
39509
|
+
af = Math.min(max, af + inc);
|
|
39510
|
+
}
|
|
39511
|
+
values[i] = sar;
|
|
39512
|
+
colors[i] = long ? BULLISH : BEARISH;
|
|
39513
|
+
}
|
|
39514
|
+
}
|
|
39515
|
+
return { plots: [{ key: "sar", title: "PSAR", values, kind: "circles", color: SERIES_LINE, colors }] };
|
|
39516
|
+
}
|
|
39517
|
+
};
|
|
39518
|
+
var vwap = {
|
|
39519
|
+
type: "vwap",
|
|
39520
|
+
title: "Volume Weighted Average Price",
|
|
39521
|
+
shortTitle: "VWAP",
|
|
39522
|
+
overlay: true,
|
|
39523
|
+
inputs: [
|
|
39524
|
+
{ key: "anchor", title: "Anchor", type: "string", defval: "Day", options: ["Day", "Week", "Month"], tooltip: "Where the accumulation resets (UTC periods)" },
|
|
39525
|
+
{ key: "source", title: "Source", type: "string", defval: "HLC3", options: ["HLC3", "OHLC4", "Close"] },
|
|
39526
|
+
colorInput(INFO)
|
|
39527
|
+
],
|
|
39528
|
+
compute: (bars, inputs) => {
|
|
39529
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39530
|
+
const source = str3(inputs, "source", "HLC3");
|
|
39531
|
+
const values = new Array(bars.length).fill(Number.NaN);
|
|
39532
|
+
let period = Number.NaN;
|
|
39533
|
+
let cumPV = 0;
|
|
39534
|
+
let cumV = 0;
|
|
39535
|
+
for (let i = 0; i < bars.length; i++) {
|
|
39536
|
+
const b = bars[i];
|
|
39537
|
+
const key = periodKey(anchor, b.time);
|
|
39538
|
+
if (key !== period) {
|
|
39539
|
+
period = key;
|
|
39540
|
+
cumPV = 0;
|
|
39541
|
+
cumV = 0;
|
|
39542
|
+
}
|
|
39543
|
+
const v = b.volume;
|
|
39544
|
+
if (v != null && Number.isFinite(v) && v > 0) {
|
|
39545
|
+
const tp = source === "Close" ? b.close : source === "OHLC4" ? (b.open + b.high + b.low + b.close) / 4 : (b.high + b.low + b.close) / 3;
|
|
39546
|
+
cumPV += tp * v;
|
|
39547
|
+
cumV += v;
|
|
39548
|
+
}
|
|
39549
|
+
if (cumV > 0) values[i] = cumPV / cumV;
|
|
39550
|
+
}
|
|
39551
|
+
return { plots: [{ key: "vwap", title: "VWAP", values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
39552
|
+
}
|
|
39553
|
+
};
|
|
39554
|
+
var pivotPoints = {
|
|
39555
|
+
type: "pivot-points",
|
|
39556
|
+
title: "Pivot Points",
|
|
39557
|
+
shortTitle: "Pivots",
|
|
39558
|
+
overlay: true,
|
|
39559
|
+
inputs: [{ key: "anchor", title: "Period", type: "string", defval: "Day", options: ["Day", "Week", "Month"] }],
|
|
39560
|
+
compute: (bars, inputs) => {
|
|
39561
|
+
const anchor = str3(inputs, "anchor", "Day");
|
|
39562
|
+
const aggs = /* @__PURE__ */ new Map();
|
|
39563
|
+
for (const b of bars) {
|
|
39564
|
+
const key = periodKey(anchor, b.time);
|
|
39565
|
+
const a = aggs.get(key);
|
|
39566
|
+
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
39567
|
+
else {
|
|
39568
|
+
a.high = Math.max(a.high, b.high);
|
|
39569
|
+
a.low = Math.min(a.low, b.low);
|
|
39570
|
+
a.close = b.close;
|
|
39571
|
+
}
|
|
39572
|
+
}
|
|
39573
|
+
const n = bars.length;
|
|
39574
|
+
const mk = () => new Array(n).fill(Number.NaN);
|
|
39575
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
39576
|
+
for (let i = 0; i < n; i++) {
|
|
39577
|
+
const prev2 = aggs.get(periodKey(anchor, bars[i].time) - 1);
|
|
39578
|
+
if (!prev2) continue;
|
|
39579
|
+
const p = (prev2.high + prev2.low + prev2.close) / 3;
|
|
39580
|
+
levels.p[i] = p;
|
|
39581
|
+
levels.r1[i] = 2 * p - prev2.low;
|
|
39582
|
+
levels.s1[i] = 2 * p - prev2.high;
|
|
39583
|
+
levels.r2[i] = p + (prev2.high - prev2.low);
|
|
39584
|
+
levels.s2[i] = p - (prev2.high - prev2.low);
|
|
39585
|
+
levels.r3[i] = prev2.high + 2 * (p - prev2.low);
|
|
39586
|
+
levels.s3[i] = prev2.low - 2 * (prev2.high - p);
|
|
39587
|
+
}
|
|
39588
|
+
const plot = (key, title, color) => ({ key, title, kind: "step", values: levels[key], color });
|
|
39589
|
+
return {
|
|
39590
|
+
plots: [
|
|
39591
|
+
plot("p", "P", WARNING),
|
|
39592
|
+
plot("r1", "R1", BEARISH),
|
|
39593
|
+
plot("s1", "S1", BULLISH),
|
|
39594
|
+
plot("r2", "R2", BEARISH),
|
|
39595
|
+
plot("s2", "S2", BULLISH),
|
|
39596
|
+
plot("r3", "R3", BEARISH),
|
|
39597
|
+
plot("s3", "S3", BULLISH)
|
|
39598
|
+
]
|
|
39599
|
+
};
|
|
39600
|
+
}
|
|
39601
|
+
};
|
|
39602
|
+
var fiftyTwoWeek = {
|
|
39603
|
+
type: "52-week-high-low",
|
|
39604
|
+
title: "52 Week High/Low",
|
|
39605
|
+
shortTitle: "52W H/L",
|
|
39606
|
+
overlay: true,
|
|
39607
|
+
inputs: [{ key: "weeks", title: "Weeks", type: "int", defval: 52, min: 1, max: 520, step: 1 }],
|
|
39608
|
+
compute: (bars, inputs) => {
|
|
39609
|
+
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
39610
|
+
const n = bars.length;
|
|
39611
|
+
const hi = new Array(n).fill(Number.NaN);
|
|
39612
|
+
const lo = new Array(n).fill(Number.NaN);
|
|
39613
|
+
const maxIdx = [];
|
|
39614
|
+
const minIdx = [];
|
|
39615
|
+
let from = 0;
|
|
39616
|
+
for (let i = 0; i < n; i++) {
|
|
39617
|
+
const b = bars[i];
|
|
39618
|
+
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
39619
|
+
maxIdx.push(i);
|
|
39620
|
+
while (minIdx.length > 0 && bars[minIdx[minIdx.length - 1]].low >= b.low) minIdx.pop();
|
|
39621
|
+
minIdx.push(i);
|
|
39622
|
+
while (bars[from].time < b.time - span) from += 1;
|
|
39623
|
+
while (maxIdx[0] < from) maxIdx.shift();
|
|
39624
|
+
while (minIdx[0] < from) minIdx.shift();
|
|
39625
|
+
hi[i] = bars[maxIdx[0]].high;
|
|
39626
|
+
lo[i] = bars[minIdx[0]].low;
|
|
39627
|
+
}
|
|
39628
|
+
return {
|
|
39629
|
+
plots: [
|
|
39630
|
+
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
39631
|
+
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
39632
|
+
]
|
|
39633
|
+
};
|
|
39634
|
+
}
|
|
39635
|
+
};
|
|
39636
|
+
var zigzag = {
|
|
39637
|
+
type: "zigzag",
|
|
39638
|
+
title: "ZigZag",
|
|
39639
|
+
overlay: true,
|
|
39640
|
+
inputs: [
|
|
39641
|
+
{ key: "deviation", title: "Deviation %", type: "float", defval: 5, min: 0.01, max: 100, step: 0.1 },
|
|
39642
|
+
{ key: "depth", title: "Depth", type: "int", defval: 10, min: 1, max: 500, step: 1 },
|
|
39643
|
+
colorInput()
|
|
39644
|
+
],
|
|
39645
|
+
compute: (bars, inputs) => {
|
|
39646
|
+
const dev = num3(inputs, "deviation", 5) / 100;
|
|
39647
|
+
const depth = num3(inputs, "depth", 10);
|
|
39648
|
+
const pivots = [];
|
|
39649
|
+
if (bars.length > 1) {
|
|
39650
|
+
let up = bars[1].close >= bars[0].close;
|
|
39651
|
+
let ext = up ? { i: 0, price: bars[0].high } : { i: 0, price: bars[0].low };
|
|
39652
|
+
for (let i = 1; i < bars.length; i++) {
|
|
39653
|
+
const b = bars[i];
|
|
39654
|
+
if (up) {
|
|
39655
|
+
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
39656
|
+
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39657
|
+
pivots.push(ext);
|
|
39658
|
+
up = false;
|
|
39659
|
+
ext = { i, price: b.low };
|
|
39660
|
+
}
|
|
39661
|
+
} else if (b.low <= ext.price) {
|
|
39662
|
+
ext = { i, price: b.low };
|
|
39663
|
+
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
39664
|
+
pivots.push(ext);
|
|
39665
|
+
up = true;
|
|
39666
|
+
ext = { i, price: b.high };
|
|
39667
|
+
}
|
|
39668
|
+
}
|
|
39669
|
+
pivots.push(ext);
|
|
39670
|
+
}
|
|
39671
|
+
return {
|
|
39672
|
+
plots: [],
|
|
39673
|
+
polylines: [
|
|
39674
|
+
{
|
|
39675
|
+
key: "zigzag",
|
|
39676
|
+
points: pivots.map((p) => ({ xloc: "bar_time", x: bars[p.i].time, price: p.price })),
|
|
39677
|
+
curved: false,
|
|
39678
|
+
closed: false,
|
|
39679
|
+
lineColor: str3(inputs, "color", SERIES_LINE),
|
|
39680
|
+
lineWidth: 2,
|
|
39681
|
+
lineStyle: "solid",
|
|
39682
|
+
arrowLeft: false,
|
|
39683
|
+
arrowRight: false
|
|
39684
|
+
}
|
|
39685
|
+
]
|
|
39686
|
+
};
|
|
39687
|
+
}
|
|
39688
|
+
};
|
|
39689
|
+
var williamsFractal = {
|
|
39690
|
+
type: "williams-fractal",
|
|
39691
|
+
title: "Williams Fractal",
|
|
39692
|
+
shortTitle: "Fractals",
|
|
39693
|
+
overlay: true,
|
|
39694
|
+
inputs: [{ key: "periods", title: "Periods", type: "int", defval: 2, min: 1, max: 50, step: 1 }],
|
|
39695
|
+
compute: (bars, inputs) => {
|
|
39696
|
+
const p = num3(inputs, "periods", 2);
|
|
39697
|
+
const markers = [];
|
|
39698
|
+
const isExtreme = (i, pick, better) => {
|
|
39699
|
+
const v = pick(bars[i]);
|
|
39700
|
+
for (let k = i - p; k <= i + p; k++) {
|
|
39701
|
+
if (k === i) continue;
|
|
39702
|
+
if (!better(v, pick(bars[k]))) return false;
|
|
39703
|
+
}
|
|
39704
|
+
return true;
|
|
39705
|
+
};
|
|
39706
|
+
for (let i = p; i < bars.length - p; i++) {
|
|
39707
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a >= b)) {
|
|
39708
|
+
markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: BULLISH });
|
|
39709
|
+
}
|
|
39710
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
39711
|
+
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
39712
|
+
}
|
|
39713
|
+
}
|
|
39714
|
+
return { plots: [], markers };
|
|
39715
|
+
}
|
|
39716
|
+
};
|
|
39717
|
+
var overlaySpecs = [parabolicSar, vwap, pivotPoints, fiftyTwoWeek, zigzag, williamsFractal];
|
|
39718
|
+
|
|
39719
|
+
// src/core/native-indicators/classics/index.ts
|
|
39720
|
+
var classicSpecs = [
|
|
39721
|
+
...averageSpecs,
|
|
39722
|
+
...bandSpecs,
|
|
39723
|
+
...oscillatorSpecs,
|
|
39724
|
+
...trendSpecs,
|
|
39725
|
+
...volatilitySpecs,
|
|
39726
|
+
...volumeSpecs,
|
|
39727
|
+
...overlaySpecs
|
|
39728
|
+
];
|
|
39729
|
+
function registerClassicIndicators() {
|
|
39730
|
+
for (const spec of classicSpecs) registerNativeIndicator(classicDescriptor(spec));
|
|
39731
|
+
}
|
|
39732
|
+
|
|
37327
39733
|
// src/Vela.ts
|
|
37328
39734
|
var Vela = class {
|
|
37329
39735
|
constructor(container, options = {}, deps = {}) {
|
|
37330
39736
|
registerBuiltinChartTypes();
|
|
37331
39737
|
registerVolume();
|
|
37332
39738
|
registerVpvr();
|
|
39739
|
+
registerClassicIndicators();
|
|
37333
39740
|
const element = resolveElement(container);
|
|
37334
39741
|
const theme = resolveTheme(options.theme);
|
|
37335
|
-
let animZoom = true;
|
|
37336
|
-
let animPan = true;
|
|
37337
|
-
if (typeof options.animations === "boolean") {
|
|
37338
|
-
animZoom = options.animations;
|
|
37339
|
-
animPan = options.animations;
|
|
37340
|
-
} else if (options.animations) {
|
|
37341
|
-
animZoom = options.animations.zoom ?? true;
|
|
37342
|
-
animPan = options.animations.pan ?? true;
|
|
37343
|
-
}
|
|
37344
39742
|
const display = {
|
|
37345
39743
|
currentPriceLine: options.currentPriceLine ?? true,
|
|
37346
39744
|
logScale: options.logScale ?? false,
|
|
37347
39745
|
nativeBackend: options.nativeBackend ?? "auto",
|
|
37348
|
-
|
|
37349
|
-
animPan,
|
|
39746
|
+
...resolveAnimations(options.animations),
|
|
37350
39747
|
glow: options.glow ?? 0,
|
|
37351
39748
|
upColor: options.upColor ?? BULLISH,
|
|
37352
39749
|
downColor: options.downColor ?? BEARISH,
|
|
@@ -37404,25 +39801,28 @@ var Vela = class {
|
|
|
37404
39801
|
}
|
|
37405
39802
|
/**
|
|
37406
39803
|
* Add a built-in NATIVE indicator (core-computed, no scripting engine) by registered `type` —
|
|
37407
|
-
* e.g. `'vpvr'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
37408
|
-
* events)
|
|
37409
|
-
*
|
|
39804
|
+
* e.g. `'vpvr'` or `'sma'`. It becomes a first-class indicator (legend row, settings, hide,
|
|
39805
|
+
* remove, events). Layer-backed types (`'volume'`, `'vpvr'`) are single-instance — a second
|
|
39806
|
+
* call returns the existing handle; the classic studies allow several instances, so each call
|
|
39807
|
+
* adds one more. Returns a fail-soft handle for an unregistered type. Native renderer only.
|
|
37410
39808
|
*/
|
|
37411
39809
|
addNativeIndicator(type, options) {
|
|
37412
39810
|
return this.orchestrator.addNativeIndicator(type, options);
|
|
37413
39811
|
}
|
|
37414
39812
|
/**
|
|
37415
39813
|
* The catalog of built-in native indicators with their live state on this chart — each entry's
|
|
37416
|
-
* `type`, `title`, whether it `supported`s the current symbol,
|
|
37417
|
-
*
|
|
37418
|
-
* Lets a host "add indicator" UI list them, gate unsupported
|
|
37419
|
-
*
|
|
39814
|
+
* `type`, `title`, whether it `supported`s the current symbol, whether it's already `present`,
|
|
39815
|
+
* and whether it allows several instances (`multiInstance`; otherwise a second
|
|
39816
|
+
* `addNativeIndicator` is a no-op). Lets a host "add indicator" UI list them, gate unsupported
|
|
39817
|
+
* ones, and de-duplicate the single-instance ones. Async because support may probe the
|
|
39818
|
+
* provider (a type may need data the symbol lacks).
|
|
37420
39819
|
*/
|
|
37421
39820
|
availableNativeIndicators() {
|
|
37422
39821
|
return this.orchestrator.availableNativeIndicators();
|
|
37423
39822
|
}
|
|
37424
39823
|
/**
|
|
37425
|
-
* The native-indicator types PRESENT on the chart right now
|
|
39824
|
+
* The native-indicator types PRESENT on the chart right now, one entry per instance (a
|
|
39825
|
+
* multi-instance type repeats) — the synchronous slice of
|
|
37426
39826
|
* {@link availableNativeIndicators} (only support probing is async; presence never is).
|
|
37427
39827
|
* Persistence snapshots read this: an unload-time flush must see an add/remove that
|
|
37428
39828
|
* happened microseconds ago, which an async catalog mirror cannot guarantee.
|
|
@@ -37658,9 +40058,6 @@ function resolveElement(container) {
|
|
|
37658
40058
|
return element;
|
|
37659
40059
|
}
|
|
37660
40060
|
|
|
37661
|
-
// src/core/options.ts
|
|
37662
|
-
var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
|
|
37663
|
-
|
|
37664
40061
|
// src/widget/statusline-model.ts
|
|
37665
40062
|
function segmentVisibility(parts, chartHidden = false) {
|
|
37666
40063
|
return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
|
|
@@ -38165,7 +40562,7 @@ var Statusline = class {
|
|
|
38165
40562
|
};
|
|
38166
40563
|
|
|
38167
40564
|
// src/widget/session-shading.ts
|
|
38168
|
-
var
|
|
40565
|
+
var DAY_MS3 = 864e5;
|
|
38169
40566
|
var MINUTE_MS = 6e4;
|
|
38170
40567
|
function parseWindow(text, allowOvernight = false) {
|
|
38171
40568
|
if (typeof text !== "string") return null;
|
|
@@ -38244,7 +40641,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38244
40641
|
const dtf = civilFormatter(spec.timezone);
|
|
38245
40642
|
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38246
40643
|
const seen = /* @__PURE__ */ new Set();
|
|
38247
|
-
for (let cursor = from -
|
|
40644
|
+
for (let cursor = from - DAY_MS3; cursor < to + DAY_MS3; cursor += DAY_MS3) {
|
|
38248
40645
|
const civil = civilParts(dtf, cursor);
|
|
38249
40646
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38250
40647
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
@@ -38269,7 +40666,7 @@ function expandSessionZones(spec, from, to) {
|
|
|
38269
40666
|
}
|
|
38270
40667
|
return { pre, post, extended: mergeAbutting(extended) };
|
|
38271
40668
|
}
|
|
38272
|
-
var COVER_PAD_MIN_MS = 2 *
|
|
40669
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS3;
|
|
38273
40670
|
var SessionShadingTracker = class {
|
|
38274
40671
|
constructor(onZones) {
|
|
38275
40672
|
this.onZones = onZones;
|
|
@@ -38293,7 +40690,7 @@ var SessionShadingTracker = class {
|
|
|
38293
40690
|
this.covered = null;
|
|
38294
40691
|
this.session = opts.session;
|
|
38295
40692
|
const tfMs = timeframeMs(opts.timeframe);
|
|
38296
|
-
this.intraday = Number.isFinite(tfMs) && tfMs <
|
|
40693
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS3;
|
|
38297
40694
|
this.lastRange = opts.range;
|
|
38298
40695
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
38299
40696
|
if (my !== this.epoch) return;
|
|
@@ -38967,8 +41364,8 @@ var ChartCell = class {
|
|
|
38967
41364
|
/** The volume auto-add rides the cell's first candles (`load:end`); until then the
|
|
38968
41365
|
* registry can't show it and the dehydrated ledger reports the INTENT instead. */
|
|
38969
41366
|
this.volumeMayBePending = true;
|
|
38970
|
-
/** Sync mirror of the chart's
|
|
38971
|
-
*
|
|
41367
|
+
/** Sync mirror of the chart's native instances (id + type) — the removal handler
|
|
41368
|
+
* looks the removed id up here to learn which type an undo must re-add. */
|
|
38972
41369
|
this.presentNatives = [];
|
|
38973
41370
|
this.rangeBars = 0;
|
|
38974
41371
|
this.pendingRange = null;
|
|
@@ -39140,15 +41537,18 @@ var ChartCell = class {
|
|
|
39140
41537
|
redo: () => this.dropInstance(snapshot)
|
|
39141
41538
|
});
|
|
39142
41539
|
} else {
|
|
39143
|
-
const
|
|
39144
|
-
|
|
41540
|
+
const gone = this.presentNatives.find((n) => n.id === id2);
|
|
41541
|
+
if (gone) {
|
|
41542
|
+
const { type } = gone;
|
|
41543
|
+
let revived = null;
|
|
39145
41544
|
this.history.push({
|
|
39146
41545
|
undo: () => {
|
|
39147
|
-
this.inner?.addNativeIndicator(type);
|
|
41546
|
+
revived = this.inner?.addNativeIndicator(type) ?? null;
|
|
39148
41547
|
this.refreshNativeCatalog();
|
|
39149
41548
|
},
|
|
39150
41549
|
redo: () => {
|
|
39151
|
-
|
|
41550
|
+
revived?.remove();
|
|
41551
|
+
revived = null;
|
|
39152
41552
|
this.refreshNativeCatalog();
|
|
39153
41553
|
}
|
|
39154
41554
|
});
|
|
@@ -39453,9 +41853,9 @@ var ChartCell = class {
|
|
|
39453
41853
|
const live = this.inner?.renderer.get("priceStyle");
|
|
39454
41854
|
return typeof live === "string" ? live : this.state.priceStyle ?? "candles";
|
|
39455
41855
|
}
|
|
39456
|
-
/** Manifest instances +
|
|
41856
|
+
/** Manifest instances + native instances — the topbar indicator count. */
|
|
39457
41857
|
get indicatorCount() {
|
|
39458
|
-
return this.instances.length + this.
|
|
41858
|
+
return this.instances.length + (this.inner ? this.nativeHandles().length : this.presentNatives.length);
|
|
39459
41859
|
}
|
|
39460
41860
|
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
39461
41861
|
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
@@ -39587,13 +41987,15 @@ var ChartCell = class {
|
|
|
39587
41987
|
if (!chart) return;
|
|
39588
41988
|
this.volumeIntent = led.natives.includes("volume");
|
|
39589
41989
|
this.history.silently(() => {
|
|
39590
|
-
const
|
|
39591
|
-
for (const type of led.natives)
|
|
39592
|
-
|
|
39593
|
-
|
|
39594
|
-
|
|
39595
|
-
if (
|
|
39596
|
-
|
|
41990
|
+
const owed = /* @__PURE__ */ new Map();
|
|
41991
|
+
for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
|
|
41992
|
+
for (const h of this.nativeHandles()) {
|
|
41993
|
+
const type = h.nativeType;
|
|
41994
|
+
const n = owed.get(type) ?? 0;
|
|
41995
|
+
if (n > 0) owed.set(type, n - 1);
|
|
41996
|
+
else h.remove();
|
|
41997
|
+
}
|
|
41998
|
+
for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
|
|
39597
41999
|
for (const it of [...this.instances]) this.dropInstance(it);
|
|
39598
42000
|
if (this.manifest.length > 0) {
|
|
39599
42001
|
for (const item of led.manifest) {
|
|
@@ -39611,34 +42013,42 @@ var ChartCell = class {
|
|
|
39611
42013
|
this.syncPresentNatives();
|
|
39612
42014
|
this.refreshNativeCatalog();
|
|
39613
42015
|
}
|
|
39614
|
-
/** The picker
|
|
42016
|
+
/** The supported natives in picker order: A→Z by title — registration order follows
|
|
42017
|
+
* the catalog's families, which is meaningless to the reader. This is the library
|
|
42018
|
+
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
42019
|
+
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
42020
|
+
supportedNatives() {
|
|
42021
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => a.title.localeCompare(b.title, "en", { sensitivity: "base" }));
|
|
42022
|
+
}
|
|
42023
|
+
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
42024
|
+
* then the manifest in the host's own order. */
|
|
39615
42025
|
libraryRows() {
|
|
39616
42026
|
return [
|
|
39617
|
-
...this.
|
|
42027
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Vela", native: true, nativeType: n.type, beta: n.beta })),
|
|
39618
42028
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
39619
42029
|
];
|
|
39620
42030
|
}
|
|
39621
|
-
/** The picker's on-chart rows:
|
|
42031
|
+
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
39622
42032
|
onChartRows() {
|
|
39623
42033
|
return [
|
|
39624
|
-
...this.
|
|
42034
|
+
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
39625
42035
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
39626
42036
|
];
|
|
39627
42037
|
}
|
|
39628
42038
|
/** Add by picker LIBRARY index (natives precede the manifest — mirrors libraryRows). */
|
|
39629
42039
|
addFromLibrary(index) {
|
|
39630
|
-
const natives = this.
|
|
42040
|
+
const natives = this.supportedNatives();
|
|
39631
42041
|
if (index < natives.length) this.addNative(natives[index].type);
|
|
39632
42042
|
else {
|
|
39633
42043
|
const entry = this.manifest[index - natives.length];
|
|
39634
42044
|
if (entry) this.addManifestInstance(entry);
|
|
39635
42045
|
}
|
|
39636
42046
|
}
|
|
39637
|
-
/** Remove by picker ON-CHART index (
|
|
42047
|
+
/** Remove by picker ON-CHART index (native instances precede script instances — mirrors onChartRows). */
|
|
39638
42048
|
removeFromChart(index) {
|
|
39639
|
-
const
|
|
39640
|
-
if (index <
|
|
39641
|
-
else this.removeInstance(index -
|
|
42049
|
+
const natives = this.nativeHandles();
|
|
42050
|
+
if (index < natives.length) this.removeNative(natives[index]);
|
|
42051
|
+
else this.removeInstance(index - natives.length);
|
|
39642
42052
|
}
|
|
39643
42053
|
/**
|
|
39644
42054
|
* Add a script indicator through the PUBLIC seam (`ctx.addIndicator`) — same undo/
|
|
@@ -39698,30 +42108,42 @@ var ChartCell = class {
|
|
|
39698
42108
|
it.handle = null;
|
|
39699
42109
|
this.deps.onIndicatorsChanged(this.id);
|
|
39700
42110
|
}
|
|
39701
|
-
/** Add a native indicator
|
|
42111
|
+
/** Add a native indicator. A multi-instance type gets a fresh instance every time; a
|
|
42112
|
+
* single-instance type already on the chart hands back its existing one — nothing
|
|
42113
|
+
* changed, so nothing enters the undo timeline. */
|
|
39702
42114
|
addNative(type) {
|
|
39703
|
-
this.inner
|
|
42115
|
+
const chart = this.inner;
|
|
42116
|
+
if (!chart) return;
|
|
42117
|
+
const before = new Set(chart.indicators().map((h) => h.id));
|
|
42118
|
+
let added = chart.addNativeIndicator(type);
|
|
39704
42119
|
this.syncPresentNatives();
|
|
39705
42120
|
this.refreshNativeCatalog();
|
|
42121
|
+
if (before.has(added.id)) return;
|
|
39706
42122
|
this.history.push({
|
|
39707
42123
|
undo: () => {
|
|
39708
|
-
|
|
42124
|
+
added?.remove();
|
|
42125
|
+
added = null;
|
|
39709
42126
|
this.refreshNativeCatalog();
|
|
39710
42127
|
},
|
|
39711
42128
|
redo: () => {
|
|
39712
|
-
this.inner?.addNativeIndicator(type);
|
|
42129
|
+
added = this.inner?.addNativeIndicator(type) ?? null;
|
|
39713
42130
|
this.refreshNativeCatalog();
|
|
39714
42131
|
}
|
|
39715
42132
|
});
|
|
39716
42133
|
}
|
|
39717
|
-
removeNative(
|
|
39718
|
-
|
|
42134
|
+
removeNative(handle) {
|
|
42135
|
+
handle.remove();
|
|
39719
42136
|
this.refreshNativeCatalog();
|
|
39720
42137
|
}
|
|
39721
|
-
/**
|
|
39722
|
-
*
|
|
42138
|
+
/** The chart's native instances, insertion order — the picker's on-chart rows and
|
|
42139
|
+
* the removal index space (script instances follow them). */
|
|
42140
|
+
nativeHandles() {
|
|
42141
|
+
return this.inner?.indicators().filter((h) => h.nativeType !== void 0) ?? [];
|
|
42142
|
+
}
|
|
42143
|
+
/** Sync mirror of the chart's native instances — the removal handler looks the
|
|
42144
|
+
* removed id up here (the registry has already forgotten it) to record its type. */
|
|
39723
42145
|
syncPresentNatives() {
|
|
39724
|
-
this.presentNatives = this.
|
|
42146
|
+
this.presentNatives = this.nativeHandles().map((h) => ({ id: h.id, type: h.nativeType }));
|
|
39725
42147
|
}
|
|
39726
42148
|
/** Refresh the native catalog (supported/present flags) for this cell's market. */
|
|
39727
42149
|
refreshNativeCatalog() {
|