@luxalgo/vela 0.7.6 → 0.7.8
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/NOTICE +28 -28
- package/dist/{chunk-EMB53WNQ.js → chunk-M6GP7DAX.js} +123 -36
- package/dist/{chunk-JSVCWYBF.js → chunk-N4ZZYLMP.js} +2409 -896
- package/dist/{chunk-ZADTVRUO.js → chunk-PVGWNF7V.js} +1 -1
- package/dist/{chunk-BKHSQ4YM.js → chunk-WTWGEB7K.js} +23 -9
- package/dist/{contributions-D6p1RB38.d.cts → contributions-DF_Ea07m.d.cts} +17 -0
- package/dist/{contributions-C7Wsm_S9.d.ts → contributions-nKOSeLy8.d.ts} +17 -0
- package/dist/index.cjs +2416 -902
- package/dist/index.d.cts +37 -6
- package/dist/index.d.ts +37 -6
- package/dist/index.js +2 -2
- package/dist/{plugin-CxWfg6vK.d.cts → plugin-6F04gjQe.d.cts} +23 -23
- package/dist/{plugin-SodaQhjQ.d.ts → plugin-BLsNipkc.d.ts} +23 -23
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/{statusline-model-gWHrsOy3.d.cts → statusline-model-BoWJ3cWQ.d.cts} +16 -3
- package/dist/{statusline-model-xBiVsKoE.d.ts → statusline-model-DAFyqVmR.d.ts} +16 -3
- package/dist/ui.cjs +23 -9
- package/dist/ui.d.cts +19 -2
- package/dist/ui.d.ts +19 -2
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +2420 -906
- package/dist/vela.global.min.js +65 -55
- package/dist/widget.cjs +2549 -935
- package/dist/widget.d.cts +5 -5
- package/dist/widget.d.ts +5 -5
- package/dist/widget.js +5 -5
- package/dist/workspace.cjs +2549 -935
- package/dist/workspace.d.cts +35 -6
- package/dist/workspace.d.ts +35 -6
- package/dist/workspace.js +4 -4
- package/package.json +21 -1
package/dist/vela.global.js
CHANGED
|
@@ -10205,6 +10205,8 @@ var Vela = (function (exports) {
|
|
|
10205
10205
|
this.liveBars = [];
|
|
10206
10206
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
10207
10207
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
10208
|
+
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
10209
|
+
this.symInfoInflight = /* @__PURE__ */ new Map();
|
|
10208
10210
|
this.cache = new CachingDataFeed(new RegistryFetchFeed(this.registry), store);
|
|
10209
10211
|
}
|
|
10210
10212
|
// ── Registry surface (driven by chart.data / DataControl) ──────────────
|
|
@@ -10265,9 +10267,36 @@ var Vela = (function (exports) {
|
|
|
10265
10267
|
async symbolInfoFor(raw) {
|
|
10266
10268
|
const resolved = this.registry.resolve(raw, { default: this.primaryProvider });
|
|
10267
10269
|
if (!resolved) return void 0;
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10270
|
+
return this.fetchSymbolInfo(resolved);
|
|
10271
|
+
}
|
|
10272
|
+
/**
|
|
10273
|
+
* One round trip per symbol, shared. A market switch probes this metadata from five
|
|
10274
|
+
* places at once (the bar load's prefetch, the renderer's tick size, the market-status
|
|
10275
|
+
* badge, the session shading, the session toggle) — without the in-flight map each
|
|
10276
|
+
* would open its own request for the same answer, and the last one to land decided how
|
|
10277
|
+
* late the chrome settled.
|
|
10278
|
+
*
|
|
10279
|
+
* Cached for {@link SYMBOL_INFO_TTL_MS}: this metadata is stable within a session
|
|
10280
|
+
* (tick size, session vocabulary, listing prefix), but not immutable — a continuous
|
|
10281
|
+
* futures row rolls its current contract — so the entry expires rather than pinning the
|
|
10282
|
+
* session's first answer forever. A failed probe is NOT remembered: the next asker retries.
|
|
10283
|
+
*/
|
|
10284
|
+
fetchSymbolInfo(resolved) {
|
|
10285
|
+
const key = symKey(resolved);
|
|
10286
|
+
const hit = this.symInfoCache.get(key);
|
|
10287
|
+
if (hit && Date.now() - hit.at < SYMBOL_INFO_TTL_MS) return Promise.resolve(hit.info);
|
|
10288
|
+
const inflight = this.symInfoInflight.get(key);
|
|
10289
|
+
if (inflight) return inflight;
|
|
10290
|
+
const getSymbolInfo = this.registry.get(resolved.provider)?.getSymbolInfo;
|
|
10291
|
+
if (!getSymbolInfo) return Promise.resolve(void 0);
|
|
10292
|
+
const p = Promise.resolve(getSymbolInfo.call(this.registry.get(resolved.provider), resolved.ticker)).then((info) => {
|
|
10293
|
+
if (info) this.symInfoCache.set(key, { info, at: Date.now() });
|
|
10294
|
+
return info;
|
|
10295
|
+
}).finally(() => {
|
|
10296
|
+
this.symInfoInflight.delete(key);
|
|
10297
|
+
});
|
|
10298
|
+
this.symInfoInflight.set(key, p);
|
|
10299
|
+
return p;
|
|
10271
10300
|
}
|
|
10272
10301
|
/**
|
|
10273
10302
|
* Per-symbol capabilities, resolved through the owning provider's `capabilitiesFor`
|
|
@@ -10311,16 +10340,10 @@ var Vela = (function (exports) {
|
|
|
10311
10340
|
symbolInfo(cfg) {
|
|
10312
10341
|
if (cfg.data && cfg.data.length > 0) return void 0;
|
|
10313
10342
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10314
|
-
return resolved ? this.symInfoCache.get(symKey(resolved)) : void 0;
|
|
10343
|
+
return resolved ? this.symInfoCache.get(symKey(resolved))?.info : void 0;
|
|
10315
10344
|
}
|
|
10316
10345
|
prefetchSymbolInfo(resolved) {
|
|
10317
|
-
|
|
10318
|
-
if (this.symInfoCache.has(key)) return;
|
|
10319
|
-
const provider = this.registry.get(resolved.provider);
|
|
10320
|
-
if (!provider?.getSymbolInfo) return;
|
|
10321
|
-
void provider.getSymbolInfo(resolved.ticker).then((info) => {
|
|
10322
|
-
if (info) this.symInfoCache.set(key, info);
|
|
10323
|
-
}).catch(() => {
|
|
10346
|
+
void this.fetchSymbolInfo(resolved).catch(() => {
|
|
10324
10347
|
});
|
|
10325
10348
|
}
|
|
10326
10349
|
async loadRange(cfg, range) {
|
|
@@ -10370,8 +10393,9 @@ var Vela = (function (exports) {
|
|
|
10370
10393
|
return { ...cfg, symbol: `${resolved.provider}:${resolved.ticker}` };
|
|
10371
10394
|
}
|
|
10372
10395
|
function symKey(resolved) {
|
|
10373
|
-
return `${resolved.provider}|${resolved.ticker}`;
|
|
10396
|
+
return `${resolved.provider}|${resolved.ticker.trim().toUpperCase()}`;
|
|
10374
10397
|
}
|
|
10398
|
+
var SYMBOL_INFO_TTL_MS = 10 * 6e4;
|
|
10375
10399
|
async function safeBars(provider, ticker, tf, range) {
|
|
10376
10400
|
try {
|
|
10377
10401
|
return await provider.getBars(ticker, tf, range);
|
|
@@ -20561,7 +20585,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20561
20585
|
recents.unshift(hex6);
|
|
20562
20586
|
if (recents.length > 10) recents.length = 10;
|
|
20563
20587
|
}
|
|
20564
|
-
function buildColorPicker(color, theme, onChange) {
|
|
20588
|
+
function buildColorPicker(color, theme, onChange, options = {}) {
|
|
20589
|
+
const commit = options.commit ?? "live";
|
|
20565
20590
|
const parsed = splitColor(color);
|
|
20566
20591
|
let curHex = parsed.hex6;
|
|
20567
20592
|
let curAlpha = parsed.alpha;
|
|
@@ -20644,7 +20669,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20644
20669
|
const r = track.getBoundingClientRect();
|
|
20645
20670
|
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
20646
20671
|
paintOpacity();
|
|
20647
|
-
emit();
|
|
20672
|
+
if (commit === "live") emit();
|
|
20673
|
+
};
|
|
20674
|
+
const endDrag = () => {
|
|
20675
|
+
if (!dragging) return;
|
|
20676
|
+
dragging = false;
|
|
20677
|
+
if (commit === "release") emit();
|
|
20648
20678
|
};
|
|
20649
20679
|
track.addEventListener("pointerdown", (e) => {
|
|
20650
20680
|
e.stopPropagation();
|
|
@@ -20655,7 +20685,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20655
20685
|
track.addEventListener("pointermove", (e) => {
|
|
20656
20686
|
if (dragging) onDrag(e.clientX);
|
|
20657
20687
|
});
|
|
20658
|
-
track.addEventListener("pointerup",
|
|
20688
|
+
track.addEventListener("pointerup", endDrag);
|
|
20689
|
+
track.addEventListener("pointercancel", endDrag);
|
|
20659
20690
|
function emit() {
|
|
20660
20691
|
onChange(combineColor(curHex, curAlpha));
|
|
20661
20692
|
}
|
|
@@ -20678,7 +20709,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20678
20709
|
return root;
|
|
20679
20710
|
}
|
|
20680
20711
|
function colorField(theme, getVal, onVal, opts) {
|
|
20681
|
-
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
20712
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover, commit: opts?.commit }).el;
|
|
20682
20713
|
}
|
|
20683
20714
|
var ColorField = class {
|
|
20684
20715
|
constructor(opts) {
|
|
@@ -20687,6 +20718,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20687
20718
|
this.getVal = opts.getVal;
|
|
20688
20719
|
this.onVal = opts.onVal;
|
|
20689
20720
|
this.popoverOpts = opts.popover;
|
|
20721
|
+
this.commit = opts.commit;
|
|
20690
20722
|
const trigger = document.createElement("button");
|
|
20691
20723
|
trigger.type = "button";
|
|
20692
20724
|
if (opts.id) trigger.id = opts.id;
|
|
@@ -20726,10 +20758,15 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20726
20758
|
boundary: this.popoverOpts?.boundary,
|
|
20727
20759
|
zIndex: this.popoverOpts?.zIndex,
|
|
20728
20760
|
className: "vela-color-field-pop",
|
|
20729
|
-
content: buildColorPicker(
|
|
20730
|
-
this.
|
|
20731
|
-
this.
|
|
20732
|
-
|
|
20761
|
+
content: buildColorPicker(
|
|
20762
|
+
this.getVal(),
|
|
20763
|
+
this.theme,
|
|
20764
|
+
(val) => {
|
|
20765
|
+
this.onVal(val);
|
|
20766
|
+
this.paint();
|
|
20767
|
+
},
|
|
20768
|
+
{ commit: this.commit }
|
|
20769
|
+
)
|
|
20733
20770
|
});
|
|
20734
20771
|
pop.show();
|
|
20735
20772
|
}
|
|
@@ -21210,7 +21247,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21210
21247
|
const el2 = colorField(desc.theme, desc.get, desc.onChange, {
|
|
21211
21248
|
shape: "circle",
|
|
21212
21249
|
id: desc.id,
|
|
21213
|
-
popover: desc.popover
|
|
21250
|
+
popover: desc.popover,
|
|
21251
|
+
commit: desc.commit
|
|
21214
21252
|
});
|
|
21215
21253
|
if (desc.title) el2.title = desc.title;
|
|
21216
21254
|
return { el: el2 };
|
|
@@ -21588,7 +21626,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21588
21626
|
id,
|
|
21589
21627
|
theme: this.host.theme(),
|
|
21590
21628
|
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
21591
|
-
onChange: (v) => emit(v)
|
|
21629
|
+
onChange: (v) => emit(v),
|
|
21630
|
+
// An input change re-executes the script over its whole history: commit the
|
|
21631
|
+
// opacity drag once on release, not once per pointer move.
|
|
21632
|
+
commit: "release"
|
|
21592
21633
|
}).el;
|
|
21593
21634
|
}
|
|
21594
21635
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
@@ -21835,10 +21876,6 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21835
21876
|
}
|
|
21836
21877
|
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
21837
21878
|
openCalendar(anchor, current, onPick) {
|
|
21838
|
-
const parsed = parseIsoDate(current);
|
|
21839
|
-
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
21840
|
-
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
21841
|
-
const selected = parsed ? isoDate(parsed) : current;
|
|
21842
21879
|
const pop = new Popover({
|
|
21843
21880
|
trigger: anchor,
|
|
21844
21881
|
theme: this.host.theme(),
|
|
@@ -21850,80 +21887,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
21850
21887
|
this.calendarPop = null;
|
|
21851
21888
|
this.calendarAnchor = null;
|
|
21852
21889
|
},
|
|
21853
|
-
content: (el) => {
|
|
21854
|
-
|
|
21855
|
-
|
|
21856
|
-
|
|
21857
|
-
prev3.type = "button";
|
|
21858
|
-
prev3.className = "vela-ind-cal-nav";
|
|
21859
|
-
prev3.setAttribute("aria-label", "Previous month");
|
|
21860
|
-
prev3.innerHTML = iconAt("chevron-left", 14);
|
|
21861
|
-
const next2 = document.createElement("button");
|
|
21862
|
-
next2.type = "button";
|
|
21863
|
-
next2.className = "vela-ind-cal-nav";
|
|
21864
|
-
next2.setAttribute("aria-label", "Next month");
|
|
21865
|
-
next2.innerHTML = iconAt("chevron-right", 14);
|
|
21866
|
-
const head = document.createElement("div");
|
|
21867
|
-
head.className = "vela-ind-cal-head";
|
|
21868
|
-
head.append(prev3, title, next2);
|
|
21869
|
-
const week = document.createElement("div");
|
|
21870
|
-
week.className = "vela-ind-cal-week";
|
|
21871
|
-
for (const d of WEEKDAY_LABELS) {
|
|
21872
|
-
const cell = document.createElement("span");
|
|
21873
|
-
cell.textContent = d;
|
|
21874
|
-
week.appendChild(cell);
|
|
21875
|
-
}
|
|
21876
|
-
const grid = document.createElement("div");
|
|
21877
|
-
grid.className = "vela-ind-cal-grid";
|
|
21878
|
-
const paint = () => {
|
|
21879
|
-
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
21880
|
-
grid.replaceChildren();
|
|
21881
|
-
const first2 = new Date(year, month, 1);
|
|
21882
|
-
const startPad = first2.getDay();
|
|
21883
|
-
const days = new Date(year, month + 1, 0).getDate();
|
|
21884
|
-
const today = isoDate(/* @__PURE__ */ new Date());
|
|
21885
|
-
for (let i = 0; i < startPad; i++) {
|
|
21886
|
-
const blank = document.createElement("span");
|
|
21887
|
-
blank.className = "vela-ind-cal-blank";
|
|
21888
|
-
grid.appendChild(blank);
|
|
21889
|
-
}
|
|
21890
|
-
for (let day = 1; day <= days; day++) {
|
|
21891
|
-
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
21892
|
-
const b = document.createElement("button");
|
|
21893
|
-
b.type = "button";
|
|
21894
|
-
b.className = "vela-ind-cal-day";
|
|
21895
|
-
b.textContent = String(day);
|
|
21896
|
-
if (iso === selected) b.dataset.checked = "1";
|
|
21897
|
-
if (iso === today) b.dataset.today = "1";
|
|
21898
|
-
b.addEventListener("click", (e) => {
|
|
21899
|
-
e.stopPropagation();
|
|
21900
|
-
pop.hide();
|
|
21901
|
-
onPick(iso);
|
|
21902
|
-
});
|
|
21903
|
-
grid.appendChild(b);
|
|
21904
|
-
}
|
|
21905
|
-
};
|
|
21906
|
-
prev3.addEventListener("click", (e) => {
|
|
21907
|
-
e.stopPropagation();
|
|
21908
|
-
month -= 1;
|
|
21909
|
-
if (month < 0) {
|
|
21910
|
-
month = 11;
|
|
21911
|
-
year -= 1;
|
|
21912
|
-
}
|
|
21913
|
-
paint();
|
|
21914
|
-
});
|
|
21915
|
-
next2.addEventListener("click", (e) => {
|
|
21916
|
-
e.stopPropagation();
|
|
21917
|
-
month += 1;
|
|
21918
|
-
if (month > 11) {
|
|
21919
|
-
month = 0;
|
|
21920
|
-
year += 1;
|
|
21921
|
-
}
|
|
21922
|
-
paint();
|
|
21923
|
-
});
|
|
21924
|
-
paint();
|
|
21925
|
-
el.append(head, week, grid);
|
|
21926
|
-
}
|
|
21890
|
+
content: (el) => fillCalendar(el, current, (iso) => {
|
|
21891
|
+
pop.hide();
|
|
21892
|
+
onPick(iso);
|
|
21893
|
+
})
|
|
21927
21894
|
});
|
|
21928
21895
|
this.calendarPop = pop;
|
|
21929
21896
|
this.calendarAnchor = anchor;
|
|
@@ -22032,7 +21999,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
22032
21999
|
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
22033
22000
|
var CLOCK_SVG = iconAt("clock", 14);
|
|
22034
22001
|
var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
|
|
22035
|
-
var DIALOG_STYLE_REV = "
|
|
22002
|
+
var DIALOG_STYLE_REV = "30";
|
|
22036
22003
|
var LEGEND_ICON_PX = 16;
|
|
22037
22004
|
function ensureDialogStyles() {
|
|
22038
22005
|
if (typeof document === "undefined") return;
|
|
@@ -22048,8 +22015,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
22048
22015
|
.vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
|
|
22049
22016
|
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
22050
22017
|
.vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
|
|
22018
|
+
.vela-ind-cal [hidden]{display:none !important;}
|
|
22051
22019
|
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
22052
|
-
.vela-ind-cal-title{flex:1;
|
|
22020
|
+
.vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
|
|
22021
|
+
.vela-ind-cal-switch{border:none;background:transparent;color:var(--vela-fg-bright);font:inherit;font-weight:600;font-size:14px;padding:2px 6px;border-radius:4px;cursor:pointer;}
|
|
22022
|
+
.vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
|
|
22023
|
+
.vela-ind-cal-switch:disabled{cursor:default;}
|
|
22053
22024
|
.vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
|
22054
22025
|
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
22055
22026
|
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
@@ -22060,6 +22031,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
22060
22031
|
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
22061
22032
|
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
22062
22033
|
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
22034
|
+
.vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
|
|
22035
|
+
.vela-ind-cal-cell{height:36px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0 4px;cursor:pointer;font:inherit;font-size:14px;}
|
|
22036
|
+
.vela-ind-cal-cell:hover{background:var(--vela-hover);}
|
|
22037
|
+
.vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
22038
|
+
.vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
22039
|
+
.vela-ind-cal-cell[data-outside]{opacity:0.45;}
|
|
22063
22040
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
22064
22041
|
.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;}
|
|
22065
22042
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
@@ -22094,6 +22071,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22094
22071
|
return { value: v, label: v };
|
|
22095
22072
|
});
|
|
22096
22073
|
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
22074
|
+
var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
22097
22075
|
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
22098
22076
|
function normalizeDateInput(raw) {
|
|
22099
22077
|
const t = raw.trim();
|
|
@@ -22115,6 +22093,151 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22115
22093
|
function isoDate(d) {
|
|
22116
22094
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
22117
22095
|
}
|
|
22096
|
+
function fillCalendar(el, current, onPick) {
|
|
22097
|
+
const today = /* @__PURE__ */ new Date();
|
|
22098
|
+
const selected = parseIsoDate(current);
|
|
22099
|
+
let year = selected?.getFullYear() ?? today.getFullYear();
|
|
22100
|
+
let month = selected?.getMonth() ?? today.getMonth();
|
|
22101
|
+
let mode = "date";
|
|
22102
|
+
const prev3 = document.createElement("button");
|
|
22103
|
+
prev3.type = "button";
|
|
22104
|
+
prev3.className = "vela-ind-cal-nav";
|
|
22105
|
+
prev3.innerHTML = iconAt("chevron-left", 14);
|
|
22106
|
+
const next2 = document.createElement("button");
|
|
22107
|
+
next2.type = "button";
|
|
22108
|
+
next2.className = "vela-ind-cal-nav";
|
|
22109
|
+
next2.innerHTML = iconAt("chevron-right", 14);
|
|
22110
|
+
const monthBtn = document.createElement("button");
|
|
22111
|
+
monthBtn.type = "button";
|
|
22112
|
+
monthBtn.className = "vela-ind-cal-switch";
|
|
22113
|
+
monthBtn.setAttribute("aria-label", "Choose month");
|
|
22114
|
+
const yearBtn = document.createElement("button");
|
|
22115
|
+
yearBtn.type = "button";
|
|
22116
|
+
yearBtn.className = "vela-ind-cal-switch";
|
|
22117
|
+
yearBtn.setAttribute("aria-label", "Choose year");
|
|
22118
|
+
const title = document.createElement("div");
|
|
22119
|
+
title.className = "vela-ind-cal-title";
|
|
22120
|
+
title.append(monthBtn, yearBtn);
|
|
22121
|
+
const head = document.createElement("div");
|
|
22122
|
+
head.className = "vela-ind-cal-head";
|
|
22123
|
+
head.append(prev3, title, next2);
|
|
22124
|
+
const week = document.createElement("div");
|
|
22125
|
+
week.className = "vela-ind-cal-week";
|
|
22126
|
+
for (const d of WEEKDAY_LABELS) {
|
|
22127
|
+
const cell = document.createElement("span");
|
|
22128
|
+
cell.textContent = d;
|
|
22129
|
+
week.appendChild(cell);
|
|
22130
|
+
}
|
|
22131
|
+
const grid = document.createElement("div");
|
|
22132
|
+
const paint = () => {
|
|
22133
|
+
const decade = Math.floor(year / 10) * 10;
|
|
22134
|
+
monthBtn.hidden = mode !== "date";
|
|
22135
|
+
monthBtn.textContent = MONTH_LABELS[month] ?? "";
|
|
22136
|
+
yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
|
|
22137
|
+
yearBtn.disabled = mode === "year";
|
|
22138
|
+
week.hidden = mode !== "date";
|
|
22139
|
+
prev3.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
|
|
22140
|
+
next2.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
|
|
22141
|
+
grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
|
|
22142
|
+
grid.replaceChildren();
|
|
22143
|
+
if (mode === "date") {
|
|
22144
|
+
const startPad = new Date(year, month, 1).getDay();
|
|
22145
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
22146
|
+
const todayIso = isoDate(today);
|
|
22147
|
+
const selectedIso = selected ? isoDate(selected) : "";
|
|
22148
|
+
for (let i = 0; i < startPad; i++) {
|
|
22149
|
+
const blank = document.createElement("span");
|
|
22150
|
+
blank.className = "vela-ind-cal-blank";
|
|
22151
|
+
grid.appendChild(blank);
|
|
22152
|
+
}
|
|
22153
|
+
for (let day = 1; day <= days; day++) {
|
|
22154
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
22155
|
+
const b = document.createElement("button");
|
|
22156
|
+
b.type = "button";
|
|
22157
|
+
b.className = "vela-ind-cal-day";
|
|
22158
|
+
b.textContent = String(day);
|
|
22159
|
+
if (iso === selectedIso) b.dataset.checked = "1";
|
|
22160
|
+
if (iso === todayIso) b.dataset.today = "1";
|
|
22161
|
+
b.addEventListener("click", (e) => {
|
|
22162
|
+
e.stopPropagation();
|
|
22163
|
+
onPick(iso);
|
|
22164
|
+
});
|
|
22165
|
+
grid.appendChild(b);
|
|
22166
|
+
}
|
|
22167
|
+
return;
|
|
22168
|
+
}
|
|
22169
|
+
if (mode === "month") {
|
|
22170
|
+
for (let m = 0; m < 12; m++) {
|
|
22171
|
+
const b = document.createElement("button");
|
|
22172
|
+
b.type = "button";
|
|
22173
|
+
b.className = "vela-ind-cal-cell";
|
|
22174
|
+
b.textContent = MONTH_SHORT[m] ?? "";
|
|
22175
|
+
if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
|
|
22176
|
+
if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
|
|
22177
|
+
b.addEventListener("click", (e) => {
|
|
22178
|
+
e.stopPropagation();
|
|
22179
|
+
month = m;
|
|
22180
|
+
mode = "date";
|
|
22181
|
+
paint();
|
|
22182
|
+
});
|
|
22183
|
+
grid.appendChild(b);
|
|
22184
|
+
}
|
|
22185
|
+
return;
|
|
22186
|
+
}
|
|
22187
|
+
for (let y = decade - 1; y <= decade + 10; y++) {
|
|
22188
|
+
const b = document.createElement("button");
|
|
22189
|
+
b.type = "button";
|
|
22190
|
+
b.className = "vela-ind-cal-cell";
|
|
22191
|
+
b.textContent = String(y);
|
|
22192
|
+
if (y < decade || y > decade + 9) b.dataset.outside = "1";
|
|
22193
|
+
if (selected?.getFullYear() === y) b.dataset.checked = "1";
|
|
22194
|
+
if (today.getFullYear() === y) b.dataset.today = "1";
|
|
22195
|
+
b.addEventListener("click", (e) => {
|
|
22196
|
+
e.stopPropagation();
|
|
22197
|
+
year = y;
|
|
22198
|
+
mode = "month";
|
|
22199
|
+
paint();
|
|
22200
|
+
});
|
|
22201
|
+
grid.appendChild(b);
|
|
22202
|
+
}
|
|
22203
|
+
};
|
|
22204
|
+
const step = (dir) => {
|
|
22205
|
+
if (mode === "date") {
|
|
22206
|
+
month += dir;
|
|
22207
|
+
if (month < 0) {
|
|
22208
|
+
month = 11;
|
|
22209
|
+
year -= 1;
|
|
22210
|
+
}
|
|
22211
|
+
if (month > 11) {
|
|
22212
|
+
month = 0;
|
|
22213
|
+
year += 1;
|
|
22214
|
+
}
|
|
22215
|
+
} else {
|
|
22216
|
+
year += mode === "month" ? dir : dir * 10;
|
|
22217
|
+
}
|
|
22218
|
+
paint();
|
|
22219
|
+
};
|
|
22220
|
+
prev3.addEventListener("click", (e) => {
|
|
22221
|
+
e.stopPropagation();
|
|
22222
|
+
step(-1);
|
|
22223
|
+
});
|
|
22224
|
+
next2.addEventListener("click", (e) => {
|
|
22225
|
+
e.stopPropagation();
|
|
22226
|
+
step(1);
|
|
22227
|
+
});
|
|
22228
|
+
monthBtn.addEventListener("click", (e) => {
|
|
22229
|
+
e.stopPropagation();
|
|
22230
|
+
mode = "month";
|
|
22231
|
+
paint();
|
|
22232
|
+
});
|
|
22233
|
+
yearBtn.addEventListener("click", (e) => {
|
|
22234
|
+
e.stopPropagation();
|
|
22235
|
+
mode = "year";
|
|
22236
|
+
paint();
|
|
22237
|
+
});
|
|
22238
|
+
paint();
|
|
22239
|
+
el.append(head, week, grid);
|
|
22240
|
+
}
|
|
22118
22241
|
function normalizeTimeInput(raw) {
|
|
22119
22242
|
const t = raw.trim();
|
|
22120
22243
|
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
@@ -23396,10 +23519,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23396
23519
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
23397
23520
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
23398
23521
|
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
23522
|
+
var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
|
|
23399
23523
|
function defaultChartStyle() {
|
|
23400
23524
|
return {
|
|
23401
23525
|
chartTypes: {},
|
|
23402
23526
|
fontSize: 11,
|
|
23527
|
+
margins: { ...DEFAULT_MARGINS },
|
|
23403
23528
|
gridVert: { visible: true, color: null },
|
|
23404
23529
|
gridHorz: { visible: true, color: null },
|
|
23405
23530
|
borderColor: null,
|
|
@@ -23523,6 +23648,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23523
23648
|
function clampSpacing(v) {
|
|
23524
23649
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
23525
23650
|
}
|
|
23651
|
+
function clampMarginPct(v) {
|
|
23652
|
+
return v < 0 ? 0 : v > 40 ? 40 : v;
|
|
23653
|
+
}
|
|
23654
|
+
function clampMarginBars(v) {
|
|
23655
|
+
return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
|
|
23656
|
+
}
|
|
23526
23657
|
function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
23527
23658
|
const bag = {};
|
|
23528
23659
|
for (const t of chartTypes()) {
|
|
@@ -23571,6 +23702,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23571
23702
|
const ps = asObject(p.priceScale);
|
|
23572
23703
|
const anim = asObject(p.animations);
|
|
23573
23704
|
const panes = asObject(p.panes);
|
|
23705
|
+
const margins = asObject(p.margins);
|
|
23574
23706
|
const trades = asObject(p.trades);
|
|
23575
23707
|
const ts = asObject(p.timeScale);
|
|
23576
23708
|
const marks = asObject(p.marks);
|
|
@@ -23630,6 +23762,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23630
23762
|
panes: {
|
|
23631
23763
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
23632
23764
|
},
|
|
23765
|
+
margins: {
|
|
23766
|
+
top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
|
|
23767
|
+
bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
|
|
23768
|
+
right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
|
|
23769
|
+
},
|
|
23633
23770
|
trades: {
|
|
23634
23771
|
visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
|
|
23635
23772
|
labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
|
|
@@ -29000,6 +29137,10 @@ void main() {
|
|
|
29000
29137
|
"canvas.grid",
|
|
29001
29138
|
"canvas.grid.vertical",
|
|
29002
29139
|
"canvas.grid.horizontal",
|
|
29140
|
+
"canvas.margins",
|
|
29141
|
+
"canvas.margins.top",
|
|
29142
|
+
"canvas.margins.bottom",
|
|
29143
|
+
"canvas.margins.right",
|
|
29003
29144
|
"canvas.theme"
|
|
29004
29145
|
];
|
|
29005
29146
|
function settingsIdCatalog(hostSections, markGroups = []) {
|
|
@@ -29480,7 +29621,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
29480
29621
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
29481
29622
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29482
29623
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29483
|
-
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options
|
|
29624
|
+
else body.append(this.selectRowLabeled(hr.label, hr.get(), normalizeSelectOptions(hr.options), (v) => hr.set(v)));
|
|
29484
29625
|
}
|
|
29485
29626
|
}
|
|
29486
29627
|
};
|
|
@@ -29530,6 +29671,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
29530
29671
|
body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
|
|
29531
29672
|
this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
|
|
29532
29673
|
]), "canvas.grid.horizontal"));
|
|
29674
|
+
body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
|
|
29675
|
+
body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
|
|
29676
|
+
body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
|
|
29677
|
+
body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
|
|
29533
29678
|
if (this.themeControl) {
|
|
29534
29679
|
const tc = this.themeControl;
|
|
29535
29680
|
body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
|
|
@@ -30101,23 +30246,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30101
30246
|
this.hintTips.push(dispose);
|
|
30102
30247
|
return el;
|
|
30103
30248
|
}
|
|
30104
|
-
|
|
30105
|
-
|
|
30106
|
-
|
|
30107
|
-
|
|
30108
|
-
|
|
30109
|
-
|
|
30110
|
-
|
|
30111
|
-
|
|
30112
|
-
|
|
30113
|
-
|
|
30114
|
-
step,
|
|
30115
|
-
fill: false,
|
|
30116
|
-
commit: "live",
|
|
30117
|
-
steppers: true,
|
|
30118
|
-
onChange
|
|
30119
|
-
}).el
|
|
30120
|
-
});
|
|
30249
|
+
/** `unit` (optional) trails the input as muted text — "%", "bars". */
|
|
30250
|
+
numberRow(label, value, min2, max2, step, onChange, unit) {
|
|
30251
|
+
const controls = [buildFieldControl({ kind: "number", value, min: min2, max: max2, step, fill: false, commit: "live", steppers: true, onChange }).el];
|
|
30252
|
+
if (unit) {
|
|
30253
|
+
const u = document.createElement("span");
|
|
30254
|
+
u.textContent = unit;
|
|
30255
|
+
u.style.cssText = "color:var(--vela-fg-muted);";
|
|
30256
|
+
controls.push(u);
|
|
30257
|
+
}
|
|
30258
|
+
return this.rowWith(label, controls);
|
|
30121
30259
|
}
|
|
30122
30260
|
/** A dropdown whose option values differ from their display labels. */
|
|
30123
30261
|
selectRowLabeled(label, value, options, onChange) {
|
|
@@ -35636,9 +35774,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35636
35774
|
}
|
|
35637
35775
|
|
|
35638
35776
|
// src/renderers/native/core/autoscale.ts
|
|
35639
|
-
|
|
35640
|
-
var MARGIN_BOTTOM = 1 / 7;
|
|
35641
|
-
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
|
|
35777
|
+
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
|
|
35642
35778
|
let min2 = Infinity;
|
|
35643
35779
|
let max2 = -Infinity;
|
|
35644
35780
|
const consider = (v) => {
|
|
@@ -35673,14 +35809,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35673
35809
|
const pad = Math.abs(min2) * 0.1 || 1;
|
|
35674
35810
|
return { min: min2 - pad, max: max2 + pad, log: log && min2 - pad > 0 };
|
|
35675
35811
|
}
|
|
35812
|
+
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
35813
|
+
const above = margins.top / 100 / content;
|
|
35814
|
+
const below = margins.bottom / 100 / content;
|
|
35676
35815
|
if (log && min2 > 0) {
|
|
35677
35816
|
const lmin = Math.log(min2);
|
|
35678
35817
|
const lmax = Math.log(max2);
|
|
35679
35818
|
const lspan = lmax - lmin;
|
|
35680
|
-
return { min: Math.exp(lmin - lspan *
|
|
35819
|
+
return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
|
|
35681
35820
|
}
|
|
35682
35821
|
const span = max2 - min2;
|
|
35683
|
-
return { min: min2 - span *
|
|
35822
|
+
return { min: min2 - span * below, max: max2 + span * above };
|
|
35684
35823
|
}
|
|
35685
35824
|
function considerSeries(s, i0, i1, off, consider) {
|
|
35686
35825
|
if (!seriesInScale(s)) return;
|
|
@@ -37507,6 +37646,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37507
37646
|
intro: this.intro.style !== false
|
|
37508
37647
|
},
|
|
37509
37648
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
37649
|
+
margins: { ...s.margins },
|
|
37510
37650
|
trades: {
|
|
37511
37651
|
visible: this.scene.tradeMarkers.visible,
|
|
37512
37652
|
labels: this.scene.tradeMarkers.labels,
|
|
@@ -37619,6 +37759,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37619
37759
|
this.animAutoscale.toggle(next2.animations.autoscale);
|
|
37620
37760
|
this.intro = { style: next2.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
|
|
37621
37761
|
s.separatorColor = keepInherit(s.separatorColor, next2.panes.separatorColor, prevTheme.borderColor);
|
|
37762
|
+
if (next2.margins.right !== s.margins.right && this.coords.barCount > 0) {
|
|
37763
|
+
this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next2.margins.right });
|
|
37764
|
+
}
|
|
37765
|
+
s.margins = { ...next2.margins };
|
|
37622
37766
|
this.scene.tradeMarkers = {
|
|
37623
37767
|
visible: next2.trades.visible,
|
|
37624
37768
|
labels: next2.trades.labels,
|
|
@@ -37862,7 +38006,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37862
38006
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
37863
38007
|
scrollToRealtime() {
|
|
37864
38008
|
if (this.coords.barCount === 0) return;
|
|
37865
|
-
this.glideRightOffset(
|
|
38009
|
+
this.glideRightOffset(this.scene.style.margins.right);
|
|
37866
38010
|
}
|
|
37867
38011
|
/** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
|
|
37868
38012
|
* instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
|
|
@@ -39804,6 +39948,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39804
39948
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
39805
39949
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
39806
39950
|
const animating = this.animator.active;
|
|
39951
|
+
const margins = this.scene.style.margins;
|
|
39807
39952
|
for (const pane of panes) {
|
|
39808
39953
|
if (pane.manualScale) {
|
|
39809
39954
|
pane.scaleTarget = pane.manualScale;
|
|
@@ -39819,7 +39964,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39819
39964
|
if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
|
|
39820
39965
|
}
|
|
39821
39966
|
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
|
|
39822
|
-
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
39967
|
+
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
39823
39968
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
39824
39969
|
pane.axisFormat = void 0;
|
|
39825
39970
|
pane.axisBands = void 0;
|
|
@@ -39830,7 +39975,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39830
39975
|
pane.axisFormat = "volume";
|
|
39831
39976
|
}
|
|
39832
39977
|
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
39833
|
-
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
39978
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
39834
39979
|
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
39835
39980
|
if (masterModels.every((m) => m.paneAxis != null)) {
|
|
39836
39981
|
pane.axisFormat = "none";
|
|
@@ -39859,7 +40004,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39859
40004
|
continue;
|
|
39860
40005
|
}
|
|
39861
40006
|
const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
|
|
39862
|
-
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
|
|
40007
|
+
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
|
|
39863
40008
|
if (!animating || !sl.initialized) {
|
|
39864
40009
|
sl.scale = { ...sl.scaleTarget };
|
|
39865
40010
|
sl.initialized = true;
|
|
@@ -39900,13 +40045,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39900
40045
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
39901
40046
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
39902
40047
|
const visibleBars = Math.min(n, 200);
|
|
39903
|
-
const rightOffset =
|
|
40048
|
+
const rightOffset = this.scene.style.margins.right;
|
|
39904
40049
|
const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
|
|
39905
40050
|
this.coords.setViewport(v);
|
|
39906
40051
|
this.targetBarSpacing = v.barSpacing;
|
|
39907
40052
|
}
|
|
39908
40053
|
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
39909
|
-
* zoom (bar spacing), re-anchor the newest bars at the
|
|
40054
|
+
* zoom (bar spacing), re-anchor the newest bars at the configured right margin.
|
|
39910
40055
|
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
39911
40056
|
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
39912
40057
|
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
@@ -39915,7 +40060,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39915
40060
|
this.panVelocity = 0;
|
|
39916
40061
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
39917
40062
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
39918
|
-
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset:
|
|
40063
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
|
|
39919
40064
|
this.coords.setViewport(v);
|
|
39920
40065
|
this.targetBarSpacing = v.barSpacing;
|
|
39921
40066
|
}
|
|
@@ -40745,7 +40890,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40745
40890
|
lineStyle: plot.lineStyle ?? "solid",
|
|
40746
40891
|
...plot.base != null ? { base: plot.base } : {}
|
|
40747
40892
|
},
|
|
40748
|
-
...plot.overlay ? { overlay: true } : {}
|
|
40893
|
+
...plot.overlay ? { overlay: true } : {},
|
|
40894
|
+
...plot.display ? { display: plot.display } : {}
|
|
40749
40895
|
});
|
|
40750
40896
|
});
|
|
40751
40897
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -40767,7 +40913,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40767
40913
|
paneId: "",
|
|
40768
40914
|
fromSeriesId: from,
|
|
40769
40915
|
toSeriesId: to,
|
|
40770
|
-
color: band.color
|
|
40916
|
+
color: band.color,
|
|
40917
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
40918
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
40771
40919
|
}
|
|
40772
40920
|
];
|
|
40773
40921
|
});
|
|
@@ -40925,6 +41073,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40925
41073
|
return Math.sqrt(sq / len);
|
|
40926
41074
|
});
|
|
40927
41075
|
}
|
|
41076
|
+
function meanDev(v, len) {
|
|
41077
|
+
return windowed(v, len, (w, from, to) => {
|
|
41078
|
+
let s = 0;
|
|
41079
|
+
for (let k = from; k <= to; k++) s += w[k];
|
|
41080
|
+
const mean = s / len;
|
|
41081
|
+
let dev = 0;
|
|
41082
|
+
for (let k = from; k <= to; k++) dev += Math.abs(w[k] - mean);
|
|
41083
|
+
return dev / len;
|
|
41084
|
+
});
|
|
41085
|
+
}
|
|
40928
41086
|
function highest(v, len) {
|
|
40929
41087
|
return windowed(v, len, (w, from, to) => {
|
|
40930
41088
|
let m = -Infinity;
|
|
@@ -41056,7 +41214,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41056
41214
|
}
|
|
41057
41215
|
return out;
|
|
41058
41216
|
}
|
|
41059
|
-
function linreg(v, len) {
|
|
41217
|
+
function linreg(v, len, offset3 = 0) {
|
|
41060
41218
|
const sx = (len - 1) * len / 2;
|
|
41061
41219
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
41062
41220
|
const denom = len * sxx - sx * sx;
|
|
@@ -41070,9 +41228,101 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41070
41228
|
}
|
|
41071
41229
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
41072
41230
|
const intercept = (sy - slope * sx) / len;
|
|
41073
|
-
return intercept + slope * (len - 1);
|
|
41231
|
+
return intercept + slope * (len - 1 - offset3);
|
|
41074
41232
|
});
|
|
41075
41233
|
}
|
|
41234
|
+
function hma(v, len) {
|
|
41235
|
+
const n = Math.max(2, Math.trunc(len));
|
|
41236
|
+
const raw = sub(map(wma(v, Math.max(1, Math.trunc(n / 2))), (x) => 2 * x), wma(v, n));
|
|
41237
|
+
return wma(raw, Math.max(1, Math.round(Math.sqrt(n))));
|
|
41238
|
+
}
|
|
41239
|
+
function alma(v, len, offset3, sigma) {
|
|
41240
|
+
const m = offset3 * (len - 1);
|
|
41241
|
+
const s = len / Math.max(sigma, 1e-9);
|
|
41242
|
+
const weights = new Array(len);
|
|
41243
|
+
let norm = 0;
|
|
41244
|
+
for (let i = 0; i < len; i++) {
|
|
41245
|
+
const d = i - m;
|
|
41246
|
+
const w = Math.exp(-(d * d) / (2 * s * s));
|
|
41247
|
+
weights[i] = w;
|
|
41248
|
+
norm += w;
|
|
41249
|
+
}
|
|
41250
|
+
return windowed(v, len, (win, from, to) => {
|
|
41251
|
+
let acc = 0;
|
|
41252
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[k - from];
|
|
41253
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
41254
|
+
});
|
|
41255
|
+
}
|
|
41256
|
+
function dema(v, len) {
|
|
41257
|
+
const e1 = ema(v, len);
|
|
41258
|
+
return sub(map(e1, (x) => 2 * x), ema(e1, len));
|
|
41259
|
+
}
|
|
41260
|
+
function tema(v, len) {
|
|
41261
|
+
const e1 = ema(v, len);
|
|
41262
|
+
const e2 = ema(e1, len);
|
|
41263
|
+
const e3 = ema(e2, len);
|
|
41264
|
+
return zip(zip(map(e1, (x) => 3 * x), map(e2, (x) => 3 * x), (a, b) => a - b), e3, (a, b) => a + b);
|
|
41265
|
+
}
|
|
41266
|
+
function kama(v, len, fastAlpha = 2 / 3, slowAlpha = 2 / 31) {
|
|
41267
|
+
const direction = map(change(v, len), Math.abs);
|
|
41268
|
+
const volatility = sum(map(change(v), Math.abs), len);
|
|
41269
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
41270
|
+
let prev3 = Number.NaN;
|
|
41271
|
+
for (let i = 0; i < v.length; i++) {
|
|
41272
|
+
const x = v[i];
|
|
41273
|
+
if (!Number.isFinite(x)) continue;
|
|
41274
|
+
const d = direction[i];
|
|
41275
|
+
const w = volatility[i];
|
|
41276
|
+
if (!Number.isFinite(d) || !Number.isFinite(w)) {
|
|
41277
|
+
prev3 = Number.isFinite(prev3) ? prev3 : x;
|
|
41278
|
+
out[i] = prev3;
|
|
41279
|
+
continue;
|
|
41280
|
+
}
|
|
41281
|
+
const er = w === 0 ? 0 : d / w;
|
|
41282
|
+
const sc = (er * (fastAlpha - slowAlpha) + slowAlpha) ** 2;
|
|
41283
|
+
prev3 = Number.isFinite(prev3) ? prev3 + sc * (x - prev3) : x;
|
|
41284
|
+
out[i] = prev3;
|
|
41285
|
+
}
|
|
41286
|
+
return out;
|
|
41287
|
+
}
|
|
41288
|
+
function mcginley(v, len) {
|
|
41289
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
41290
|
+
let prev3 = Number.NaN;
|
|
41291
|
+
for (let i = 0; i < v.length; i++) {
|
|
41292
|
+
const x = v[i];
|
|
41293
|
+
if (!Number.isFinite(x)) continue;
|
|
41294
|
+
if (!Number.isFinite(prev3)) {
|
|
41295
|
+
prev3 = x;
|
|
41296
|
+
} else {
|
|
41297
|
+
const ratio = prev3 === 0 ? 0 : x / prev3;
|
|
41298
|
+
const denom = len * ratio ** 4;
|
|
41299
|
+
prev3 += denom === 0 ? 0 : (x - prev3) / denom;
|
|
41300
|
+
}
|
|
41301
|
+
out[i] = prev3;
|
|
41302
|
+
}
|
|
41303
|
+
return out;
|
|
41304
|
+
}
|
|
41305
|
+
function hamming(v, len, a0 = 0.54, a1 = 0.46) {
|
|
41306
|
+
if (len <= 1) return v.map((x) => Number.isFinite(x) ? x : Number.NaN);
|
|
41307
|
+
const weights = new Array(len);
|
|
41308
|
+
let norm = 0;
|
|
41309
|
+
for (let i = 0; i < len; i++) {
|
|
41310
|
+
const w = a0 - a1 * Math.cos(2 * Math.PI * i / (len - 1));
|
|
41311
|
+
weights[i] = w;
|
|
41312
|
+
norm += w;
|
|
41313
|
+
}
|
|
41314
|
+
return windowed(v, len, (win, from, to) => {
|
|
41315
|
+
let acc = 0;
|
|
41316
|
+
for (let k = from; k <= to; k++) acc += win[k] * weights[to - k];
|
|
41317
|
+
return norm === 0 ? Number.NaN : acc / norm;
|
|
41318
|
+
});
|
|
41319
|
+
}
|
|
41320
|
+
function cmo(v, len) {
|
|
41321
|
+
const d = change(v);
|
|
41322
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
41323
|
+
const dn = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
41324
|
+
return zip(up, dn, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
41325
|
+
}
|
|
41076
41326
|
function swma(v) {
|
|
41077
41327
|
const out = new Array(v.length).fill(Number.NaN);
|
|
41078
41328
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -41122,38 +41372,253 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41122
41372
|
}
|
|
41123
41373
|
|
|
41124
41374
|
// src/core/native-indicators/classics/shared.ts
|
|
41125
|
-
|
|
41126
|
-
|
|
41375
|
+
var SETTINGS = "Settings";
|
|
41376
|
+
var STYLE = "Style";
|
|
41377
|
+
function lengthInput(defval, key = "length", title = "Length", max2 = 5e3, tooltip) {
|
|
41378
|
+
return { key, title, type: "int", defval, min: 1, max: max2, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
41379
|
+
}
|
|
41380
|
+
function intInput(key, title, defval, min2 = 1, max2 = 5e3, tooltip) {
|
|
41381
|
+
return { key, title, type: "int", defval, min: min2, max: max2, step: 1, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
41127
41382
|
}
|
|
41128
|
-
function
|
|
41129
|
-
return { key: "
|
|
41383
|
+
function floatInput(key, title, defval, min2 = 0, max2 = 1e3, step = 0.1, tooltip) {
|
|
41384
|
+
return { key, title, type: "float", defval, min: min2, max: max2, step, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
41130
41385
|
}
|
|
41131
|
-
function
|
|
41132
|
-
return { key, title, type: "
|
|
41386
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
41387
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
41388
|
+
}
|
|
41389
|
+
function optionInput(key, title, defval, options, tooltip) {
|
|
41390
|
+
return { key, title, type: "string", defval, options, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
41391
|
+
}
|
|
41392
|
+
function sourceInput(defval = "Close", key = "source", title = "Source", tooltip) {
|
|
41393
|
+
return { key, title, type: "string", defval, options: SOURCES2, group: SETTINGS, ...tooltip != null ? { tooltip } : {} };
|
|
41394
|
+
}
|
|
41395
|
+
function colorInput(defval = INFO, title = "Color", key = "color", tooltip) {
|
|
41396
|
+
return { key, title, type: "color", defval, group: STYLE, ...tooltip != null ? { tooltip } : {} };
|
|
41397
|
+
}
|
|
41398
|
+
function widthInput(defval = 2, key = "lineWidth", title = "Line Width") {
|
|
41399
|
+
return { key, title, type: "int", defval, min: 1, max: 5, step: 1, group: STYLE, tooltip: "Width of the plotted line, in pixels." };
|
|
41400
|
+
}
|
|
41401
|
+
function transp(hex, transparency) {
|
|
41402
|
+
return withAlpha2(hex, (100 - transparency) / 100);
|
|
41133
41403
|
}
|
|
41134
41404
|
function withAlpha2(hex, alpha = 0.08) {
|
|
41135
41405
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
41136
41406
|
if (!m) return hex;
|
|
41137
|
-
const a = Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
41407
|
+
const a = Math.round(Math.max(0, Math.min(1, alpha)) * 255).toString(16).padStart(2, "0");
|
|
41138
41408
|
return `#${m[1]}${a}`;
|
|
41139
41409
|
}
|
|
41410
|
+
function channelStyleInputs(basisTitle = "Basis") {
|
|
41411
|
+
return [
|
|
41412
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the lower band and of the basis while price holds above it."),
|
|
41413
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the upper band and of the basis while price sits below it."),
|
|
41414
|
+
colorInput(NEUTRAL, basisTitle, "basisColor", "Color of the basis line while price sits exactly on it.")
|
|
41415
|
+
];
|
|
41416
|
+
}
|
|
41417
|
+
function directionalChannel(bars, basis, upper, lower, ink, key) {
|
|
41418
|
+
const basisColors = basis.map((b, i) => {
|
|
41419
|
+
if (!Number.isFinite(b)) return null;
|
|
41420
|
+
const c = bars[i].close;
|
|
41421
|
+
return c > b ? ink.bull : c < b ? ink.bear : ink.neutral;
|
|
41422
|
+
});
|
|
41423
|
+
const wash = upper.map((u, i) => {
|
|
41424
|
+
const l = lower[i];
|
|
41425
|
+
if (!Number.isFinite(u) || !Number.isFinite(l)) return null;
|
|
41426
|
+
return { topValue: u, bottomValue: l, topColor: transp(ink.bear, 90), bottomColor: transp(ink.bull, 90) };
|
|
41427
|
+
});
|
|
41428
|
+
return {
|
|
41429
|
+
plots: [
|
|
41430
|
+
{ key: "basis", title: "Basis", values: [...basis], color: ink.neutral, colors: basisColors },
|
|
41431
|
+
{ key: "upper", title: "Upper", values: [...upper], color: transp(ink.bear, 30) },
|
|
41432
|
+
{ key: "lower", title: "Lower", values: [...lower], color: transp(ink.bull, 30) }
|
|
41433
|
+
],
|
|
41434
|
+
bands: [{ key, from: "upper", to: "lower", color: transp(NEUTRAL, 90), gradient: wash }]
|
|
41435
|
+
};
|
|
41436
|
+
}
|
|
41437
|
+
function anchorPlot(key, title, length, level) {
|
|
41438
|
+
return {
|
|
41439
|
+
key,
|
|
41440
|
+
title,
|
|
41441
|
+
values: new Array(length).fill(level),
|
|
41442
|
+
color: transp(NEUTRAL, 100),
|
|
41443
|
+
display: { pane: false, legend: false, dataWindow: false }
|
|
41444
|
+
};
|
|
41445
|
+
}
|
|
41446
|
+
function thresholdZones(length, levels, ink, transparency = 92) {
|
|
41447
|
+
return {
|
|
41448
|
+
plots: [
|
|
41449
|
+
anchorPlot("obAnchor", "Overbought Anchor", length, levels.overbought),
|
|
41450
|
+
anchorPlot("osAnchor", "Oversold Anchor", length, levels.oversold),
|
|
41451
|
+
anchorPlot("topAnchor", "Top Anchor", length, levels.top),
|
|
41452
|
+
anchorPlot("bottomAnchor", "Bottom Anchor", length, levels.bottom)
|
|
41453
|
+
],
|
|
41454
|
+
bands: [
|
|
41455
|
+
{ key: "obZone", from: "obAnchor", to: "topAnchor", color: transp(ink.overbought, transparency) },
|
|
41456
|
+
{ key: "osZone", from: "osAnchor", to: "bottomAnchor", color: transp(ink.oversold, transparency) }
|
|
41457
|
+
]
|
|
41458
|
+
};
|
|
41459
|
+
}
|
|
41460
|
+
function rampColors(values, levels, ink) {
|
|
41461
|
+
return values.map((x) => {
|
|
41462
|
+
if (!Number.isFinite(x)) return null;
|
|
41463
|
+
return x <= levels.mid ? gradient(x, levels.low, levels.mid, ink.low, ink.mid) : gradient(x, levels.mid, levels.high, ink.mid, ink.high);
|
|
41464
|
+
});
|
|
41465
|
+
}
|
|
41466
|
+
function baselineGradient(values, baseline, inkOf, valueTransparency = 50, baseTransparency = 100) {
|
|
41467
|
+
return values.map((x, i) => {
|
|
41468
|
+
if (!Number.isFinite(x)) return null;
|
|
41469
|
+
const ink = inkOf(x, i);
|
|
41470
|
+
const above = x > baseline;
|
|
41471
|
+
return {
|
|
41472
|
+
topValue: Math.max(x, baseline),
|
|
41473
|
+
bottomValue: Math.min(x, baseline),
|
|
41474
|
+
topColor: transp(ink, above ? valueTransparency : baseTransparency),
|
|
41475
|
+
bottomColor: transp(ink, above ? baseTransparency : valueTransparency)
|
|
41476
|
+
};
|
|
41477
|
+
});
|
|
41478
|
+
}
|
|
41479
|
+
function momentumColumnColors(values, ink) {
|
|
41480
|
+
return values.map((x, i) => {
|
|
41481
|
+
if (!Number.isFinite(x)) return null;
|
|
41482
|
+
const prev3 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
41483
|
+
const rising = x > prev3;
|
|
41484
|
+
if (x >= 0) return rising ? ink.growAbove : ink.fallAbove;
|
|
41485
|
+
return rising ? ink.growBelow : ink.fallBelow;
|
|
41486
|
+
});
|
|
41487
|
+
}
|
|
41488
|
+
function boundedOscillator(opts) {
|
|
41489
|
+
const mid = (opts.overbought + opts.oversold) / 2;
|
|
41490
|
+
const ramp = rampColors(opts.values, { low: opts.oversold, mid, high: opts.overbought }, opts.ink);
|
|
41491
|
+
const zones = thresholdZones(
|
|
41492
|
+
opts.values.length,
|
|
41493
|
+
{ overbought: opts.overbought, oversold: opts.oversold, top: opts.scale.top, bottom: opts.scale.bottom },
|
|
41494
|
+
{ overbought: opts.ink.high, oversold: opts.ink.low },
|
|
41495
|
+
opts.zoneTransparency
|
|
41496
|
+
);
|
|
41497
|
+
const plots = [{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.mid, width: opts.width ?? 1, colors: ramp }, ...zones.plots];
|
|
41498
|
+
const bands = [...zones.bands];
|
|
41499
|
+
if (opts.bandFill != null) bands.push({ key: "band", from: "obAnchor", to: "osAnchor", color: opts.bandFill });
|
|
41500
|
+
if (opts.midpointWash) {
|
|
41501
|
+
plots.push(anchorPlot("midAnchor", "Midpoint Anchor", opts.values.length, mid));
|
|
41502
|
+
bands.push({ key: "wash", from: opts.key, to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, mid, (_x, i) => ramp[i] ?? opts.ink.mid) });
|
|
41503
|
+
}
|
|
41504
|
+
const levels = [
|
|
41505
|
+
{ key: "overbought", price: opts.overbought, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.overbought ?? "Overbought" },
|
|
41506
|
+
{ key: "oversold", price: opts.oversold, color: opts.levelInk, lineStyle: "dashed", title: opts.levelTitles?.oversold ?? "Oversold" }
|
|
41507
|
+
];
|
|
41508
|
+
if (opts.midline != null) levels.splice(1, 0, { key: "midline", price: opts.midline, color: opts.levelInk, lineStyle: "dotted", title: "Midline" });
|
|
41509
|
+
return { plots, bands, levels };
|
|
41510
|
+
}
|
|
41511
|
+
function centeredOscillator(opts) {
|
|
41512
|
+
const baseline = opts.baseline ?? 0;
|
|
41513
|
+
const inkOf = (x) => x > baseline ? opts.ink.bull : x < baseline ? opts.ink.bear : opts.ink.neutral;
|
|
41514
|
+
const plots = [
|
|
41515
|
+
{ key: opts.key, title: opts.title, values: opts.values, color: opts.ink.neutral, width: opts.width ?? 1, colors: opts.values.map((x) => Number.isFinite(x) ? inkOf(x) : null) },
|
|
41516
|
+
...opts.extraPlots ?? []
|
|
41517
|
+
];
|
|
41518
|
+
const bands = [];
|
|
41519
|
+
if (opts.fill !== false) {
|
|
41520
|
+
plots.push(anchorPlot("baseline", "Baseline Anchor", opts.values.length, baseline));
|
|
41521
|
+
bands.push({ key: "wash", from: opts.key, to: "baseline", color: transp(NEUTRAL, 100), gradient: baselineGradient(opts.values, baseline, inkOf, opts.valueTransparency) });
|
|
41522
|
+
}
|
|
41523
|
+
return {
|
|
41524
|
+
plots,
|
|
41525
|
+
bands,
|
|
41526
|
+
levels: [{ key: "zero", price: baseline, color: opts.zeroInk, lineStyle: opts.zeroLineStyle ?? "dashed", title: opts.zeroTitle ?? "Zero Line" }, ...opts.extraLevels ?? []]
|
|
41527
|
+
};
|
|
41528
|
+
}
|
|
41529
|
+
function channels(hex) {
|
|
41530
|
+
const m = /^#([0-9a-f]{6})/i.exec(hex.trim());
|
|
41531
|
+
if (!m) return null;
|
|
41532
|
+
const n = parseInt(m[1], 16);
|
|
41533
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
41534
|
+
}
|
|
41535
|
+
function gradient(value, low, high, from, to) {
|
|
41536
|
+
const a = channels(from);
|
|
41537
|
+
const b = channels(to);
|
|
41538
|
+
if (!a || !b || !Number.isFinite(value)) return from;
|
|
41539
|
+
const span = high - low;
|
|
41540
|
+
const t = span === 0 ? 0 : Math.max(0, Math.min(1, (value - low) / span));
|
|
41541
|
+
const mix2 = (i) => Math.round(a[i] + (b[i] - a[i]) * t).toString(16).padStart(2, "0");
|
|
41542
|
+
return `#${mix2(0)}${mix2(1)}${mix2(2)}`;
|
|
41543
|
+
}
|
|
41140
41544
|
|
|
41141
41545
|
// src/core/native-indicators/classics/averages.ts
|
|
41142
|
-
var
|
|
41143
|
-
var
|
|
41546
|
+
var MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "ALMA", "VWMA", "DEMA", "TEMA", "KAMA", "LSMA", "McGinley", "Hamming"];
|
|
41547
|
+
var ENVELOPE_MA_TYPES = ["SMA", "EMA", "WMA", "RMA (SMMA)", "HMA", "VWMA"];
|
|
41548
|
+
function movingAverageOf(kind, src, bars, len, opts = {}) {
|
|
41549
|
+
switch (kind) {
|
|
41550
|
+
case "EMA":
|
|
41551
|
+
return ema(src, len);
|
|
41552
|
+
case "WMA":
|
|
41553
|
+
return wma(src, len);
|
|
41554
|
+
case "RMA":
|
|
41555
|
+
case "RMA (SMMA)":
|
|
41556
|
+
return rma(src, len);
|
|
41557
|
+
case "HMA":
|
|
41558
|
+
return hma(src, len);
|
|
41559
|
+
case "ALMA":
|
|
41560
|
+
return alma(src, len, opts.almaOffset ?? 0.85, opts.almaSigma ?? 6);
|
|
41561
|
+
case "VWMA":
|
|
41562
|
+
return vwma(src, volumes(bars), len);
|
|
41563
|
+
case "DEMA":
|
|
41564
|
+
return dema(src, len);
|
|
41565
|
+
case "TEMA":
|
|
41566
|
+
return tema(src, len);
|
|
41567
|
+
case "KAMA":
|
|
41568
|
+
return kama(src, len);
|
|
41569
|
+
case "LSMA":
|
|
41570
|
+
return linreg(src, len, opts.lsmaOffset ?? 0);
|
|
41571
|
+
case "McGinley":
|
|
41572
|
+
return mcginley(src, len);
|
|
41573
|
+
case "Hamming":
|
|
41574
|
+
return hamming(src, len);
|
|
41575
|
+
default:
|
|
41576
|
+
return sma(src, len);
|
|
41577
|
+
}
|
|
41578
|
+
}
|
|
41579
|
+
function slopeColors(values, bull, bear, flat) {
|
|
41580
|
+
return values.map((x, i) => {
|
|
41581
|
+
if (!Number.isFinite(x)) return null;
|
|
41582
|
+
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
41583
|
+
if (!Number.isFinite(prev3)) return flat;
|
|
41584
|
+
return x > prev3 ? bull : x < prev3 ? bear : flat;
|
|
41585
|
+
});
|
|
41586
|
+
}
|
|
41587
|
+
function directionInputs() {
|
|
41588
|
+
return [
|
|
41589
|
+
boolInput("trendColor", "Slope Trend Coloring", true, STYLE, "Colors the line by its slope \u2014 bullish while it rises, bearish while it falls. Disable to draw it in the neutral color."),
|
|
41590
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
41591
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
41592
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
41593
|
+
widthInput()
|
|
41594
|
+
];
|
|
41595
|
+
}
|
|
41596
|
+
function slopeColoredPlot(key, title, values, inputs) {
|
|
41597
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
41598
|
+
const colored = bool(inputs, "trendColor", true);
|
|
41599
|
+
return {
|
|
41600
|
+
key,
|
|
41601
|
+
title,
|
|
41602
|
+
values,
|
|
41603
|
+
color: neutral,
|
|
41604
|
+
width: num3(inputs, "lineWidth", 2),
|
|
41605
|
+
...colored ? { colors: slopeColors(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), neutral) } : {}
|
|
41606
|
+
};
|
|
41607
|
+
}
|
|
41608
|
+
var offsetInput = { key: "offset", title: "Offset", type: "int", defval: 0, min: -500, max: 500, step: 1, group: SETTINGS, tooltip: "Bars the plotted line is displaced by. Positive values push it to the right." };
|
|
41144
41609
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
41145
41610
|
return {
|
|
41146
41611
|
type,
|
|
41147
41612
|
title,
|
|
41148
41613
|
shortTitle,
|
|
41149
41614
|
overlay: true,
|
|
41150
|
-
inputs: [lengthInput(20), sourceInput(), offsetInput, colorInput()],
|
|
41615
|
+
inputs: [lengthInput(20, "length", "Length", 5e3, `Number of bars used in the ${shortTitle} calculation.`), sourceInput("Close", "source", "Source", `Price series the ${shortTitle} is calculated on.`), offsetInput, colorInput()],
|
|
41151
41616
|
compute: (bars, inputs) => {
|
|
41152
41617
|
const len = num3(inputs, "length", 20);
|
|
41153
41618
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
41154
41619
|
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
41155
41620
|
if (offset3 !== 0) values = shift3(values, offset3);
|
|
41156
|
-
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color",
|
|
41621
|
+
return { plots: [{ key: shortTitle.toLowerCase(), title: shortTitle, values, color: str3(inputs, "color", INFO), width: 2 }] };
|
|
41157
41622
|
}
|
|
41158
41623
|
};
|
|
41159
41624
|
}
|
|
@@ -41165,36 +41630,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41165
41630
|
shortTitle: "MA",
|
|
41166
41631
|
overlay: true,
|
|
41167
41632
|
inputs: [
|
|
41168
|
-
|
|
41169
|
-
lengthInput(20),
|
|
41170
|
-
sourceInput(),
|
|
41633
|
+
optionInput("maType", "MA 1 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 1."),
|
|
41634
|
+
lengthInput(20, "length", "MA 1 Length", 5e3, "Number of bars used in the MA 1 calculation."),
|
|
41635
|
+
sourceInput("Close", "source", "MA 1 Source", "Price series MA 1 is calculated on."),
|
|
41636
|
+
boolInput("showMa2", "Show MA 2", false, SETTINGS, "Displays the optional second moving average."),
|
|
41637
|
+
optionInput("ma2Type", "MA 2 Type", "SMA", MA_TYPES, "Moving average calculation used for MA 2."),
|
|
41638
|
+
lengthInput(50, "ma2Length", "MA 2 Length", 5e3, "Number of bars used in the MA 2 calculation."),
|
|
41639
|
+
sourceInput("Close", "ma2Source", "MA 2 Source", "Price series MA 2 is calculated on."),
|
|
41640
|
+
floatInput("almaOffset", "ALMA Offset", 0.85, 0, 1, 0.05, "ALMA type only. Values near 1 make the average more responsive, values near 0 make it smoother."),
|
|
41641
|
+
floatInput("almaSigma", "ALMA Sigma", 6, 0.1, 100, 0.5, "ALMA type only. Larger values widen the Gaussian window for a smoother average."),
|
|
41642
|
+
intInput("lsmaOffset", "LSMA Offset", 0, 0, 500, "LSMA type only. Bar offset applied to the linear regression value."),
|
|
41171
41643
|
offsetInput,
|
|
41172
|
-
colorInput()
|
|
41644
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
41645
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
41173
41646
|
],
|
|
41174
41647
|
compute: (bars, inputs) => {
|
|
41175
|
-
const
|
|
41176
|
-
|
|
41648
|
+
const opts = {
|
|
41649
|
+
almaOffset: num3(inputs, "almaOffset", 0.85),
|
|
41650
|
+
almaSigma: num3(inputs, "almaSigma", 6),
|
|
41651
|
+
lsmaOffset: Math.trunc(num3(inputs, "lsmaOffset", 0))
|
|
41652
|
+
};
|
|
41653
|
+
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
41654
|
+
const displace = (v) => offset3 === 0 ? v : shift3(v, offset3);
|
|
41177
41655
|
const kind = str3(inputs, "maType", "SMA");
|
|
41178
|
-
|
|
41179
|
-
|
|
41180
|
-
|
|
41181
|
-
|
|
41182
|
-
|
|
41183
|
-
|
|
41184
|
-
values = wma(src, len);
|
|
41185
|
-
break;
|
|
41186
|
-
case "RMA":
|
|
41187
|
-
values = rma(src, len);
|
|
41188
|
-
break;
|
|
41189
|
-
case "VWMA":
|
|
41190
|
-
values = vwma(src, volumes(bars), len);
|
|
41191
|
-
break;
|
|
41192
|
-
default:
|
|
41193
|
-
values = sma(src, len);
|
|
41656
|
+
const ma1 = displace(movingAverageOf(kind, sourceValues(bars, str3(inputs, "source", "Close")), bars, num3(inputs, "length", 20), opts));
|
|
41657
|
+
const plots = [{ key: "ma", title: kind, values: ma1, color: str3(inputs, "color", INFO), width: 2 }];
|
|
41658
|
+
if (bool(inputs, "showMa2", false)) {
|
|
41659
|
+
const kind2 = str3(inputs, "ma2Type", "SMA");
|
|
41660
|
+
const ma2 = displace(movingAverageOf(kind2, sourceValues(bars, str3(inputs, "ma2Source", "Close")), bars, num3(inputs, "ma2Length", 50), opts));
|
|
41661
|
+
plots.push({ key: "ma2", title: `${kind2} 2`, values: ma2, color: str3(inputs, "ma2Color", WARNING), width: 2 });
|
|
41194
41662
|
}
|
|
41195
|
-
|
|
41196
|
-
if (offset3 !== 0) values = shift3(values, offset3);
|
|
41197
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
41663
|
+
return { plots };
|
|
41198
41664
|
}
|
|
41199
41665
|
};
|
|
41200
41666
|
var smoothedMa = {
|
|
@@ -41202,17 +41668,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41202
41668
|
title: "Smoothed Moving Average",
|
|
41203
41669
|
shortTitle: "RMA",
|
|
41204
41670
|
overlay: true,
|
|
41205
|
-
inputs: [
|
|
41671
|
+
inputs: [
|
|
41672
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. Wilder computed his 1978 indicators on the close, but the recursion accepts any source."),
|
|
41673
|
+
lengthInput(14, "length", "RMA Length", 5e3, "Number of bars N in Wilder's smoothing: the line keeps (N \u2212 1)/N of its previous value and blends in 1/N of the new price. That alpha is smaller than an EMA's, so an N-period RMA responds like a (2N \u2212 1)-period EMA."),
|
|
41674
|
+
...directionInputs()
|
|
41675
|
+
],
|
|
41206
41676
|
compute: (bars, inputs) => ({
|
|
41207
|
-
plots: [
|
|
41208
|
-
{
|
|
41209
|
-
key: "rma",
|
|
41210
|
-
title: "RMA",
|
|
41211
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
41212
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
41213
|
-
width: 2
|
|
41214
|
-
}
|
|
41215
|
-
]
|
|
41677
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
41216
41678
|
})
|
|
41217
41679
|
};
|
|
41218
41680
|
var zlema = {
|
|
@@ -41220,61 +41682,86 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41220
41682
|
title: "Zero-Lag Exponential Moving Average",
|
|
41221
41683
|
shortTitle: "ZLEMA",
|
|
41222
41684
|
overlay: true,
|
|
41223
|
-
inputs: [
|
|
41685
|
+
inputs: [
|
|
41686
|
+
sourceInput("Close", "source", "Source", "Price series the average de-lags and then smooths."),
|
|
41687
|
+
lengthInput(21, "length", "ZLEMA Length", 5e3, "EMA length N. It sets both the smoothing (alpha = 2 / (N + 1)) and the correction: an EMA of length N lags by about (N \u2212 1)/2 bars, and the de-lagging step is sized to cancel exactly that."),
|
|
41688
|
+
...directionInputs()
|
|
41689
|
+
],
|
|
41224
41690
|
compute: (bars, inputs) => {
|
|
41225
|
-
const len = num3(inputs, "length",
|
|
41691
|
+
const len = num3(inputs, "length", 21);
|
|
41226
41692
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41227
|
-
const lag = Math.
|
|
41228
|
-
const
|
|
41229
|
-
|
|
41230
|
-
return { plots: [{ key: "zlema", title: "ZLEMA", values: ema(delagged, len), color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
41693
|
+
const lag = Math.round((len - 1) / 2);
|
|
41694
|
+
const delagged = zip(src, shift3(src, lag), (a, b) => 2 * a - b);
|
|
41695
|
+
return { plots: [slopeColoredPlot("zlema", "ZLEMA", ema(delagged, len), inputs)] };
|
|
41231
41696
|
}
|
|
41232
41697
|
};
|
|
41698
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
41699
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
41700
|
+
var REGIME = "Trend Regime";
|
|
41233
41701
|
var vidya = {
|
|
41234
41702
|
type: "vidya",
|
|
41235
41703
|
title: "Variable Index Dynamic Average",
|
|
41236
41704
|
shortTitle: "VIDYA",
|
|
41237
41705
|
overlay: true,
|
|
41238
|
-
inputs: [
|
|
41706
|
+
inputs: [
|
|
41707
|
+
sourceInput("Close", "source", "Source", "Price series the average smooths. VIDYA is classically computed on the close."),
|
|
41708
|
+
lengthInput(12, "length", "VIDYA Length", 5e3, "EMA-equivalent length N of the base smoothing constant, alpha = 2 / (N + 1). The adaptive scaler rescales this base speed bar by bar."),
|
|
41709
|
+
optionInput("scaler", "Adaptive Scaler", MOMENTUM_SCALER, [MOMENTUM_SCALER, VOLATILITY_SCALER], "Bar-by-bar measure that rescales the smoothing constant. Momentum divides the absolute Chande Momentum Oscillator by 100; Volatility is Chande's original short-term over reference standard deviation."),
|
|
41710
|
+
intInput("scalerLength", "Scaler Lookback", 9, 1, 5e3, "Lookback of the adaptive scaler \u2014 the CMO period in momentum mode, or the short-term standard deviation window in volatility mode."),
|
|
41711
|
+
intInput("refLength", "Reference StDev Length", 30, 2, 5e3, "Volatility mode only \u2014 window of the reference standard deviation the short-term reading is divided by."),
|
|
41712
|
+
{ ...floatInput("flatThreshold", "Flat Threshold", 0.05, 0, 10, 0.01, "One-bar change of VIDYA, as a fraction of ATR, below which the line reads as flat."), group: REGIME },
|
|
41713
|
+
{ ...intInput("atrLength", "ATR Normalization Length", 14, 1, 5e3, "ATR window used to normalize VIDYA's one-bar slope, so the flat threshold behaves consistently across symbols and timeframes."), group: REGIME },
|
|
41714
|
+
boolInput("trendColor", "Trend Regime Coloring", true, STYLE, "Colors VIDYA by its regime read \u2014 bullish while sloping up beyond the flat threshold, bearish while sloping down, flat inside congestion."),
|
|
41715
|
+
boolInput("adaptiveGradient", "Adaptive Gradient", true, STYLE, "Blends the trend color toward the flat color according to the adaptive scaler, so the line pales as momentum dies."),
|
|
41716
|
+
colorInput(BULLISH, "Bullish", "bullColor"),
|
|
41717
|
+
colorInput(BEARISH, "Bearish", "bearColor"),
|
|
41718
|
+
colorInput(NEUTRAL, "Flat", "flatColor"),
|
|
41719
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor"),
|
|
41720
|
+
widthInput()
|
|
41721
|
+
],
|
|
41239
41722
|
compute: (bars, inputs) => {
|
|
41240
|
-
const len = num3(inputs, "length",
|
|
41241
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
41723
|
+
const len = num3(inputs, "length", 12);
|
|
41242
41724
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41243
|
-
const
|
|
41244
|
-
const
|
|
41245
|
-
const
|
|
41246
|
-
|
|
41247
|
-
|
|
41248
|
-
|
|
41249
|
-
|
|
41250
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
41251
|
-
const x = d[k];
|
|
41252
|
-
if (!Number.isFinite(x)) {
|
|
41253
|
-
ok = false;
|
|
41254
|
-
break;
|
|
41255
|
-
}
|
|
41256
|
-
if (x > 0) u += x;
|
|
41257
|
-
else w -= x;
|
|
41258
|
-
}
|
|
41259
|
-
if (ok) {
|
|
41260
|
-
su[i] = u;
|
|
41261
|
-
sd[i] = w;
|
|
41262
|
-
}
|
|
41263
|
-
}
|
|
41264
|
-
const alpha = 2 / (len + 1);
|
|
41725
|
+
const scalerLen = num3(inputs, "scalerLength", 9);
|
|
41726
|
+
const momentum = map(cmo(src, scalerLen), (x) => Math.abs(x) / 100);
|
|
41727
|
+
const shortDev = stdev(src, scalerLen);
|
|
41728
|
+
const refDev = stdev(src, num3(inputs, "refLength", 30));
|
|
41729
|
+
const volatility = zip(shortDev, refDev, (s, r) => r > 0 ? s / r : 0);
|
|
41730
|
+
const scale = str3(inputs, "scaler", MOMENTUM_SCALER) === VOLATILITY_SCALER ? volatility : momentum;
|
|
41731
|
+
const alphaBase = 2 / (len + 1);
|
|
41265
41732
|
const out = new Array(src.length).fill(Number.NaN);
|
|
41266
41733
|
let prev3 = Number.NaN;
|
|
41267
41734
|
for (let i = 0; i < src.length; i++) {
|
|
41268
41735
|
const x = src[i];
|
|
41269
|
-
|
|
41270
|
-
const
|
|
41271
|
-
if (!Number.isFinite(
|
|
41272
|
-
|
|
41273
|
-
|
|
41274
|
-
|
|
41736
|
+
if (!Number.isFinite(x)) continue;
|
|
41737
|
+
const k = scale[i];
|
|
41738
|
+
if (!Number.isFinite(prev3) || !Number.isFinite(k)) prev3 = x;
|
|
41739
|
+
else {
|
|
41740
|
+
const alpha = Math.min(alphaBase * k, 1);
|
|
41741
|
+
prev3 = alpha * x + (1 - alpha) * prev3;
|
|
41742
|
+
}
|
|
41275
41743
|
out[i] = prev3;
|
|
41276
41744
|
}
|
|
41277
|
-
|
|
41745
|
+
const neutral = str3(inputs, "neutralColor", NEUTRAL);
|
|
41746
|
+
if (!bool(inputs, "trendColor", true)) {
|
|
41747
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: neutral, width: num3(inputs, "lineWidth", 2) }] };
|
|
41748
|
+
}
|
|
41749
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41750
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41751
|
+
const flat = str3(inputs, "flatColor", NEUTRAL);
|
|
41752
|
+
const threshold = num3(inputs, "flatThreshold", 0.05);
|
|
41753
|
+
const range = atr(bars, num3(inputs, "atrLength", 14));
|
|
41754
|
+
const fade = bool(inputs, "adaptiveGradient", true);
|
|
41755
|
+
const colors = out.map((x, i) => {
|
|
41756
|
+
if (!Number.isFinite(x)) return null;
|
|
41757
|
+
const r = range[i];
|
|
41758
|
+
const slope = i > 0 && Number.isFinite(out[i - 1]) ? x - out[i - 1] : 0;
|
|
41759
|
+
const normalized = Number.isFinite(r) && r !== 0 ? slope / r : 0;
|
|
41760
|
+
const ink = normalized > threshold ? bull : normalized < -threshold ? bear : null;
|
|
41761
|
+
if (ink == null) return flat;
|
|
41762
|
+
return fade ? gradient(Math.min(scale[i] ?? 0, 1), 0, 1, flat, ink) : ink;
|
|
41763
|
+
});
|
|
41764
|
+
return { plots: [{ key: "vidya", title: "VIDYA", values: out, color: flat, width: num3(inputs, "lineWidth", 2), colors }] };
|
|
41278
41765
|
}
|
|
41279
41766
|
};
|
|
41280
41767
|
var maEnvelope = {
|
|
@@ -41283,115 +41770,218 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41283
41770
|
shortTitle: "MA Env",
|
|
41284
41771
|
overlay: true,
|
|
41285
41772
|
inputs: [
|
|
41286
|
-
|
|
41287
|
-
lengthInput(20),
|
|
41288
|
-
sourceInput(),
|
|
41289
|
-
|
|
41290
|
-
|
|
41773
|
+
optionInput("maType", "MA Type", "SMA", ENVELOPE_MA_TYPES, "Moving average type used as the envelope basis."),
|
|
41774
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars in the basis moving average."), min: 2 },
|
|
41775
|
+
sourceInput("Close", "source", "Source", "Price series the basis moving average is calculated on."),
|
|
41776
|
+
floatInput("percent", "Percent", 2.5, 0, 50, 0.1, "Envelope offset as a percentage of the basis. Upper = basis \xD7 (1 + percent / 100), lower = basis \xD7 (1 \u2212 percent / 100)."),
|
|
41777
|
+
...channelStyleInputs()
|
|
41291
41778
|
],
|
|
41292
41779
|
compute: (bars, inputs) => {
|
|
41293
|
-
const len = num3(inputs, "length", 20);
|
|
41294
41780
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41295
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
41781
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
41296
41782
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
41297
|
-
const ink = str3(inputs, "
|
|
41298
|
-
return
|
|
41299
|
-
plots: [
|
|
41300
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
41301
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
41302
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
41303
|
-
],
|
|
41304
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
41305
|
-
};
|
|
41783
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
41784
|
+
return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
|
|
41306
41785
|
}
|
|
41307
41786
|
};
|
|
41787
|
+
var LINREG_CURVE = "Curve";
|
|
41788
|
+
var LINREG_SLOPE = "Slope";
|
|
41308
41789
|
var linearRegression = {
|
|
41309
41790
|
type: "linear-regression",
|
|
41310
41791
|
title: "Linear Regression Curve",
|
|
41311
41792
|
shortTitle: "LinReg",
|
|
41312
|
-
|
|
41313
|
-
|
|
41314
|
-
|
|
41315
|
-
plots
|
|
41316
|
-
|
|
41317
|
-
|
|
41318
|
-
|
|
41319
|
-
|
|
41320
|
-
|
|
41321
|
-
|
|
41322
|
-
|
|
41323
|
-
|
|
41324
|
-
|
|
41793
|
+
// Slope lives in its own pane; Curve force-overlays itself onto the price candles.
|
|
41794
|
+
overlay: false,
|
|
41795
|
+
inputs: [
|
|
41796
|
+
optionInput("mode", "Mode", LINREG_CURVE, [LINREG_CURVE, LINREG_SLOPE], "Curve plots the regression value on the price chart; Slope plots the per-bar slope of the fit in the pane."),
|
|
41797
|
+
{ ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
|
|
41798
|
+
sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
|
|
41799
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
|
|
41800
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
|
|
41801
|
+
colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
|
|
41802
|
+
],
|
|
41803
|
+
compute: (bars, inputs) => {
|
|
41804
|
+
const len = num3(inputs, "length", 100);
|
|
41805
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41806
|
+
const curve = linreg(src, len, 0);
|
|
41807
|
+
const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
|
|
41808
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41809
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41810
|
+
const flat = str3(inputs, "lineColor", NEUTRAL);
|
|
41811
|
+
const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
|
|
41812
|
+
if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
|
|
41813
|
+
return {
|
|
41814
|
+
plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
|
|
41815
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
|
|
41816
|
+
};
|
|
41817
|
+
}
|
|
41818
|
+
return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
|
|
41819
|
+
}
|
|
41325
41820
|
};
|
|
41326
41821
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
41327
41822
|
|
|
41328
41823
|
// src/core/native-indicators/classics/bands.ts
|
|
41824
|
+
var JAW_COLOR = "#ab47bc";
|
|
41825
|
+
var TEETH_COLOR = WARNING;
|
|
41826
|
+
var LIPS_COLOR = "#ffd700";
|
|
41329
41827
|
function bollinger(bars, inputs) {
|
|
41330
41828
|
const len = num3(inputs, "length", 20);
|
|
41331
41829
|
const mult = num3(inputs, "mult", 2);
|
|
41332
41830
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41333
41831
|
const basis = sma(src, len);
|
|
41334
41832
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
41335
|
-
return { basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
41833
|
+
return { src, basis, upper: zip(basis, dev, (b, d) => b + d), lower: zip(basis, dev, (b, d) => b - d) };
|
|
41834
|
+
}
|
|
41835
|
+
function bollingerInputs(multMin, multStep) {
|
|
41836
|
+
return [
|
|
41837
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Number of bars used for the Bollinger Bands basis (a simple moving average) and for the standard deviation."), min: 2 },
|
|
41838
|
+
sourceInput("Close", "source", "Source", "Price series the bands are computed from."),
|
|
41839
|
+
floatInput("mult", "Multiplier", 2, multMin, 50, multStep, "Standard deviation multiplier for the upper and lower bands.")
|
|
41840
|
+
];
|
|
41336
41841
|
}
|
|
41337
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
41338
41842
|
var bollingerBands = {
|
|
41339
41843
|
type: "bollinger-bands",
|
|
41340
41844
|
title: "Bollinger Bands",
|
|
41341
41845
|
shortTitle: "BB",
|
|
41342
41846
|
overlay: true,
|
|
41343
|
-
inputs: [
|
|
41847
|
+
inputs: [
|
|
41848
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to calculate the basis SMA and the standard deviation."),
|
|
41849
|
+
sourceInput("Close", "source", "Source", "Price series the bands are calculated on."),
|
|
41850
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "Number of standard deviations added above and below the basis to form the bands."),
|
|
41851
|
+
colorInput(WARNING, "Basis", "basisColor", "Color of the basis (middle band) moving average."),
|
|
41852
|
+
colorInput(INFO, "Bands", "color", "Color of the upper and lower bands."),
|
|
41853
|
+
colorInput(transp(INFO, 92), "Fill", "fillColor", "Fill color of the area between the upper and lower bands.")
|
|
41854
|
+
],
|
|
41344
41855
|
compute: (bars, inputs) => {
|
|
41345
41856
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
41346
|
-
const ink = str3(inputs, "color",
|
|
41857
|
+
const ink = str3(inputs, "color", INFO);
|
|
41347
41858
|
return {
|
|
41348
41859
|
plots: [
|
|
41349
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
41860
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
41350
41861
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
41351
41862
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
41352
41863
|
],
|
|
41353
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
41864
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
41354
41865
|
};
|
|
41355
41866
|
}
|
|
41356
41867
|
};
|
|
41868
|
+
var PERCENT_B_MID = 0.5;
|
|
41869
|
+
var THRESHOLDS = "Thresholds";
|
|
41357
41870
|
var percentB = {
|
|
41358
41871
|
type: "percent-b",
|
|
41359
41872
|
title: "Bollinger Bands %B",
|
|
41360
41873
|
shortTitle: "%B",
|
|
41361
41874
|
overlay: false,
|
|
41362
|
-
inputs: [
|
|
41875
|
+
inputs: [
|
|
41876
|
+
...bollingerInputs(1e-3, 0.25),
|
|
41877
|
+
{ ...floatInput("overbought", "Overbought", 1, -10, 10, 0.05, "Level treated as overbought. 1 is the upper band itself, so readings above the default mean price sits outside the bands."), group: THRESHOLDS },
|
|
41878
|
+
{ ...floatInput("oversold", "Oversold", 0, -10, 10, 0.05, "Level treated as oversold. 0 is the lower band itself, so readings below the default mean price sits outside the bands."), group: THRESHOLDS },
|
|
41879
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of %B above the 0.5 midline, where price trades in the upper half of the bands."),
|
|
41880
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of %B below the 0.5 midline, where price trades in the lower half of the bands."),
|
|
41881
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between %B and the 0.5 midline, fully transparent at the midline and more opaque at the oscillator value."),
|
|
41882
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed overbought/oversold levels and of the dotted midline.")
|
|
41883
|
+
],
|
|
41363
41884
|
compute: (bars, inputs) => {
|
|
41364
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
41365
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41885
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
41366
41886
|
const values = src.map((x, i) => {
|
|
41367
41887
|
const u = upper[i];
|
|
41368
41888
|
const l = lower[i];
|
|
41369
41889
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
41370
41890
|
});
|
|
41371
|
-
|
|
41372
|
-
|
|
41373
|
-
|
|
41374
|
-
|
|
41375
|
-
|
|
41376
|
-
|
|
41377
|
-
|
|
41378
|
-
|
|
41891
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
41892
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
41893
|
+
const levels = str3(inputs, "levelsColor", NEUTRAL);
|
|
41894
|
+
const colors = values.map((x) => Number.isFinite(x) ? x > PERCENT_B_MID ? bull : bear : null);
|
|
41895
|
+
const plots = [
|
|
41896
|
+
{ key: "pb", title: "%B", values, color: bear, colors },
|
|
41897
|
+
{ key: "mid", title: "Midline", values: values.map((x) => Number.isFinite(x) ? PERCENT_B_MID : Number.NaN), color: levels, display: { pane: false, legend: false, dataWindow: false } }
|
|
41898
|
+
];
|
|
41899
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
41900
|
+
{
|
|
41901
|
+
key: "pbFill",
|
|
41902
|
+
from: "pb",
|
|
41903
|
+
to: "mid",
|
|
41904
|
+
color: transp(NEUTRAL, 100),
|
|
41905
|
+
// Opaque at the oscillator, invisible where it meets the midline.
|
|
41906
|
+
gradient: values.map((x) => {
|
|
41907
|
+
if (!Number.isFinite(x)) return null;
|
|
41908
|
+
const up = x > PERCENT_B_MID;
|
|
41909
|
+
const ink = up ? bull : bear;
|
|
41910
|
+
return {
|
|
41911
|
+
topValue: Math.max(x, PERCENT_B_MID),
|
|
41912
|
+
bottomValue: Math.min(x, PERCENT_B_MID),
|
|
41913
|
+
topColor: up ? transp(ink, 50) : transp(ink, 100),
|
|
41914
|
+
bottomColor: up ? transp(ink, 100) : transp(ink, 50)
|
|
41915
|
+
};
|
|
41916
|
+
})
|
|
41917
|
+
}
|
|
41918
|
+
] : [];
|
|
41919
|
+
return {
|
|
41920
|
+
plots,
|
|
41921
|
+
bands,
|
|
41922
|
+
levels: [
|
|
41923
|
+
{ key: "overbought", price: num3(inputs, "overbought", 1), color: levels, lineStyle: "dashed", title: "Overbought" },
|
|
41924
|
+
{ key: "oversold", price: num3(inputs, "oversold", 0), color: levels, lineStyle: "dashed", title: "Oversold" },
|
|
41925
|
+
{ key: "midline", price: PERCENT_B_MID, color: transp(levels, 50), lineStyle: "dotted", title: "Midline" }
|
|
41926
|
+
]
|
|
41927
|
+
};
|
|
41379
41928
|
}
|
|
41380
41929
|
};
|
|
41930
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
41381
41931
|
var bandwidth = {
|
|
41382
41932
|
type: "bandwidth",
|
|
41383
41933
|
title: "Bollinger Bands Width",
|
|
41384
41934
|
shortTitle: "BandWidth",
|
|
41385
41935
|
overlay: false,
|
|
41386
|
-
inputs: [
|
|
41936
|
+
inputs: [
|
|
41937
|
+
...bollingerInputs(1e-3, 0.25),
|
|
41938
|
+
{ ...intInput("lookback", "Reference Lookback", 125, 2, 5e3, "Window used to locate the lowest (Squeeze) and highest (Bulge) BandWidth references."), group: SQUEEZE_BULGE },
|
|
41939
|
+
boolInput("showSqueeze", "Show Squeeze Level", true, SQUEEZE_BULGE, "Plots the lowest BandWidth of the reference lookback \u2014 unusually compressed volatility, a condition rather than a directional signal."),
|
|
41940
|
+
boolInput("showBulge", "Show Bulge Level", true, SQUEEZE_BULGE, "Plots the highest BandWidth of the reference lookback \u2014 extreme width that is unlikely to be sustained."),
|
|
41941
|
+
colorInput(NEUTRAL, "BandWidth", "color", "Color of the BandWidth line. At its Squeeze reference the line takes the Squeeze color, at its Bulge reference the Bulge color."),
|
|
41942
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between zero and BandWidth with a vertical gradient, fully transparent at zero."),
|
|
41943
|
+
colorInput(WARNING, "Squeeze Level", "squeezeColor", "Color of the lowest-BandWidth reference line."),
|
|
41944
|
+
colorInput(BEARISH, "Bulge Level", "bulgeColor", "Color of the highest-BandWidth reference line.")
|
|
41945
|
+
],
|
|
41387
41946
|
compute: (bars, inputs) => {
|
|
41388
41947
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
41389
41948
|
const values = basis.map((b, i) => {
|
|
41390
41949
|
const u = upper[i];
|
|
41391
41950
|
const l = lower[i];
|
|
41392
|
-
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b
|
|
41951
|
+
return Number.isFinite(b) && b !== 0 && Number.isFinite(u) && Number.isFinite(l) ? (u - l) / b : Number.NaN;
|
|
41393
41952
|
});
|
|
41394
|
-
|
|
41953
|
+
const lookback = num3(inputs, "lookback", 125);
|
|
41954
|
+
const squeezeLevel = lowest(values, lookback);
|
|
41955
|
+
const bulgeLevel = highest(values, lookback);
|
|
41956
|
+
const base = str3(inputs, "color", NEUTRAL);
|
|
41957
|
+
const squeezeInk = str3(inputs, "squeezeColor", WARNING);
|
|
41958
|
+
const bulgeInk = str3(inputs, "bulgeColor", BEARISH);
|
|
41959
|
+
const colors = values.map((x, i) => {
|
|
41960
|
+
if (!Number.isFinite(x)) return null;
|
|
41961
|
+
if (x <= squeezeLevel[i]) return squeezeInk;
|
|
41962
|
+
if (x >= bulgeLevel[i]) return bulgeInk;
|
|
41963
|
+
return base;
|
|
41964
|
+
});
|
|
41965
|
+
const plots = [
|
|
41966
|
+
{ key: "bbw", title: "BandWidth", values, color: base, colors },
|
|
41967
|
+
{ key: "zero", title: "Zero Line", values: values.map((x) => Number.isFinite(x) ? 0 : Number.NaN), color: transp(NEUTRAL, 90) }
|
|
41968
|
+
];
|
|
41969
|
+
if (bool(inputs, "showSqueeze", true)) plots.push({ key: "squeeze", title: "Squeeze Level", values: squeezeLevel, color: squeezeInk });
|
|
41970
|
+
if (bool(inputs, "showBulge", true)) plots.push({ key: "bulge", title: "Bulge Level", values: bulgeLevel, color: bulgeInk });
|
|
41971
|
+
const bands = bool(inputs, "gradientFill", true) ? [
|
|
41972
|
+
{
|
|
41973
|
+
key: "bbwFill",
|
|
41974
|
+
from: "bbw",
|
|
41975
|
+
to: "zero",
|
|
41976
|
+
color: transp(NEUTRAL, 100),
|
|
41977
|
+
gradient: values.map((x, i) => {
|
|
41978
|
+
if (!Number.isFinite(x)) return null;
|
|
41979
|
+
const ink = colors[i] ?? base;
|
|
41980
|
+
return { topValue: x, bottomValue: 0, topColor: transp(ink, 50), bottomColor: transp(ink, 100) };
|
|
41981
|
+
})
|
|
41982
|
+
}
|
|
41983
|
+
] : [];
|
|
41984
|
+
return { plots, bands };
|
|
41395
41985
|
}
|
|
41396
41986
|
};
|
|
41397
41987
|
var keltner = {
|
|
@@ -41400,26 +41990,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41400
41990
|
shortTitle: "KC",
|
|
41401
41991
|
overlay: true,
|
|
41402
41992
|
inputs: [
|
|
41403
|
-
lengthInput(20),
|
|
41404
|
-
|
|
41405
|
-
|
|
41406
|
-
sourceInput(),
|
|
41407
|
-
|
|
41993
|
+
lengthInput(20, "length", "EMA Length", 5e3, "Number of bars used to compute the exponential moving average basis line."),
|
|
41994
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Average True Range (Wilder smoothing)."),
|
|
41995
|
+
floatInput("mult", "Multiplier", 2, 0, 50, 0.1, "ATR multiple used to offset the upper and lower bands from the basis line."),
|
|
41996
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the basis line."),
|
|
41997
|
+
...channelStyleInputs("Basis Color")
|
|
41408
41998
|
],
|
|
41409
41999
|
compute: (bars, inputs) => {
|
|
41410
|
-
const
|
|
41411
|
-
const
|
|
41412
|
-
const
|
|
41413
|
-
|
|
41414
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
41415
|
-
return {
|
|
41416
|
-
plots: [
|
|
41417
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
41418
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
41419
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
41420
|
-
],
|
|
41421
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
41422
|
-
};
|
|
42000
|
+
const basis = ema(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
42001
|
+
const range = map(atr(bars, num3(inputs, "atrLength", 10)), (x) => x * num3(inputs, "mult", 2));
|
|
42002
|
+
const ink = { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "basisColor", NEUTRAL) };
|
|
42003
|
+
return directionalChannel(bars, basis, zip(basis, range, (b, r) => b + r), zip(basis, range, (b, r) => b - r), ink, "kc");
|
|
41423
42004
|
}
|
|
41424
42005
|
};
|
|
41425
42006
|
var donchian = {
|
|
@@ -41427,19 +42008,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41427
42008
|
title: "Donchian Channels",
|
|
41428
42009
|
shortTitle: "DC",
|
|
41429
42010
|
overlay: true,
|
|
41430
|
-
inputs: [
|
|
42011
|
+
inputs: [
|
|
42012
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length. The upper and lower channels are the highest high and lowest low over this many bars."),
|
|
42013
|
+
colorInput(BEARISH, "Upper", "upperColor", "Color of the upper channel line."),
|
|
42014
|
+
colorInput(NEUTRAL, "Middle", "middleColor", "Color of the channel midline."),
|
|
42015
|
+
colorInput(BULLISH, "Lower", "lowerColor", "Color of the lower channel line."),
|
|
42016
|
+
colorInput(transp(NEUTRAL, 92), "Fill", "fillColor", "Color of the channel interior fill.")
|
|
42017
|
+
],
|
|
41431
42018
|
compute: (bars, inputs) => {
|
|
41432
42019
|
const len = num3(inputs, "length", 20);
|
|
41433
42020
|
const upper = highest(highs(bars), len);
|
|
41434
42021
|
const lower = lowest(lows(bars), len);
|
|
41435
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
41436
42022
|
return {
|
|
41437
42023
|
plots: [
|
|
41438
|
-
{ key: "
|
|
41439
|
-
{ key: "
|
|
41440
|
-
{ key: "lower", title: "Lower", values: lower, color:
|
|
42024
|
+
{ key: "upper", title: "Upper", values: upper, color: str3(inputs, "upperColor", BEARISH) },
|
|
42025
|
+
{ key: "middle", title: "Middle", kind: "circles", values: zip(upper, lower, (u, l) => (u + l) / 2), color: str3(inputs, "middleColor", NEUTRAL) },
|
|
42026
|
+
{ key: "lower", title: "Lower", values: lower, color: str3(inputs, "lowerColor", BULLISH) }
|
|
41441
42027
|
],
|
|
41442
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
42028
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
41443
42029
|
};
|
|
41444
42030
|
}
|
|
41445
42031
|
};
|
|
@@ -41448,16 +42034,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41448
42034
|
title: "Chandelier Exit",
|
|
41449
42035
|
shortTitle: "CE",
|
|
41450
42036
|
overlay: true,
|
|
41451
|
-
inputs: [
|
|
42037
|
+
inputs: [
|
|
42038
|
+
lengthInput(22, "length", "Length", 5e3, "Lookback used for both the highest high / lowest low extremes and the ATR."),
|
|
42039
|
+
floatInput("mult", "ATR Multiplier", 3, 0, 50, 0.1, "Multiple of ATR subtracted from the highest high (long stop) and added to the lowest low (short stop)."),
|
|
42040
|
+
boolInput("ratchet", "Ratchet Stops", false, void 0, "Prevents the long stop from falling and the short stop from rising, resetting once close crosses through the stop."),
|
|
42041
|
+
colorInput(BULLISH, "Long Stop", "longColor", "Color of the long trailing stop line."),
|
|
42042
|
+
colorInput(BEARISH, "Short Stop", "shortColor", "Color of the short trailing stop line.")
|
|
42043
|
+
],
|
|
41452
42044
|
compute: (bars, inputs) => {
|
|
41453
42045
|
const len = num3(inputs, "length", 22);
|
|
41454
42046
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
41455
|
-
const
|
|
41456
|
-
const
|
|
42047
|
+
const basicLong = zip(highest(highs(bars), len), range, (h, r) => h - r);
|
|
42048
|
+
const basicShort = zip(lowest(lows(bars), len), range, (l, r) => l + r);
|
|
42049
|
+
let long = basicLong;
|
|
42050
|
+
let short = basicShort;
|
|
42051
|
+
if (bool(inputs, "ratchet", false)) {
|
|
42052
|
+
long = new Array(bars.length).fill(Number.NaN);
|
|
42053
|
+
short = new Array(bars.length).fill(Number.NaN);
|
|
42054
|
+
for (let i = 0; i < bars.length; i++) {
|
|
42055
|
+
const bl = basicLong[i];
|
|
42056
|
+
const bs = basicShort[i];
|
|
42057
|
+
const prevClose = i > 0 ? bars[i - 1].close : bars[i].close;
|
|
42058
|
+
const prevLong = i > 0 && Number.isFinite(long[i - 1]) ? long[i - 1] : bl;
|
|
42059
|
+
const prevShort = i > 0 && Number.isFinite(short[i - 1]) ? short[i - 1] : bs;
|
|
42060
|
+
long[i] = Number.isFinite(bl) ? prevClose > prevLong ? Math.max(bl, prevLong) : bl : Number.NaN;
|
|
42061
|
+
short[i] = Number.isFinite(bs) ? prevClose < prevShort ? Math.min(bs, prevShort) : bs : Number.NaN;
|
|
42062
|
+
}
|
|
42063
|
+
}
|
|
41457
42064
|
return {
|
|
41458
42065
|
plots: [
|
|
41459
|
-
{ key: "long", title: "Long
|
|
41460
|
-
{ key: "short", title: "Short
|
|
42066
|
+
{ key: "long", title: "Long Stop", values: long, color: str3(inputs, "longColor", BULLISH) },
|
|
42067
|
+
{ key: "short", title: "Short Stop", values: short, color: str3(inputs, "shortColor", BEARISH) }
|
|
41461
42068
|
]
|
|
41462
42069
|
};
|
|
41463
42070
|
}
|
|
@@ -41468,21 +42075,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41468
42075
|
shortTitle: "CKS",
|
|
41469
42076
|
overlay: true,
|
|
41470
42077
|
inputs: [
|
|
41471
|
-
|
|
41472
|
-
|
|
41473
|
-
|
|
42078
|
+
intInput("p", "ATR Length (P)", 10, 1, 5e3, "Lookback used for both the ATR and the highest high / lowest low price extremes."),
|
|
42079
|
+
floatInput("x", "ATR Multiplier (X)", 1, 0, 50, 0.1, "Multiplier applied to the ATR to offset the preliminary stops from the price extremes."),
|
|
42080
|
+
intInput("q", "Stop Length (Q)", 9, 1, 5e3, "Lookback over which the preliminary stops are extended into the final stop levels."),
|
|
42081
|
+
colorInput(BEARISH, "Stop Short", "shortColor", "Color of the protective stop line for short positions."),
|
|
42082
|
+
colorInput(BULLISH, "Stop Long", "longColor", "Color of the protective stop line for long positions.")
|
|
41474
42083
|
],
|
|
41475
42084
|
compute: (bars, inputs) => {
|
|
41476
42085
|
const p = num3(inputs, "p", 10);
|
|
41477
|
-
const x = num3(inputs, "x", 1);
|
|
41478
42086
|
const q = num3(inputs, "q", 9);
|
|
41479
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
42087
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
41480
42088
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
41481
42089
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
41482
42090
|
return {
|
|
41483
42091
|
plots: [
|
|
41484
|
-
{ key: "stopShort", title: "Stop
|
|
41485
|
-
{ key: "stopLong", title: "Stop
|
|
42092
|
+
{ key: "stopShort", title: "Stop Short", values: highest(firstHigh, q), color: str3(inputs, "shortColor", BEARISH) },
|
|
42093
|
+
{ key: "stopLong", title: "Stop Long", values: lowest(firstLow, q), color: str3(inputs, "longColor", BULLISH) }
|
|
41486
42094
|
]
|
|
41487
42095
|
};
|
|
41488
42096
|
}
|
|
@@ -41493,36 +42101,42 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41493
42101
|
shortTitle: "SuperTrend",
|
|
41494
42102
|
overlay: true,
|
|
41495
42103
|
inputs: [
|
|
41496
|
-
|
|
41497
|
-
|
|
42104
|
+
intInput("atrLength", "ATR Length", 10, 1, 5e3, "Number of bars used to compute the Wilder ATR."),
|
|
42105
|
+
floatInput("mult", "Factor", 3, 0, 50, 0.1, "ATR multiplier setting how far the trailing stop sits from the bar midpoint."),
|
|
42106
|
+
colorInput(BULLISH, "Uptrend Color", "upColor", "Line color while the trend is up."),
|
|
42107
|
+
colorInput(BEARISH, "Downtrend Color", "downColor", "Line color while the trend is down.")
|
|
41498
42108
|
],
|
|
41499
42109
|
compute: (bars, inputs) => {
|
|
41500
|
-
const len = num3(inputs, "atrLength", 10);
|
|
41501
42110
|
const mult = num3(inputs, "mult", 3);
|
|
41502
|
-
const range = atr(bars,
|
|
42111
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
41503
42112
|
const n = bars.length;
|
|
41504
|
-
const
|
|
41505
|
-
const
|
|
41506
|
-
let
|
|
41507
|
-
let
|
|
41508
|
-
let trend = 1;
|
|
42113
|
+
const up = new Array(n).fill(Number.NaN);
|
|
42114
|
+
const down = new Array(n).fill(Number.NaN);
|
|
42115
|
+
let finalUpper = Number.NaN;
|
|
42116
|
+
let finalLower = Number.NaN;
|
|
42117
|
+
let trend = -1;
|
|
41509
42118
|
for (let i = 0; i < n; i++) {
|
|
41510
42119
|
const r = range[i];
|
|
41511
42120
|
if (!Number.isFinite(r)) continue;
|
|
41512
42121
|
const b = bars[i];
|
|
41513
42122
|
const mid = (b.high + b.low) / 2;
|
|
41514
|
-
const
|
|
41515
|
-
const
|
|
42123
|
+
const basicUpper = mid + mult * r;
|
|
42124
|
+
const basicLower = mid - mult * r;
|
|
42125
|
+
const prevUpper = Number.isFinite(finalUpper) ? finalUpper : basicUpper;
|
|
42126
|
+
const prevLower = Number.isFinite(finalLower) ? finalLower : basicLower;
|
|
41516
42127
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
41517
|
-
|
|
41518
|
-
|
|
41519
|
-
|
|
41520
|
-
|
|
41521
|
-
|
|
41522
|
-
st[i] = trend === 1 ? up : dn;
|
|
42128
|
+
finalUpper = prevClose <= prevUpper ? Math.min(basicUpper, prevUpper) : basicUpper;
|
|
42129
|
+
finalLower = prevClose >= prevLower ? Math.max(basicLower, prevLower) : basicLower;
|
|
42130
|
+
trend = b.close > finalUpper ? 1 : b.close < finalLower ? -1 : trend;
|
|
42131
|
+
if (trend === 1) up[i] = finalLower;
|
|
42132
|
+
else down[i] = finalUpper;
|
|
41523
42133
|
}
|
|
41524
|
-
|
|
41525
|
-
|
|
42134
|
+
return {
|
|
42135
|
+
plots: [
|
|
42136
|
+
{ key: "up", title: "Uptrend", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
42137
|
+
{ key: "down", title: "Downtrend", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
42138
|
+
]
|
|
42139
|
+
};
|
|
41526
42140
|
}
|
|
41527
42141
|
};
|
|
41528
42142
|
var alligator = {
|
|
@@ -41531,12 +42145,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41531
42145
|
shortTitle: "Alligator",
|
|
41532
42146
|
overlay: true,
|
|
41533
42147
|
inputs: [
|
|
41534
|
-
|
|
41535
|
-
|
|
41536
|
-
|
|
41537
|
-
|
|
41538
|
-
|
|
41539
|
-
|
|
42148
|
+
intInput("jawLength", "Jaw Length", 13, 1, 5e3, "Smoothing length of the Jaw SMMA of median price (hl2), 13 in the classic setup."),
|
|
42149
|
+
intInput("teethLength", "Teeth Length", 8, 1, 5e3, "Smoothing length of the Teeth SMMA of median price (hl2), 8 in the classic setup."),
|
|
42150
|
+
intInput("lipsLength", "Lips Length", 5, 1, 5e3, "Smoothing length of the Lips SMMA of median price (hl2), 5 in the classic setup."),
|
|
42151
|
+
intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Number of bars the Jaw plot is shifted into the future, 8 in the classic setup."),
|
|
42152
|
+
intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Number of bars the Teeth plot is shifted into the future, 5 in the classic setup."),
|
|
42153
|
+
intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Number of bars the Lips plot is shifted into the future, 3 in the classic setup."),
|
|
42154
|
+
colorInput(JAW_COLOR, "Jaw", "jawColor", "Color of the Jaw line."),
|
|
42155
|
+
colorInput(TEETH_COLOR, "Teeth", "teethColor", "Color of the Teeth line."),
|
|
42156
|
+
colorInput(LIPS_COLOR, "Lips", "lipsColor", "Color of the Lips line.")
|
|
41540
42157
|
],
|
|
41541
42158
|
compute: (bars, inputs) => {
|
|
41542
42159
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -41545,99 +42162,134 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41545
42162
|
const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
41546
42163
|
return {
|
|
41547
42164
|
plots: [
|
|
41548
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
41549
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
41550
|
-
{ key: "lips", title: "Lips", values: lips, color:
|
|
42165
|
+
{ key: "jaw", title: "Jaw", values: jaw, color: str3(inputs, "jawColor", JAW_COLOR) },
|
|
42166
|
+
{ key: "teeth", title: "Teeth", values: teeth, color: str3(inputs, "teethColor", TEETH_COLOR) },
|
|
42167
|
+
{ key: "lips", title: "Lips", values: lips, color: str3(inputs, "lipsColor", LIPS_COLOR) }
|
|
41551
42168
|
]
|
|
41552
42169
|
};
|
|
41553
42170
|
}
|
|
41554
42171
|
};
|
|
42172
|
+
var GATOR_LINES = "Alligator Lines";
|
|
42173
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
41555
42174
|
var gator = {
|
|
41556
42175
|
type: "gator-oscillator",
|
|
41557
42176
|
title: "Gator Oscillator",
|
|
41558
42177
|
shortTitle: "Gator Oscillator",
|
|
41559
42178
|
overlay: false,
|
|
41560
|
-
inputs: [
|
|
41561
|
-
|
|
41562
|
-
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
|
|
42179
|
+
inputs: [
|
|
42180
|
+
{ ...sourceInput("HL2", "source", "Source", "Price series fed to the three Alligator averages. Bill Williams' original recipe uses the median price (high + low) / 2."), group: GATOR_LINES },
|
|
42181
|
+
{ ...optionInput("maType", "Smoothing", SMMA_TYPE, [SMMA_TYPE, "SMA", "EMA"], "Moving average applied to the source for the jaw, teeth and lips. The original uses the smoothed (Wilder-style) moving average."), group: GATOR_LINES },
|
|
42182
|
+
{ ...intInput("jawLength", "Jaw Length", 13, 1, 500, "Averaging length of the jaw line."), group: GATOR_LINES },
|
|
42183
|
+
{ ...intInput("jawOffset", "Jaw Offset", 8, 0, 100, "Bars the jaw is displaced forward, 8 in the original recipe. The upper histogram measures the absolute spread between the displaced jaw and teeth."), group: GATOR_LINES },
|
|
42184
|
+
{ ...intInput("teethLength", "Teeth Length", 8, 1, 500, "Averaging length of the teeth line."), group: GATOR_LINES },
|
|
42185
|
+
{ ...intInput("teethOffset", "Teeth Offset", 5, 0, 100, "Bars the teeth are displaced forward, 5 in the original recipe. The teeth line enters both spreads."), group: GATOR_LINES },
|
|
42186
|
+
{ ...intInput("lipsLength", "Lips Length", 5, 1, 500, "Averaging length of the lips line."), group: GATOR_LINES },
|
|
42187
|
+
{ ...intInput("lipsOffset", "Lips Offset", 3, 0, 100, "Bars the lips are displaced forward, 3 in the original recipe. The lower histogram measures the negative absolute spread between the displaced teeth and lips."), group: GATOR_LINES },
|
|
42188
|
+
colorInput(BULLISH, "Expanding", "growColor", "Column color while a spread grew against its own previous bar \u2014 the averages are spreading apart."),
|
|
42189
|
+
colorInput(BEARISH, "Contracting", "shrinkColor", "Column color while a spread shrank \u2014 the averages are converging. Color encodes expansion, not trade direction."),
|
|
42190
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero line separating the two histograms.")
|
|
42191
|
+
],
|
|
42192
|
+
compute: (bars, inputs) => {
|
|
42193
|
+
const src = sourceValues(bars, str3(inputs, "source", "HL2"));
|
|
42194
|
+
const kind = str3(inputs, "maType", SMMA_TYPE);
|
|
42195
|
+
const smooth = (len) => kind === "SMA" ? sma(src, len) : kind === "EMA" ? ema(src, len) : rma(src, len);
|
|
42196
|
+
const jaw = shift3(smooth(num3(inputs, "jawLength", 13)), num3(inputs, "jawOffset", 8));
|
|
42197
|
+
const teeth = shift3(smooth(num3(inputs, "teethLength", 8)), num3(inputs, "teethOffset", 5));
|
|
42198
|
+
const lips = shift3(smooth(num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
41566
42199
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
41567
|
-
const lower = map(sub(teeth, lips),
|
|
41568
|
-
const
|
|
41569
|
-
|
|
41570
|
-
|
|
41571
|
-
|
|
41572
|
-
|
|
42200
|
+
const lower = map(sub(teeth, lips), Math.abs);
|
|
42201
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
42202
|
+
const shrink = str3(inputs, "shrinkColor", BEARISH);
|
|
42203
|
+
const expansionColors2 = (v) => {
|
|
42204
|
+
let growing = false;
|
|
42205
|
+
return v.map((x, i) => {
|
|
42206
|
+
if (!Number.isFinite(x)) return null;
|
|
42207
|
+
const prev3 = i > 0 ? v[i - 1] : Number.NaN;
|
|
42208
|
+
if (Number.isFinite(prev3)) growing = x > prev3 ? true : x < prev3 ? false : growing;
|
|
42209
|
+
return growing ? grow : shrink;
|
|
42210
|
+
});
|
|
42211
|
+
};
|
|
41573
42212
|
return {
|
|
41574
42213
|
plots: [
|
|
41575
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
41576
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
41577
|
-
]
|
|
42214
|
+
{ key: "upper", title: "Upper Histogram (Jaw - Teeth)", values: upper, kind: "columns", color: grow, colors: expansionColors2(upper), base: 0 },
|
|
42215
|
+
{ key: "lower", title: "Lower Histogram (Teeth - Lips)", values: map(lower, (x) => -x), kind: "columns", color: shrink, colors: expansionColors2(lower), base: 0 }
|
|
42216
|
+
],
|
|
42217
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "solid", title: "Zero Line" }]
|
|
41578
42218
|
};
|
|
41579
42219
|
}
|
|
41580
42220
|
};
|
|
41581
42221
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
41582
42222
|
|
|
41583
42223
|
// src/core/native-indicators/classics/oscillators.ts
|
|
41584
|
-
function
|
|
41585
|
-
|
|
41586
|
-
|
|
41587
|
-
|
|
42224
|
+
function thresholdInputs(obDefault, osDefault, min2 = 0, max2 = 100, obTitle = "Overbought Level", osTitle = "Oversold Level") {
|
|
42225
|
+
return [
|
|
42226
|
+
floatInput("overbought", obTitle, obDefault, min2, max2, 1, "Level above which the oscillator is considered overbought."),
|
|
42227
|
+
floatInput("oversold", osTitle, osDefault, min2, max2, 1, "Level below which the oscillator is considered oversold.")
|
|
41588
42228
|
];
|
|
41589
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
41590
|
-
return levels;
|
|
41591
|
-
}
|
|
41592
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
41593
|
-
function histColors(values) {
|
|
41594
|
-
return values.map((x, i) => {
|
|
41595
|
-
if (!Number.isFinite(x)) return null;
|
|
41596
|
-
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
41597
|
-
const rising = Number.isFinite(prev3) ? x >= prev3 : true;
|
|
41598
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
41599
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
41600
|
-
});
|
|
41601
42229
|
}
|
|
41602
42230
|
var rsiSpec = {
|
|
41603
42231
|
type: "rsi",
|
|
41604
42232
|
title: "Relative Strength Index",
|
|
41605
42233
|
shortTitle: "RSI",
|
|
41606
42234
|
overlay: false,
|
|
41607
|
-
inputs: [
|
|
41608
|
-
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41612
|
-
|
|
41613
|
-
|
|
41614
|
-
|
|
41615
|
-
|
|
41616
|
-
|
|
41617
|
-
|
|
41618
|
-
|
|
42235
|
+
inputs: [
|
|
42236
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used in the Wilder smoothing of average gains and losses."),
|
|
42237
|
+
sourceInput("Close", "source", "Source", "Price series the RSI is calculated on."),
|
|
42238
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
42239
|
+
colorInput(NEUTRAL, "RSI", "color", "Color of the RSI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
42240
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
42241
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
42242
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, midline and oversold levels."),
|
|
42243
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Color of the fill between the overbought and oversold levels.")
|
|
42244
|
+
],
|
|
42245
|
+
compute: (bars, inputs) => boundedOscillator({
|
|
42246
|
+
key: "rsi",
|
|
42247
|
+
title: "RSI",
|
|
42248
|
+
values: rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
42249
|
+
scale: { top: 100, bottom: 0 },
|
|
42250
|
+
overbought: num3(inputs, "overbought", 70),
|
|
42251
|
+
oversold: num3(inputs, "oversold", 30),
|
|
42252
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
42253
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
42254
|
+
width: 2,
|
|
42255
|
+
midline: 50,
|
|
42256
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90))
|
|
41619
42257
|
})
|
|
41620
42258
|
};
|
|
42259
|
+
function stochasticOutput(k, d, inputs) {
|
|
42260
|
+
const overbought = num3(inputs, "overbought", 80);
|
|
42261
|
+
const oversold = num3(inputs, "oversold", 20);
|
|
42262
|
+
return {
|
|
42263
|
+
plots: [
|
|
42264
|
+
{ key: "k", title: "%K", values: k, color: str3(inputs, "kColor", INFO) },
|
|
42265
|
+
{ key: "d", title: "%D", values: d, color: str3(inputs, "dColor", WARNING) },
|
|
42266
|
+
anchorPlot("obAnchor", "Overbought Anchor", k.length, overbought),
|
|
42267
|
+
anchorPlot("osAnchor", "Oversold Anchor", k.length, oversold)
|
|
42268
|
+
],
|
|
42269
|
+
bands: [{ key: "band", from: "obAnchor", to: "osAnchor", color: str3(inputs, "fillColor", transp(INFO, 90)) }],
|
|
42270
|
+
levels: [
|
|
42271
|
+
{ key: "overbought", price: overbought, color: NEUTRAL, lineStyle: "dashed", title: "Overbought" },
|
|
42272
|
+
{ key: "oversold", price: oversold, color: NEUTRAL, lineStyle: "dashed", title: "Oversold" }
|
|
42273
|
+
]
|
|
42274
|
+
};
|
|
42275
|
+
}
|
|
41621
42276
|
var stochastic = {
|
|
41622
42277
|
type: "stochastic",
|
|
41623
42278
|
title: "Stochastic",
|
|
41624
42279
|
shortTitle: "Stoch",
|
|
41625
42280
|
overlay: false,
|
|
41626
42281
|
inputs: [
|
|
41627
|
-
lengthInput(14, "kLength", "%K
|
|
41628
|
-
lengthInput(
|
|
41629
|
-
lengthInput(3, "dLength", "%D
|
|
42282
|
+
lengthInput(14, "kLength", "%K Length", 5e3, "Number of bars used for the highest high / lowest low range of the raw %K."),
|
|
42283
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the raw %K. 1 gives the fast stochastic, 3 the classic slow stochastic."),
|
|
42284
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length of the %D signal line, applied to the smoothed %K."),
|
|
42285
|
+
...thresholdInputs(80, 20),
|
|
42286
|
+
colorInput(INFO, "%K", "kColor", "Color of the %K line."),
|
|
42287
|
+
colorInput(WARNING, "%D", "dColor", "Color of the %D signal line."),
|
|
42288
|
+
colorInput(transp(INFO, 90), "Band Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
41630
42289
|
],
|
|
41631
42290
|
compute: (bars, inputs) => {
|
|
41632
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
41633
|
-
|
|
41634
|
-
return {
|
|
41635
|
-
plots: [
|
|
41636
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
41637
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
41638
|
-
],
|
|
41639
|
-
levels: guideLevels(20, 80)
|
|
41640
|
-
};
|
|
42291
|
+
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
42292
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
41641
42293
|
}
|
|
41642
42294
|
};
|
|
41643
42295
|
var stochasticRsi = {
|
|
@@ -41646,23 +42298,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41646
42298
|
shortTitle: "Stoch RSI",
|
|
41647
42299
|
overlay: false,
|
|
41648
42300
|
inputs: [
|
|
41649
|
-
|
|
41650
|
-
lengthInput(14, "
|
|
41651
|
-
lengthInput(
|
|
41652
|
-
lengthInput(3, "
|
|
41653
|
-
|
|
42301
|
+
sourceInput("Close", "source", "RSI Source", "Price series the RSI is computed on."),
|
|
42302
|
+
lengthInput(14, "rsiLength", "RSI Length", 5e3, "Lookback length of the Wilder RSI."),
|
|
42303
|
+
lengthInput(14, "stochLength", "Stochastic Length", 5e3, "Lookback over which the highest and lowest RSI values are taken."),
|
|
42304
|
+
lengthInput(3, "kSmoothing", "%K Smoothing", 500, "Simple moving average length applied to the stochastic of RSI to form %K."),
|
|
42305
|
+
lengthInput(3, "dLength", "%D Smoothing", 500, "Simple moving average length applied to %K to form the %D signal line."),
|
|
42306
|
+
...thresholdInputs(80, 20),
|
|
42307
|
+
colorInput(INFO, "%K Color", "kColor", "Color of the %K line."),
|
|
42308
|
+
colorInput(WARNING, "%D Color", "dColor", "Color of the %D signal line."),
|
|
42309
|
+
colorInput(transp(INFO, 90), "Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
41654
42310
|
],
|
|
41655
42311
|
compute: (bars, inputs) => {
|
|
41656
42312
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
41657
42313
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
41658
|
-
|
|
41659
|
-
return {
|
|
41660
|
-
plots: [
|
|
41661
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
41662
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
41663
|
-
],
|
|
41664
|
-
levels: guideLevels(20, 80)
|
|
41665
|
-
};
|
|
42314
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
41666
42315
|
}
|
|
41667
42316
|
};
|
|
41668
42317
|
var macd = {
|
|
@@ -41670,23 +42319,35 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41670
42319
|
title: "MACD",
|
|
41671
42320
|
overlay: false,
|
|
41672
42321
|
inputs: [
|
|
41673
|
-
lengthInput(12, "fastLength", "Fast
|
|
41674
|
-
lengthInput(26, "slowLength", "Slow
|
|
41675
|
-
lengthInput(9, "signalLength", "Signal
|
|
41676
|
-
sourceInput()
|
|
42322
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast EMA."),
|
|
42323
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow EMA."),
|
|
42324
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the EMA applied to the MACD line to obtain the signal line."),
|
|
42325
|
+
sourceInput("Close", "source", "Source", "Price series used to compute the fast and slow EMAs."),
|
|
42326
|
+
colorInput(INFO, "MACD Line", "color", "Color of the MACD line."),
|
|
42327
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
42328
|
+
colorInput(BULLISH, "Grow Above", "growAboveColor", "Histogram color when above zero and rising."),
|
|
42329
|
+
colorInput(transp(BULLISH, 50), "Fall Above", "fallAboveColor", "Histogram color when above zero and falling."),
|
|
42330
|
+
colorInput(transp(BEARISH, 50), "Grow Below", "growBelowColor", "Histogram color when below zero and rising."),
|
|
42331
|
+
colorInput(BEARISH, "Fall Below", "fallBelowColor", "Histogram color when below zero and falling.")
|
|
41677
42332
|
],
|
|
41678
42333
|
compute: (bars, inputs) => {
|
|
41679
42334
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41680
42335
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
41681
42336
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
41682
42337
|
const hist = sub(line, signal);
|
|
42338
|
+
const colors = momentumColumnColors(hist, {
|
|
42339
|
+
growAbove: str3(inputs, "growAboveColor", BULLISH),
|
|
42340
|
+
fallAbove: str3(inputs, "fallAboveColor", transp(BULLISH, 50)),
|
|
42341
|
+
growBelow: str3(inputs, "growBelowColor", transp(BEARISH, 50)),
|
|
42342
|
+
fallBelow: str3(inputs, "fallBelowColor", BEARISH)
|
|
42343
|
+
});
|
|
41683
42344
|
return {
|
|
41684
42345
|
plots: [
|
|
41685
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
41686
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
41687
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
42346
|
+
{ key: "hist", title: "Histogram", values: hist, kind: "columns", color: BULLISH, colors, base: 0 },
|
|
42347
|
+
{ key: "macd", title: "MACD", values: line, color: str3(inputs, "color", INFO) },
|
|
42348
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
41688
42349
|
],
|
|
41689
|
-
levels:
|
|
42350
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
41690
42351
|
};
|
|
41691
42352
|
}
|
|
41692
42353
|
};
|
|
@@ -41696,26 +42357,49 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41696
42357
|
shortTitle: "PPO",
|
|
41697
42358
|
overlay: false,
|
|
41698
42359
|
inputs: [
|
|
41699
|
-
|
|
41700
|
-
lengthInput(
|
|
41701
|
-
lengthInput(
|
|
41702
|
-
|
|
42360
|
+
sourceInput("Close", "source", "Source", "Price series fed into the fast and slow averages."),
|
|
42361
|
+
lengthInput(12, "fastLength", "Fast Length", 5e3, "Length of the fast moving average."),
|
|
42362
|
+
lengthInput(26, "slowLength", "Slow Length", 5e3, "Length of the slow moving average. The spread is divided by it and scaled to a percentage of price."),
|
|
42363
|
+
optionInput("maType", "Average Type", "EMA", ["EMA", "SMA"], "Moving average type used for the fast and slow averages."),
|
|
42364
|
+
lengthInput(9, "signalLength", "Signal Smoothing", 500, "Length of the signal line that smooths the PPO line."),
|
|
42365
|
+
optionInput("signalType", "Signal Type", "EMA", ["EMA", "SMA"], "Moving average type used for the signal line."),
|
|
42366
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the PPO line, gradient fill and histogram above the zero line."),
|
|
42367
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the PPO line, gradient fill and histogram below the zero line."),
|
|
42368
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
42369
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Displays the histogram (PPO minus signal line) as columns. Bright columns mark a spread still widening, faded ones a spread narrowing back."),
|
|
42370
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the PPO line with a vertical gradient.")
|
|
41703
42371
|
],
|
|
41704
42372
|
compute: (bars, inputs) => {
|
|
41705
42373
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41706
|
-
const
|
|
41707
|
-
const
|
|
42374
|
+
const average = (kind2, v, len) => kind2 === "SMA" ? sma(v, len) : ema(v, len);
|
|
42375
|
+
const kind = str3(inputs, "maType", "EMA");
|
|
42376
|
+
const fast = average(kind, src, num3(inputs, "fastLength", 12));
|
|
42377
|
+
const slow = average(kind, src, num3(inputs, "slowLength", 26));
|
|
41708
42378
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
41709
|
-
const signal =
|
|
42379
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
41710
42380
|
const hist = sub(line, signal);
|
|
41711
|
-
|
|
41712
|
-
|
|
41713
|
-
|
|
41714
|
-
|
|
41715
|
-
|
|
41716
|
-
|
|
41717
|
-
|
|
41718
|
-
|
|
42381
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42382
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
42383
|
+
const out = centeredOscillator({
|
|
42384
|
+
key: "ppo",
|
|
42385
|
+
title: "PPO Line",
|
|
42386
|
+
values: line,
|
|
42387
|
+
ink: { bull, bear, neutral: bear },
|
|
42388
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
42389
|
+
fill: bool(inputs, "gradientFill", true),
|
|
42390
|
+
extraPlots: [{ key: "signal", title: "Signal Line", values: signal, color: str3(inputs, "signalColor", WARNING) }]
|
|
42391
|
+
});
|
|
42392
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
42393
|
+
const colors = hist.map((x, i) => {
|
|
42394
|
+
if (!Number.isFinite(x)) return null;
|
|
42395
|
+
const prev3 = i > 0 && Number.isFinite(hist[i - 1]) ? hist[i - 1] : x;
|
|
42396
|
+
const ink = x >= 0 ? bull : bear;
|
|
42397
|
+
const expanding = x >= 0 ? x > prev3 : x < prev3;
|
|
42398
|
+
return transp(ink, expanding ? 40 : 70);
|
|
42399
|
+
});
|
|
42400
|
+
out.plots.unshift({ key: "hist", title: "Histogram", values: hist, kind: "columns", color: bull, colors, base: 0 });
|
|
42401
|
+
}
|
|
42402
|
+
return out;
|
|
41719
42403
|
}
|
|
41720
42404
|
};
|
|
41721
42405
|
var awesome = {
|
|
@@ -41723,48 +42407,67 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41723
42407
|
title: "Awesome Oscillator",
|
|
41724
42408
|
shortTitle: "AO",
|
|
41725
42409
|
overlay: false,
|
|
41726
|
-
inputs: [
|
|
41727
|
-
|
|
42410
|
+
inputs: [
|
|
42411
|
+
colorInput(BULLISH, "Rising", "risingColor", "Histogram color when the oscillator is higher than on the previous bar."),
|
|
42412
|
+
colorInput(BEARISH, "Falling", "fallingColor", "Histogram color when the oscillator is lower than or equal to the previous bar."),
|
|
42413
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
42414
|
+
],
|
|
42415
|
+
compute: (bars, inputs) => {
|
|
41728
42416
|
const hl2 = sourceValues(bars, "HL2");
|
|
41729
42417
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
42418
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
42419
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
41730
42420
|
const colors = ao.map((x, i) => {
|
|
41731
42421
|
if (!Number.isFinite(x)) return null;
|
|
41732
42422
|
const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
41733
|
-
return Number.isFinite(prev3) && x
|
|
42423
|
+
return Number.isFinite(prev3) && x > prev3 ? rising : falling;
|
|
41734
42424
|
});
|
|
41735
42425
|
return {
|
|
41736
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
41737
|
-
levels:
|
|
42426
|
+
plots: [{ key: "ao", title: "AO", values: ao, kind: "columns", color: rising, colors, base: 0 }],
|
|
42427
|
+
levels: [{ key: "zero", price: 0, color: str3(inputs, "zeroColor", NEUTRAL), lineStyle: "dashed", title: "Zero Line" }]
|
|
41738
42428
|
};
|
|
41739
42429
|
}
|
|
41740
42430
|
};
|
|
42431
|
+
var CCI_SCALING = 0.015;
|
|
41741
42432
|
var cci = {
|
|
41742
42433
|
type: "commodity-channel-index",
|
|
41743
42434
|
title: "Commodity Channel Index",
|
|
41744
42435
|
shortTitle: "CCI",
|
|
41745
42436
|
overlay: false,
|
|
41746
|
-
inputs: [
|
|
42437
|
+
inputs: [
|
|
42438
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used for the moving average and the mean absolute deviation of the source."),
|
|
42439
|
+
sourceInput("HLC3", "source", "Source", "Price series measured by the CCI; the typical price hlc3 is the classic choice."),
|
|
42440
|
+
floatInput("upper", "Upper Level", 100, -1e3, 1e3, 10, "Overbought reference level, +100 classically."),
|
|
42441
|
+
floatInput("lower", "Lower Level", -100, -1e3, 1e3, 10, "Oversold reference level, \u2212100 classically."),
|
|
42442
|
+
colorInput(NEUTRAL, "CCI Line", "color", "Color of the CCI line."),
|
|
42443
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CCI reads above zero."),
|
|
42444
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CCI reads below zero."),
|
|
42445
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the upper, lower and zero reference lines.")
|
|
42446
|
+
],
|
|
41747
42447
|
compute: (bars, inputs) => {
|
|
41748
42448
|
const len = num3(inputs, "length", 20);
|
|
41749
42449
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
41750
42450
|
const basis = sma(src, len);
|
|
41751
|
-
const dev =
|
|
41752
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
41753
|
-
const mean = basis[i];
|
|
41754
|
-
if (!Number.isFinite(mean)) continue;
|
|
41755
|
-
let d = 0;
|
|
41756
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
41757
|
-
dev[i] = d / len;
|
|
41758
|
-
}
|
|
42451
|
+
const dev = meanDev(src, len);
|
|
41759
42452
|
const values = src.map((x, i) => {
|
|
41760
42453
|
const b = basis[i];
|
|
41761
42454
|
const d = dev[i];
|
|
41762
|
-
|
|
42455
|
+
if (!Number.isFinite(b) || !Number.isFinite(d)) return Number.NaN;
|
|
42456
|
+
return d === 0 ? 0 : (x - b) / (CCI_SCALING * d);
|
|
42457
|
+
});
|
|
42458
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
42459
|
+
return centeredOscillator({
|
|
42460
|
+
key: "cci",
|
|
42461
|
+
title: "CCI",
|
|
42462
|
+
values,
|
|
42463
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42464
|
+
zeroInk: levelInk,
|
|
42465
|
+
zeroLineStyle: "dotted",
|
|
42466
|
+
extraLevels: [
|
|
42467
|
+
{ key: "upper", price: num3(inputs, "upper", 100), color: levelInk, lineStyle: "dashed", title: "Upper Level" },
|
|
42468
|
+
{ key: "lower", price: num3(inputs, "lower", -100), color: levelInk, lineStyle: "dashed", title: "Lower Level" }
|
|
42469
|
+
]
|
|
41763
42470
|
});
|
|
41764
|
-
return {
|
|
41765
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
41766
|
-
levels: guideLevels(-100, 100, 0)
|
|
41767
|
-
};
|
|
41768
42471
|
}
|
|
41769
42472
|
};
|
|
41770
42473
|
var williamsR = {
|
|
@@ -41772,31 +42475,78 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41772
42475
|
title: "Williams %R",
|
|
41773
42476
|
shortTitle: "%R",
|
|
41774
42477
|
overlay: false,
|
|
41775
|
-
inputs: [
|
|
42478
|
+
inputs: [
|
|
42479
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length of the highest high / lowest low range. 14 is the classic setting."),
|
|
42480
|
+
sourceInput("Close", "source", "Source", "Series located within the lookback range. Close is the classic choice."),
|
|
42481
|
+
...thresholdInputs(-20, -80, -100, 0),
|
|
42482
|
+
colorInput(NEUTRAL, "%R Line", "color", "Color of the %R line between the thresholds. Toward either level it blends into that level\u2019s color."),
|
|
42483
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
42484
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
42485
|
+
],
|
|
41776
42486
|
compute: (bars, inputs) => {
|
|
41777
|
-
const
|
|
41778
|
-
|
|
41779
|
-
|
|
41780
|
-
|
|
41781
|
-
|
|
42487
|
+
const len = num3(inputs, "length", 14);
|
|
42488
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42489
|
+
const hh = highest(highs(bars), len);
|
|
42490
|
+
const ll = lowest(lows(bars), len);
|
|
42491
|
+
const values = src.map((x, i) => {
|
|
42492
|
+
const h = hh[i];
|
|
42493
|
+
const l = ll[i];
|
|
42494
|
+
return Number.isFinite(h) && Number.isFinite(l) && h > l ? -100 * (h - x) / (h - l) : Number.NaN;
|
|
42495
|
+
});
|
|
42496
|
+
return boundedOscillator({
|
|
42497
|
+
key: "wr",
|
|
42498
|
+
title: "%R",
|
|
42499
|
+
values,
|
|
42500
|
+
scale: { top: 0, bottom: -100 },
|
|
42501
|
+
overbought: num3(inputs, "overbought", -20),
|
|
42502
|
+
oversold: num3(inputs, "oversold", -80),
|
|
42503
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
42504
|
+
levelInk: NEUTRAL
|
|
42505
|
+
});
|
|
41782
42506
|
}
|
|
41783
42507
|
};
|
|
41784
|
-
var
|
|
42508
|
+
var cmoSpec = {
|
|
41785
42509
|
type: "chande-momentum-oscillator",
|
|
41786
42510
|
title: "Chande Momentum Oscillator",
|
|
41787
42511
|
shortTitle: "CMO",
|
|
41788
42512
|
overlay: false,
|
|
41789
|
-
inputs: [
|
|
42513
|
+
inputs: [
|
|
42514
|
+
lengthInput(9, "length", "Length", 5e3, "Number of bars over which up moves and down moves are summed."),
|
|
42515
|
+
sourceInput("Close", "source", "Source", "Price series used to measure bar-to-bar changes."),
|
|
42516
|
+
floatInput("overbought", "Overbought Level", 50, 0, 100, 1, "Level above which the oscillator is considered overbought."),
|
|
42517
|
+
floatInput("oversold", "Oversold Level", -50, -100, 0, 1, "Level below which the oscillator is considered oversold."),
|
|
42518
|
+
colorInput(NEUTRAL, "CMO Line", "color", "Color of the CMO line."),
|
|
42519
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while momentum is positive, and shading of the oversold zone."),
|
|
42520
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while momentum is negative, and shading of the overbought zone."),
|
|
42521
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought, oversold, and zero lines.")
|
|
42522
|
+
],
|
|
41790
42523
|
compute: (bars, inputs) => {
|
|
41791
42524
|
const len = num3(inputs, "length", 9);
|
|
41792
42525
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
41793
|
-
const
|
|
41794
|
-
const
|
|
41795
|
-
const values = zip(
|
|
41796
|
-
|
|
41797
|
-
|
|
41798
|
-
|
|
41799
|
-
|
|
42526
|
+
const up = sum(map(d, (x) => Math.max(x, 0)), len);
|
|
42527
|
+
const down = sum(map(d, (x) => Math.max(-x, 0)), len);
|
|
42528
|
+
const values = zip(up, down, (u, w) => u + w === 0 ? 0 : 100 * (u - w) / (u + w));
|
|
42529
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42530
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
42531
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
42532
|
+
const overbought = num3(inputs, "overbought", 50);
|
|
42533
|
+
const oversold = num3(inputs, "oversold", -50);
|
|
42534
|
+
const out = centeredOscillator({
|
|
42535
|
+
key: "cmo",
|
|
42536
|
+
title: "CMO",
|
|
42537
|
+
values,
|
|
42538
|
+
ink: { bull, bear, neutral: str3(inputs, "color", NEUTRAL) },
|
|
42539
|
+
zeroInk: levelInk,
|
|
42540
|
+
zeroLineStyle: "dotted",
|
|
42541
|
+
extraLevels: [
|
|
42542
|
+
{ key: "overbought", price: overbought, color: levelInk, lineStyle: "dashed", title: "Overbought" },
|
|
42543
|
+
{ key: "oversold", price: oversold, color: levelInk, lineStyle: "dashed", title: "Oversold" }
|
|
42544
|
+
]
|
|
42545
|
+
});
|
|
42546
|
+
const zones = thresholdZones(values.length, { overbought, oversold, top: 100, bottom: -100 }, { overbought: bear, oversold: bull });
|
|
42547
|
+
out.plots.push(...zones.plots);
|
|
42548
|
+
out.bands = [...out.bands ?? [], ...zones.bands];
|
|
42549
|
+
return out;
|
|
41800
42550
|
}
|
|
41801
42551
|
};
|
|
41802
42552
|
var connorsRsi = {
|
|
@@ -41805,10 +42555,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41805
42555
|
shortTitle: "CRSI",
|
|
41806
42556
|
overlay: false,
|
|
41807
42557
|
inputs: [
|
|
41808
|
-
lengthInput(3, "rsiLength", "RSI
|
|
41809
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
41810
|
-
lengthInput(100, "rankLength", "Percent
|
|
41811
|
-
|
|
42558
|
+
lengthInput(3, "rsiLength", "RSI Length", 5e3, "Length of the Wilder RSI applied to the closing price."),
|
|
42559
|
+
lengthInput(2, "streakLength", "Streak RSI Length", 5e3, "Length of the Wilder RSI applied to the up/down close streak."),
|
|
42560
|
+
lengthInput(100, "rankLength", "Percent Rank Length", 5e3, "Lookback of the percent rank applied to the 1-bar rate of change of the closing price."),
|
|
42561
|
+
...thresholdInputs(90, 10),
|
|
42562
|
+
colorInput(NEUTRAL, "CRSI Line", "color", "Color of the Connors RSI line."),
|
|
42563
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Line, fill and zone color toward and below the oversold level."),
|
|
42564
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Line, fill and zone color toward and above the overbought level."),
|
|
42565
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels.")
|
|
41812
42566
|
],
|
|
41813
42567
|
compute: (bars, inputs) => {
|
|
41814
42568
|
const c = closes(bars);
|
|
@@ -41826,10 +42580,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41826
42580
|
const z = r[i];
|
|
41827
42581
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
41828
42582
|
});
|
|
41829
|
-
return {
|
|
41830
|
-
|
|
41831
|
-
|
|
41832
|
-
|
|
42583
|
+
return boundedOscillator({
|
|
42584
|
+
key: "crsi",
|
|
42585
|
+
title: "Connors RSI",
|
|
42586
|
+
values,
|
|
42587
|
+
scale: { top: 100, bottom: 0 },
|
|
42588
|
+
overbought: num3(inputs, "overbought", 90),
|
|
42589
|
+
oversold: num3(inputs, "oversold", 10),
|
|
42590
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
42591
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
42592
|
+
midpointWash: true
|
|
42593
|
+
});
|
|
41833
42594
|
}
|
|
41834
42595
|
};
|
|
41835
42596
|
var fisher = {
|
|
@@ -41837,7 +42598,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41837
42598
|
title: "Fisher Transform",
|
|
41838
42599
|
shortTitle: "Fisher",
|
|
41839
42600
|
overlay: false,
|
|
41840
|
-
inputs: [
|
|
42601
|
+
inputs: [
|
|
42602
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period used to locate the median price hl2 within its highest-lowest range. Ehlers' original length is 10; 9 is the common platform default."),
|
|
42603
|
+
colorInput(NEUTRAL, "Fisher Line", "color", "Color of the Fisher line when it sits exactly on its trigger."),
|
|
42604
|
+
colorInput(NEUTRAL, "Trigger Line", "triggerColor", "Color of the trigger line, the Fisher value delayed one bar."),
|
|
42605
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the Fisher line and fill while it is above the trigger."),
|
|
42606
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Fisher line and fill while it is below the trigger.")
|
|
42607
|
+
],
|
|
41841
42608
|
compute: (bars, inputs) => {
|
|
41842
42609
|
const len = num3(inputs, "length", 9);
|
|
41843
42610
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -41845,23 +42612,39 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41845
42612
|
const lo = lowest(hl2, len);
|
|
41846
42613
|
const n = bars.length;
|
|
41847
42614
|
const out = new Array(n).fill(Number.NaN);
|
|
42615
|
+
let position = 0;
|
|
41848
42616
|
let value = 0;
|
|
41849
|
-
let fish = 0;
|
|
41850
42617
|
for (let i = 0; i < n; i++) {
|
|
41851
42618
|
const h = hi[i];
|
|
41852
42619
|
const l = lo[i];
|
|
41853
42620
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
41854
|
-
const
|
|
41855
|
-
|
|
41856
|
-
|
|
41857
|
-
|
|
41858
|
-
|
|
42621
|
+
const raw = h > l ? (hl2[i] - l) / (h - l) - 0.5 : 0;
|
|
42622
|
+
position = 0.66 * raw + 0.67 * position;
|
|
42623
|
+
position = position > 0.99 ? 0.999 : position < -0.99 ? -0.999 : position;
|
|
42624
|
+
value = 0.5 * Math.log((1 + position) / (1 - position)) + 0.5 * value;
|
|
42625
|
+
out[i] = value;
|
|
42626
|
+
}
|
|
42627
|
+
const trigger = shift3(out, 1);
|
|
42628
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
42629
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42630
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
42631
|
+
const inkOf = (i) => {
|
|
42632
|
+
const f = out[i];
|
|
42633
|
+
const t = trigger[i];
|
|
42634
|
+
if (!Number.isFinite(t)) return neutral;
|
|
42635
|
+
return f > t ? bull : f < t ? bear : neutral;
|
|
42636
|
+
};
|
|
41859
42637
|
return {
|
|
41860
42638
|
plots: [
|
|
41861
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
41862
|
-
{ key: "trigger", title: "Trigger", values:
|
|
42639
|
+
{ key: "fisher", title: "Fisher", values: out, color: neutral, width: 2, colors: out.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
42640
|
+
{ key: "trigger", title: "Trigger", values: trigger, color: str3(inputs, "triggerColor", NEUTRAL) }
|
|
41863
42641
|
],
|
|
41864
|
-
|
|
42642
|
+
bands: [{ key: "spread", from: "fisher", to: "trigger", color: transp(neutral, 80), colors: out.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 80) : null) }],
|
|
42643
|
+
levels: [
|
|
42644
|
+
{ key: "upper", price: 1.5, color: NEUTRAL, lineStyle: "dashed", title: "Upper Level" },
|
|
42645
|
+
{ key: "lower", price: -1.5, color: NEUTRAL, lineStyle: "dashed", title: "Lower Level" },
|
|
42646
|
+
{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dotted", title: "Zero Line" }
|
|
42647
|
+
]
|
|
41865
42648
|
};
|
|
41866
42649
|
}
|
|
41867
42650
|
};
|
|
@@ -41869,93 +42652,180 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41869
42652
|
type: "trix",
|
|
41870
42653
|
title: "TRIX",
|
|
41871
42654
|
overlay: false,
|
|
41872
|
-
inputs: [
|
|
42655
|
+
inputs: [
|
|
42656
|
+
lengthInput(18, "length", "Length", 5e3, "Length of each of the three EMA smoothing passes applied to the close price."),
|
|
42657
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Length of the EMA applied to TRIX to form the signal line."),
|
|
42658
|
+
colorInput(NEUTRAL, "TRIX Color", "color", "Color of the TRIX line."),
|
|
42659
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line."),
|
|
42660
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TRIX line and its gradient fill while TRIX holds above zero."),
|
|
42661
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TRIX line and its gradient fill while TRIX holds below zero.")
|
|
42662
|
+
],
|
|
41873
42663
|
compute: (bars, inputs) => {
|
|
41874
42664
|
const len = num3(inputs, "length", 18);
|
|
41875
|
-
const
|
|
41876
|
-
const
|
|
41877
|
-
|
|
41878
|
-
|
|
41879
|
-
|
|
41880
|
-
|
|
41881
|
-
|
|
42665
|
+
const smooth = ema(ema(ema(closes(bars), len), len), len);
|
|
42666
|
+
const values = roc(smooth, 1);
|
|
42667
|
+
return centeredOscillator({
|
|
42668
|
+
key: "trix",
|
|
42669
|
+
title: "TRIX",
|
|
42670
|
+
values,
|
|
42671
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42672
|
+
zeroInk: NEUTRAL,
|
|
42673
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
42674
|
+
});
|
|
41882
42675
|
}
|
|
41883
42676
|
};
|
|
41884
|
-
function
|
|
42677
|
+
function tsiRatio(src, long, short) {
|
|
41885
42678
|
const mom = change(src);
|
|
41886
42679
|
const numer = ema(ema(mom, long), short);
|
|
41887
42680
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
41888
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
42681
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
41889
42682
|
}
|
|
41890
42683
|
var tsi = {
|
|
41891
42684
|
type: "true-strength-index",
|
|
41892
42685
|
title: "True Strength Index",
|
|
41893
42686
|
shortTitle: "TSI",
|
|
41894
42687
|
overlay: false,
|
|
41895
|
-
inputs: [
|
|
42688
|
+
inputs: [
|
|
42689
|
+
lengthInput(25, "longLength", "Long Length", 5e3, "Length of the first (long) EMA applied to the 1-bar price change."),
|
|
42690
|
+
lengthInput(13, "shortLength", "Short Length", 5e3, "Length of the second (short) EMA applied to the long-smoothed price change."),
|
|
42691
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA of TSI used as the signal line."),
|
|
42692
|
+
colorInput(NEUTRAL, "TSI", "color", "Color of the TSI line."),
|
|
42693
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
42694
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the TSI line and its gradient fill while TSI holds above zero."),
|
|
42695
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the TSI line and its gradient fill while TSI holds below zero.")
|
|
42696
|
+
],
|
|
41896
42697
|
compute: (bars, inputs) => {
|
|
41897
|
-
const
|
|
41898
|
-
return {
|
|
41899
|
-
|
|
41900
|
-
|
|
41901
|
-
|
|
41902
|
-
|
|
41903
|
-
|
|
41904
|
-
|
|
42698
|
+
const values = map(tsiRatio(closes(bars), num3(inputs, "longLength", 25), num3(inputs, "shortLength", 13)), (x) => 100 * x);
|
|
42699
|
+
return centeredOscillator({
|
|
42700
|
+
key: "tsi",
|
|
42701
|
+
title: "TSI",
|
|
42702
|
+
values,
|
|
42703
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42704
|
+
zeroInk: NEUTRAL,
|
|
42705
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(values, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
42706
|
+
});
|
|
41905
42707
|
}
|
|
41906
42708
|
};
|
|
42709
|
+
var SMI_INDICATOR = "Indicator";
|
|
42710
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
41907
42711
|
var smiErgodic = {
|
|
41908
42712
|
type: "smi-ergodic",
|
|
41909
42713
|
title: "SMI Ergodic Oscillator",
|
|
41910
42714
|
shortTitle: "SMIE",
|
|
41911
42715
|
overlay: false,
|
|
41912
|
-
inputs: [
|
|
42716
|
+
inputs: [
|
|
42717
|
+
lengthInput(20, "longLength", "Long Length", 5e3, "Length of the first EMA smoothing applied to the momentum of the closing price."),
|
|
42718
|
+
lengthInput(5, "shortLength", "Short Length", 5e3, "Length of the second EMA smoothing applied to the momentum of the closing price."),
|
|
42719
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the EMA of the SMI that forms the signal line."),
|
|
42720
|
+
optionInput("display", "Display", SMI_INDICATOR, [SMI_INDICATOR, SMI_OSCILLATOR], "Indicator plots the SMI and its signal line. Oscillator plots the difference between them as a histogram."),
|
|
42721
|
+
colorInput(NEUTRAL, "SMI", "color", "Color of the SMI line while it sits exactly on its signal line."),
|
|
42722
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line."),
|
|
42723
|
+
colorInput(BULLISH, "Oscillator Grow", "growColor", "Color of the rising oscillator histogram, and of the SMI line while it leads its signal."),
|
|
42724
|
+
colorInput(BEARISH, "Oscillator Fall", "fallColor", "Color of the falling oscillator histogram, and of the SMI line while it lags its signal.")
|
|
42725
|
+
],
|
|
41913
42726
|
compute: (bars, inputs) => {
|
|
41914
|
-
const
|
|
41915
|
-
const signal = ema(
|
|
41916
|
-
const
|
|
42727
|
+
const smi = tsiRatio(closes(bars), num3(inputs, "longLength", 20), num3(inputs, "shortLength", 5));
|
|
42728
|
+
const signal = ema(smi, num3(inputs, "signalLength", 5));
|
|
42729
|
+
const grow = str3(inputs, "growColor", BULLISH);
|
|
42730
|
+
const fall = str3(inputs, "fallColor", BEARISH);
|
|
42731
|
+
const zero = [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }];
|
|
42732
|
+
if (str3(inputs, "display", SMI_INDICATOR) === SMI_OSCILLATOR) {
|
|
42733
|
+
const osc = sub(smi, signal);
|
|
42734
|
+
const colors = osc.map((x, i) => {
|
|
42735
|
+
if (!Number.isFinite(x)) return null;
|
|
42736
|
+
const prev3 = i > 0 && Number.isFinite(osc[i - 1]) ? osc[i - 1] : x;
|
|
42737
|
+
return x > prev3 ? grow : fall;
|
|
42738
|
+
});
|
|
42739
|
+
return { plots: [{ key: "osc", title: "Oscillator", values: osc, kind: "columns", color: grow, colors, base: 0 }], levels: zero };
|
|
42740
|
+
}
|
|
42741
|
+
const neutral = str3(inputs, "color", NEUTRAL);
|
|
42742
|
+
const inkOf = (i) => {
|
|
42743
|
+
const a = smi[i];
|
|
42744
|
+
const b = signal[i];
|
|
42745
|
+
if (!Number.isFinite(b)) return neutral;
|
|
42746
|
+
return a > b ? grow : a < b ? fall : neutral;
|
|
42747
|
+
};
|
|
41917
42748
|
return {
|
|
41918
42749
|
plots: [
|
|
41919
|
-
{ key: "
|
|
41920
|
-
{ key: "
|
|
41921
|
-
{ key: "signal", title: "Signal", values: signal, color: WARNING }
|
|
42750
|
+
{ key: "smi", title: "SMI", values: smi, color: neutral, colors: smi.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) },
|
|
42751
|
+
{ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) }
|
|
41922
42752
|
],
|
|
41923
|
-
|
|
42753
|
+
bands: [{ key: "spread", from: "smi", to: "signal", color: transp(neutral, 75), colors: smi.map((x, i) => Number.isFinite(x) ? transp(inkOf(i), 75) : null) }],
|
|
42754
|
+
levels: zero
|
|
41924
42755
|
};
|
|
41925
42756
|
}
|
|
41926
42757
|
};
|
|
42758
|
+
var STC_MID = 50;
|
|
42759
|
+
var TRIGGERS = "Trigger Levels";
|
|
41927
42760
|
var stc = {
|
|
41928
42761
|
type: "schaff-trend-cycle",
|
|
41929
42762
|
title: "Schaff Trend Cycle",
|
|
41930
42763
|
shortTitle: "Schaff Trend Cycle",
|
|
41931
42764
|
overlay: false,
|
|
41932
|
-
inputs: [
|
|
42765
|
+
inputs: [
|
|
42766
|
+
sourceInput("Close", "source", "Source", "Price series used to build the MACD that the trend cycle is measured on."),
|
|
42767
|
+
lengthInput(23, "fastLength", "Fast Length", 5e3, "Fast EMA length of the underlying MACD."),
|
|
42768
|
+
lengthInput(50, "slowLength", "Slow Length", 5e3, "Slow EMA length of the underlying MACD."),
|
|
42769
|
+
{ ...lengthInput(10, "cycleLength", "Cycle Length", 5e3, "Window of the two stochastic passes. Each pass measures where its input sits inside the range of its own values over this many bars."), min: 2 },
|
|
42770
|
+
floatInput("factor", "Smoothing Factor", 0.5, 0.01, 1, 0.05, "%D style recursive smoothing applied after each stochastic pass. 0.5 is the published default."),
|
|
42771
|
+
{ ...floatInput("upper", "Upper Trigger", 75, 0, 100, 1, "Upper trigger line. A cross down through it flags an emerging downtrend."), group: TRIGGERS },
|
|
42772
|
+
{ ...floatInput("lower", "Lower Trigger", 25, 0, 100, 1, "Lower trigger line. A cross up through it flags an emerging uptrend."), group: TRIGGERS },
|
|
42773
|
+
colorInput(BULLISH, "Bullish Color", "bullColor", "Color of the trend cycle line and gradient while the cycle holds the upper half of its 0-100 scale."),
|
|
42774
|
+
colorInput(BEARISH, "Bearish Color", "bearColor", "Color of the trend cycle line and gradient while the cycle holds the lower half of its 0-100 scale."),
|
|
42775
|
+
colorInput(NEUTRAL, "Levels Color", "levelsColor", "Color of the dashed trigger lines and the dotted midline."),
|
|
42776
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between the midline and the trend cycle, fading out toward the midline.")
|
|
42777
|
+
],
|
|
41933
42778
|
compute: (bars, inputs) => {
|
|
41934
42779
|
const len = num3(inputs, "cycleLength", 10);
|
|
41935
|
-
const
|
|
42780
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
42781
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41936
42782
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
41937
|
-
const
|
|
42783
|
+
const stochNorm = (v) => {
|
|
41938
42784
|
const lo = lowest(v, len);
|
|
41939
42785
|
const hi = highest(v, len);
|
|
41940
42786
|
const out = new Array(v.length).fill(Number.NaN);
|
|
41941
|
-
let
|
|
41942
|
-
let smoothed = Number.NaN;
|
|
42787
|
+
let pct = Number.NaN;
|
|
41943
42788
|
for (let i = 0; i < v.length; i++) {
|
|
41944
42789
|
const x = v[i];
|
|
41945
42790
|
const l = lo[i];
|
|
41946
42791
|
const h = hi[i];
|
|
41947
42792
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
41948
|
-
|
|
41949
|
-
|
|
41950
|
-
|
|
42793
|
+
if (h > l) pct = 100 * (x - l) / (h - l);
|
|
42794
|
+
out[i] = pct;
|
|
42795
|
+
}
|
|
42796
|
+
return out;
|
|
42797
|
+
};
|
|
42798
|
+
const smoothStep = (v) => {
|
|
42799
|
+
const out = new Array(v.length).fill(Number.NaN);
|
|
42800
|
+
let smoothed = Number.NaN;
|
|
42801
|
+
for (let i = 0; i < v.length; i++) {
|
|
42802
|
+
const x = v[i];
|
|
42803
|
+
if (!Number.isFinite(x)) continue;
|
|
42804
|
+
smoothed = Number.isFinite(smoothed) ? smoothed + factor * (x - smoothed) : x;
|
|
41951
42805
|
out[i] = smoothed;
|
|
41952
42806
|
}
|
|
41953
42807
|
return out;
|
|
41954
42808
|
};
|
|
41955
|
-
const values =
|
|
42809
|
+
const values = smoothStep(stochNorm(smoothStep(stochNorm(macdLine))));
|
|
42810
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
42811
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42812
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
42813
|
+
const plots = [
|
|
42814
|
+
{ key: "stc", title: "Schaff Trend Cycle", values, color: bear, width: 2, colors: values.map((x) => Number.isFinite(x) ? x > STC_MID ? bull : bear : null) }
|
|
42815
|
+
];
|
|
42816
|
+
const bands = [];
|
|
42817
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
42818
|
+
plots.push(anchorPlot("midAnchor", "Midline Anchor", values.length, STC_MID));
|
|
42819
|
+
bands.push({ key: "wash", from: "stc", to: "midAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, STC_MID, (x) => x > STC_MID ? bull : bear) });
|
|
42820
|
+
}
|
|
41956
42821
|
return {
|
|
41957
|
-
plots
|
|
41958
|
-
|
|
42822
|
+
plots,
|
|
42823
|
+
bands,
|
|
42824
|
+
levels: [
|
|
42825
|
+
{ key: "upper", price: num3(inputs, "upper", 75), color: transp(levelInk, 25), lineStyle: "dashed", title: "Upper Trigger" },
|
|
42826
|
+
{ key: "lower", price: num3(inputs, "lower", 25), color: transp(levelInk, 25), lineStyle: "dashed", title: "Lower Trigger" },
|
|
42827
|
+
{ key: "midline", price: STC_MID, color: transp(levelInk, 60), lineStyle: "dotted", title: "Midline" }
|
|
42828
|
+
]
|
|
41959
42829
|
};
|
|
41960
42830
|
}
|
|
41961
42831
|
};
|
|
@@ -41964,22 +42834,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41964
42834
|
title: "Know Sure Thing",
|
|
41965
42835
|
shortTitle: "KST",
|
|
41966
42836
|
overlay: false,
|
|
41967
|
-
inputs: [
|
|
42837
|
+
inputs: [
|
|
42838
|
+
lengthInput(10, "rocLength1", "ROC Length #1", 5e3, "Lookback of the first (shortest) rate of change."),
|
|
42839
|
+
lengthInput(15, "rocLength2", "ROC Length #2", 5e3, "Lookback of the second rate of change."),
|
|
42840
|
+
lengthInput(20, "rocLength3", "ROC Length #3", 5e3, "Lookback of the third rate of change."),
|
|
42841
|
+
lengthInput(30, "rocLength4", "ROC Length #4", 5e3, "Lookback of the fourth (longest) rate of change."),
|
|
42842
|
+
lengthInput(10, "smaLength1", "SMA Length #1", 5e3, "Smoothing length applied to the first rate of change (RCMA #1)."),
|
|
42843
|
+
lengthInput(10, "smaLength2", "SMA Length #2", 5e3, "Smoothing length applied to the second rate of change (RCMA #2)."),
|
|
42844
|
+
lengthInput(10, "smaLength3", "SMA Length #3", 5e3, "Smoothing length applied to the third rate of change (RCMA #3)."),
|
|
42845
|
+
lengthInput(15, "smaLength4", "SMA Length #4", 5e3, "Smoothing length applied to the fourth rate of change (RCMA #4)."),
|
|
42846
|
+
lengthInput(9, "signalLength", "Signal Length", 500, "Simple moving average length of the signal line."),
|
|
42847
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the KST line while above zero."),
|
|
42848
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the KST line while below zero."),
|
|
42849
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
42850
|
+
],
|
|
41968
42851
|
compute: (bars, inputs) => {
|
|
41969
|
-
const
|
|
41970
|
-
const
|
|
41971
|
-
|
|
41972
|
-
|
|
41973
|
-
|
|
41974
|
-
|
|
41975
|
-
|
|
41976
|
-
return {
|
|
41977
|
-
|
|
41978
|
-
|
|
41979
|
-
|
|
41980
|
-
|
|
41981
|
-
|
|
41982
|
-
|
|
42852
|
+
const c = closes(bars);
|
|
42853
|
+
const rcma = (rocKey, rocDefault, smaKey, smaDefault) => sma(roc(c, num3(inputs, rocKey, rocDefault)), num3(inputs, smaKey, smaDefault));
|
|
42854
|
+
const values = [rcma("rocLength1", 10, "smaLength1", 10), rcma("rocLength2", 15, "smaLength2", 10), rcma("rocLength3", 20, "smaLength3", 10), rcma("rocLength4", 30, "smaLength4", 15)].reduce(
|
|
42855
|
+
(acc, r, i) => zip(acc, r, (a, b) => a + b * (i + 1)),
|
|
42856
|
+
new Array(c.length).fill(0)
|
|
42857
|
+
);
|
|
42858
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
42859
|
+
return centeredOscillator({
|
|
42860
|
+
key: "kst",
|
|
42861
|
+
title: "KST",
|
|
42862
|
+
values,
|
|
42863
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
42864
|
+
zeroInk: NEUTRAL,
|
|
42865
|
+
valueTransparency: 60,
|
|
42866
|
+
extraPlots: [{ key: "signal", title: "Signal", values: sma(values, num3(inputs, "signalLength", 9)), color: str3(inputs, "signalColor", WARNING) }]
|
|
42867
|
+
});
|
|
41983
42868
|
}
|
|
41984
42869
|
};
|
|
41985
42870
|
var coppock = {
|
|
@@ -41988,19 +42873,25 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41988
42873
|
shortTitle: "Coppock",
|
|
41989
42874
|
overlay: false,
|
|
41990
42875
|
inputs: [
|
|
41991
|
-
lengthInput(10, "wmaLength", "WMA
|
|
41992
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
41993
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
41994
|
-
sourceInput(),
|
|
41995
|
-
colorInput()
|
|
42876
|
+
lengthInput(10, "wmaLength", "WMA Length", 5e3, "Length of the weighted moving average applied to the sum of the two rates of change."),
|
|
42877
|
+
lengthInput(14, "longRoc", "Long RoC Length", 5e3, "Lookback of the long rate of change, in bars. The original definition uses 14 monthly closes."),
|
|
42878
|
+
lengthInput(11, "shortRoc", "Short RoC Length", 5e3, "Lookback of the short rate of change, in bars. The original definition uses 11 monthly closes."),
|
|
42879
|
+
sourceInput("Close", "source", "Source", "Price series used for the rate of change calculations."),
|
|
42880
|
+
colorInput(NEUTRAL, "Coppock Curve", "color", "Color of the Coppock Curve line."),
|
|
42881
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the curve reads above zero."),
|
|
42882
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the curve reads below zero."),
|
|
42883
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
41996
42884
|
],
|
|
41997
42885
|
compute: (bars, inputs) => {
|
|
41998
42886
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41999
42887
|
const values = wma(zip(roc(src, num3(inputs, "longRoc", 14)), roc(src, num3(inputs, "shortRoc", 11)), (a, b) => a + b), num3(inputs, "wmaLength", 10));
|
|
42000
|
-
return {
|
|
42001
|
-
|
|
42002
|
-
|
|
42003
|
-
|
|
42888
|
+
return centeredOscillator({
|
|
42889
|
+
key: "coppock",
|
|
42890
|
+
title: "Coppock Curve",
|
|
42891
|
+
values,
|
|
42892
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42893
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
42894
|
+
});
|
|
42004
42895
|
}
|
|
42005
42896
|
};
|
|
42006
42897
|
var dpo = {
|
|
@@ -42008,42 +42899,76 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42008
42899
|
title: "Detrended Price Oscillator",
|
|
42009
42900
|
shortTitle: "DPO",
|
|
42010
42901
|
overlay: false,
|
|
42011
|
-
inputs: [
|
|
42902
|
+
inputs: [
|
|
42903
|
+
lengthInput(21, "length", "Length", 5e3, "Lookback of the simple moving average that price is detrended against. The comparison is displaced by Length / 2 + 1 bars."),
|
|
42904
|
+
sourceInput("Close", "source", "Source", "Price series used in the detrending calculation."),
|
|
42905
|
+
boolInput("centered", "Centered", false, void 0, "Shifts the oscillator back by Length / 2 + 1 bars so it is centered on the prices it detrends. The most recent bars then show no value."),
|
|
42906
|
+
colorInput(NEUTRAL, "DPO", "color", "Color of the line when it sits exactly at zero."),
|
|
42907
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while the oscillator is above zero."),
|
|
42908
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while the oscillator is below zero."),
|
|
42909
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
42910
|
+
],
|
|
42012
42911
|
compute: (bars, inputs) => {
|
|
42013
42912
|
const len = num3(inputs, "length", 21);
|
|
42014
|
-
const
|
|
42015
|
-
const
|
|
42016
|
-
|
|
42017
|
-
|
|
42018
|
-
|
|
42019
|
-
|
|
42913
|
+
const barsBack = Math.trunc(len / 2) + 1;
|
|
42914
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42915
|
+
const basis = sma(src, len);
|
|
42916
|
+
const centered = bool(inputs, "centered", false);
|
|
42917
|
+
const values = centered ? shift3(sub(shift3(src, barsBack), basis), -barsBack) : sub(src, shift3(basis, barsBack));
|
|
42918
|
+
return centeredOscillator({
|
|
42919
|
+
key: "dpo",
|
|
42920
|
+
title: "DPO",
|
|
42921
|
+
values,
|
|
42922
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42923
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
42924
|
+
});
|
|
42020
42925
|
}
|
|
42021
42926
|
};
|
|
42927
|
+
var ROC_PERCENT = "ROC %";
|
|
42928
|
+
var ROC_MOMENTUM = "Momentum";
|
|
42022
42929
|
var rocSpec = {
|
|
42023
42930
|
type: "rate-of-change",
|
|
42024
42931
|
title: "Rate of Change",
|
|
42025
42932
|
shortTitle: "ROC",
|
|
42026
42933
|
overlay: false,
|
|
42027
|
-
inputs: [
|
|
42028
|
-
|
|
42029
|
-
|
|
42030
|
-
|
|
42031
|
-
|
|
42032
|
-
|
|
42033
|
-
|
|
42034
|
-
|
|
42035
|
-
|
|
42036
|
-
|
|
42037
|
-
|
|
42038
|
-
|
|
42039
|
-
|
|
42934
|
+
inputs: [
|
|
42935
|
+
lengthInput(9, "length", "Length", 5e3, "Lookback period; the source is compared with its value this many bars ago."),
|
|
42936
|
+
sourceInput("Close", "source", "Source", "Series used as input for the calculation."),
|
|
42937
|
+
optionInput("mode", "Mode", ROC_PERCENT, [ROC_PERCENT, ROC_MOMENTUM], "ROC % plots the change as a percentage of the source value from the lookback; Momentum plots the raw difference."),
|
|
42938
|
+
colorInput(NEUTRAL, "Line", "color", "Color of the line when the reading sits exactly at zero."),
|
|
42939
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and fill while the reading is above zero."),
|
|
42940
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and fill while the reading is below zero."),
|
|
42941
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the line and zero, following the line color.")
|
|
42942
|
+
],
|
|
42943
|
+
compute: (bars, inputs) => {
|
|
42944
|
+
const len = num3(inputs, "length", 9);
|
|
42945
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42946
|
+
const values = str3(inputs, "mode", ROC_PERCENT) === ROC_MOMENTUM ? change(src, len) : roc(src, len);
|
|
42947
|
+
return centeredOscillator({
|
|
42948
|
+
key: "roc",
|
|
42949
|
+
title: "ROC",
|
|
42950
|
+
values,
|
|
42951
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
42952
|
+
zeroInk: NEUTRAL,
|
|
42953
|
+
fill: bool(inputs, "gradientFill", true)
|
|
42954
|
+
});
|
|
42955
|
+
}
|
|
42040
42956
|
};
|
|
42041
42957
|
var ultimate = {
|
|
42042
42958
|
type: "ultimate-oscillator",
|
|
42043
42959
|
title: "Ultimate Oscillator",
|
|
42044
42960
|
shortTitle: "UO",
|
|
42045
42961
|
overlay: false,
|
|
42046
|
-
inputs: [
|
|
42962
|
+
inputs: [
|
|
42963
|
+
lengthInput(7, "fastLength", "Fast Length", 5e3, "Bars in the fastest averaging window. Weighted 4 in the final blend, so it dominates the oscillator."),
|
|
42964
|
+
lengthInput(14, "middleLength", "Middle Length", 5e3, "Bars in the intermediate averaging window. Weighted 2 in the final blend."),
|
|
42965
|
+
lengthInput(28, "slowLength", "Slow Length", 5e3, "Bars in the slowest averaging window. Weighted 1 in the final blend."),
|
|
42966
|
+
...thresholdInputs(70, 30, 0, 100, "Overbought", "Oversold"),
|
|
42967
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the line midway between the levels. Toward either level it blends into that level\u2019s color."),
|
|
42968
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Color of the overbought and oversold levels."),
|
|
42969
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line at the oversold level and of the oversold zone shading."),
|
|
42970
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line at the overbought level and of the overbought zone shading.")
|
|
42971
|
+
],
|
|
42047
42972
|
compute: (bars, inputs) => {
|
|
42048
42973
|
const n = bars.length;
|
|
42049
42974
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -42051,10 +42976,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42051
42976
|
for (let i = 0; i < n; i++) {
|
|
42052
42977
|
const b = bars[i];
|
|
42053
42978
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
42054
|
-
const
|
|
42055
|
-
|
|
42056
|
-
|
|
42057
|
-
tr[i] = hi - lo;
|
|
42979
|
+
const trueLow = Math.min(b.low, pc);
|
|
42980
|
+
bp[i] = b.close - trueLow;
|
|
42981
|
+
tr[i] = Math.max(b.high, pc) - trueLow;
|
|
42058
42982
|
}
|
|
42059
42983
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
42060
42984
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -42065,10 +42989,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42065
42989
|
const z = a28[i];
|
|
42066
42990
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
42067
42991
|
});
|
|
42068
|
-
return {
|
|
42069
|
-
|
|
42070
|
-
|
|
42071
|
-
|
|
42992
|
+
return boundedOscillator({
|
|
42993
|
+
key: "uo",
|
|
42994
|
+
title: "UO",
|
|
42995
|
+
values,
|
|
42996
|
+
scale: { top: 100, bottom: 0 },
|
|
42997
|
+
overbought: num3(inputs, "overbought", 70),
|
|
42998
|
+
oversold: num3(inputs, "oversold", 30),
|
|
42999
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
43000
|
+
levelInk: str3(inputs, "levelsColor", NEUTRAL),
|
|
43001
|
+
zoneTransparency: 91
|
|
43002
|
+
});
|
|
42072
43003
|
}
|
|
42073
43004
|
};
|
|
42074
43005
|
var rvgi = {
|
|
@@ -42076,7 +43007,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42076
43007
|
title: "Relative Vigor Index",
|
|
42077
43008
|
shortTitle: "RVGI",
|
|
42078
43009
|
overlay: false,
|
|
42079
|
-
inputs: [
|
|
43010
|
+
inputs: [
|
|
43011
|
+
lengthInput(10, "length", "Length", 5e3, "Number of bars summed for the smoothed numerator (close \u2212 open) and denominator (high \u2212 low)."),
|
|
43012
|
+
colorInput(BULLISH, "RVGI", "color", "Color of the RVGI line."),
|
|
43013
|
+
colorInput(BEARISH, "Signal", "signalColor", "Color of the signal line.")
|
|
43014
|
+
],
|
|
42080
43015
|
compute: (bars, inputs) => {
|
|
42081
43016
|
const len = num3(inputs, "length", 10);
|
|
42082
43017
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -42084,31 +43019,49 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42084
43019
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
42085
43020
|
return {
|
|
42086
43021
|
plots: [
|
|
42087
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
42088
|
-
{ key: "signal", title: "Signal", values: swma(line), color:
|
|
43022
|
+
{ key: "rvgi", title: "RVGI", values: line, color: str3(inputs, "color", BULLISH) },
|
|
43023
|
+
{ key: "signal", title: "Signal", values: swma(line), color: str3(inputs, "signalColor", BEARISH) }
|
|
42089
43024
|
],
|
|
42090
|
-
levels:
|
|
43025
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
42091
43026
|
};
|
|
42092
43027
|
}
|
|
42093
43028
|
};
|
|
43029
|
+
var RVI_EMA = "EMA";
|
|
43030
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
42094
43031
|
var relVolatility = {
|
|
42095
43032
|
type: "relative-volatility-index",
|
|
42096
43033
|
title: "Relative Volatility Index",
|
|
42097
43034
|
shortTitle: "RVI",
|
|
42098
43035
|
overlay: false,
|
|
42099
|
-
inputs: [
|
|
43036
|
+
inputs: [
|
|
43037
|
+
lengthInput(10, "stdevLength", "Stdev Length", 5e3, "Number of bars used for the standard deviation of closing prices that serves as each bar's volatility reading."),
|
|
43038
|
+
lengthInput(14, "smoothLength", "Smoothing Length", 5e3, "Number of bars used to smooth the up and down volatility streams."),
|
|
43039
|
+
optionInput("smoothType", "Smoothing Type", RVI_EMA, [RVI_EMA, RVI_RMA], "Moving average applied to the volatility streams. EMA is Dorsey's classic calculation; Wilder (RMA) matches platforms that use Wilder smoothing."),
|
|
43040
|
+
...thresholdInputs(80, 20),
|
|
43041
|
+
colorInput(NEUTRAL, "RVI Line", "color", "Color of the RVI line at the midline. Toward either threshold it blends into that threshold\u2019s color."),
|
|
43042
|
+
colorInput(BULLISH, "Up Volatility", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
43043
|
+
colorInput(BEARISH, "Down Volatility", "oversoldColor", "Color of the line and zone shading at and below the oversold level.")
|
|
43044
|
+
],
|
|
42100
43045
|
compute: (bars, inputs) => {
|
|
43046
|
+
const c = closes(bars);
|
|
43047
|
+
const sigma = stdev(c, num3(inputs, "stdevLength", 10));
|
|
43048
|
+
const d = change(c);
|
|
43049
|
+
const up = zip(sigma, d, (s, x) => x > 0 ? s : 0);
|
|
43050
|
+
const down = zip(sigma, d, (s, x) => x < 0 ? s : 0);
|
|
42101
43051
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
42102
|
-
const
|
|
42103
|
-
const
|
|
42104
|
-
|
|
42105
|
-
|
|
42106
|
-
|
|
42107
|
-
|
|
42108
|
-
|
|
42109
|
-
|
|
42110
|
-
|
|
42111
|
-
|
|
43052
|
+
const smooth = str3(inputs, "smoothType", RVI_EMA) === RVI_RMA ? rma : ema;
|
|
43053
|
+
const values = zip(smooth(up, smoothLen), smooth(down, smoothLen), (u, w) => u + w === 0 ? 0 : 100 * u / (u + w));
|
|
43054
|
+
return boundedOscillator({
|
|
43055
|
+
key: "rvi",
|
|
43056
|
+
title: "RVI",
|
|
43057
|
+
values,
|
|
43058
|
+
scale: { top: 100, bottom: 0 },
|
|
43059
|
+
overbought: num3(inputs, "overbought", 80),
|
|
43060
|
+
oversold: num3(inputs, "oversold", 20),
|
|
43061
|
+
ink: { low: str3(inputs, "oversoldColor", BEARISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BULLISH) },
|
|
43062
|
+
levelInk: NEUTRAL,
|
|
43063
|
+
width: 2
|
|
43064
|
+
});
|
|
42112
43065
|
}
|
|
42113
43066
|
};
|
|
42114
43067
|
var bop = {
|
|
@@ -42116,28 +43069,43 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42116
43069
|
title: "Balance of Power",
|
|
42117
43070
|
shortTitle: "BOP",
|
|
42118
43071
|
overlay: false,
|
|
42119
|
-
inputs: [
|
|
43072
|
+
inputs: [
|
|
43073
|
+
boolInput("smoothing", "Smoothing", false, void 0, "Smooth the raw Balance of Power with a simple moving average instead of plotting the single-bar value."),
|
|
43074
|
+
lengthInput(14, "smoothLength", "Length", 5e3, "Simple moving average length applied when smoothing is enabled."),
|
|
43075
|
+
colorInput(NEUTRAL, "BOP Color", "color", "Color of the line at exactly zero."),
|
|
43076
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the Balance of Power is above zero \u2014 buyers in control of the bar."),
|
|
43077
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the Balance of Power is below zero \u2014 sellers in control of the bar.")
|
|
43078
|
+
],
|
|
42120
43079
|
compute: (bars, inputs) => {
|
|
42121
|
-
const
|
|
42122
|
-
|
|
42123
|
-
|
|
42124
|
-
|
|
42125
|
-
|
|
43080
|
+
const raw = bars.map((b) => b.high === b.low ? 0 : (b.close - b.open) / (b.high - b.low));
|
|
43081
|
+
const values = bool(inputs, "smoothing", false) ? sma(raw, num3(inputs, "smoothLength", 14)) : raw;
|
|
43082
|
+
return centeredOscillator({
|
|
43083
|
+
key: "bop",
|
|
43084
|
+
title: "BOP",
|
|
43085
|
+
values,
|
|
43086
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
43087
|
+
zeroInk: NEUTRAL,
|
|
43088
|
+
valueTransparency: 60
|
|
43089
|
+
});
|
|
42126
43090
|
}
|
|
42127
43091
|
};
|
|
42128
43092
|
var elderRay = {
|
|
42129
43093
|
type: "elder-ray",
|
|
42130
43094
|
title: "Elder Ray",
|
|
42131
43095
|
overlay: false,
|
|
42132
|
-
inputs: [
|
|
43096
|
+
inputs: [
|
|
43097
|
+
lengthInput(13, "length", "EMA Length", 5e3, "Number of bars in the exponential moving average of closing prices used as the reference level for Bull Power and Bear Power. Alexander Elder's classic setting is 13."),
|
|
43098
|
+
colorInput(BULLISH, "Bull Power", "bullColor", "Color of the Bull Power columns."),
|
|
43099
|
+
colorInput(BEARISH, "Bear Power", "bearColor", "Color of the Bear Power columns.")
|
|
43100
|
+
],
|
|
42133
43101
|
compute: (bars, inputs) => {
|
|
42134
43102
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
42135
43103
|
return {
|
|
42136
43104
|
plots: [
|
|
42137
|
-
{ key: "bull", title: "Bull
|
|
42138
|
-
{ key: "bear", title: "Bear
|
|
43105
|
+
{ key: "bull", title: "Bull Power", values: sub(highs(bars), basis), kind: "columns", color: str3(inputs, "bullColor", BULLISH), base: 0 },
|
|
43106
|
+
{ key: "bear", title: "Bear Power", values: sub(lows(bars), basis), kind: "columns", color: str3(inputs, "bearColor", BEARISH), base: 0 }
|
|
42139
43107
|
],
|
|
42140
|
-
levels:
|
|
43108
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
42141
43109
|
};
|
|
42142
43110
|
}
|
|
42143
43111
|
};
|
|
@@ -42146,31 +43114,66 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42146
43114
|
title: "TTM Squeeze",
|
|
42147
43115
|
overlay: false,
|
|
42148
43116
|
inputs: [
|
|
42149
|
-
lengthInput(20),
|
|
42150
|
-
|
|
42151
|
-
|
|
43117
|
+
{ ...lengthInput(20, "length", "Length", 5e3, "Shared lookback for the Bollinger Bands, the Keltner Channels, and the momentum regression. John Carter's original setting is 20."), min: 2 },
|
|
43118
|
+
sourceInput("Close", "source", "Source", "Price series used for the shared moving average basis, the standard deviation, and the momentum displacement."),
|
|
43119
|
+
floatInput("bbMult", "Bollinger Bands Multiplier", 2, 0.1, 50, 0.1, "Standard deviation multiplier for the Bollinger Bands."),
|
|
43120
|
+
floatInput("kcMult", "Keltner Channels Multiplier", 1.5, 0.1, 50, 0.1, "ATR multiplier for the Keltner Channels. A smaller multiplier demands a tighter compression before the squeeze turns on."),
|
|
43121
|
+
colorInput(BULLISH, "Momentum Up", "bullColor", "Histogram color while momentum is positive. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
43122
|
+
colorInput(BEARISH, "Momentum Down", "bearColor", "Histogram color while momentum is negative. Bars building away from zero print solid, bars fading back print dimmed."),
|
|
43123
|
+
colorInput(WARNING, "Squeeze On", "squeezeOnColor", "Zero-line dot color while both Bollinger Bands sit fully inside the Keltner Channels."),
|
|
43124
|
+
colorInput(NEUTRAL, "Squeeze Off", "squeezeOffColor", "Zero-line dot color while the Bollinger Bands trade outside the Keltner Channels."),
|
|
43125
|
+
boolInput("markFires", "Mark Squeeze Fires", true, STYLE, "Draws a cross on the zero line on the first bar where the bands re-emerge outside the channels, colored by the side of the momentum histogram.")
|
|
42152
43126
|
],
|
|
42153
43127
|
compute: (bars, inputs) => {
|
|
42154
43128
|
const len = num3(inputs, "length", 20);
|
|
42155
|
-
const
|
|
42156
|
-
const basis = sma(
|
|
42157
|
-
const dev = map(stdev(
|
|
42158
|
-
const kcRange = map(
|
|
42159
|
-
const
|
|
42160
|
-
const anchor = zip(
|
|
42161
|
-
const mom = linreg(sub(
|
|
42162
|
-
const
|
|
43129
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
43130
|
+
const basis = sma(src, len);
|
|
43131
|
+
const dev = map(stdev(src, len), (x) => x * num3(inputs, "bbMult", 2));
|
|
43132
|
+
const kcRange = map(atr(bars, len), (x) => x * num3(inputs, "kcMult", 1.5));
|
|
43133
|
+
const rangeMid = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => (h + l) / 2);
|
|
43134
|
+
const anchor = zip(rangeMid, basis, (d, b) => (d + b) / 2);
|
|
43135
|
+
const mom = linreg(sub(src, anchor), len, 0);
|
|
43136
|
+
const squeezeOn = basis.map((b, i) => {
|
|
42163
43137
|
const d = dev[i];
|
|
42164
43138
|
const r = kcRange[i];
|
|
42165
|
-
|
|
43139
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
43140
|
+
return d < r;
|
|
42166
43141
|
});
|
|
42167
|
-
const
|
|
42168
|
-
|
|
42169
|
-
|
|
42170
|
-
|
|
42171
|
-
|
|
42172
|
-
|
|
42173
|
-
|
|
43142
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
43143
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
43144
|
+
const onInk = str3(inputs, "squeezeOnColor", WARNING);
|
|
43145
|
+
const offInk = str3(inputs, "squeezeOffColor", NEUTRAL);
|
|
43146
|
+
const momColors = mom.map((x, i) => {
|
|
43147
|
+
if (!Number.isFinite(x)) return null;
|
|
43148
|
+
const prev3 = i > 0 && Number.isFinite(mom[i - 1]) ? mom[i - 1] : x;
|
|
43149
|
+
const rising = x > prev3;
|
|
43150
|
+
return x >= 0 ? rising ? bull : transp(bull, 60) : rising ? transp(bear, 60) : bear;
|
|
43151
|
+
});
|
|
43152
|
+
const plots = [
|
|
43153
|
+
{ key: "mom", title: "Momentum", values: mom, kind: "columns", color: bull, colors: momColors, base: 0 },
|
|
43154
|
+
{
|
|
43155
|
+
key: "state",
|
|
43156
|
+
title: "Squeeze State",
|
|
43157
|
+
values: squeezeOn.map((s) => s == null ? Number.NaN : 0),
|
|
43158
|
+
kind: "circles",
|
|
43159
|
+
color: offInk,
|
|
43160
|
+
width: 2,
|
|
43161
|
+
colors: squeezeOn.map((s) => s == null ? null : s ? onInk : offInk)
|
|
43162
|
+
}
|
|
43163
|
+
];
|
|
43164
|
+
if (bool(inputs, "markFires", true)) {
|
|
43165
|
+
const fires = squeezeOn.map((s, i) => s === false && squeezeOn[i - 1] === true ? 0 : Number.NaN);
|
|
43166
|
+
plots.push({
|
|
43167
|
+
key: "fire",
|
|
43168
|
+
title: "Squeeze Fire",
|
|
43169
|
+
values: fires,
|
|
43170
|
+
kind: "cross",
|
|
43171
|
+
color: offInk,
|
|
43172
|
+
width: 3,
|
|
43173
|
+
colors: fires.map((x, i) => Number.isFinite(x) ? Number.isFinite(mom[i]) ? mom[i] >= 0 ? bull : bear : offInk : null)
|
|
43174
|
+
});
|
|
43175
|
+
}
|
|
43176
|
+
return { plots };
|
|
42174
43177
|
}
|
|
42175
43178
|
};
|
|
42176
43179
|
var oscillatorSpecs = [
|
|
@@ -42182,7 +43185,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42182
43185
|
awesome,
|
|
42183
43186
|
cci,
|
|
42184
43187
|
williamsR,
|
|
42185
|
-
|
|
43188
|
+
cmoSpec,
|
|
42186
43189
|
connorsRsi,
|
|
42187
43190
|
fisher,
|
|
42188
43191
|
trix,
|
|
@@ -42206,15 +43209,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42206
43209
|
type: "aroon",
|
|
42207
43210
|
title: "Aroon",
|
|
42208
43211
|
overlay: false,
|
|
42209
|
-
inputs: [
|
|
43212
|
+
inputs: [
|
|
43213
|
+
lengthInput(14, "length", "Length", 5e3, "Lookback length used to locate the highest high and lowest low."),
|
|
43214
|
+
colorInput(BULLISH, "Aroon Up", "upColor", "Color of the Aroon Up line."),
|
|
43215
|
+
colorInput(BEARISH, "Aroon Down", "downColor", "Color of the Aroon Down line.")
|
|
43216
|
+
],
|
|
42210
43217
|
compute: (bars, inputs) => {
|
|
42211
43218
|
const len = num3(inputs, "length", 14);
|
|
42212
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
42213
|
-
const down = map(barsSinceLowest(lows(bars), len
|
|
43219
|
+
const up = map(barsSinceHighest(highs(bars), len), (x) => 100 * (len - x) / len);
|
|
43220
|
+
const down = map(barsSinceLowest(lows(bars), len), (x) => 100 * (len - x) / len);
|
|
42214
43221
|
return {
|
|
42215
43222
|
plots: [
|
|
42216
|
-
{ key: "up", title: "Aroon
|
|
42217
|
-
{ key: "down", title: "Aroon
|
|
43223
|
+
{ key: "up", title: "Aroon Up", values: up, color: str3(inputs, "upColor", BULLISH) },
|
|
43224
|
+
{ key: "down", title: "Aroon Down", values: down, color: str3(inputs, "downColor", BEARISH) }
|
|
42218
43225
|
]
|
|
42219
43226
|
};
|
|
42220
43227
|
}
|
|
@@ -42224,10 +43231,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42224
43231
|
title: "Average Directional Index",
|
|
42225
43232
|
shortTitle: "ADX",
|
|
42226
43233
|
overlay: false,
|
|
42227
|
-
inputs: [
|
|
43234
|
+
inputs: [
|
|
43235
|
+
lengthInput(14, "diLength", "DI Length", 5e3, "Wilder RMA smoothing length applied to directional movement and true range when computing +DI and \u2212DI."),
|
|
43236
|
+
lengthInput(14, "adxLength", "ADX Smoothing", 5e3, "Wilder RMA smoothing length applied to DX to obtain the ADX line."),
|
|
43237
|
+
floatInput("keyLevel", "Key Level", 25, 0, 100, 1, "Trend strength reference level; ADX above it is commonly read as a trending market."),
|
|
43238
|
+
colorInput(BULLISH, "+DI Color", "plusDiColor", "Color of the plus directional indicator, and of the ADX line above the key level while +DI leads."),
|
|
43239
|
+
colorInput(BEARISH, "-DI Color", "minusDiColor", "Color of the minus directional indicator, and of the ADX line above the key level while \u2212DI leads."),
|
|
43240
|
+
colorInput(NEUTRAL, "ADX Color", "adxColor", "Color of the ADX line while it sits below the key level \u2014 no trend strong enough to trade.")
|
|
43241
|
+
],
|
|
42228
43242
|
compute: (bars, inputs) => {
|
|
42229
43243
|
const diLen = num3(inputs, "diLength", 14);
|
|
42230
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
42231
43244
|
const n = bars.length;
|
|
42232
43245
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
42233
43246
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -42241,13 +43254,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42241
43254
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
42242
43255
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
42243
43256
|
const dx = zip(plusDi, minusDi, (p, m) => p + m === 0 ? 0 : 100 * Math.abs(p - m) / (p + m));
|
|
43257
|
+
const adxLine = rma(dx, num3(inputs, "adxLength", 14));
|
|
43258
|
+
const plusInk = str3(inputs, "plusDiColor", BULLISH);
|
|
43259
|
+
const minusInk = str3(inputs, "minusDiColor", BEARISH);
|
|
43260
|
+
const neutralInk = str3(inputs, "adxColor", NEUTRAL);
|
|
43261
|
+
const keyLevel = num3(inputs, "keyLevel", 25);
|
|
43262
|
+
const adxColors = adxLine.map((x, i) => {
|
|
43263
|
+
if (!Number.isFinite(x)) return null;
|
|
43264
|
+
if (x <= keyLevel) return neutralInk;
|
|
43265
|
+
return (plusDi[i] ?? 0) > (minusDi[i] ?? 0) ? plusInk : minusInk;
|
|
43266
|
+
});
|
|
42244
43267
|
return {
|
|
42245
43268
|
plots: [
|
|
42246
|
-
{ key: "
|
|
42247
|
-
{ key: "
|
|
42248
|
-
{ key: "
|
|
43269
|
+
{ key: "plusDi", title: "+DI", values: plusDi, color: transp(plusInk, 30) },
|
|
43270
|
+
{ key: "minusDi", title: "-DI", values: minusDi, color: transp(minusInk, 30) },
|
|
43271
|
+
{ key: "adx", title: "ADX", values: adxLine, color: neutralInk, width: 2, colors: adxColors }
|
|
42249
43272
|
],
|
|
42250
|
-
levels: [{ key: "
|
|
43273
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
42251
43274
|
};
|
|
42252
43275
|
}
|
|
42253
43276
|
};
|
|
@@ -42256,7 +43279,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42256
43279
|
title: "Vortex Indicator",
|
|
42257
43280
|
shortTitle: "VI",
|
|
42258
43281
|
overlay: false,
|
|
42259
|
-
inputs: [
|
|
43282
|
+
inputs: [
|
|
43283
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum the vortex movements (VM+ and VM\u2212) and the true range."),
|
|
43284
|
+
colorInput(BULLISH, "VI+", "plusColor", "Color of the positive vortex line (VI+)."),
|
|
43285
|
+
colorInput(BEARISH, "VI-", "minusColor", "Color of the negative vortex line (VI\u2212)."),
|
|
43286
|
+
colorInput(NEUTRAL, "Baseline", "baselineColor", "Color of the dotted baseline drawn at 1.")
|
|
43287
|
+
],
|
|
42260
43288
|
compute: (bars, inputs) => {
|
|
42261
43289
|
const len = num3(inputs, "length", 14);
|
|
42262
43290
|
const n = bars.length;
|
|
@@ -42269,25 +43297,48 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42269
43297
|
const trSum = sum(trueRange(bars), len);
|
|
42270
43298
|
return {
|
|
42271
43299
|
plots: [
|
|
42272
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
42273
|
-
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
43300
|
+
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "plusColor", BULLISH) },
|
|
43301
|
+
{ key: "viMinus", title: "VI-", values: zip(sum(vmMinus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color: str3(inputs, "minusColor", BEARISH) }
|
|
42274
43302
|
],
|
|
42275
|
-
levels: [{ key: "
|
|
43303
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
42276
43304
|
};
|
|
42277
43305
|
}
|
|
42278
43306
|
};
|
|
42279
43307
|
var trendSpecs = [aroon, adx, vortex];
|
|
42280
43308
|
|
|
42281
43309
|
// src/core/native-indicators/classics/volatility.ts
|
|
43310
|
+
function expansionColors(values, calm, expanding) {
|
|
43311
|
+
return values.map((x, i) => {
|
|
43312
|
+
if (!Number.isFinite(x)) return null;
|
|
43313
|
+
const prev3 = i > 0 && Number.isFinite(values[i - 1]) ? values[i - 1] : x;
|
|
43314
|
+
return x > prev3 ? expanding : calm;
|
|
43315
|
+
});
|
|
43316
|
+
}
|
|
43317
|
+
function washedToZero(key, title, values, colors, fallback, valueTransparency, width = 1) {
|
|
43318
|
+
return {
|
|
43319
|
+
plots: [{ key, title, values, color: fallback, width, colors }, anchorPlot("zeroAnchor", "Zero Anchor", values.length, 0)],
|
|
43320
|
+
bands: [{ key: "wash", from: key, to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? fallback, valueTransparency) }]
|
|
43321
|
+
};
|
|
43322
|
+
}
|
|
42282
43323
|
var atrSpec = {
|
|
42283
43324
|
type: "average-true-range",
|
|
42284
43325
|
title: "Average True Range",
|
|
42285
43326
|
shortTitle: "ATR",
|
|
42286
43327
|
overlay: false,
|
|
42287
|
-
inputs: [
|
|
42288
|
-
|
|
42289
|
-
|
|
42290
|
-
|
|
43328
|
+
inputs: [
|
|
43329
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to average the true range."),
|
|
43330
|
+
optionInput("smoothing", "Smoothing", "RMA", ["RMA", "SMA", "EMA", "WMA"], "Moving average applied to the true range. RMA is Wilder's original smoothing used by the classic ATR."),
|
|
43331
|
+
colorInput(NEUTRAL, "ATR Line", "color", "Color of the ATR line while volatility contracts or holds flat."),
|
|
43332
|
+
colorInput(WARNING, "Expanding", "expandColor", "Color of the ATR line while it rises \u2014 volatility expanding bar to bar.")
|
|
43333
|
+
],
|
|
43334
|
+
compute: (bars, inputs) => {
|
|
43335
|
+
const len = num3(inputs, "length", 14);
|
|
43336
|
+
const tr = trueRange(bars);
|
|
43337
|
+
const kind = str3(inputs, "smoothing", "RMA");
|
|
43338
|
+
const values = kind === "SMA" ? sma(tr, len) : kind === "EMA" ? ema(tr, len) : kind === "WMA" ? wma(tr, len) : rma(tr, len);
|
|
43339
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
43340
|
+
return washedToZero("atr", "ATR", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 75);
|
|
43341
|
+
}
|
|
42291
43342
|
};
|
|
42292
43343
|
var historicalVolatility = {
|
|
42293
43344
|
type: "historical-volatility",
|
|
@@ -42295,14 +43346,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42295
43346
|
shortTitle: "HV",
|
|
42296
43347
|
overlay: false,
|
|
42297
43348
|
inputs: [
|
|
42298
|
-
lengthInput(10),
|
|
42299
|
-
|
|
42300
|
-
|
|
43349
|
+
{ ...lengthInput(10, "length", "Length", 5e3, "Lookback window in bars for the standard deviation of close-to-close log returns."), min: 2 },
|
|
43350
|
+
boolInput("annualize", "Annualize", true, void 0, "Scale the per-bar volatility to an annual figure by multiplying by the square root of the periods per year."),
|
|
43351
|
+
intInput("periodsPerYear", "Periods Per Year", 252, 1, 1e5, "Number of periods in a year used for annualization: 252 for daily bars, 52 for weekly, 12 for monthly."),
|
|
43352
|
+
colorInput(WARNING, "Volatility Line", "color", "Color of the line at its recent highs; it fades toward grey as volatility contracts.")
|
|
42301
43353
|
],
|
|
42302
43354
|
compute: (bars, inputs) => {
|
|
42303
|
-
const
|
|
42304
|
-
const
|
|
42305
|
-
|
|
43355
|
+
const len = num3(inputs, "length", 10);
|
|
43356
|
+
const logReturns = sub(map(closes(bars), Math.log), shift3(map(closes(bars), Math.log), 1));
|
|
43357
|
+
const meanReturn = map(sum(logReturns, len), (s) => s / len);
|
|
43358
|
+
const squaredSum = sum(map(logReturns, (r) => r * r), len);
|
|
43359
|
+
const factor = bool(inputs, "annualize", true) ? Math.sqrt(num3(inputs, "periodsPerYear", 252)) : 1;
|
|
43360
|
+
const values = zip(squaredSum, meanReturn, (sq, m) => Math.sqrt(Math.max(sq - len * m * m, 0) / (len - 1)) * factor * 100);
|
|
43361
|
+
const low = lowest(values, 100);
|
|
43362
|
+
const high = highest(values, 100);
|
|
43363
|
+
const accent = str3(inputs, "color", WARNING);
|
|
43364
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? gradient(x, low[i] ?? 0, high[i] ?? 0, NEUTRAL, accent) : null);
|
|
43365
|
+
return washedToZero("hv", "Historical Volatility", values, colors, accent, 75, 2);
|
|
42306
43366
|
}
|
|
42307
43367
|
};
|
|
42308
43368
|
var chaikinVolatility = {
|
|
@@ -42310,13 +43370,26 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42310
43370
|
title: "Chaikin Volatility",
|
|
42311
43371
|
shortTitle: "CHV",
|
|
42312
43372
|
overlay: false,
|
|
42313
|
-
inputs: [
|
|
43373
|
+
inputs: [
|
|
43374
|
+
lengthInput(10, "emaLength", "EMA Length", 5e3, "Length of the exponential moving average applied to the bar high-low range."),
|
|
43375
|
+
lengthInput(10, "rocLength", "ROC Length", 5e3, "Number of bars used for the rate of change of the smoothed high-low range."),
|
|
43376
|
+
colorInput(NEUTRAL, "CHV Color", "color", "Color of the Chaikin Volatility line."),
|
|
43377
|
+
colorInput(WARNING, "Expansion", "expansionColor", "Line and fill color while the reading is above zero \u2014 the smoothed range is wider than it was ROC Length bars ago.")
|
|
43378
|
+
],
|
|
42314
43379
|
compute: (bars, inputs) => {
|
|
42315
43380
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
42316
|
-
|
|
42317
|
-
|
|
42318
|
-
|
|
42319
|
-
|
|
43381
|
+
const prior = shift3(range, num3(inputs, "rocLength", 10));
|
|
43382
|
+
const values = zip(range, prior, (r, p) => p === 0 ? Number.NaN : 100 * (r - p) / p);
|
|
43383
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
43384
|
+
const expansion = str3(inputs, "expansionColor", WARNING);
|
|
43385
|
+
return centeredOscillator({
|
|
43386
|
+
key: "chv",
|
|
43387
|
+
title: "Chaikin Volatility",
|
|
43388
|
+
values,
|
|
43389
|
+
// Only expansion is a state worth accenting; contraction stays structural grey.
|
|
43390
|
+
ink: { bull: expansion, bear: calm, neutral: calm },
|
|
43391
|
+
zeroInk: NEUTRAL
|
|
43392
|
+
});
|
|
42320
43393
|
}
|
|
42321
43394
|
};
|
|
42322
43395
|
var massIndex = {
|
|
@@ -42324,14 +43397,42 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42324
43397
|
title: "Mass Index",
|
|
42325
43398
|
shortTitle: "MI",
|
|
42326
43399
|
overlay: false,
|
|
42327
|
-
inputs: [
|
|
43400
|
+
inputs: [
|
|
43401
|
+
lengthInput(25, "length", "Sum Length", 5e3, "Number of bars over which the single/double EMA ratio is summed."),
|
|
43402
|
+
lengthInput(9, "emaLength", "EMA Length", 5e3, "Length of the single EMA of the high-low range and of its double smoothing."),
|
|
43403
|
+
floatInput("setupLevel", "Setup Level", 27, 0, 1e3, 0.1, "Reversal bulge set-up level; a rise above it arms the pattern."),
|
|
43404
|
+
floatInput("triggerLevel", "Trigger Level", 26.5, 0, 1e3, 0.1, "Reversal bulge trigger level; a decline below it after a set-up completes the pattern."),
|
|
43405
|
+
colorInput(NEUTRAL, "Mass Index", "color", "Line color while no reversal bulge is armed."),
|
|
43406
|
+
colorInput(WARNING, "Armed", "armedColor", "Line color from the rise above the setup level until the decline below the trigger level."),
|
|
43407
|
+
colorInput(NEUTRAL, "Levels", "levelsColor", "Reversal bulge levels color.")
|
|
43408
|
+
],
|
|
42328
43409
|
compute: (bars, inputs) => {
|
|
42329
|
-
const
|
|
42330
|
-
const single = ema(
|
|
42331
|
-
const ratio = zip(single, ema(single,
|
|
43410
|
+
const emaLen = num3(inputs, "emaLength", 9);
|
|
43411
|
+
const single = ema(sub(highs(bars), lows(bars)), emaLen);
|
|
43412
|
+
const ratio = zip(single, ema(single, emaLen), (a, b) => b === 0 ? 1 : a / b);
|
|
43413
|
+
const values = sum(ratio, num3(inputs, "length", 25));
|
|
43414
|
+
const setup = num3(inputs, "setupLevel", 27);
|
|
43415
|
+
const trigger = num3(inputs, "triggerLevel", 26.5);
|
|
43416
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
43417
|
+
const armedInk = str3(inputs, "armedColor", WARNING);
|
|
43418
|
+
const levelInk = str3(inputs, "levelsColor", NEUTRAL);
|
|
43419
|
+
let armed = false;
|
|
43420
|
+
const colors = values.map((x) => {
|
|
43421
|
+
if (!Number.isFinite(x)) return null;
|
|
43422
|
+
armed = x > setup ? true : x < trigger ? false : armed;
|
|
43423
|
+
return armed ? armedInk : calm;
|
|
43424
|
+
});
|
|
42332
43425
|
return {
|
|
42333
|
-
plots: [
|
|
42334
|
-
|
|
43426
|
+
plots: [
|
|
43427
|
+
{ key: "mi", title: "Mass Index", values, color: calm, colors },
|
|
43428
|
+
anchorPlot("setupAnchor", "Setup Anchor", values.length, setup),
|
|
43429
|
+
anchorPlot("triggerAnchor", "Trigger Anchor", values.length, trigger)
|
|
43430
|
+
],
|
|
43431
|
+
bands: [{ key: "bulge", from: "setupAnchor", to: "triggerAnchor", color: transp(armedInk, 90) }],
|
|
43432
|
+
levels: [
|
|
43433
|
+
{ key: "setup", price: setup, color: levelInk, lineStyle: "dashed", title: "Setup Level" },
|
|
43434
|
+
{ key: "trigger", price: trigger, color: levelInk, lineStyle: "dashed", title: "Trigger Level" }
|
|
43435
|
+
]
|
|
42335
43436
|
};
|
|
42336
43437
|
}
|
|
42337
43438
|
};
|
|
@@ -42340,30 +43441,83 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42340
43441
|
title: "Standard Deviation",
|
|
42341
43442
|
shortTitle: "StdDev",
|
|
42342
43443
|
overlay: false,
|
|
42343
|
-
inputs: [
|
|
42344
|
-
|
|
42345
|
-
|
|
42346
|
-
|
|
42347
|
-
|
|
42348
|
-
|
|
42349
|
-
|
|
42350
|
-
|
|
42351
|
-
|
|
42352
|
-
|
|
42353
|
-
|
|
42354
|
-
})
|
|
43444
|
+
inputs: [
|
|
43445
|
+
lengthInput(20, "length", "Length", 5e3, "Number of bars used to compute the standard deviation."),
|
|
43446
|
+
sourceInput("Close", "source", "Source", "Price series the standard deviation is calculated on."),
|
|
43447
|
+
colorInput(NEUTRAL, "Standard Deviation", "color", "Line color while volatility contracts or holds flat."),
|
|
43448
|
+
colorInput(WARNING, "Expanding", "expandColor", "Line and fill color while volatility expands bar to bar.")
|
|
43449
|
+
],
|
|
43450
|
+
compute: (bars, inputs) => {
|
|
43451
|
+
const values = stdev(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 20));
|
|
43452
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
43453
|
+
return washedToZero("stdev", "Standard Deviation", values, expansionColors(values, calm, str3(inputs, "expandColor", WARNING)), calm, 80);
|
|
43454
|
+
}
|
|
42355
43455
|
};
|
|
42356
43456
|
var ulcerIndex = {
|
|
42357
43457
|
type: "ulcer-index",
|
|
42358
43458
|
title: "Ulcer Index",
|
|
42359
43459
|
overlay: false,
|
|
42360
|
-
inputs: [
|
|
43460
|
+
inputs: [
|
|
43461
|
+
{ ...lengthInput(14, "length", "Length", 500, "Lookback window. At each bar the percentage drawdown from the window's running maximum is squared; the squares are averaged and the root taken."), min: 2 },
|
|
43462
|
+
sourceInput("Close", "source", "Source", "Price series the Ulcer Index is computed on. Close is the standard choice."),
|
|
43463
|
+
floatInput("threshold", "Alert Threshold", 5, 0, 1e3, 0.25, "Reference level drawn as a dashed line. The index scales with the instrument's volatility and the window length, so set it relative to its own history."),
|
|
43464
|
+
colorInput(NEUTRAL, "Ulcer Index", "color", "Color of the index near zero; it blends toward the stress color as the reading approaches the threshold."),
|
|
43465
|
+
colorInput(BEARISH, "Stress", "stressColor", "Color of the index at and beyond the alert threshold."),
|
|
43466
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Vertical gradient between zero and the Ulcer Index, fully transparent at zero."),
|
|
43467
|
+
boolInput("showDrawdown", "Drawdown %", false, STYLE, "Plot the current percentage drawdown from the window high as columns below zero \u2014 the raw ingredient the index averages."),
|
|
43468
|
+
colorInput(transp(BEARISH, 50), "Drawdown Color", "drawdownColor", "Color of the drawdown columns."),
|
|
43469
|
+
colorInput(NEUTRAL, "Threshold", "thresholdColor", "Color of the alert threshold line.")
|
|
43470
|
+
],
|
|
42361
43471
|
compute: (bars, inputs) => {
|
|
42362
43472
|
const len = num3(inputs, "length", 14);
|
|
42363
|
-
const
|
|
42364
|
-
const
|
|
42365
|
-
const values =
|
|
42366
|
-
|
|
43473
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
43474
|
+
const n = src.length;
|
|
43475
|
+
const values = new Array(n).fill(Number.NaN);
|
|
43476
|
+
const drawdown = new Array(n).fill(Number.NaN);
|
|
43477
|
+
for (let i = len - 1; i < n; i++) {
|
|
43478
|
+
let runMax = Number.NaN;
|
|
43479
|
+
let sumSq = 0;
|
|
43480
|
+
let valid = true;
|
|
43481
|
+
let last2 = Number.NaN;
|
|
43482
|
+
for (let k = i - len + 1; k <= i; k++) {
|
|
43483
|
+
const price = src[k];
|
|
43484
|
+
if (!Number.isFinite(price)) {
|
|
43485
|
+
valid = false;
|
|
43486
|
+
break;
|
|
43487
|
+
}
|
|
43488
|
+
runMax = Number.isFinite(runMax) ? Math.max(runMax, price) : price;
|
|
43489
|
+
if (runMax <= 0) {
|
|
43490
|
+
valid = false;
|
|
43491
|
+
break;
|
|
43492
|
+
}
|
|
43493
|
+
last2 = 100 * (price - runMax) / runMax;
|
|
43494
|
+
sumSq += last2 * last2;
|
|
43495
|
+
}
|
|
43496
|
+
if (!valid) continue;
|
|
43497
|
+
values[i] = Math.sqrt(sumSq / len);
|
|
43498
|
+
drawdown[i] = last2;
|
|
43499
|
+
}
|
|
43500
|
+
const calm = str3(inputs, "color", NEUTRAL);
|
|
43501
|
+
const stress = str3(inputs, "stressColor", BEARISH);
|
|
43502
|
+
const threshold = num3(inputs, "threshold", 5);
|
|
43503
|
+
const colors = values.map((x) => Number.isFinite(x) ? threshold > 0 ? gradient(x, 0, threshold, calm, stress) : stress : null);
|
|
43504
|
+
const plots = [{ key: "ui", title: "Ulcer Index", values, color: calm, colors }];
|
|
43505
|
+
const bands = [];
|
|
43506
|
+
if (bool(inputs, "gradientFill", true)) {
|
|
43507
|
+
plots.push(anchorPlot("zeroAnchor", "Zero Base", n, 0));
|
|
43508
|
+
bands.push({ key: "wash", from: "ui", to: "zeroAnchor", color: transp(NEUTRAL, 100), gradient: baselineGradient(values, 0, (_x, i) => colors[i] ?? calm) });
|
|
43509
|
+
}
|
|
43510
|
+
if (bool(inputs, "showDrawdown", false)) {
|
|
43511
|
+
plots.push({ key: "dd", title: "Drawdown %", values: drawdown, kind: "columns", color: str3(inputs, "drawdownColor", transp(BEARISH, 50)), base: 0 });
|
|
43512
|
+
}
|
|
43513
|
+
return {
|
|
43514
|
+
plots,
|
|
43515
|
+
bands,
|
|
43516
|
+
levels: [
|
|
43517
|
+
{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dotted", title: "Zero Level" },
|
|
43518
|
+
{ key: "threshold", price: threshold, color: str3(inputs, "thresholdColor", NEUTRAL), lineStyle: "dashed", title: "Alert Threshold" }
|
|
43519
|
+
]
|
|
43520
|
+
};
|
|
42367
43521
|
}
|
|
42368
43522
|
};
|
|
42369
43523
|
var choppiness = {
|
|
@@ -42371,19 +43525,33 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42371
43525
|
title: "Choppiness Index",
|
|
42372
43526
|
shortTitle: "CHOP",
|
|
42373
43527
|
overlay: false,
|
|
42374
|
-
inputs: [
|
|
43528
|
+
inputs: [
|
|
43529
|
+
{ ...lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum true ranges and to measure the highest high \u2212 lowest low range."), min: 2 },
|
|
43530
|
+
floatInput("upperBand", "Upper Band", 61.8, 0, 100, 0.1, "Readings above this level indicate a choppy, range-bound market."),
|
|
43531
|
+
floatInput("lowerBand", "Lower Band", 38.2, 0, 100, 0.1, "Readings below this level indicate a strong trend."),
|
|
43532
|
+
colorInput(NEUTRAL, "CHOP Line", "color", "Color of the Choppiness Index line."),
|
|
43533
|
+
colorInput(BULLISH, "Trending", "trendingColor", "Line, fill and zone color toward and below the lower band \u2014 a trending market."),
|
|
43534
|
+
colorInput(BEARISH, "Choppy", "choppyColor", "Line, fill and zone color toward and above the upper band \u2014 a choppy, range-bound market."),
|
|
43535
|
+
colorInput(transp(NEUTRAL, 90), "Band Fill", "fillColor", "Fill color of the zone between the upper and lower bands.")
|
|
43536
|
+
],
|
|
42375
43537
|
compute: (bars, inputs) => {
|
|
42376
43538
|
const len = num3(inputs, "length", 14);
|
|
42377
43539
|
const trSum = sum(trueRange(bars), len);
|
|
42378
43540
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
42379
43541
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
42380
|
-
return {
|
|
42381
|
-
|
|
42382
|
-
|
|
42383
|
-
|
|
42384
|
-
|
|
42385
|
-
|
|
42386
|
-
|
|
43542
|
+
return boundedOscillator({
|
|
43543
|
+
key: "chop",
|
|
43544
|
+
title: "CHOP",
|
|
43545
|
+
values,
|
|
43546
|
+
scale: { top: 100, bottom: 0 },
|
|
43547
|
+
overbought: num3(inputs, "upperBand", 61.8),
|
|
43548
|
+
oversold: num3(inputs, "lowerBand", 38.2),
|
|
43549
|
+
ink: { low: str3(inputs, "trendingColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "choppyColor", BEARISH) },
|
|
43550
|
+
levelInk: NEUTRAL,
|
|
43551
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 90)),
|
|
43552
|
+
midpointWash: true,
|
|
43553
|
+
levelTitles: { overbought: "Upper Band", oversold: "Lower Band" }
|
|
43554
|
+
});
|
|
42387
43555
|
}
|
|
42388
43556
|
};
|
|
42389
43557
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -42396,17 +43564,85 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42396
43564
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
42397
43565
|
});
|
|
42398
43566
|
}
|
|
43567
|
+
function slopeColors2(values, bull, bear, flat) {
|
|
43568
|
+
return values.map((x, i) => {
|
|
43569
|
+
if (!Number.isFinite(x)) return null;
|
|
43570
|
+
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
43571
|
+
if (!Number.isFinite(prev3)) return flat;
|
|
43572
|
+
return x > prev3 ? bull : x < prev3 ? bear : flat;
|
|
43573
|
+
});
|
|
43574
|
+
}
|
|
43575
|
+
function volumeIndex(bars, src, base, when) {
|
|
43576
|
+
const out = new Array(bars.length).fill(Number.NaN);
|
|
43577
|
+
let index = base;
|
|
43578
|
+
for (let i = 0; i < bars.length; i++) {
|
|
43579
|
+
const v = bars[i].volume;
|
|
43580
|
+
const pv = i > 0 ? bars[i - 1].volume : null;
|
|
43581
|
+
const prev3 = i > 0 ? src[i - 1] : Number.NaN;
|
|
43582
|
+
const active = v != null && pv != null && Number.isFinite(v) && Number.isFinite(pv) && when(v, pv);
|
|
43583
|
+
if (active && Number.isFinite(prev3) && prev3 !== 0) index *= 1 + (src[i] - prev3) / prev3;
|
|
43584
|
+
out[i] = index;
|
|
43585
|
+
}
|
|
43586
|
+
return out;
|
|
43587
|
+
}
|
|
43588
|
+
function regimeIndex(key, title, values, signal, ink, showSignal, gradientFill, singleColor = false) {
|
|
43589
|
+
const inkOf = (i) => {
|
|
43590
|
+
const s = signal[i];
|
|
43591
|
+
const x = values[i];
|
|
43592
|
+
if (singleColor || !Number.isFinite(s)) return ink.neutral;
|
|
43593
|
+
return x > s ? ink.bull : x < s ? ink.bear : ink.neutral;
|
|
43594
|
+
};
|
|
43595
|
+
const plots = [
|
|
43596
|
+
{
|
|
43597
|
+
key: "signal",
|
|
43598
|
+
title: "Signal EMA",
|
|
43599
|
+
values: signal,
|
|
43600
|
+
color: ink.signal,
|
|
43601
|
+
...showSignal ? {} : { display: { pane: false } }
|
|
43602
|
+
},
|
|
43603
|
+
{ key, title, values, color: ink.neutral, width: 2, colors: values.map((x, i) => Number.isFinite(x) ? inkOf(i) : null) }
|
|
43604
|
+
];
|
|
43605
|
+
const bands = gradientFill ? [
|
|
43606
|
+
{
|
|
43607
|
+
key: "regime",
|
|
43608
|
+
from: key,
|
|
43609
|
+
to: "signal",
|
|
43610
|
+
color: transp(NEUTRAL, 100),
|
|
43611
|
+
gradient: values.map((x, i) => {
|
|
43612
|
+
const s = signal[i];
|
|
43613
|
+
if (!Number.isFinite(x) || !Number.isFinite(s)) return null;
|
|
43614
|
+
const above = x > s;
|
|
43615
|
+
const c = above ? ink.bull : ink.bear;
|
|
43616
|
+
return { topValue: Math.max(x, s), bottomValue: Math.min(x, s), topColor: transp(c, above ? 50 : 100), bottomColor: transp(c, above ? 100 : 50) };
|
|
43617
|
+
})
|
|
43618
|
+
}
|
|
43619
|
+
] : [];
|
|
43620
|
+
return { plots, bands };
|
|
43621
|
+
}
|
|
42399
43622
|
var obv = {
|
|
42400
43623
|
type: "on-balance-volume",
|
|
42401
43624
|
title: "On Balance Volume",
|
|
42402
43625
|
shortTitle: "OBV",
|
|
42403
43626
|
overlay: false,
|
|
42404
|
-
inputs: [
|
|
43627
|
+
inputs: [
|
|
43628
|
+
boolInput("smoothing", "Smoothing MA", false, SETTINGS, "Display a moving average of OBV to act as a smoothing/signal line."),
|
|
43629
|
+
optionInput("maType", "MA Type", "SMA", ["SMA", "EMA", "WMA", "RMA (SMMA)"], "Moving average type used for the smoothing MA."),
|
|
43630
|
+
lengthInput(20, "maLength", "MA Length", 5e3, "Number of bars used for the smoothing MA."),
|
|
43631
|
+
colorInput(INFO, "OBV", "color", "Color of the OBV line."),
|
|
43632
|
+
colorInput(WARNING, "Smoothing MA", "maColor", "Color of the smoothing MA line.")
|
|
43633
|
+
],
|
|
42405
43634
|
compute: (bars, inputs) => {
|
|
42406
43635
|
const vol = volumes(bars);
|
|
42407
43636
|
const d = change(closes(bars));
|
|
42408
|
-
const
|
|
42409
|
-
|
|
43637
|
+
const values = cumSum(zip(vol, d, (v, x) => x > 0 ? v : x < 0 ? -v : 0));
|
|
43638
|
+
const plots = [{ key: "obv", title: "OBV", values, color: str3(inputs, "color", INFO) }];
|
|
43639
|
+
if (bool(inputs, "smoothing", false)) {
|
|
43640
|
+
const len = num3(inputs, "maLength", 20);
|
|
43641
|
+
const kind = str3(inputs, "maType", "SMA");
|
|
43642
|
+
const ma = kind === "EMA" ? ema(values, len) : kind === "WMA" ? wma(values, len) : kind === "RMA (SMMA)" ? rma(values, len) : sma(values, len);
|
|
43643
|
+
plots.push({ key: "ma", title: "Smoothing MA", values: ma, color: str3(inputs, "maColor", WARNING) });
|
|
43644
|
+
}
|
|
43645
|
+
return { plots };
|
|
42410
43646
|
}
|
|
42411
43647
|
};
|
|
42412
43648
|
var accDist = {
|
|
@@ -42414,24 +43650,50 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42414
43650
|
title: "Accumulation / Distribution",
|
|
42415
43651
|
shortTitle: "A/D",
|
|
42416
43652
|
overlay: false,
|
|
42417
|
-
inputs: [
|
|
42418
|
-
|
|
42419
|
-
|
|
42420
|
-
|
|
43653
|
+
inputs: [
|
|
43654
|
+
boolInput("slopeColoring", "Slope Coloring", true, STYLE, "Color the line by its slope \u2014 bullish while rising (accumulation), bearish while falling (distribution)."),
|
|
43655
|
+
colorInput(NEUTRAL, "A/D Line", "color", "Color of the line when slope coloring is disabled or the line is flat."),
|
|
43656
|
+
colorInput(BULLISH, "Rising", "bullColor", "Line color while the A/D Line rises."),
|
|
43657
|
+
colorInput(BEARISH, "Falling", "bearColor", "Line color while the A/D Line falls.")
|
|
43658
|
+
],
|
|
43659
|
+
compute: (bars, inputs) => {
|
|
43660
|
+
const values = cumSum(moneyFlowVolume(bars));
|
|
43661
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
43662
|
+
return {
|
|
43663
|
+
plots: [
|
|
43664
|
+
{
|
|
43665
|
+
key: "ad",
|
|
43666
|
+
title: "A/D",
|
|
43667
|
+
values,
|
|
43668
|
+
color: flat,
|
|
43669
|
+
...bool(inputs, "slopeColoring", true) ? { colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) } : {}
|
|
43670
|
+
}
|
|
43671
|
+
]
|
|
43672
|
+
};
|
|
43673
|
+
}
|
|
42421
43674
|
};
|
|
42422
43675
|
var cmf = {
|
|
42423
43676
|
type: "chaikin-money-flow",
|
|
42424
43677
|
title: "Chaikin Money Flow",
|
|
42425
43678
|
shortTitle: "CMF",
|
|
42426
43679
|
overlay: false,
|
|
42427
|
-
inputs: [
|
|
43680
|
+
inputs: [
|
|
43681
|
+
lengthInput(20, "length", "Length", 5e3, "Lookback length over which money flow volume and volume are summed."),
|
|
43682
|
+
colorInput(NEUTRAL, "CMF Line", "color", "Color of the Chaikin Money Flow line."),
|
|
43683
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the CMF reads above zero (net money flowing in)."),
|
|
43684
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the CMF reads below zero (net money flowing out)."),
|
|
43685
|
+
colorInput(NEUTRAL, "Zero Line", "zeroColor", "Color of the zero reference line.")
|
|
43686
|
+
],
|
|
42428
43687
|
compute: (bars, inputs) => {
|
|
42429
43688
|
const len = num3(inputs, "length", 20);
|
|
42430
43689
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
42431
|
-
return {
|
|
42432
|
-
|
|
42433
|
-
|
|
42434
|
-
|
|
43690
|
+
return centeredOscillator({
|
|
43691
|
+
key: "cmf",
|
|
43692
|
+
title: "CMF",
|
|
43693
|
+
values,
|
|
43694
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
43695
|
+
zeroInk: str3(inputs, "zeroColor", NEUTRAL)
|
|
43696
|
+
});
|
|
42435
43697
|
}
|
|
42436
43698
|
};
|
|
42437
43699
|
var chaikinOscillator = {
|
|
@@ -42439,14 +43701,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42439
43701
|
title: "Chaikin Oscillator",
|
|
42440
43702
|
shortTitle: "Chaikin Osc",
|
|
42441
43703
|
overlay: false,
|
|
42442
|
-
inputs: [
|
|
43704
|
+
inputs: [
|
|
43705
|
+
lengthInput(3, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the accumulation/distribution line."),
|
|
43706
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the accumulation/distribution line."),
|
|
43707
|
+
colorInput(NEUTRAL, "Oscillator", "color", "Color of the Chaikin Oscillator line."),
|
|
43708
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Line and fill color while the oscillator reads above zero (accumulation)."),
|
|
43709
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Line and fill color while the oscillator reads below zero (distribution).")
|
|
43710
|
+
],
|
|
42443
43711
|
compute: (bars, inputs) => {
|
|
42444
43712
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
42445
43713
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
42446
|
-
return {
|
|
42447
|
-
|
|
42448
|
-
|
|
42449
|
-
|
|
43714
|
+
return centeredOscillator({
|
|
43715
|
+
key: "co",
|
|
43716
|
+
title: "Chaikin Oscillator",
|
|
43717
|
+
values,
|
|
43718
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
43719
|
+
zeroInk: NEUTRAL
|
|
43720
|
+
});
|
|
42450
43721
|
}
|
|
42451
43722
|
};
|
|
42452
43723
|
var eom = {
|
|
@@ -42454,20 +43725,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42454
43725
|
title: "Ease of Movement",
|
|
42455
43726
|
shortTitle: "EOM",
|
|
42456
43727
|
overlay: false,
|
|
42457
|
-
inputs: [
|
|
43728
|
+
inputs: [
|
|
43729
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars in the simple moving average smoothing the 1-bar Ease of Movement values."),
|
|
43730
|
+
intInput("divisor", "Divisor", 1e4, 1, 1e9, "Volume scaling constant of the box ratio; larger values scale the oscillator up. 10000 is the conventional default."),
|
|
43731
|
+
colorInput(NEUTRAL, "EOM Line", "color", "Color of the line when it sits exactly at zero."),
|
|
43732
|
+
colorInput(BULLISH, "Above Zero", "bullColor", "Color of the line and fill while Ease of Movement is positive."),
|
|
43733
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while Ease of Movement is negative.")
|
|
43734
|
+
],
|
|
42458
43735
|
compute: (bars, inputs) => {
|
|
42459
43736
|
const div = num3(inputs, "divisor", 1e4);
|
|
42460
|
-
const
|
|
42461
|
-
const move = change(hl2);
|
|
43737
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
42462
43738
|
const raw = bars.map((b, i) => {
|
|
42463
43739
|
const m = move[i];
|
|
42464
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
42465
|
-
return div * m * (b.high - b.low) / b.volume;
|
|
43740
|
+
if (b.volume == null || !Number.isFinite(b.volume) || !Number.isFinite(m)) return Number.NaN;
|
|
43741
|
+
return b.volume === 0 ? 0 : div * m * (b.high - b.low) / b.volume;
|
|
43742
|
+
});
|
|
43743
|
+
return centeredOscillator({
|
|
43744
|
+
key: "eom",
|
|
43745
|
+
title: "EOM",
|
|
43746
|
+
values: sma(raw, num3(inputs, "length", 14)),
|
|
43747
|
+
ink: { bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "color", NEUTRAL) },
|
|
43748
|
+
zeroInk: NEUTRAL
|
|
42466
43749
|
});
|
|
42467
|
-
return {
|
|
42468
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
42469
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
42470
|
-
};
|
|
42471
43750
|
}
|
|
42472
43751
|
};
|
|
42473
43752
|
var forceIndex = {
|
|
@@ -42475,13 +43754,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42475
43754
|
title: "Force Index",
|
|
42476
43755
|
shortTitle: "FI",
|
|
42477
43756
|
overlay: false,
|
|
42478
|
-
inputs: [
|
|
43757
|
+
inputs: [
|
|
43758
|
+
lengthInput(13, "length", "Length", 5e3, "EMA length applied to the raw 1-bar force index, (close \u2212 close[1]) \xD7 volume. Elder used 13 for the intermediate trend and 2 for short-term timing."),
|
|
43759
|
+
colorInput(BULLISH, "Force Index", "color", "Color of the line while above zero (buying pressure)."),
|
|
43760
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line while below zero (selling pressure).")
|
|
43761
|
+
],
|
|
42479
43762
|
compute: (bars, inputs) => {
|
|
42480
43763
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
42481
|
-
|
|
42482
|
-
|
|
42483
|
-
|
|
42484
|
-
|
|
43764
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
43765
|
+
return centeredOscillator({
|
|
43766
|
+
key: "force",
|
|
43767
|
+
title: "Force Index",
|
|
43768
|
+
values: ema(raw, num3(inputs, "length", 13)),
|
|
43769
|
+
// The reference reads an exact zero as buying pressure, so neutral shares the bull ink.
|
|
43770
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
43771
|
+
zeroInk: NEUTRAL,
|
|
43772
|
+
width: 2,
|
|
43773
|
+
valueTransparency: 60
|
|
43774
|
+
});
|
|
42485
43775
|
}
|
|
42486
43776
|
};
|
|
42487
43777
|
var klinger = {
|
|
@@ -42489,19 +43779,44 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42489
43779
|
title: "Klinger Oscillator",
|
|
42490
43780
|
shortTitle: "KVO",
|
|
42491
43781
|
overlay: false,
|
|
42492
|
-
inputs: [
|
|
43782
|
+
inputs: [
|
|
43783
|
+
lengthInput(34, "fastLength", "Fast Length", 5e3, "Length of the fast EMA applied to the volume force."),
|
|
43784
|
+
lengthInput(55, "slowLength", "Slow Length", 5e3, "Length of the slow EMA applied to the volume force."),
|
|
43785
|
+
lengthInput(13, "signalLength", "Signal Length", 500, "Length of the EMA applied to the oscillator to obtain the signal line."),
|
|
43786
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the Klinger oscillator while above zero."),
|
|
43787
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the Klinger oscillator while below zero."),
|
|
43788
|
+
colorInput(WARNING, "Signal", "signalColor", "Color of the signal line.")
|
|
43789
|
+
],
|
|
42493
43790
|
compute: (bars, inputs) => {
|
|
42494
|
-
const
|
|
42495
|
-
const
|
|
42496
|
-
|
|
42497
|
-
|
|
42498
|
-
|
|
42499
|
-
|
|
42500
|
-
|
|
42501
|
-
|
|
42502
|
-
|
|
42503
|
-
|
|
42504
|
-
|
|
43791
|
+
const n = bars.length;
|
|
43792
|
+
const force = new Array(n).fill(Number.NaN);
|
|
43793
|
+
let trend = 1;
|
|
43794
|
+
let prevTrend = 1;
|
|
43795
|
+
let cm = 0;
|
|
43796
|
+
let prevDm = 0;
|
|
43797
|
+
for (let i = 0; i < n; i++) {
|
|
43798
|
+
const b = bars[i];
|
|
43799
|
+
const hlc = b.high + b.low + b.close;
|
|
43800
|
+
const prevHlc = i > 0 ? bars[i - 1].high + bars[i - 1].low + bars[i - 1].close : hlc;
|
|
43801
|
+
trend = hlc > prevHlc ? 1 : -1;
|
|
43802
|
+
const dm = b.high - b.low;
|
|
43803
|
+
cm = i > 0 && trend === prevTrend ? cm + dm : prevDm + dm;
|
|
43804
|
+
const v = b.volume;
|
|
43805
|
+
force[i] = v == null || !Number.isFinite(v) ? Number.NaN : cm === 0 ? 0 : v * Math.abs(2 * (dm / cm) - 1) * trend * 100;
|
|
43806
|
+
prevTrend = trend;
|
|
43807
|
+
prevDm = dm;
|
|
43808
|
+
}
|
|
43809
|
+
const line = sub(ema(force, num3(inputs, "fastLength", 34)), ema(force, num3(inputs, "slowLength", 55)));
|
|
43810
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
43811
|
+
return centeredOscillator({
|
|
43812
|
+
key: "kvo",
|
|
43813
|
+
title: "KVO",
|
|
43814
|
+
values: line,
|
|
43815
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
43816
|
+
zeroInk: transp(NEUTRAL, 50),
|
|
43817
|
+
valueTransparency: 60,
|
|
43818
|
+
extraPlots: [{ key: "signal", title: "Signal", values: ema(line, num3(inputs, "signalLength", 13)), color: str3(inputs, "signalColor", WARNING) }]
|
|
43819
|
+
});
|
|
42505
43820
|
}
|
|
42506
43821
|
};
|
|
42507
43822
|
var mfi = {
|
|
@@ -42509,7 +43824,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42509
43824
|
title: "Money Flow Index",
|
|
42510
43825
|
shortTitle: "MFI",
|
|
42511
43826
|
overlay: false,
|
|
42512
|
-
inputs: [
|
|
43827
|
+
inputs: [
|
|
43828
|
+
lengthInput(14, "length", "Length", 5e3, "Number of bars used to sum positive and negative money flow."),
|
|
43829
|
+
floatInput("overbought", "Overbought Level", 80, 0, 100, 1, "Level above which the MFI is considered overbought."),
|
|
43830
|
+
floatInput("oversold", "Oversold Level", 20, 0, 100, 1, "Level below which the MFI is considered oversold."),
|
|
43831
|
+
colorInput(NEUTRAL, "MFI", "color", "Color of the MFI line between the levels. Toward either level it blends into that level\u2019s color."),
|
|
43832
|
+
colorInput(BEARISH, "Overbought", "overboughtColor", "Color of the line and zone shading at and above the overbought level."),
|
|
43833
|
+
colorInput(BULLISH, "Oversold", "oversoldColor", "Color of the line and zone shading at and below the oversold level."),
|
|
43834
|
+
colorInput(transp(NEUTRAL, 94), "OB/OS Zone Fill", "fillColor", "Fill color of the area between the overbought and oversold levels.")
|
|
43835
|
+
],
|
|
42513
43836
|
compute: (bars, inputs) => {
|
|
42514
43837
|
const len = num3(inputs, "length", 14);
|
|
42515
43838
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -42517,48 +43840,57 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42517
43840
|
const d = change(tp);
|
|
42518
43841
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
42519
43842
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
42520
|
-
const values = zip(pos, neg, (p,
|
|
42521
|
-
return {
|
|
42522
|
-
|
|
42523
|
-
|
|
42524
|
-
|
|
42525
|
-
|
|
42526
|
-
|
|
42527
|
-
|
|
43843
|
+
const values = zip(pos, neg, (p, w) => p + w > 0 ? 100 * p / (p + w) : Number.NaN);
|
|
43844
|
+
return boundedOscillator({
|
|
43845
|
+
key: "mfi",
|
|
43846
|
+
title: "MFI",
|
|
43847
|
+
values,
|
|
43848
|
+
scale: { top: 100, bottom: 0 },
|
|
43849
|
+
overbought: num3(inputs, "overbought", 80),
|
|
43850
|
+
oversold: num3(inputs, "oversold", 20),
|
|
43851
|
+
ink: { low: str3(inputs, "oversoldColor", BULLISH), mid: str3(inputs, "color", NEUTRAL), high: str3(inputs, "overboughtColor", BEARISH) },
|
|
43852
|
+
levelInk: NEUTRAL,
|
|
43853
|
+
zoneTransparency: 91,
|
|
43854
|
+
bandFill: str3(inputs, "fillColor", transp(NEUTRAL, 94))
|
|
43855
|
+
});
|
|
42528
43856
|
}
|
|
42529
43857
|
};
|
|
42530
|
-
function
|
|
42531
|
-
|
|
42532
|
-
|
|
42533
|
-
|
|
42534
|
-
|
|
42535
|
-
|
|
42536
|
-
|
|
42537
|
-
|
|
42538
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
42539
|
-
if (started) out[i] = index;
|
|
42540
|
-
continue;
|
|
42541
|
-
}
|
|
42542
|
-
started = true;
|
|
42543
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
42544
|
-
out[i] = index;
|
|
42545
|
-
}
|
|
42546
|
-
return out;
|
|
43858
|
+
function regimeStyleInputs(signalTitle) {
|
|
43859
|
+
return [
|
|
43860
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the line and gradient fill while it holds above its signal EMA."),
|
|
43861
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the line and gradient fill while it holds below its signal EMA."),
|
|
43862
|
+
colorInput(NEUTRAL, "Neutral", "neutralColor", "Color used while the signal EMA is still forming, or the line sits exactly on it."),
|
|
43863
|
+
colorInput(NEUTRAL, signalTitle, "signalColor", "Color of the signal EMA overlay."),
|
|
43864
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the gap between the line and its signal EMA with a vertical gradient \u2014 transparent at the average, strongest at the line.")
|
|
43865
|
+
];
|
|
42547
43866
|
}
|
|
43867
|
+
var NVI_POSITION = "Position vs Average";
|
|
43868
|
+
var NVI_SINGLE = "Single Color";
|
|
42548
43869
|
var nvi = {
|
|
42549
43870
|
type: "negative-volume-index",
|
|
42550
43871
|
title: "Negative Volume Index",
|
|
42551
43872
|
shortTitle: "Negative Volume Index",
|
|
42552
43873
|
overlay: false,
|
|
42553
|
-
inputs: [
|
|
43874
|
+
inputs: [
|
|
43875
|
+
floatInput("base", "Base Level", 1e3, 0.01, 1e6, 1, "Arbitrary seed of the cumulative line, conventionally 1000. The absolute level carries no information \u2014 the line is read by slope and by position against the signal EMA."),
|
|
43876
|
+
boolInput("showSignal", "Signal EMA", true, SETTINGS, "Displays the conventional signal overlay. Hiding it only removes the plotted average; the regime coloring and fill still use it."),
|
|
43877
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The convention is one trading year \u2014 255 sessions on daily charts."),
|
|
43878
|
+
optionInput("colorMode", "Line Coloring", NVI_POSITION, [NVI_POSITION, NVI_SINGLE], "'Position vs Average' colors the line by the classic regime read; 'Single Color' draws the whole line in the neutral color."),
|
|
43879
|
+
...regimeStyleInputs("Signal EMA")
|
|
43880
|
+
],
|
|
42554
43881
|
compute: (bars, inputs) => {
|
|
42555
|
-
const
|
|
42556
|
-
|
|
42557
|
-
|
|
42558
|
-
|
|
42559
|
-
|
|
42560
|
-
|
|
42561
|
-
|
|
43882
|
+
const values = volumeIndex(bars, closes(bars), num3(inputs, "base", 1e3), (v, pv) => v < pv);
|
|
43883
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
43884
|
+
return regimeIndex(
|
|
43885
|
+
"nvi",
|
|
43886
|
+
"NVI",
|
|
43887
|
+
values,
|
|
43888
|
+
signal,
|
|
43889
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
43890
|
+
bool(inputs, "showSignal", true),
|
|
43891
|
+
bool(inputs, "gradientFill", true),
|
|
43892
|
+
str3(inputs, "colorMode", NVI_POSITION) === NVI_SINGLE
|
|
43893
|
+
);
|
|
42562
43894
|
}
|
|
42563
43895
|
};
|
|
42564
43896
|
var pvi = {
|
|
@@ -42566,15 +43898,26 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42566
43898
|
title: "Positive Volume Index",
|
|
42567
43899
|
shortTitle: "Positive Volume Index",
|
|
42568
43900
|
overlay: false,
|
|
42569
|
-
inputs: [
|
|
43901
|
+
inputs: [
|
|
43902
|
+
sourceInput("Close", "source", "Source", "Price series whose one-bar percentage change moves PVI on its active sessions."),
|
|
43903
|
+
floatInput("base", "Starting Value", 1e3, 1, 1e6, 1, "Seed of the cumulative line. The absolute level carries no information, so the reads are the slope and the position against the signal EMA."),
|
|
43904
|
+
boolInput("showSignal", "Signal EMA", true, SETTINGS, "Plots the long EMA of PVI that anchors the classic regime read. Hiding it keeps the regime coloring and fill, which are always measured against it."),
|
|
43905
|
+
lengthInput(255, "signalLength", "Signal Length", 5e3, "Length of the signal EMA. The classic reference is a one-year average \u2014 255 trading days on daily charts."),
|
|
43906
|
+
...regimeStyleInputs("Signal Line")
|
|
43907
|
+
],
|
|
42570
43908
|
compute: (bars, inputs) => {
|
|
42571
|
-
const
|
|
42572
|
-
|
|
42573
|
-
|
|
42574
|
-
|
|
42575
|
-
|
|
42576
|
-
|
|
42577
|
-
|
|
43909
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
43910
|
+
const values = volumeIndex(bars, src, num3(inputs, "base", 1e3), (v, pv) => v > pv);
|
|
43911
|
+
const signal = ema(values, num3(inputs, "signalLength", 255));
|
|
43912
|
+
return regimeIndex(
|
|
43913
|
+
"pvi",
|
|
43914
|
+
"PVI",
|
|
43915
|
+
values,
|
|
43916
|
+
signal,
|
|
43917
|
+
{ bull: str3(inputs, "bullColor", BULLISH), bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "neutralColor", NEUTRAL), signal: str3(inputs, "signalColor", NEUTRAL) },
|
|
43918
|
+
bool(inputs, "showSignal", true),
|
|
43919
|
+
bool(inputs, "gradientFill", true)
|
|
43920
|
+
);
|
|
42578
43921
|
}
|
|
42579
43922
|
};
|
|
42580
43923
|
var pvt = {
|
|
@@ -42582,7 +43925,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42582
43925
|
title: "Price Volume Trend",
|
|
42583
43926
|
shortTitle: "PVT",
|
|
42584
43927
|
overlay: false,
|
|
42585
|
-
inputs: [
|
|
43928
|
+
inputs: [
|
|
43929
|
+
boolInput("showSignal", "Signal Line", false, SETTINGS, "Display a moving average of PVT acting as a signal line."),
|
|
43930
|
+
optionInput("signalType", "Signal Type", "SMA", ["SMA", "EMA"], "Moving average type used for the signal line."),
|
|
43931
|
+
lengthInput(21, "signalLength", "Signal Length", 5e3, "Number of bars used to compute the signal line."),
|
|
43932
|
+
colorInput(NEUTRAL, "PVT Color", "color", "Color of the PVT line while it is flat."),
|
|
43933
|
+
colorInput(BULLISH, "Rising", "bullColor", "Color of the PVT line while it rises bar to bar."),
|
|
43934
|
+
colorInput(BEARISH, "Falling", "bearColor", "Color of the PVT line while it falls bar to bar."),
|
|
43935
|
+
colorInput(WARNING, "Signal Color", "signalColor", "Color of the signal line.")
|
|
43936
|
+
],
|
|
42586
43937
|
compute: (bars, inputs) => {
|
|
42587
43938
|
const c = closes(bars);
|
|
42588
43939
|
const raw = bars.map((b, i) => {
|
|
@@ -42590,7 +43941,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42590
43941
|
const pc = c[i - 1];
|
|
42591
43942
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
42592
43943
|
});
|
|
42593
|
-
|
|
43944
|
+
const values = cumSum(raw);
|
|
43945
|
+
const flat = str3(inputs, "color", NEUTRAL);
|
|
43946
|
+
const plots = [
|
|
43947
|
+
{ key: "pvt", title: "PVT", values, color: flat, width: 2, colors: slopeColors2(values, str3(inputs, "bullColor", BULLISH), str3(inputs, "bearColor", BEARISH), flat) }
|
|
43948
|
+
];
|
|
43949
|
+
if (bool(inputs, "showSignal", false)) {
|
|
43950
|
+
const len = num3(inputs, "signalLength", 21);
|
|
43951
|
+
const signal = str3(inputs, "signalType", "SMA") === "EMA" ? ema(values, len) : sma(values, len);
|
|
43952
|
+
plots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
43953
|
+
}
|
|
43954
|
+
return { plots };
|
|
42594
43955
|
}
|
|
42595
43956
|
};
|
|
42596
43957
|
var volumeOscillator = {
|
|
@@ -42598,39 +43959,75 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42598
43959
|
title: "Volume Oscillator",
|
|
42599
43960
|
shortTitle: "Vol Osc",
|
|
42600
43961
|
overlay: false,
|
|
42601
|
-
inputs: [
|
|
43962
|
+
inputs: [
|
|
43963
|
+
lengthInput(5, "fastLength", "Fast Length", 5e3, "Length of the fast EMA of volume."),
|
|
43964
|
+
lengthInput(10, "slowLength", "Slow Length", 5e3, "Length of the slow EMA of volume."),
|
|
43965
|
+
colorInput(BULLISH, "Above Zero", "color", "Color of the line and fill while it holds above zero \u2014 volume expanding."),
|
|
43966
|
+
colorInput(BEARISH, "Below Zero", "bearColor", "Color of the line and fill while it holds below zero \u2014 volume contracting.")
|
|
43967
|
+
],
|
|
42602
43968
|
compute: (bars, inputs) => {
|
|
42603
43969
|
const vol = volumes(bars);
|
|
42604
43970
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
42605
43971
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
42606
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
42607
|
-
|
|
42608
|
-
|
|
42609
|
-
|
|
42610
|
-
|
|
43972
|
+
const values = zip(fast, slow, (f, s) => s === 0 ? 0 : 100 * (f - s) / s);
|
|
43973
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
43974
|
+
return centeredOscillator({
|
|
43975
|
+
key: "vo",
|
|
43976
|
+
title: "Volume Oscillator",
|
|
43977
|
+
values,
|
|
43978
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: bull },
|
|
43979
|
+
zeroInk: NEUTRAL,
|
|
43980
|
+
valueTransparency: 60
|
|
43981
|
+
});
|
|
42611
43982
|
}
|
|
42612
43983
|
};
|
|
43984
|
+
var OSCILLATOR = "Oscillator";
|
|
42613
43985
|
var pvo = {
|
|
42614
43986
|
type: "pvo",
|
|
42615
43987
|
title: "Percentage Volume Oscillator",
|
|
42616
43988
|
shortTitle: "PVO",
|
|
42617
43989
|
overlay: false,
|
|
42618
|
-
inputs: [
|
|
43990
|
+
inputs: [
|
|
43991
|
+
{ ...lengthInput(12, "fastLength", "Fast Length", 5e3, "Period of the fast EMA of volume. The conventional setting is 12."), group: OSCILLATOR },
|
|
43992
|
+
{ ...lengthInput(26, "slowLength", "Slow Length", 5e3, "Period of the slow EMA of volume. The PVO expresses the fast/slow spread as a percentage of this slow average."), group: OSCILLATOR },
|
|
43993
|
+
{ ...lengthInput(9, "signalLength", "Signal Length", 500, "Period of the EMA applied to the PVO to form the signal line."), group: OSCILLATOR },
|
|
43994
|
+
colorInput(BULLISH, "Expansion Color", "color", "Color of the oscillator and fill while participation expands."),
|
|
43995
|
+
colorInput(BEARISH, "Contraction Color", "bearColor", "Color of the oscillator and fill while participation contracts."),
|
|
43996
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
43997
|
+
colorInput(transp(NEUTRAL, 50), "Zero Line", "zeroColor", "Color of the zero reference line."),
|
|
43998
|
+
boolInput("showSignal", "Show Signal Line", true, STYLE, "Plot the signal line, an EMA of the PVO."),
|
|
43999
|
+
boolInput("showHistogram", "Show Histogram", true, STYLE, "Plot the difference between the PVO and its signal line as columns behind the oscillator.")
|
|
44000
|
+
],
|
|
42619
44001
|
compute: (bars, inputs) => {
|
|
42620
44002
|
const vol = volumes(bars);
|
|
42621
44003
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
42622
44004
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
42623
|
-
const
|
|
42624
|
-
const signal = ema(
|
|
42625
|
-
const
|
|
42626
|
-
|
|
42627
|
-
|
|
42628
|
-
|
|
42629
|
-
|
|
42630
|
-
|
|
42631
|
-
|
|
42632
|
-
|
|
42633
|
-
|
|
44005
|
+
const values = zip(fast, slow, (f, s) => s > 0 ? 100 * (f - s) / s : Number.NaN);
|
|
44006
|
+
const signal = ema(values, num3(inputs, "signalLength", 9));
|
|
44007
|
+
const expansion = str3(inputs, "color", BULLISH);
|
|
44008
|
+
const contraction = str3(inputs, "bearColor", BEARISH);
|
|
44009
|
+
const extraPlots = [];
|
|
44010
|
+
if (bool(inputs, "showHistogram", true)) {
|
|
44011
|
+
const hist = sub(values, signal);
|
|
44012
|
+
extraPlots.push({
|
|
44013
|
+
key: "hist",
|
|
44014
|
+
title: "Histogram",
|
|
44015
|
+
values: hist,
|
|
44016
|
+
kind: "columns",
|
|
44017
|
+
color: transp(expansion, 60),
|
|
44018
|
+
colors: hist.map((x) => Number.isFinite(x) ? transp(x > 0 ? expansion : contraction, 60) : null),
|
|
44019
|
+
base: 0
|
|
44020
|
+
});
|
|
44021
|
+
}
|
|
44022
|
+
if (bool(inputs, "showSignal", true)) extraPlots.push({ key: "signal", title: "Signal", values: signal, color: str3(inputs, "signalColor", WARNING) });
|
|
44023
|
+
return centeredOscillator({
|
|
44024
|
+
key: "pvo",
|
|
44025
|
+
title: "PVO",
|
|
44026
|
+
values,
|
|
44027
|
+
ink: { bull: expansion, bear: contraction, neutral: contraction },
|
|
44028
|
+
zeroInk: str3(inputs, "zeroColor", transp(NEUTRAL, 50)),
|
|
44029
|
+
extraPlots
|
|
44030
|
+
});
|
|
42634
44031
|
}
|
|
42635
44032
|
};
|
|
42636
44033
|
var vfi = {
|
|
@@ -42639,55 +44036,86 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42639
44036
|
shortTitle: "Volume Flow Indicator",
|
|
42640
44037
|
overlay: false,
|
|
42641
44038
|
inputs: [
|
|
42642
|
-
lengthInput(130),
|
|
42643
|
-
|
|
42644
|
-
|
|
42645
|
-
lengthInput(
|
|
44039
|
+
{ ...lengthInput(130, "length", "Length", 5e3, "Rolling summation window, also used for the average volume that scales the result. The published default of 130 bars targets position-horizon work on daily charts."), min: 2 },
|
|
44040
|
+
floatInput("coef", "Cutoff Coefficient", 0.2, 0, 10, 0.05, "Scales the volatility-based minimum change in typical price. Moves smaller than the cutoff count as noise and contribute no volume."),
|
|
44041
|
+
floatInput("volCoef", "Volume Cap Multiplier", 2.5, 0.1, 50, 0.1, "Caps each bar's volume contribution at this multiple of the average volume, so a single climactic print stays bounded."),
|
|
44042
|
+
{ ...lengthInput(30, "volLength", "Volatility Length", 5e3, "Window of the standard deviation of the log change in typical price used to build the adaptive cutoff."), min: 2 },
|
|
44043
|
+
boolInput("smooth", "Smooth VFI", true, SETTINGS, "Applies the published light smoothing \u2014 an EMA of the summed line \u2014 before plotting."),
|
|
44044
|
+
lengthInput(3, "smoothLength", "Smoothing Length", 500, "Length of the smoothing EMA. The published version uses 3 bars."),
|
|
44045
|
+
boolInput("showSignal", "Signal Line", true, SETTINGS, "Plots an EMA of VFI as a signal line."),
|
|
44046
|
+
lengthInput(5, "signalLength", "Signal Length", 500, "Length of the signal-line EMA."),
|
|
44047
|
+
colorInput(BULLISH, "Bullish", "color", "Color of the VFI line and gradient fill above the zero line \u2014 the net accumulation regime."),
|
|
44048
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the VFI line and gradient fill below the zero line \u2014 the net distribution regime."),
|
|
44049
|
+
colorInput(WARNING, "Signal Line", "signalColor", "Color of the signal line."),
|
|
44050
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the VFI line with a vertical gradient.")
|
|
42646
44051
|
],
|
|
42647
44052
|
compute: (bars, inputs) => {
|
|
42648
44053
|
const len = num3(inputs, "length", 130);
|
|
44054
|
+
const typical = sourceValues(bars, "HLC3");
|
|
44055
|
+
const logChange = sub(map(typical, Math.log), shift3(map(typical, Math.log), 1));
|
|
44056
|
+
const volatility = stdev(logChange, num3(inputs, "volLength", 30));
|
|
42649
44057
|
const coef = num3(inputs, "coef", 0.2);
|
|
42650
44058
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
42651
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
42652
|
-
const inter = change(typical);
|
|
42653
|
-
const vInter = stdev(inter, 30);
|
|
42654
44059
|
const vol = volumes(bars);
|
|
42655
|
-
const
|
|
42656
|
-
const
|
|
42657
|
-
const
|
|
42658
|
-
const
|
|
44060
|
+
const avgVolume = shift3(sma(vol, len), 1);
|
|
44061
|
+
const typicalChange = change(typical);
|
|
44062
|
+
const signed3 = bars.map((b, i) => {
|
|
44063
|
+
const sd = volatility[i];
|
|
44064
|
+
const m = typicalChange[i];
|
|
42659
44065
|
const v = vol[i];
|
|
42660
|
-
|
|
42661
|
-
const
|
|
42662
|
-
|
|
42663
|
-
const capped = Math.min(v,
|
|
44066
|
+
if (!Number.isFinite(sd) || !Number.isFinite(m) || !Number.isFinite(v)) return Number.NaN;
|
|
44067
|
+
const cutoff = coef * sd * b.close;
|
|
44068
|
+
const avg = avgVolume[i];
|
|
44069
|
+
const capped = Number.isFinite(avg) ? Math.min(v, avg * volCoef) : v;
|
|
42664
44070
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
42665
44071
|
});
|
|
42666
|
-
const
|
|
42667
|
-
|
|
42668
|
-
|
|
44072
|
+
const raw = zip(sum(signed3, len), avgVolume, (f, a) => a > 0 ? f / a : Number.NaN);
|
|
44073
|
+
const values = bool(inputs, "smooth", true) ? ema(raw, num3(inputs, "smoothLength", 3)) : raw;
|
|
44074
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
44075
|
+
const extraPlots = bool(inputs, "showSignal", true) ? [{ key: "signal", title: "Signal Line", values: ema(values, num3(inputs, "signalLength", 5)), color: str3(inputs, "signalColor", WARNING) }] : [];
|
|
44076
|
+
return centeredOscillator({
|
|
44077
|
+
key: "vfi",
|
|
44078
|
+
title: "VFI",
|
|
44079
|
+
values,
|
|
44080
|
+
ink: { bull, bear: str3(inputs, "bearColor", BEARISH), neutral: str3(inputs, "bearColor", BEARISH) },
|
|
44081
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
44082
|
+
fill: bool(inputs, "gradientFill", true),
|
|
44083
|
+
extraPlots
|
|
42669
44084
|
});
|
|
42670
|
-
const flowSum = sum(raw, len);
|
|
42671
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
42672
|
-
return {
|
|
42673
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
42674
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
42675
|
-
};
|
|
42676
44085
|
}
|
|
42677
44086
|
};
|
|
44087
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
44088
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
42678
44089
|
var intradayIntensity = {
|
|
42679
44090
|
type: "intraday-intensity",
|
|
42680
44091
|
title: "Intraday Intensity",
|
|
42681
44092
|
shortTitle: "Intraday Intensity",
|
|
42682
44093
|
overlay: false,
|
|
42683
|
-
inputs: [
|
|
44094
|
+
inputs: [
|
|
44095
|
+
optionInput("mode", "Display", II_OSCILLATOR, [II_OSCILLATOR, II_CUMULATIVE], "The two published forms. The cumulative line is open-ended and read by slope against price; the normalized oscillator divides a rolling sum of intensity by the rolling sum of volume and oscillates around zero."),
|
|
44096
|
+
lengthInput(21, "length", "Oscillator Length", 5e3, "Window of the rolling sums in the normalized form. The common setting is 21 bars. It has no effect on the cumulative line."),
|
|
44097
|
+
colorInput(BULLISH, "Bullish", "color", "Color of positive readings, the rising cumulative line and the fill above zero \u2014 the accumulation side."),
|
|
44098
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of negative readings, the falling cumulative line and the fill below zero \u2014 the distribution side."),
|
|
44099
|
+
boolInput("gradientFill", "Gradient Fill", true, STYLE, "Fills the area between the zero line and the oscillator with a vertical gradient. Applies to the normalized oscillator only.")
|
|
44100
|
+
],
|
|
42684
44101
|
compute: (bars, inputs) => {
|
|
44102
|
+
const intensity = moneyFlowVolume(bars);
|
|
44103
|
+
const bull = str3(inputs, "color", BULLISH);
|
|
44104
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
44105
|
+
if (str3(inputs, "mode", II_OSCILLATOR) === II_CUMULATIVE) {
|
|
44106
|
+
const values2 = cumSum(intensity);
|
|
44107
|
+
return { plots: [{ key: "ii", title: "Intraday Intensity", values: values2, color: bull, colors: slopeColors2(values2, bull, bear, bull) }] };
|
|
44108
|
+
}
|
|
42685
44109
|
const len = num3(inputs, "length", 21);
|
|
42686
|
-
const values = zip(sum(
|
|
42687
|
-
return {
|
|
42688
|
-
|
|
42689
|
-
|
|
42690
|
-
|
|
44110
|
+
const values = zip(sum(intensity, len), sum(volumes(bars), len), (m, v) => v > 0 ? 100 * m / v : Number.NaN);
|
|
44111
|
+
return centeredOscillator({
|
|
44112
|
+
key: "ii",
|
|
44113
|
+
title: "Intraday Intensity",
|
|
44114
|
+
values,
|
|
44115
|
+
ink: { bull, bear, neutral: bear },
|
|
44116
|
+
zeroInk: transp(NEUTRAL, 90),
|
|
44117
|
+
fill: bool(inputs, "gradientFill", true)
|
|
44118
|
+
});
|
|
42691
44119
|
}
|
|
42692
44120
|
};
|
|
42693
44121
|
var volumeSpecs = [
|
|
@@ -42720,15 +44148,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42720
44148
|
}
|
|
42721
44149
|
return Math.floor(time / DAY_MS2);
|
|
42722
44150
|
}
|
|
44151
|
+
function barInterval(bars) {
|
|
44152
|
+
if (bars.length < 2) return 0;
|
|
44153
|
+
const gaps = [];
|
|
44154
|
+
for (let i = 1; i < bars.length; i++) gaps.push(bars[i].time - bars[i - 1].time);
|
|
44155
|
+
gaps.sort((a, b) => a - b);
|
|
44156
|
+
return gaps[Math.floor(gaps.length / 2)] ?? 0;
|
|
44157
|
+
}
|
|
42723
44158
|
var parabolicSar = {
|
|
42724
44159
|
type: "parabolic-sar",
|
|
42725
44160
|
title: "Parabolic SAR",
|
|
42726
44161
|
shortTitle: "SAR",
|
|
42727
44162
|
overlay: true,
|
|
42728
44163
|
inputs: [
|
|
42729
|
-
|
|
42730
|
-
|
|
42731
|
-
|
|
44164
|
+
floatInput("start", "Start", 0.02, 0, 1, 0.01, "Initial acceleration factor applied when a new trend begins."),
|
|
44165
|
+
floatInput("increment", "Increment", 0.02, 0, 1, 0.01, "Amount added to the acceleration factor each time the trend makes a new extreme point."),
|
|
44166
|
+
floatInput("maximum", "Maximum", 0.2, 0.01, 1, 0.01, "Upper cap on the acceleration factor."),
|
|
44167
|
+
colorInput(BULLISH, "Bullish", "bullColor", "Color of the SAR crosses while they trail below price \u2014 uptrend, the stop sits under the market."),
|
|
44168
|
+
colorInput(BEARISH, "Bearish", "bearColor", "Color of the SAR crosses while they sit above price \u2014 downtrend, the stop sits over the market.")
|
|
42732
44169
|
],
|
|
42733
44170
|
compute: (bars, inputs) => {
|
|
42734
44171
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -42736,7 +44173,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42736
44173
|
const max2 = num3(inputs, "maximum", 0.2);
|
|
42737
44174
|
const n = bars.length;
|
|
42738
44175
|
const values = new Array(n).fill(Number.NaN);
|
|
42739
|
-
const colors = new Array(n).fill(null);
|
|
42740
44176
|
if (n >= 2) {
|
|
42741
44177
|
let long = bars[1].close >= bars[0].close;
|
|
42742
44178
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -42761,18 +44197,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42761
44197
|
af = Math.min(max2, af + inc);
|
|
42762
44198
|
}
|
|
42763
44199
|
values[i] = sar;
|
|
42764
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
42765
44200
|
}
|
|
42766
44201
|
}
|
|
42767
|
-
|
|
44202
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
44203
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
44204
|
+
const colors = values.map((x, i) => Number.isFinite(x) ? x < bars[i].close ? bull : bear : null);
|
|
44205
|
+
return { plots: [{ key: "sar", title: "SAR", values, kind: "cross", color: bull, colors }] };
|
|
42768
44206
|
}
|
|
42769
44207
|
};
|
|
42770
|
-
var
|
|
42771
|
-
var
|
|
44208
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
44209
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
44210
|
+
var BANDS_PERCENT = "Percentage";
|
|
42772
44211
|
var VWAP_BANDS = [
|
|
42773
|
-
{
|
|
42774
|
-
{
|
|
42775
|
-
{
|
|
44212
|
+
{ show: "band1", mult: "band1Mult", defMult: 1, defOn: true },
|
|
44213
|
+
{ show: "band2", mult: "band2Mult", defMult: 2, defOn: false },
|
|
44214
|
+
{ show: "band3", mult: "band3Mult", defMult: 3, defOn: false }
|
|
42776
44215
|
];
|
|
42777
44216
|
var vwap = {
|
|
42778
44217
|
type: "vwap",
|
|
@@ -42780,39 +44219,36 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42780
44219
|
shortTitle: "VWAP",
|
|
42781
44220
|
overlay: true,
|
|
42782
44221
|
inputs: [
|
|
42783
|
-
|
|
42784
|
-
sourceInput("HLC3"),
|
|
42785
|
-
|
|
44222
|
+
optionInput("anchor", "Anchor Period", "Session", VWAP_ANCHORS, "Period anchoring the average. VWAP and its bands reset on the first bar of each new session, week, month, quarter or year (UTC)."),
|
|
44223
|
+
sourceInput("HLC3", "source", "Source", "Price used in the volume weighted average. Typical price (HLC3) is the classic choice."),
|
|
44224
|
+
optionInput("bandsMode", "Bands Mode", BANDS_STDEV, [BANDS_STDEV, BANDS_PERCENT], "Bands offset method: multiples of the volume weighted standard deviation of price around VWAP, or a percentage of the VWAP value."),
|
|
42786
44225
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
42787
|
-
|
|
42788
|
-
|
|
44226
|
+
boolInput(b.show, `Band #${i + 1}`, b.defOn, SETTINGS, `Show the ${["first", "second", "third"][i]} band pair.`),
|
|
44227
|
+
floatInput(b.mult, `Band #${i + 1} Multiplier`, b.defMult, 0, 50, 0.5, "Multiplier for this band pair, in standard deviations or percent depending on the bands mode.")
|
|
42789
44228
|
]),
|
|
42790
|
-
|
|
42791
|
-
|
|
42792
|
-
|
|
42793
|
-
|
|
42794
|
-
|
|
42795
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
42796
|
-
])
|
|
44229
|
+
boolInput("hideDwm", "Hide VWAP on 1D or Above", true, SETTINGS, "Hide the VWAP and its bands on daily and higher timeframes, where an intraday anchored VWAP is not meaningful."),
|
|
44230
|
+
colorInput(BULLISH, "Bullish", "bullColor", "VWAP line color while price closes above it, and the color of the lower (support) bands."),
|
|
44231
|
+
colorInput(BEARISH, "Bearish", "bearColor", "VWAP line color while price closes below it, and the color of the upper (resistance) bands."),
|
|
44232
|
+
colorInput(NEUTRAL, "Bands", "bandsColor", "Deviation bands fill color."),
|
|
44233
|
+
boolInput("fill", "Bands Fill", true, STYLE, "Fill the area between each visible band pair.")
|
|
42797
44234
|
],
|
|
42798
44235
|
compute: (bars, inputs) => {
|
|
42799
|
-
const anchor = str3(inputs, "anchor", "
|
|
44236
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
42800
44237
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
42801
44238
|
const n = bars.length;
|
|
44239
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
42802
44240
|
const values = new Array(n).fill(Number.NaN);
|
|
42803
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
44241
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
42804
44242
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
42805
|
-
color: str3(inputs, b.color, b.defColor),
|
|
42806
|
-
fill: bool(inputs, b.fill, true),
|
|
42807
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
42808
44243
|
up: new Array(n).fill(Number.NaN),
|
|
42809
44244
|
down: new Array(n).fill(Number.NaN)
|
|
42810
44245
|
}));
|
|
44246
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
42811
44247
|
let period = Number.NaN;
|
|
42812
44248
|
let cumPV = 0;
|
|
42813
44249
|
let cumPV2 = 0;
|
|
42814
44250
|
let cumV = 0;
|
|
42815
|
-
for (let i = 0; i < n; i++) {
|
|
44251
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
42816
44252
|
const b = bars[i];
|
|
42817
44253
|
const key = periodKey(anchor, b.time);
|
|
42818
44254
|
if (key !== period) {
|
|
@@ -42830,38 +44266,58 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42830
44266
|
}
|
|
42831
44267
|
if (cumV <= 0) continue;
|
|
42832
44268
|
const mean = cumPV / cumV;
|
|
42833
|
-
const
|
|
44269
|
+
const offset3 = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
42834
44270
|
values[i] = mean;
|
|
42835
44271
|
for (const band of bands) {
|
|
42836
|
-
band.up[i] = mean + band.mult *
|
|
42837
|
-
band.down[i] = mean - band.mult *
|
|
44272
|
+
band.up[i] = mean + band.mult * offset3;
|
|
44273
|
+
band.down[i] = mean - band.mult * offset3;
|
|
42838
44274
|
}
|
|
42839
44275
|
}
|
|
42840
|
-
const
|
|
44276
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
44277
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
44278
|
+
const fillColor = transp(str3(inputs, "bandsColor", NEUTRAL), 95);
|
|
44279
|
+
const plots = [
|
|
44280
|
+
{ key: "vwap", title: "VWAP", values, color: bull, colors: values.map((x, i) => Number.isFinite(x) ? bars[i].close >= x ? bull : bear : null) }
|
|
44281
|
+
];
|
|
42841
44282
|
const fills = [];
|
|
42842
44283
|
bands.forEach((band, i) => {
|
|
42843
44284
|
plots.push(
|
|
42844
|
-
{ key: `up${i}`, title: `Upper
|
|
42845
|
-
{ key: `down${i}`, title: `Lower
|
|
44285
|
+
{ key: `up${i}`, title: `Upper Band #${i + 1}`, values: band.up, color: transp(bear, 40) },
|
|
44286
|
+
{ key: `down${i}`, title: `Lower Band #${i + 1}`, values: band.down, color: transp(bull, 40) }
|
|
42846
44287
|
);
|
|
42847
|
-
if (
|
|
44288
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
42848
44289
|
});
|
|
42849
44290
|
return { plots, bands: fills };
|
|
42850
44291
|
}
|
|
42851
44292
|
};
|
|
44293
|
+
var PIVOT_TRADITIONAL = "Traditional";
|
|
44294
|
+
var PIVOT_FIBONACCI = "Fibonacci";
|
|
44295
|
+
var PIVOT_CAMARILLA = "Camarilla";
|
|
44296
|
+
var PIVOT_WOODIE = "Woodie";
|
|
44297
|
+
var PIVOT_AUTO = "Auto";
|
|
42852
44298
|
var pivotPoints = {
|
|
42853
44299
|
type: "pivot-points",
|
|
42854
|
-
title: "Pivot Points",
|
|
44300
|
+
title: "Pivot Points Standard",
|
|
42855
44301
|
shortTitle: "Pivots",
|
|
42856
44302
|
overlay: true,
|
|
42857
|
-
inputs: [
|
|
44303
|
+
inputs: [
|
|
44304
|
+
optionInput("kind", "Type", PIVOT_TRADITIONAL, [PIVOT_TRADITIONAL, PIVOT_FIBONACCI, PIVOT_CAMARILLA, PIVOT_WOODIE], "Formula used to calculate the levels. Traditional is the classic floor-trader method; Fibonacci projects 38.2/61.8/100% of the prior range around the pivot; Camarilla scales the prior range around the prior close; Woodie gives extra weight to the period open."),
|
|
44305
|
+
optionInput("anchor", "Pivots Timeframe", PIVOT_AUTO, [PIVOT_AUTO, "Daily", "Weekly", "Monthly", "Yearly"], "Period the pivots are anchored to. 'Auto' follows the chart's bar spacing: daily pivots up to 15-minute bars, weekly on other intraday bars, monthly on daily bars, yearly above."),
|
|
44306
|
+
colorInput(WARNING, "Pivot", "pivotColor", "Color of the central pivot (P) level."),
|
|
44307
|
+
colorInput(BEARISH, "Resistances", "resistanceColor", "Color of the resistance (R) levels."),
|
|
44308
|
+
colorInput(BULLISH, "Supports", "supportColor", "Color of the support (S) levels.")
|
|
44309
|
+
],
|
|
42858
44310
|
compute: (bars, inputs) => {
|
|
42859
|
-
const
|
|
44311
|
+
const interval = barInterval(bars);
|
|
44312
|
+
const requested = str3(inputs, "anchor", PIVOT_AUTO);
|
|
44313
|
+
const auto = interval <= 15 * 6e4 ? "Session" : interval < DAY_MS2 ? "Week" : interval <= DAY_MS2 ? "Month" : "Year";
|
|
44314
|
+
const anchor = requested === "Daily" ? "Session" : requested === "Weekly" ? "Week" : requested === "Monthly" ? "Month" : requested === "Yearly" ? "Year" : auto;
|
|
44315
|
+
const kind = str3(inputs, "kind", PIVOT_TRADITIONAL);
|
|
42860
44316
|
const aggs = /* @__PURE__ */ new Map();
|
|
42861
44317
|
for (const b of bars) {
|
|
42862
44318
|
const key = periodKey(anchor, b.time);
|
|
42863
44319
|
const a = aggs.get(key);
|
|
42864
|
-
if (!a) aggs.set(key, { high: b.high, low: b.low, close: b.close });
|
|
44320
|
+
if (!a) aggs.set(key, { open: b.open, high: b.high, low: b.low, close: b.close });
|
|
42865
44321
|
else {
|
|
42866
44322
|
a.high = Math.max(a.high, b.high);
|
|
42867
44323
|
a.low = Math.min(a.low, b.low);
|
|
@@ -42870,31 +44326,61 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42870
44326
|
}
|
|
42871
44327
|
const n = bars.length;
|
|
42872
44328
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
42873
|
-
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk() };
|
|
44329
|
+
const levels = { p: mk(), r1: mk(), s1: mk(), r2: mk(), s2: mk(), r3: mk(), s3: mk(), r4: mk(), s4: mk() };
|
|
44330
|
+
let lastKey = Number.NaN;
|
|
42874
44331
|
for (let i = 0; i < n; i++) {
|
|
42875
|
-
const
|
|
42876
|
-
|
|
42877
|
-
|
|
44332
|
+
const key = periodKey(anchor, bars[i].time);
|
|
44333
|
+
const fresh = key !== lastKey;
|
|
44334
|
+
lastKey = key;
|
|
44335
|
+
if (fresh) continue;
|
|
44336
|
+
const prev3 = aggs.get(key - 1);
|
|
44337
|
+
const curr = aggs.get(key);
|
|
44338
|
+
if (!prev3 || !curr) continue;
|
|
44339
|
+
const range = prev3.high - prev3.low;
|
|
44340
|
+
const traditional = (prev3.high + prev3.low + prev3.close) / 3;
|
|
44341
|
+
const p = kind === PIVOT_WOODIE ? (prev3.high + prev3.low + 2 * curr.open) / 4 : traditional;
|
|
42878
44342
|
levels.p[i] = p;
|
|
42879
|
-
|
|
42880
|
-
|
|
42881
|
-
|
|
42882
|
-
|
|
42883
|
-
|
|
42884
|
-
|
|
42885
|
-
|
|
42886
|
-
|
|
42887
|
-
|
|
42888
|
-
|
|
42889
|
-
|
|
42890
|
-
|
|
42891
|
-
|
|
42892
|
-
|
|
42893
|
-
|
|
42894
|
-
|
|
42895
|
-
|
|
42896
|
-
|
|
42897
|
-
|
|
44343
|
+
if (kind === PIVOT_FIBONACCI) {
|
|
44344
|
+
levels.r1[i] = traditional + 0.382 * range;
|
|
44345
|
+
levels.s1[i] = traditional - 0.382 * range;
|
|
44346
|
+
levels.r2[i] = traditional + 0.618 * range;
|
|
44347
|
+
levels.s2[i] = traditional - 0.618 * range;
|
|
44348
|
+
levels.r3[i] = traditional + range;
|
|
44349
|
+
levels.s3[i] = traditional - range;
|
|
44350
|
+
} else if (kind === PIVOT_CAMARILLA) {
|
|
44351
|
+
const cam = 1.1 * range;
|
|
44352
|
+
levels.r1[i] = prev3.close + cam / 12;
|
|
44353
|
+
levels.s1[i] = prev3.close - cam / 12;
|
|
44354
|
+
levels.r2[i] = prev3.close + cam / 6;
|
|
44355
|
+
levels.s2[i] = prev3.close - cam / 6;
|
|
44356
|
+
levels.r3[i] = prev3.close + cam / 4;
|
|
44357
|
+
levels.s3[i] = prev3.close - cam / 4;
|
|
44358
|
+
levels.r4[i] = prev3.close + cam / 2;
|
|
44359
|
+
levels.s4[i] = prev3.close - cam / 2;
|
|
44360
|
+
} else {
|
|
44361
|
+
levels.r1[i] = 2 * p - prev3.low;
|
|
44362
|
+
levels.s1[i] = 2 * p - prev3.high;
|
|
44363
|
+
levels.r2[i] = p + range;
|
|
44364
|
+
levels.s2[i] = p - range;
|
|
44365
|
+
if (kind !== PIVOT_WOODIE) {
|
|
44366
|
+
levels.r3[i] = prev3.high + 2 * (p - prev3.low);
|
|
44367
|
+
levels.s3[i] = prev3.low - 2 * (prev3.high - p);
|
|
44368
|
+
}
|
|
44369
|
+
}
|
|
44370
|
+
}
|
|
44371
|
+
const resistance = str3(inputs, "resistanceColor", BEARISH);
|
|
44372
|
+
const support = str3(inputs, "supportColor", BULLISH);
|
|
44373
|
+
const plot = (key, title, color) => ({ key, title, values: levels[key], color });
|
|
44374
|
+
const plots = [
|
|
44375
|
+
plot("p", "P", str3(inputs, "pivotColor", WARNING)),
|
|
44376
|
+
plot("r1", "R1", resistance),
|
|
44377
|
+
plot("s1", "S1", support),
|
|
44378
|
+
plot("r2", "R2", resistance),
|
|
44379
|
+
plot("s2", "S2", support)
|
|
44380
|
+
];
|
|
44381
|
+
if (kind !== PIVOT_WOODIE) plots.push(plot("r3", "R3", resistance), plot("s3", "S3", support));
|
|
44382
|
+
if (kind === PIVOT_CAMARILLA) plots.push(plot("r4", "R4", resistance), plot("s4", "S4", support));
|
|
44383
|
+
return { plots };
|
|
42898
44384
|
}
|
|
42899
44385
|
};
|
|
42900
44386
|
var fiftyTwoWeek = {
|
|
@@ -42902,15 +44388,25 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42902
44388
|
title: "52 Week High/Low",
|
|
42903
44389
|
shortTitle: "52W H/L",
|
|
42904
44390
|
overlay: true,
|
|
42905
|
-
inputs: [
|
|
44391
|
+
inputs: [
|
|
44392
|
+
intInput("weeks", "Weeks", 52, 1, 520, "Length of the rolling window, in weeks."),
|
|
44393
|
+
boolInput("showAllTime", "Show All-Time High/Low", false, SETTINGS, "Additionally display the all-time high and low, tracked over the symbol's full loaded history."),
|
|
44394
|
+
colorInput(BULLISH, "52 Week High", "highColor", "Color of the 52 week high level."),
|
|
44395
|
+
colorInput(BEARISH, "52 Week Low", "lowColor", "Color of the 52 week low level."),
|
|
44396
|
+
colorInput(NEUTRAL, "All-Time High/Low", "allTimeColor", "Color of the all-time high and all-time low levels.")
|
|
44397
|
+
],
|
|
42906
44398
|
compute: (bars, inputs) => {
|
|
42907
44399
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
42908
44400
|
const n = bars.length;
|
|
42909
44401
|
const hi = new Array(n).fill(Number.NaN);
|
|
42910
44402
|
const lo = new Array(n).fill(Number.NaN);
|
|
44403
|
+
const ath = new Array(n).fill(Number.NaN);
|
|
44404
|
+
const atl = new Array(n).fill(Number.NaN);
|
|
42911
44405
|
const maxIdx = [];
|
|
42912
44406
|
const minIdx = [];
|
|
42913
44407
|
let from = 0;
|
|
44408
|
+
let runHigh = -Infinity;
|
|
44409
|
+
let runLow = Infinity;
|
|
42914
44410
|
for (let i = 0; i < n; i++) {
|
|
42915
44411
|
const b = bars[i];
|
|
42916
44412
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -42922,13 +44418,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42922
44418
|
while (minIdx[0] < from) minIdx.shift();
|
|
42923
44419
|
hi[i] = bars[maxIdx[0]].high;
|
|
42924
44420
|
lo[i] = bars[minIdx[0]].low;
|
|
44421
|
+
runHigh = Math.max(runHigh, b.high);
|
|
44422
|
+
runLow = Math.min(runLow, b.low);
|
|
44423
|
+
ath[i] = runHigh;
|
|
44424
|
+
atl[i] = runLow;
|
|
44425
|
+
}
|
|
44426
|
+
const plots = [
|
|
44427
|
+
{ key: "high", title: "52 Week High", values: hi, kind: "step", color: str3(inputs, "highColor", BULLISH), width: 2 },
|
|
44428
|
+
{ key: "low", title: "52 Week Low", values: lo, kind: "step", color: str3(inputs, "lowColor", BEARISH), width: 2 }
|
|
44429
|
+
];
|
|
44430
|
+
if (bool(inputs, "showAllTime", false)) {
|
|
44431
|
+
const ink = str3(inputs, "allTimeColor", NEUTRAL);
|
|
44432
|
+
plots.push({ key: "ath", title: "All-Time High", values: ath, kind: "step", color: ink }, { key: "atl", title: "All-Time Low", values: atl, kind: "step", color: ink });
|
|
42925
44433
|
}
|
|
42926
|
-
return {
|
|
42927
|
-
plots: [
|
|
42928
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
42929
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
42930
|
-
]
|
|
42931
|
-
};
|
|
44434
|
+
return { plots };
|
|
42932
44435
|
}
|
|
42933
44436
|
};
|
|
42934
44437
|
var zigzag = {
|
|
@@ -42936,9 +44439,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42936
44439
|
title: "ZigZag",
|
|
42937
44440
|
overlay: true,
|
|
42938
44441
|
inputs: [
|
|
42939
|
-
|
|
42940
|
-
{
|
|
42941
|
-
colorInput()
|
|
44442
|
+
floatInput("deviation", "Deviation (%)", 5, 0.01, 100, 0.1, "Minimum reversal from the leg extreme, as a percentage of that extreme, required to confirm a new pivot."),
|
|
44443
|
+
{ ...intInput("depth", "Depth", 10, 2, 500, "Minimum number of bars required between two consecutive pivots.") },
|
|
44444
|
+
colorInput(BULLISH, "Up Leg", "bullColor", "Color of rising zigzag segments."),
|
|
44445
|
+
colorInput(BEARISH, "Down Leg", "bearColor", "Color of falling zigzag segments."),
|
|
44446
|
+
{ key: "lineWidth", title: "Width", type: "int", defval: 2, min: 1, max: 10, step: 1, group: STYLE, tooltip: "Width of the zigzag segments." }
|
|
42942
44447
|
],
|
|
42943
44448
|
compute: (bars, inputs) => {
|
|
42944
44449
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -42951,14 +44456,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42951
44456
|
const b = bars[i];
|
|
42952
44457
|
if (up) {
|
|
42953
44458
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
42954
|
-
else if (b.low <= ext.price * (1 - dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
44459
|
+
else if (b.low <= ext.price * (1 - dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
42955
44460
|
pivots.push(ext);
|
|
42956
44461
|
up = false;
|
|
42957
44462
|
ext = { i, price: b.low };
|
|
42958
44463
|
}
|
|
42959
44464
|
} else if (b.low <= ext.price) {
|
|
42960
44465
|
ext = { i, price: b.low };
|
|
42961
|
-
} else if (b.high >= ext.price * (1 + dev) && i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
44466
|
+
} else if (b.high >= ext.price * (1 + dev) && ext.i - (pivots[pivots.length - 1]?.i ?? -depth) >= depth) {
|
|
42962
44467
|
pivots.push(ext);
|
|
42963
44468
|
up = true;
|
|
42964
44469
|
ext = { i, price: b.high };
|
|
@@ -42966,22 +44471,29 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42966
44471
|
}
|
|
42967
44472
|
pivots.push(ext);
|
|
42968
44473
|
}
|
|
42969
|
-
|
|
42970
|
-
|
|
42971
|
-
|
|
42972
|
-
|
|
42973
|
-
|
|
42974
|
-
|
|
42975
|
-
|
|
42976
|
-
|
|
42977
|
-
|
|
42978
|
-
|
|
42979
|
-
|
|
42980
|
-
|
|
42981
|
-
|
|
42982
|
-
|
|
42983
|
-
|
|
42984
|
-
|
|
44474
|
+
const bull = str3(inputs, "bullColor", BULLISH);
|
|
44475
|
+
const bear = str3(inputs, "bearColor", BEARISH);
|
|
44476
|
+
const width = num3(inputs, "lineWidth", 2);
|
|
44477
|
+
const polylines = [];
|
|
44478
|
+
for (let k = 1; k < pivots.length; k++) {
|
|
44479
|
+
const a = pivots[k - 1];
|
|
44480
|
+
const b = pivots[k];
|
|
44481
|
+
polylines.push({
|
|
44482
|
+
key: `leg${k}`,
|
|
44483
|
+
points: [
|
|
44484
|
+
{ xloc: "bar_time", x: bars[a.i].time, price: a.price },
|
|
44485
|
+
{ xloc: "bar_time", x: bars[b.i].time, price: b.price }
|
|
44486
|
+
],
|
|
44487
|
+
curved: false,
|
|
44488
|
+
closed: false,
|
|
44489
|
+
lineColor: b.price >= a.price ? bull : bear,
|
|
44490
|
+
lineWidth: width,
|
|
44491
|
+
lineStyle: "solid",
|
|
44492
|
+
arrowLeft: false,
|
|
44493
|
+
arrowRight: false
|
|
44494
|
+
});
|
|
44495
|
+
}
|
|
44496
|
+
return { plots: [], polylines };
|
|
42985
44497
|
}
|
|
42986
44498
|
};
|
|
42987
44499
|
var williamsFractal = {
|
|
@@ -42989,10 +44501,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42989
44501
|
title: "Williams Fractal",
|
|
42990
44502
|
shortTitle: "Fractals",
|
|
42991
44503
|
overlay: true,
|
|
42992
|
-
inputs: [
|
|
44504
|
+
inputs: [
|
|
44505
|
+
intInput("periods", "Periods", 2, 2, 50, "Bars required on each side of the candidate bar. 2 is Bill Williams' classic 5-bar fractal; a fractal is confirmed only once this many later bars have closed."),
|
|
44506
|
+
colorInput(BEARISH, "Up Fractal", "upColor", "Color of the up fractal marker (swing high), drawn above the fractal bar."),
|
|
44507
|
+
colorInput(BULLISH, "Down Fractal", "downColor", "Color of the down fractal marker (swing low), drawn below the fractal bar.")
|
|
44508
|
+
],
|
|
42993
44509
|
compute: (bars, inputs) => {
|
|
42994
44510
|
const p = num3(inputs, "periods", 2);
|
|
42995
44511
|
const markers = [];
|
|
44512
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
44513
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
42996
44514
|
const isExtreme = (i, pick, better) => {
|
|
42997
44515
|
const v = pick(bars[i]);
|
|
42998
44516
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -43002,12 +44520,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43002
44520
|
return true;
|
|
43003
44521
|
};
|
|
43004
44522
|
for (let i = p; i < bars.length - p; i++) {
|
|
43005
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
43006
|
-
|
|
43007
|
-
}
|
|
43008
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
43009
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
43010
|
-
}
|
|
44523
|
+
if (isExtreme(i, (b) => b.high, (a, b) => a > b)) markers.push({ time: bars[i].time, position: "aboveBar", shape: "triangleup", color: up });
|
|
44524
|
+
if (isExtreme(i, (b) => b.low, (a, b) => a < b)) markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: down });
|
|
43011
44525
|
}
|
|
43012
44526
|
return { plots: [], markers };
|
|
43013
44527
|
}
|