@luxalgo/vela 0.7.7 → 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-ZA2XWTGH.js → chunk-M6GP7DAX.js} +41 -7
- package/dist/{chunk-RVQWJOEE.js → chunk-N4ZZYLMP.js} +2179 -786
- package/dist/index.cjs +2176 -783
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +1 -1
- package/dist/{plugin-DPyVJFoA.d.cts → plugin-6F04gjQe.d.cts} +22 -22
- package/dist/{plugin-0oFDnexb.d.ts → plugin-BLsNipkc.d.ts} +22 -22
- package/dist/plugin.d.cts +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/vela.global.js +2176 -783
- package/dist/vela.global.min.js +54 -54
- package/dist/widget.cjs +2216 -789
- package/dist/widget.js +3 -3
- package/dist/workspace.cjs +2216 -789
- package/dist/workspace.d.cts +2 -0
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.js +2 -2
- package/package.json +1 -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);
|
|
@@ -29597,7 +29621,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
29597
29621
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
29598
29622
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29599
29623
|
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
29600
|
-
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)));
|
|
29601
29625
|
}
|
|
29602
29626
|
}
|
|
29603
29627
|
};
|
|
@@ -40866,7 +40890,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40866
40890
|
lineStyle: plot.lineStyle ?? "solid",
|
|
40867
40891
|
...plot.base != null ? { base: plot.base } : {}
|
|
40868
40892
|
},
|
|
40869
|
-
...plot.overlay ? { overlay: true } : {}
|
|
40893
|
+
...plot.overlay ? { overlay: true } : {},
|
|
40894
|
+
...plot.display ? { display: plot.display } : {}
|
|
40870
40895
|
});
|
|
40871
40896
|
});
|
|
40872
40897
|
if (out.markers && out.markers.length > 0) {
|
|
@@ -40888,7 +40913,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
40888
40913
|
paneId: "",
|
|
40889
40914
|
fromSeriesId: from,
|
|
40890
40915
|
toSeriesId: to,
|
|
40891
|
-
color: band.color
|
|
40916
|
+
color: band.color,
|
|
40917
|
+
...band.colors ? { colors: [...band.colors] } : {},
|
|
40918
|
+
...band.gradient ? { gradient: [...band.gradient] } : {}
|
|
40892
40919
|
}
|
|
40893
40920
|
];
|
|
40894
40921
|
});
|
|
@@ -41046,6 +41073,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41046
41073
|
return Math.sqrt(sq / len);
|
|
41047
41074
|
});
|
|
41048
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
|
+
}
|
|
41049
41086
|
function highest(v, len) {
|
|
41050
41087
|
return windowed(v, len, (w, from, to) => {
|
|
41051
41088
|
let m = -Infinity;
|
|
@@ -41177,7 +41214,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41177
41214
|
}
|
|
41178
41215
|
return out;
|
|
41179
41216
|
}
|
|
41180
|
-
function linreg(v, len) {
|
|
41217
|
+
function linreg(v, len, offset3 = 0) {
|
|
41181
41218
|
const sx = (len - 1) * len / 2;
|
|
41182
41219
|
const sxx = (len - 1) * len * (2 * len - 1) / 6;
|
|
41183
41220
|
const denom = len * sxx - sx * sx;
|
|
@@ -41191,9 +41228,101 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41191
41228
|
}
|
|
41192
41229
|
const slope = denom === 0 ? 0 : (len * sxy - sx * sy) / denom;
|
|
41193
41230
|
const intercept = (sy - slope * sx) / len;
|
|
41194
|
-
return intercept + slope * (len - 1);
|
|
41231
|
+
return intercept + slope * (len - 1 - offset3);
|
|
41232
|
+
});
|
|
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;
|
|
41195
41318
|
});
|
|
41196
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
|
+
}
|
|
41197
41326
|
function swma(v) {
|
|
41198
41327
|
const out = new Array(v.length).fill(Number.NaN);
|
|
41199
41328
|
for (let i = 3; i < v.length; i++) {
|
|
@@ -41243,38 +41372,253 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41243
41372
|
}
|
|
41244
41373
|
|
|
41245
41374
|
// src/core/native-indicators/classics/shared.ts
|
|
41246
|
-
|
|
41247
|
-
|
|
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 } : {} };
|
|
41382
|
+
}
|
|
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 } : {} };
|
|
41248
41385
|
}
|
|
41249
|
-
function
|
|
41250
|
-
return { key
|
|
41386
|
+
function boolInput(key, title, defval, group = SETTINGS, tooltip) {
|
|
41387
|
+
return { key, title, type: "bool", defval, group, ...tooltip != null ? { tooltip } : {} };
|
|
41251
41388
|
}
|
|
41252
|
-
function
|
|
41253
|
-
return { key, title, type: "
|
|
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);
|
|
41254
41403
|
}
|
|
41255
41404
|
function withAlpha2(hex, alpha = 0.08) {
|
|
41256
41405
|
const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
41257
41406
|
if (!m) return hex;
|
|
41258
|
-
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");
|
|
41259
41408
|
return `#${m[1]}${a}`;
|
|
41260
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
|
+
}
|
|
41261
41544
|
|
|
41262
41545
|
// src/core/native-indicators/classics/averages.ts
|
|
41263
|
-
var
|
|
41264
|
-
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." };
|
|
41265
41609
|
function fixedAverage(type, title, shortTitle, smooth) {
|
|
41266
41610
|
return {
|
|
41267
41611
|
type,
|
|
41268
41612
|
title,
|
|
41269
41613
|
shortTitle,
|
|
41270
41614
|
overlay: true,
|
|
41271
|
-
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()],
|
|
41272
41616
|
compute: (bars, inputs) => {
|
|
41273
41617
|
const len = num3(inputs, "length", 20);
|
|
41274
41618
|
let values = smooth(sourceValues(bars, str3(inputs, "source", "Close")), len);
|
|
41275
41619
|
const offset3 = Math.trunc(num3(inputs, "offset", 0));
|
|
41276
41620
|
if (offset3 !== 0) values = shift3(values, offset3);
|
|
41277
|
-
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 }] };
|
|
41278
41622
|
}
|
|
41279
41623
|
};
|
|
41280
41624
|
}
|
|
@@ -41286,36 +41630,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41286
41630
|
shortTitle: "MA",
|
|
41287
41631
|
overlay: true,
|
|
41288
41632
|
inputs: [
|
|
41289
|
-
|
|
41290
|
-
lengthInput(20),
|
|
41291
|
-
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."),
|
|
41292
41643
|
offsetInput,
|
|
41293
|
-
colorInput()
|
|
41644
|
+
colorInput(INFO, "MA 1", "color", "MA 1 line color."),
|
|
41645
|
+
colorInput(WARNING, "MA 2", "ma2Color", "MA 2 line color.")
|
|
41294
41646
|
],
|
|
41295
41647
|
compute: (bars, inputs) => {
|
|
41296
|
-
const
|
|
41297
|
-
|
|
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);
|
|
41298
41655
|
const kind = str3(inputs, "maType", "SMA");
|
|
41299
|
-
|
|
41300
|
-
|
|
41301
|
-
|
|
41302
|
-
|
|
41303
|
-
|
|
41304
|
-
|
|
41305
|
-
values = wma(src, len);
|
|
41306
|
-
break;
|
|
41307
|
-
case "RMA":
|
|
41308
|
-
values = rma(src, len);
|
|
41309
|
-
break;
|
|
41310
|
-
case "VWMA":
|
|
41311
|
-
values = vwma(src, volumes(bars), len);
|
|
41312
|
-
break;
|
|
41313
|
-
default:
|
|
41314
|
-
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 });
|
|
41315
41662
|
}
|
|
41316
|
-
|
|
41317
|
-
if (offset3 !== 0) values = shift3(values, offset3);
|
|
41318
|
-
return { plots: [{ key: "ma", title: kind, values, color: str3(inputs, "color", SERIES_LINE), width: 2 }] };
|
|
41663
|
+
return { plots };
|
|
41319
41664
|
}
|
|
41320
41665
|
};
|
|
41321
41666
|
var smoothedMa = {
|
|
@@ -41323,17 +41668,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41323
41668
|
title: "Smoothed Moving Average",
|
|
41324
41669
|
shortTitle: "RMA",
|
|
41325
41670
|
overlay: true,
|
|
41326
|
-
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
|
+
],
|
|
41327
41676
|
compute: (bars, inputs) => ({
|
|
41328
|
-
plots: [
|
|
41329
|
-
{
|
|
41330
|
-
key: "rma",
|
|
41331
|
-
title: "RMA",
|
|
41332
|
-
values: rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)),
|
|
41333
|
-
color: str3(inputs, "color", SERIES_LINE),
|
|
41334
|
-
width: 2
|
|
41335
|
-
}
|
|
41336
|
-
]
|
|
41677
|
+
plots: [slopeColoredPlot("rma", "RMA", rma(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "length", 14)), inputs)]
|
|
41337
41678
|
})
|
|
41338
41679
|
};
|
|
41339
41680
|
var zlema = {
|
|
@@ -41341,61 +41682,86 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41341
41682
|
title: "Zero-Lag Exponential Moving Average",
|
|
41342
41683
|
shortTitle: "ZLEMA",
|
|
41343
41684
|
overlay: true,
|
|
41344
|
-
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
|
+
],
|
|
41345
41690
|
compute: (bars, inputs) => {
|
|
41346
|
-
const len = num3(inputs, "length",
|
|
41691
|
+
const len = num3(inputs, "length", 21);
|
|
41347
41692
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41348
|
-
const lag = Math.
|
|
41349
|
-
const
|
|
41350
|
-
|
|
41351
|
-
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)] };
|
|
41352
41696
|
}
|
|
41353
41697
|
};
|
|
41698
|
+
var MOMENTUM_SCALER = "Momentum (|CMO|)";
|
|
41699
|
+
var VOLATILITY_SCALER = "Volatility (StDev Ratio)";
|
|
41700
|
+
var REGIME = "Trend Regime";
|
|
41354
41701
|
var vidya = {
|
|
41355
41702
|
type: "vidya",
|
|
41356
41703
|
title: "Variable Index Dynamic Average",
|
|
41357
41704
|
shortTitle: "VIDYA",
|
|
41358
41705
|
overlay: true,
|
|
41359
|
-
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
|
+
],
|
|
41360
41722
|
compute: (bars, inputs) => {
|
|
41361
|
-
const len = num3(inputs, "length",
|
|
41362
|
-
const cmoLen = num3(inputs, "cmoLength", 9);
|
|
41723
|
+
const len = num3(inputs, "length", 12);
|
|
41363
41724
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41364
|
-
const
|
|
41365
|
-
const
|
|
41366
|
-
const
|
|
41367
|
-
|
|
41368
|
-
|
|
41369
|
-
|
|
41370
|
-
|
|
41371
|
-
for (let k = i - cmoLen + 1; k <= i; k++) {
|
|
41372
|
-
const x = d[k];
|
|
41373
|
-
if (!Number.isFinite(x)) {
|
|
41374
|
-
ok = false;
|
|
41375
|
-
break;
|
|
41376
|
-
}
|
|
41377
|
-
if (x > 0) u += x;
|
|
41378
|
-
else w -= x;
|
|
41379
|
-
}
|
|
41380
|
-
if (ok) {
|
|
41381
|
-
su[i] = u;
|
|
41382
|
-
sd[i] = w;
|
|
41383
|
-
}
|
|
41384
|
-
}
|
|
41385
|
-
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);
|
|
41386
41732
|
const out = new Array(src.length).fill(Number.NaN);
|
|
41387
41733
|
let prev3 = Number.NaN;
|
|
41388
41734
|
for (let i = 0; i < src.length; i++) {
|
|
41389
41735
|
const x = src[i];
|
|
41390
|
-
|
|
41391
|
-
const
|
|
41392
|
-
if (!Number.isFinite(
|
|
41393
|
-
|
|
41394
|
-
|
|
41395
|
-
|
|
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
|
+
}
|
|
41396
41743
|
out[i] = prev3;
|
|
41397
41744
|
}
|
|
41398
|
-
|
|
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 }] };
|
|
41399
41765
|
}
|
|
41400
41766
|
};
|
|
41401
41767
|
var maEnvelope = {
|
|
@@ -41404,115 +41770,218 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41404
41770
|
shortTitle: "MA Env",
|
|
41405
41771
|
overlay: true,
|
|
41406
41772
|
inputs: [
|
|
41407
|
-
|
|
41408
|
-
lengthInput(20),
|
|
41409
|
-
sourceInput(),
|
|
41410
|
-
|
|
41411
|
-
|
|
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()
|
|
41412
41778
|
],
|
|
41413
41779
|
compute: (bars, inputs) => {
|
|
41414
|
-
const len = num3(inputs, "length", 20);
|
|
41415
41780
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41416
|
-
const basis = str3(inputs, "maType", "SMA")
|
|
41781
|
+
const basis = movingAverageOf(str3(inputs, "maType", "SMA"), src, bars, num3(inputs, "length", 20));
|
|
41417
41782
|
const k = num3(inputs, "percent", 2.5) / 100;
|
|
41418
|
-
const ink = str3(inputs, "
|
|
41419
|
-
return
|
|
41420
|
-
plots: [
|
|
41421
|
-
{ key: "basis", title: "Basis", values: basis, color: ink, width: 2 },
|
|
41422
|
-
{ key: "upper", title: "Upper", values: map(basis, (x) => x * (1 + k)), color: ink },
|
|
41423
|
-
{ key: "lower", title: "Lower", values: map(basis, (x) => x * (1 - k)), color: ink }
|
|
41424
|
-
],
|
|
41425
|
-
bands: [{ key: "envelope", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
41426
|
-
};
|
|
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");
|
|
41427
41785
|
}
|
|
41428
41786
|
};
|
|
41787
|
+
var LINREG_CURVE = "Curve";
|
|
41788
|
+
var LINREG_SLOPE = "Slope";
|
|
41429
41789
|
var linearRegression = {
|
|
41430
41790
|
type: "linear-regression",
|
|
41431
41791
|
title: "Linear Regression Curve",
|
|
41432
41792
|
shortTitle: "LinReg",
|
|
41433
|
-
|
|
41434
|
-
|
|
41435
|
-
|
|
41436
|
-
plots
|
|
41437
|
-
|
|
41438
|
-
|
|
41439
|
-
|
|
41440
|
-
|
|
41441
|
-
|
|
41442
|
-
|
|
41443
|
-
|
|
41444
|
-
|
|
41445
|
-
|
|
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
|
+
}
|
|
41446
41820
|
};
|
|
41447
41821
|
var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
|
|
41448
41822
|
|
|
41449
41823
|
// src/core/native-indicators/classics/bands.ts
|
|
41824
|
+
var JAW_COLOR = "#ab47bc";
|
|
41825
|
+
var TEETH_COLOR = WARNING;
|
|
41826
|
+
var LIPS_COLOR = "#ffd700";
|
|
41450
41827
|
function bollinger(bars, inputs) {
|
|
41451
41828
|
const len = num3(inputs, "length", 20);
|
|
41452
41829
|
const mult = num3(inputs, "mult", 2);
|
|
41453
41830
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41454
41831
|
const basis = sma(src, len);
|
|
41455
41832
|
const dev = map(stdev(src, len), (x) => x * mult);
|
|
41456
|
-
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
|
+
];
|
|
41457
41841
|
}
|
|
41458
|
-
var BB_INPUTS = [lengthInput(20), sourceInput(), { key: "mult", title: "StdDev", type: "float", defval: 2, min: 0.1, max: 50, step: 0.1 }];
|
|
41459
41842
|
var bollingerBands = {
|
|
41460
41843
|
type: "bollinger-bands",
|
|
41461
41844
|
title: "Bollinger Bands",
|
|
41462
41845
|
shortTitle: "BB",
|
|
41463
41846
|
overlay: true,
|
|
41464
|
-
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
|
+
],
|
|
41465
41855
|
compute: (bars, inputs) => {
|
|
41466
41856
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
41467
|
-
const ink = str3(inputs, "color",
|
|
41857
|
+
const ink = str3(inputs, "color", INFO);
|
|
41468
41858
|
return {
|
|
41469
41859
|
plots: [
|
|
41470
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
41860
|
+
{ key: "basis", title: "Basis", values: basis, color: str3(inputs, "basisColor", WARNING) },
|
|
41471
41861
|
{ key: "upper", title: "Upper", values: upper, color: ink },
|
|
41472
41862
|
{ key: "lower", title: "Lower", values: lower, color: ink }
|
|
41473
41863
|
],
|
|
41474
|
-
bands: [{ key: "bb", from: "upper", to: "lower", color:
|
|
41864
|
+
bands: [{ key: "bb", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(INFO, 92)) }]
|
|
41475
41865
|
};
|
|
41476
41866
|
}
|
|
41477
41867
|
};
|
|
41868
|
+
var PERCENT_B_MID = 0.5;
|
|
41869
|
+
var THRESHOLDS = "Thresholds";
|
|
41478
41870
|
var percentB = {
|
|
41479
41871
|
type: "percent-b",
|
|
41480
41872
|
title: "Bollinger Bands %B",
|
|
41481
41873
|
shortTitle: "%B",
|
|
41482
41874
|
overlay: false,
|
|
41483
|
-
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
|
+
],
|
|
41484
41884
|
compute: (bars, inputs) => {
|
|
41485
|
-
const { upper, lower } = bollinger(bars, inputs);
|
|
41486
|
-
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41885
|
+
const { src, upper, lower } = bollinger(bars, inputs);
|
|
41487
41886
|
const values = src.map((x, i) => {
|
|
41488
41887
|
const u = upper[i];
|
|
41489
41888
|
const l = lower[i];
|
|
41490
41889
|
return Number.isFinite(u) && Number.isFinite(l) && u !== l ? (x - l) / (u - l) : Number.NaN;
|
|
41491
41890
|
});
|
|
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
|
+
] : [];
|
|
41492
41919
|
return {
|
|
41493
|
-
plots
|
|
41920
|
+
plots,
|
|
41921
|
+
bands,
|
|
41494
41922
|
levels: [
|
|
41495
|
-
{ key: "
|
|
41496
|
-
{ key: "
|
|
41497
|
-
{ key: "
|
|
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" }
|
|
41498
41926
|
]
|
|
41499
41927
|
};
|
|
41500
41928
|
}
|
|
41501
41929
|
};
|
|
41930
|
+
var SQUEEZE_BULGE = "Squeeze & Bulge";
|
|
41502
41931
|
var bandwidth = {
|
|
41503
41932
|
type: "bandwidth",
|
|
41504
41933
|
title: "Bollinger Bands Width",
|
|
41505
41934
|
shortTitle: "BandWidth",
|
|
41506
41935
|
overlay: false,
|
|
41507
|
-
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
|
+
],
|
|
41508
41946
|
compute: (bars, inputs) => {
|
|
41509
41947
|
const { basis, upper, lower } = bollinger(bars, inputs);
|
|
41510
41948
|
const values = basis.map((b, i) => {
|
|
41511
41949
|
const u = upper[i];
|
|
41512
41950
|
const l = lower[i];
|
|
41513
|
-
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;
|
|
41952
|
+
});
|
|
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;
|
|
41514
41964
|
});
|
|
41515
|
-
|
|
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 };
|
|
41516
41985
|
}
|
|
41517
41986
|
};
|
|
41518
41987
|
var keltner = {
|
|
@@ -41521,26 +41990,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41521
41990
|
shortTitle: "KC",
|
|
41522
41991
|
overlay: true,
|
|
41523
41992
|
inputs: [
|
|
41524
|
-
lengthInput(20),
|
|
41525
|
-
|
|
41526
|
-
|
|
41527
|
-
sourceInput(),
|
|
41528
|
-
|
|
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")
|
|
41529
41998
|
],
|
|
41530
41999
|
compute: (bars, inputs) => {
|
|
41531
|
-
const
|
|
41532
|
-
const
|
|
41533
|
-
const
|
|
41534
|
-
|
|
41535
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
41536
|
-
return {
|
|
41537
|
-
plots: [
|
|
41538
|
-
{ key: "basis", title: "Basis", values: basis, color: WARNING },
|
|
41539
|
-
{ key: "upper", title: "Upper", values: zip(basis, range, (b, r) => b + r), color: ink },
|
|
41540
|
-
{ key: "lower", title: "Lower", values: zip(basis, range, (b, r) => b - r), color: ink }
|
|
41541
|
-
],
|
|
41542
|
-
bands: [{ key: "kc", from: "upper", to: "lower", color: withAlpha2(ink) }]
|
|
41543
|
-
};
|
|
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");
|
|
41544
42004
|
}
|
|
41545
42005
|
};
|
|
41546
42006
|
var donchian = {
|
|
@@ -41548,19 +42008,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41548
42008
|
title: "Donchian Channels",
|
|
41549
42009
|
shortTitle: "DC",
|
|
41550
42010
|
overlay: true,
|
|
41551
|
-
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
|
+
],
|
|
41552
42018
|
compute: (bars, inputs) => {
|
|
41553
42019
|
const len = num3(inputs, "length", 20);
|
|
41554
42020
|
const upper = highest(highs(bars), len);
|
|
41555
42021
|
const lower = lowest(lows(bars), len);
|
|
41556
|
-
const ink = str3(inputs, "color", SERIES_LINE);
|
|
41557
42022
|
return {
|
|
41558
42023
|
plots: [
|
|
41559
|
-
{ key: "
|
|
41560
|
-
{ key: "
|
|
41561
|
-
{ 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) }
|
|
41562
42027
|
],
|
|
41563
|
-
bands: [{ key: "dc", from: "upper", to: "lower", color:
|
|
42028
|
+
bands: [{ key: "dc", from: "upper", to: "lower", color: str3(inputs, "fillColor", transp(NEUTRAL, 92)) }]
|
|
41564
42029
|
};
|
|
41565
42030
|
}
|
|
41566
42031
|
};
|
|
@@ -41569,16 +42034,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41569
42034
|
title: "Chandelier Exit",
|
|
41570
42035
|
shortTitle: "CE",
|
|
41571
42036
|
overlay: true,
|
|
41572
|
-
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
|
+
],
|
|
41573
42044
|
compute: (bars, inputs) => {
|
|
41574
42045
|
const len = num3(inputs, "length", 22);
|
|
41575
42046
|
const range = map(atr(bars, len), (x) => x * num3(inputs, "mult", 3));
|
|
41576
|
-
const
|
|
41577
|
-
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
|
+
}
|
|
41578
42064
|
return {
|
|
41579
42065
|
plots: [
|
|
41580
|
-
{ key: "long", title: "Long
|
|
41581
|
-
{ 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) }
|
|
41582
42068
|
]
|
|
41583
42069
|
};
|
|
41584
42070
|
}
|
|
@@ -41589,21 +42075,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41589
42075
|
shortTitle: "CKS",
|
|
41590
42076
|
overlay: true,
|
|
41591
42077
|
inputs: [
|
|
41592
|
-
|
|
41593
|
-
|
|
41594
|
-
|
|
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.")
|
|
41595
42083
|
],
|
|
41596
42084
|
compute: (bars, inputs) => {
|
|
41597
42085
|
const p = num3(inputs, "p", 10);
|
|
41598
|
-
const x = num3(inputs, "x", 1);
|
|
41599
42086
|
const q = num3(inputs, "q", 9);
|
|
41600
|
-
const range = map(atr(bars, p), (v) => v * x);
|
|
42087
|
+
const range = map(atr(bars, p), (v) => v * num3(inputs, "x", 1));
|
|
41601
42088
|
const firstHigh = zip(highest(highs(bars), p), range, (h, r) => h - r);
|
|
41602
42089
|
const firstLow = zip(lowest(lows(bars), p), range, (l, r) => l + r);
|
|
41603
42090
|
return {
|
|
41604
42091
|
plots: [
|
|
41605
|
-
{ key: "stopShort", title: "Stop
|
|
41606
|
-
{ 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) }
|
|
41607
42094
|
]
|
|
41608
42095
|
};
|
|
41609
42096
|
}
|
|
@@ -41614,36 +42101,42 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41614
42101
|
shortTitle: "SuperTrend",
|
|
41615
42102
|
overlay: true,
|
|
41616
42103
|
inputs: [
|
|
41617
|
-
|
|
41618
|
-
|
|
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.")
|
|
41619
42108
|
],
|
|
41620
42109
|
compute: (bars, inputs) => {
|
|
41621
|
-
const len = num3(inputs, "atrLength", 10);
|
|
41622
42110
|
const mult = num3(inputs, "mult", 3);
|
|
41623
|
-
const range = atr(bars,
|
|
42111
|
+
const range = atr(bars, num3(inputs, "atrLength", 10));
|
|
41624
42112
|
const n = bars.length;
|
|
41625
|
-
const
|
|
41626
|
-
const
|
|
41627
|
-
let
|
|
41628
|
-
let
|
|
41629
|
-
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;
|
|
41630
42118
|
for (let i = 0; i < n; i++) {
|
|
41631
42119
|
const r = range[i];
|
|
41632
42120
|
if (!Number.isFinite(r)) continue;
|
|
41633
42121
|
const b = bars[i];
|
|
41634
42122
|
const mid = (b.high + b.low) / 2;
|
|
41635
|
-
const
|
|
41636
|
-
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;
|
|
41637
42127
|
const prevClose = i > 0 ? bars[i - 1].close : b.close;
|
|
41638
|
-
|
|
41639
|
-
|
|
41640
|
-
|
|
41641
|
-
|
|
41642
|
-
|
|
41643
|
-
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;
|
|
41644
42133
|
}
|
|
41645
|
-
|
|
41646
|
-
|
|
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
|
+
};
|
|
41647
42140
|
}
|
|
41648
42141
|
};
|
|
41649
42142
|
var alligator = {
|
|
@@ -41652,12 +42145,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41652
42145
|
shortTitle: "Alligator",
|
|
41653
42146
|
overlay: true,
|
|
41654
42147
|
inputs: [
|
|
41655
|
-
|
|
41656
|
-
|
|
41657
|
-
|
|
41658
|
-
|
|
41659
|
-
|
|
41660
|
-
|
|
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.")
|
|
41661
42157
|
],
|
|
41662
42158
|
compute: (bars, inputs) => {
|
|
41663
42159
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -41666,99 +42162,134 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41666
42162
|
const lips = shift3(rma(hl2, num3(inputs, "lipsLength", 5)), num3(inputs, "lipsOffset", 3));
|
|
41667
42163
|
return {
|
|
41668
42164
|
plots: [
|
|
41669
|
-
{ key: "jaw", title: "Jaw", values: jaw, color:
|
|
41670
|
-
{ key: "teeth", title: "Teeth", values: teeth, color:
|
|
41671
|
-
{ 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) }
|
|
41672
42168
|
]
|
|
41673
42169
|
};
|
|
41674
42170
|
}
|
|
41675
42171
|
};
|
|
42172
|
+
var GATOR_LINES = "Alligator Lines";
|
|
42173
|
+
var SMMA_TYPE = "SMMA (Wilder)";
|
|
41676
42174
|
var gator = {
|
|
41677
42175
|
type: "gator-oscillator",
|
|
41678
42176
|
title: "Gator Oscillator",
|
|
41679
42177
|
shortTitle: "Gator Oscillator",
|
|
41680
42178
|
overlay: false,
|
|
41681
|
-
inputs: [
|
|
41682
|
-
|
|
41683
|
-
|
|
41684
|
-
|
|
41685
|
-
|
|
41686
|
-
|
|
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));
|
|
41687
42199
|
const upper = map(sub(jaw, teeth), Math.abs);
|
|
41688
|
-
const lower = map(sub(teeth, lips),
|
|
41689
|
-
const
|
|
41690
|
-
|
|
41691
|
-
|
|
41692
|
-
|
|
41693
|
-
|
|
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
|
+
};
|
|
41694
42212
|
return {
|
|
41695
42213
|
plots: [
|
|
41696
|
-
{ key: "upper", title: "Upper", values: upper, kind: "
|
|
41697
|
-
{ key: "lower", title: "Lower", values: lower, kind: "
|
|
41698
|
-
]
|
|
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" }]
|
|
41699
42218
|
};
|
|
41700
42219
|
}
|
|
41701
42220
|
};
|
|
41702
42221
|
var bandSpecs = [bollingerBands, percentB, bandwidth, keltner, donchian, chandelier, chandeKroll, supertrend, alligator, gator];
|
|
41703
42222
|
|
|
41704
42223
|
// src/core/native-indicators/classics/oscillators.ts
|
|
41705
|
-
function
|
|
41706
|
-
|
|
41707
|
-
|
|
41708
|
-
|
|
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.")
|
|
41709
42228
|
];
|
|
41710
|
-
if (mid != null) levels.splice(1, 0, { key: "middle", price: mid, color: NEUTRAL, lineStyle: "dotted" });
|
|
41711
|
-
return levels;
|
|
41712
|
-
}
|
|
41713
|
-
var ZERO_LEVEL = [{ key: "zero", price: 0, color: NEUTRAL }];
|
|
41714
|
-
function histColors(values) {
|
|
41715
|
-
return values.map((x, i) => {
|
|
41716
|
-
if (!Number.isFinite(x)) return null;
|
|
41717
|
-
const prev3 = i > 0 ? values[i - 1] : Number.NaN;
|
|
41718
|
-
const rising = Number.isFinite(prev3) ? x >= prev3 : true;
|
|
41719
|
-
if (x >= 0) return rising ? BULLISH : `${BULLISH}80`;
|
|
41720
|
-
return rising ? `${BEARISH}80` : BEARISH;
|
|
41721
|
-
});
|
|
41722
42229
|
}
|
|
41723
42230
|
var rsiSpec = {
|
|
41724
42231
|
type: "rsi",
|
|
41725
42232
|
title: "Relative Strength Index",
|
|
41726
42233
|
shortTitle: "RSI",
|
|
41727
42234
|
overlay: false,
|
|
41728
|
-
inputs: [
|
|
41729
|
-
|
|
41730
|
-
|
|
41731
|
-
|
|
41732
|
-
|
|
41733
|
-
|
|
41734
|
-
|
|
41735
|
-
|
|
41736
|
-
|
|
41737
|
-
|
|
41738
|
-
|
|
41739
|
-
|
|
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))
|
|
41740
42257
|
})
|
|
41741
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
|
+
}
|
|
41742
42276
|
var stochastic = {
|
|
41743
42277
|
type: "stochastic",
|
|
41744
42278
|
title: "Stochastic",
|
|
41745
42279
|
shortTitle: "Stoch",
|
|
41746
42280
|
overlay: false,
|
|
41747
42281
|
inputs: [
|
|
41748
|
-
lengthInput(14, "kLength", "%K
|
|
41749
|
-
lengthInput(
|
|
41750
|
-
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.")
|
|
41751
42289
|
],
|
|
41752
42290
|
compute: (bars, inputs) => {
|
|
41753
|
-
const k = sma(stoch(closes(bars), highs(bars), lows(bars), num3(inputs, "kLength", 14)), num3(inputs, "kSmoothing",
|
|
41754
|
-
|
|
41755
|
-
return {
|
|
41756
|
-
plots: [
|
|
41757
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
41758
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
41759
|
-
],
|
|
41760
|
-
levels: guideLevels(20, 80)
|
|
41761
|
-
};
|
|
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);
|
|
41762
42293
|
}
|
|
41763
42294
|
};
|
|
41764
42295
|
var stochasticRsi = {
|
|
@@ -41767,23 +42298,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41767
42298
|
shortTitle: "Stoch RSI",
|
|
41768
42299
|
overlay: false,
|
|
41769
42300
|
inputs: [
|
|
41770
|
-
|
|
41771
|
-
lengthInput(14, "
|
|
41772
|
-
lengthInput(
|
|
41773
|
-
lengthInput(3, "
|
|
41774
|
-
|
|
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.")
|
|
41775
42310
|
],
|
|
41776
42311
|
compute: (bars, inputs) => {
|
|
41777
42312
|
const r = rsi(sourceValues(bars, str3(inputs, "source", "Close")), num3(inputs, "rsiLength", 14));
|
|
41778
42313
|
const k = sma(stoch(r, r, r, num3(inputs, "stochLength", 14)), num3(inputs, "kSmoothing", 3));
|
|
41779
|
-
|
|
41780
|
-
return {
|
|
41781
|
-
plots: [
|
|
41782
|
-
{ key: "k", title: "%K", values: k, color: SERIES_LINE, width: 2 },
|
|
41783
|
-
{ key: "d", title: "%D", values: d, color: WARNING }
|
|
41784
|
-
],
|
|
41785
|
-
levels: guideLevels(20, 80)
|
|
41786
|
-
};
|
|
42314
|
+
return stochasticOutput(k, sma(k, num3(inputs, "dLength", 3)), inputs);
|
|
41787
42315
|
}
|
|
41788
42316
|
};
|
|
41789
42317
|
var macd = {
|
|
@@ -41791,23 +42319,35 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41791
42319
|
title: "MACD",
|
|
41792
42320
|
overlay: false,
|
|
41793
42321
|
inputs: [
|
|
41794
|
-
lengthInput(12, "fastLength", "Fast
|
|
41795
|
-
lengthInput(26, "slowLength", "Slow
|
|
41796
|
-
lengthInput(9, "signalLength", "Signal
|
|
41797
|
-
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.")
|
|
41798
42332
|
],
|
|
41799
42333
|
compute: (bars, inputs) => {
|
|
41800
42334
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41801
42335
|
const line = sub(ema(src, num3(inputs, "fastLength", 12)), ema(src, num3(inputs, "slowLength", 26)));
|
|
41802
42336
|
const signal = ema(line, num3(inputs, "signalLength", 9));
|
|
41803
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
|
+
});
|
|
41804
42344
|
return {
|
|
41805
42345
|
plots: [
|
|
41806
|
-
{ key: "hist", title: "Histogram", values: hist, kind: "
|
|
41807
|
-
{ key: "macd", title: "MACD", values: line, color:
|
|
41808
|
-
{ 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) }
|
|
41809
42349
|
],
|
|
41810
|
-
levels:
|
|
42350
|
+
levels: [{ key: "zero", price: 0, color: NEUTRAL, lineStyle: "dashed", title: "Zero Line" }]
|
|
41811
42351
|
};
|
|
41812
42352
|
}
|
|
41813
42353
|
};
|
|
@@ -41817,26 +42357,49 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41817
42357
|
shortTitle: "PPO",
|
|
41818
42358
|
overlay: false,
|
|
41819
42359
|
inputs: [
|
|
41820
|
-
|
|
41821
|
-
lengthInput(
|
|
41822
|
-
lengthInput(
|
|
41823
|
-
|
|
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.")
|
|
41824
42371
|
],
|
|
41825
42372
|
compute: (bars, inputs) => {
|
|
41826
42373
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
41827
|
-
const
|
|
41828
|
-
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));
|
|
41829
42378
|
const line = zip(fast, slow, (f, s) => s === 0 ? Number.NaN : (f - s) / s * 100);
|
|
41830
|
-
const signal =
|
|
42379
|
+
const signal = average(str3(inputs, "signalType", "EMA"), line, num3(inputs, "signalLength", 9));
|
|
41831
42380
|
const hist = sub(line, signal);
|
|
41832
|
-
|
|
41833
|
-
|
|
41834
|
-
|
|
41835
|
-
|
|
41836
|
-
|
|
41837
|
-
|
|
41838
|
-
|
|
41839
|
-
|
|
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;
|
|
41840
42403
|
}
|
|
41841
42404
|
};
|
|
41842
42405
|
var awesome = {
|
|
@@ -41844,48 +42407,67 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41844
42407
|
title: "Awesome Oscillator",
|
|
41845
42408
|
shortTitle: "AO",
|
|
41846
42409
|
overlay: false,
|
|
41847
|
-
inputs: [
|
|
41848
|
-
|
|
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) => {
|
|
41849
42416
|
const hl2 = sourceValues(bars, "HL2");
|
|
41850
42417
|
const ao = sub(sma(hl2, 5), sma(hl2, 34));
|
|
42418
|
+
const rising = str3(inputs, "risingColor", BULLISH);
|
|
42419
|
+
const falling = str3(inputs, "fallingColor", BEARISH);
|
|
41851
42420
|
const colors = ao.map((x, i) => {
|
|
41852
42421
|
if (!Number.isFinite(x)) return null;
|
|
41853
42422
|
const prev3 = i > 0 ? ao[i - 1] : Number.NaN;
|
|
41854
|
-
return Number.isFinite(prev3) && x
|
|
42423
|
+
return Number.isFinite(prev3) && x > prev3 ? rising : falling;
|
|
41855
42424
|
});
|
|
41856
42425
|
return {
|
|
41857
|
-
plots: [{ key: "ao", title: "AO", values: ao, kind: "
|
|
41858
|
-
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" }]
|
|
41859
42428
|
};
|
|
41860
42429
|
}
|
|
41861
42430
|
};
|
|
42431
|
+
var CCI_SCALING = 0.015;
|
|
41862
42432
|
var cci = {
|
|
41863
42433
|
type: "commodity-channel-index",
|
|
41864
42434
|
title: "Commodity Channel Index",
|
|
41865
42435
|
shortTitle: "CCI",
|
|
41866
42436
|
overlay: false,
|
|
41867
|
-
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
|
+
],
|
|
41868
42447
|
compute: (bars, inputs) => {
|
|
41869
42448
|
const len = num3(inputs, "length", 20);
|
|
41870
42449
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
41871
42450
|
const basis = sma(src, len);
|
|
41872
|
-
const dev =
|
|
41873
|
-
for (let i = len - 1; i < src.length; i++) {
|
|
41874
|
-
const mean = basis[i];
|
|
41875
|
-
if (!Number.isFinite(mean)) continue;
|
|
41876
|
-
let d = 0;
|
|
41877
|
-
for (let k = i - len + 1; k <= i; k++) d += Math.abs(src[k] - mean);
|
|
41878
|
-
dev[i] = d / len;
|
|
41879
|
-
}
|
|
42451
|
+
const dev = meanDev(src, len);
|
|
41880
42452
|
const values = src.map((x, i) => {
|
|
41881
42453
|
const b = basis[i];
|
|
41882
42454
|
const d = dev[i];
|
|
41883
|
-
|
|
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
|
+
]
|
|
41884
42470
|
});
|
|
41885
|
-
return {
|
|
41886
|
-
plots: [{ key: "cci", title: "CCI", values, color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
41887
|
-
levels: guideLevels(-100, 100, 0)
|
|
41888
|
-
};
|
|
41889
42471
|
}
|
|
41890
42472
|
};
|
|
41891
42473
|
var williamsR = {
|
|
@@ -41893,31 +42475,78 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41893
42475
|
title: "Williams %R",
|
|
41894
42476
|
shortTitle: "%R",
|
|
41895
42477
|
overlay: false,
|
|
41896
|
-
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
|
+
],
|
|
41897
42486
|
compute: (bars, inputs) => {
|
|
41898
|
-
const
|
|
41899
|
-
|
|
41900
|
-
|
|
41901
|
-
|
|
41902
|
-
|
|
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
|
+
});
|
|
41903
42506
|
}
|
|
41904
42507
|
};
|
|
41905
|
-
var
|
|
42508
|
+
var cmoSpec = {
|
|
41906
42509
|
type: "chande-momentum-oscillator",
|
|
41907
42510
|
title: "Chande Momentum Oscillator",
|
|
41908
42511
|
shortTitle: "CMO",
|
|
41909
42512
|
overlay: false,
|
|
41910
|
-
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
|
+
],
|
|
41911
42523
|
compute: (bars, inputs) => {
|
|
41912
42524
|
const len = num3(inputs, "length", 9);
|
|
41913
42525
|
const d = change(sourceValues(bars, str3(inputs, "source", "Close")));
|
|
41914
|
-
const
|
|
41915
|
-
const
|
|
41916
|
-
const values = zip(
|
|
41917
|
-
|
|
41918
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
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;
|
|
41921
42550
|
}
|
|
41922
42551
|
};
|
|
41923
42552
|
var connorsRsi = {
|
|
@@ -41926,10 +42555,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41926
42555
|
shortTitle: "CRSI",
|
|
41927
42556
|
overlay: false,
|
|
41928
42557
|
inputs: [
|
|
41929
|
-
lengthInput(3, "rsiLength", "RSI
|
|
41930
|
-
lengthInput(2, "streakLength", "Streak RSI
|
|
41931
|
-
lengthInput(100, "rankLength", "Percent
|
|
41932
|
-
|
|
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.")
|
|
41933
42566
|
],
|
|
41934
42567
|
compute: (bars, inputs) => {
|
|
41935
42568
|
const c = closes(bars);
|
|
@@ -41947,10 +42580,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41947
42580
|
const z = r[i];
|
|
41948
42581
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? (x + y + z) / 3 : Number.NaN;
|
|
41949
42582
|
});
|
|
41950
|
-
return {
|
|
41951
|
-
|
|
41952
|
-
|
|
41953
|
-
|
|
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
|
+
});
|
|
41954
42594
|
}
|
|
41955
42595
|
};
|
|
41956
42596
|
var fisher = {
|
|
@@ -41958,7 +42598,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41958
42598
|
title: "Fisher Transform",
|
|
41959
42599
|
shortTitle: "Fisher",
|
|
41960
42600
|
overlay: false,
|
|
41961
|
-
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
|
+
],
|
|
41962
42608
|
compute: (bars, inputs) => {
|
|
41963
42609
|
const len = num3(inputs, "length", 9);
|
|
41964
42610
|
const hl2 = sourceValues(bars, "HL2");
|
|
@@ -41966,23 +42612,39 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41966
42612
|
const lo = lowest(hl2, len);
|
|
41967
42613
|
const n = bars.length;
|
|
41968
42614
|
const out = new Array(n).fill(Number.NaN);
|
|
42615
|
+
let position = 0;
|
|
41969
42616
|
let value = 0;
|
|
41970
|
-
let fish = 0;
|
|
41971
42617
|
for (let i = 0; i < n; i++) {
|
|
41972
42618
|
const h = hi[i];
|
|
41973
42619
|
const l = lo[i];
|
|
41974
42620
|
if (!Number.isFinite(h) || !Number.isFinite(l)) continue;
|
|
41975
|
-
const
|
|
41976
|
-
|
|
41977
|
-
|
|
41978
|
-
|
|
41979
|
-
|
|
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
|
+
};
|
|
41980
42637
|
return {
|
|
41981
42638
|
plots: [
|
|
41982
|
-
{ key: "fisher", title: "Fisher", values: out, color:
|
|
41983
|
-
{ 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) }
|
|
41984
42641
|
],
|
|
41985
|
-
|
|
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
|
+
]
|
|
41986
42648
|
};
|
|
41987
42649
|
}
|
|
41988
42650
|
};
|
|
@@ -41990,93 +42652,180 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
41990
42652
|
type: "trix",
|
|
41991
42653
|
title: "TRIX",
|
|
41992
42654
|
overlay: false,
|
|
41993
|
-
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
|
+
],
|
|
41994
42663
|
compute: (bars, inputs) => {
|
|
41995
42664
|
const len = num3(inputs, "length", 18);
|
|
41996
|
-
const
|
|
41997
|
-
const
|
|
41998
|
-
|
|
41999
|
-
|
|
42000
|
-
|
|
42001
|
-
|
|
42002
|
-
|
|
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
|
+
});
|
|
42003
42675
|
}
|
|
42004
42676
|
};
|
|
42005
|
-
function
|
|
42677
|
+
function tsiRatio(src, long, short) {
|
|
42006
42678
|
const mom = change(src);
|
|
42007
42679
|
const numer = ema(ema(mom, long), short);
|
|
42008
42680
|
const denom = ema(ema(map(mom, Math.abs), long), short);
|
|
42009
|
-
return zip(numer, denom, (a, b) => b === 0 ? 0 :
|
|
42681
|
+
return zip(numer, denom, (a, b) => b === 0 ? 0 : a / b);
|
|
42010
42682
|
}
|
|
42011
42683
|
var tsi = {
|
|
42012
42684
|
type: "true-strength-index",
|
|
42013
42685
|
title: "True Strength Index",
|
|
42014
42686
|
shortTitle: "TSI",
|
|
42015
42687
|
overlay: false,
|
|
42016
|
-
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
|
+
],
|
|
42017
42697
|
compute: (bars, inputs) => {
|
|
42018
|
-
const
|
|
42019
|
-
return {
|
|
42020
|
-
|
|
42021
|
-
|
|
42022
|
-
|
|
42023
|
-
|
|
42024
|
-
|
|
42025
|
-
|
|
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
|
+
});
|
|
42026
42707
|
}
|
|
42027
42708
|
};
|
|
42709
|
+
var SMI_INDICATOR = "Indicator";
|
|
42710
|
+
var SMI_OSCILLATOR = "Oscillator";
|
|
42028
42711
|
var smiErgodic = {
|
|
42029
42712
|
type: "smi-ergodic",
|
|
42030
42713
|
title: "SMI Ergodic Oscillator",
|
|
42031
42714
|
shortTitle: "SMIE",
|
|
42032
42715
|
overlay: false,
|
|
42033
|
-
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
|
+
],
|
|
42034
42726
|
compute: (bars, inputs) => {
|
|
42035
|
-
const
|
|
42036
|
-
const signal = ema(
|
|
42037
|
-
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
|
+
};
|
|
42038
42748
|
return {
|
|
42039
42749
|
plots: [
|
|
42040
|
-
{ key: "
|
|
42041
|
-
{ key: "
|
|
42042
|
-
{ 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) }
|
|
42043
42752
|
],
|
|
42044
|
-
|
|
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
|
|
42045
42755
|
};
|
|
42046
42756
|
}
|
|
42047
42757
|
};
|
|
42758
|
+
var STC_MID = 50;
|
|
42759
|
+
var TRIGGERS = "Trigger Levels";
|
|
42048
42760
|
var stc = {
|
|
42049
42761
|
type: "schaff-trend-cycle",
|
|
42050
42762
|
title: "Schaff Trend Cycle",
|
|
42051
42763
|
shortTitle: "Schaff Trend Cycle",
|
|
42052
42764
|
overlay: false,
|
|
42053
|
-
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
|
+
],
|
|
42054
42778
|
compute: (bars, inputs) => {
|
|
42055
42779
|
const len = num3(inputs, "cycleLength", 10);
|
|
42056
|
-
const
|
|
42780
|
+
const factor = num3(inputs, "factor", 0.5);
|
|
42781
|
+
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42057
42782
|
const macdLine = sub(ema(src, num3(inputs, "fastLength", 23)), ema(src, num3(inputs, "slowLength", 50)));
|
|
42058
|
-
const
|
|
42783
|
+
const stochNorm = (v) => {
|
|
42059
42784
|
const lo = lowest(v, len);
|
|
42060
42785
|
const hi = highest(v, len);
|
|
42061
42786
|
const out = new Array(v.length).fill(Number.NaN);
|
|
42062
|
-
let
|
|
42063
|
-
let smoothed = Number.NaN;
|
|
42787
|
+
let pct = Number.NaN;
|
|
42064
42788
|
for (let i = 0; i < v.length; i++) {
|
|
42065
42789
|
const x = v[i];
|
|
42066
42790
|
const l = lo[i];
|
|
42067
42791
|
const h = hi[i];
|
|
42068
42792
|
if (!Number.isFinite(x) || !Number.isFinite(l) || !Number.isFinite(h)) continue;
|
|
42069
|
-
|
|
42070
|
-
|
|
42071
|
-
|
|
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;
|
|
42072
42805
|
out[i] = smoothed;
|
|
42073
42806
|
}
|
|
42074
42807
|
return out;
|
|
42075
42808
|
};
|
|
42076
|
-
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
|
+
}
|
|
42077
42821
|
return {
|
|
42078
|
-
plots
|
|
42079
|
-
|
|
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
|
+
]
|
|
42080
42829
|
};
|
|
42081
42830
|
}
|
|
42082
42831
|
};
|
|
@@ -42085,22 +42834,37 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42085
42834
|
title: "Know Sure Thing",
|
|
42086
42835
|
shortTitle: "KST",
|
|
42087
42836
|
overlay: false,
|
|
42088
|
-
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
|
+
],
|
|
42089
42851
|
compute: (bars, inputs) => {
|
|
42090
|
-
const
|
|
42091
|
-
const
|
|
42092
|
-
|
|
42093
|
-
|
|
42094
|
-
|
|
42095
|
-
|
|
42096
|
-
|
|
42097
|
-
return {
|
|
42098
|
-
|
|
42099
|
-
|
|
42100
|
-
|
|
42101
|
-
|
|
42102
|
-
|
|
42103
|
-
|
|
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
|
+
});
|
|
42104
42868
|
}
|
|
42105
42869
|
};
|
|
42106
42870
|
var coppock = {
|
|
@@ -42109,19 +42873,25 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42109
42873
|
shortTitle: "Coppock",
|
|
42110
42874
|
overlay: false,
|
|
42111
42875
|
inputs: [
|
|
42112
|
-
lengthInput(10, "wmaLength", "WMA
|
|
42113
|
-
lengthInput(14, "longRoc", "Long RoC
|
|
42114
|
-
lengthInput(11, "shortRoc", "Short RoC
|
|
42115
|
-
sourceInput(),
|
|
42116
|
-
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.")
|
|
42117
42884
|
],
|
|
42118
42885
|
compute: (bars, inputs) => {
|
|
42119
42886
|
const src = sourceValues(bars, str3(inputs, "source", "Close"));
|
|
42120
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));
|
|
42121
|
-
return {
|
|
42122
|
-
|
|
42123
|
-
|
|
42124
|
-
|
|
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
|
+
});
|
|
42125
42895
|
}
|
|
42126
42896
|
};
|
|
42127
42897
|
var dpo = {
|
|
@@ -42129,42 +42899,76 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42129
42899
|
title: "Detrended Price Oscillator",
|
|
42130
42900
|
shortTitle: "DPO",
|
|
42131
42901
|
overlay: false,
|
|
42132
|
-
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
|
+
],
|
|
42133
42911
|
compute: (bars, inputs) => {
|
|
42134
42912
|
const len = num3(inputs, "length", 21);
|
|
42135
|
-
const
|
|
42136
|
-
const
|
|
42137
|
-
|
|
42138
|
-
|
|
42139
|
-
|
|
42140
|
-
|
|
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
|
+
});
|
|
42141
42925
|
}
|
|
42142
42926
|
};
|
|
42927
|
+
var ROC_PERCENT = "ROC %";
|
|
42928
|
+
var ROC_MOMENTUM = "Momentum";
|
|
42143
42929
|
var rocSpec = {
|
|
42144
42930
|
type: "rate-of-change",
|
|
42145
42931
|
title: "Rate of Change",
|
|
42146
42932
|
shortTitle: "ROC",
|
|
42147
42933
|
overlay: false,
|
|
42148
|
-
inputs: [
|
|
42149
|
-
|
|
42150
|
-
|
|
42151
|
-
|
|
42152
|
-
|
|
42153
|
-
|
|
42154
|
-
|
|
42155
|
-
|
|
42156
|
-
|
|
42157
|
-
|
|
42158
|
-
|
|
42159
|
-
|
|
42160
|
-
|
|
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
|
+
}
|
|
42161
42956
|
};
|
|
42162
42957
|
var ultimate = {
|
|
42163
42958
|
type: "ultimate-oscillator",
|
|
42164
42959
|
title: "Ultimate Oscillator",
|
|
42165
42960
|
shortTitle: "UO",
|
|
42166
42961
|
overlay: false,
|
|
42167
|
-
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
|
+
],
|
|
42168
42972
|
compute: (bars, inputs) => {
|
|
42169
42973
|
const n = bars.length;
|
|
42170
42974
|
const bp = new Array(n).fill(Number.NaN);
|
|
@@ -42172,10 +42976,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42172
42976
|
for (let i = 0; i < n; i++) {
|
|
42173
42977
|
const b = bars[i];
|
|
42174
42978
|
const pc = i > 0 ? bars[i - 1].close : b.close;
|
|
42175
|
-
const
|
|
42176
|
-
|
|
42177
|
-
|
|
42178
|
-
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;
|
|
42179
42982
|
}
|
|
42180
42983
|
const avg = (len) => zip(sum(bp, len), sum(tr, len), (a, b) => b === 0 ? Number.NaN : a / b);
|
|
42181
42984
|
const a7 = avg(num3(inputs, "fastLength", 7));
|
|
@@ -42186,10 +42989,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42186
42989
|
const z = a28[i];
|
|
42187
42990
|
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) ? 100 * (4 * x + 2 * y + z) / 7 : Number.NaN;
|
|
42188
42991
|
});
|
|
42189
|
-
return {
|
|
42190
|
-
|
|
42191
|
-
|
|
42192
|
-
|
|
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
|
+
});
|
|
42193
43003
|
}
|
|
42194
43004
|
};
|
|
42195
43005
|
var rvgi = {
|
|
@@ -42197,7 +43007,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42197
43007
|
title: "Relative Vigor Index",
|
|
42198
43008
|
shortTitle: "RVGI",
|
|
42199
43009
|
overlay: false,
|
|
42200
|
-
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
|
+
],
|
|
42201
43015
|
compute: (bars, inputs) => {
|
|
42202
43016
|
const len = num3(inputs, "length", 10);
|
|
42203
43017
|
const co = swma(sub(closes(bars), opens(bars)));
|
|
@@ -42205,31 +43019,49 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42205
43019
|
const line = zip(sum(co, len), sum(hl, len), (a, b) => b === 0 ? 0 : a / b);
|
|
42206
43020
|
return {
|
|
42207
43021
|
plots: [
|
|
42208
|
-
{ key: "rvgi", title: "RVGI", values: line, color:
|
|
42209
|
-
{ 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) }
|
|
42210
43024
|
],
|
|
42211
|
-
levels:
|
|
43025
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed", title: "Zero" }]
|
|
42212
43026
|
};
|
|
42213
43027
|
}
|
|
42214
43028
|
};
|
|
43029
|
+
var RVI_EMA = "EMA";
|
|
43030
|
+
var RVI_RMA = "Wilder (RMA)";
|
|
42215
43031
|
var relVolatility = {
|
|
42216
43032
|
type: "relative-volatility-index",
|
|
42217
43033
|
title: "Relative Volatility Index",
|
|
42218
43034
|
shortTitle: "RVI",
|
|
42219
43035
|
overlay: false,
|
|
42220
|
-
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
|
+
],
|
|
42221
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);
|
|
42222
43051
|
const smoothLen = num3(inputs, "smoothLength", 14);
|
|
42223
|
-
const
|
|
42224
|
-
const
|
|
42225
|
-
|
|
42226
|
-
|
|
42227
|
-
|
|
42228
|
-
|
|
42229
|
-
|
|
42230
|
-
|
|
42231
|
-
|
|
42232
|
-
|
|
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
|
+
});
|
|
42233
43065
|
}
|
|
42234
43066
|
};
|
|
42235
43067
|
var bop = {
|
|
@@ -42237,28 +43069,43 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42237
43069
|
title: "Balance of Power",
|
|
42238
43070
|
shortTitle: "BOP",
|
|
42239
43071
|
overlay: false,
|
|
42240
|
-
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
|
+
],
|
|
42241
43079
|
compute: (bars, inputs) => {
|
|
42242
|
-
const
|
|
42243
|
-
|
|
42244
|
-
|
|
42245
|
-
|
|
42246
|
-
|
|
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
|
+
});
|
|
42247
43090
|
}
|
|
42248
43091
|
};
|
|
42249
43092
|
var elderRay = {
|
|
42250
43093
|
type: "elder-ray",
|
|
42251
43094
|
title: "Elder Ray",
|
|
42252
43095
|
overlay: false,
|
|
42253
|
-
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
|
+
],
|
|
42254
43101
|
compute: (bars, inputs) => {
|
|
42255
43102
|
const basis = ema(closes(bars), num3(inputs, "length", 13));
|
|
42256
43103
|
return {
|
|
42257
43104
|
plots: [
|
|
42258
|
-
{ key: "bull", title: "Bull
|
|
42259
|
-
{ 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 }
|
|
42260
43107
|
],
|
|
42261
|
-
levels:
|
|
43108
|
+
levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 30), lineStyle: "dashed", title: "Zero Line" }]
|
|
42262
43109
|
};
|
|
42263
43110
|
}
|
|
42264
43111
|
};
|
|
@@ -42267,31 +43114,66 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42267
43114
|
title: "TTM Squeeze",
|
|
42268
43115
|
overlay: false,
|
|
42269
43116
|
inputs: [
|
|
42270
|
-
lengthInput(20),
|
|
42271
|
-
|
|
42272
|
-
|
|
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.")
|
|
42273
43126
|
],
|
|
42274
43127
|
compute: (bars, inputs) => {
|
|
42275
43128
|
const len = num3(inputs, "length", 20);
|
|
42276
|
-
const
|
|
42277
|
-
const basis = sma(
|
|
42278
|
-
const dev = map(stdev(
|
|
42279
|
-
const kcRange = map(
|
|
42280
|
-
const
|
|
42281
|
-
const anchor = zip(
|
|
42282
|
-
const mom = linreg(sub(
|
|
42283
|
-
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) => {
|
|
42284
43137
|
const d = dev[i];
|
|
42285
43138
|
const r = kcRange[i];
|
|
42286
|
-
|
|
43139
|
+
if (!Number.isFinite(b) || !Number.isFinite(d) || !Number.isFinite(r)) return null;
|
|
43140
|
+
return d < r;
|
|
42287
43141
|
});
|
|
42288
|
-
const
|
|
42289
|
-
|
|
42290
|
-
|
|
42291
|
-
|
|
42292
|
-
|
|
42293
|
-
|
|
42294
|
-
|
|
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 };
|
|
42295
43177
|
}
|
|
42296
43178
|
};
|
|
42297
43179
|
var oscillatorSpecs = [
|
|
@@ -42303,7 +43185,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42303
43185
|
awesome,
|
|
42304
43186
|
cci,
|
|
42305
43187
|
williamsR,
|
|
42306
|
-
|
|
43188
|
+
cmoSpec,
|
|
42307
43189
|
connorsRsi,
|
|
42308
43190
|
fisher,
|
|
42309
43191
|
trix,
|
|
@@ -42327,15 +43209,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42327
43209
|
type: "aroon",
|
|
42328
43210
|
title: "Aroon",
|
|
42329
43211
|
overlay: false,
|
|
42330
|
-
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
|
+
],
|
|
42331
43217
|
compute: (bars, inputs) => {
|
|
42332
43218
|
const len = num3(inputs, "length", 14);
|
|
42333
|
-
const up = map(barsSinceHighest(highs(bars), len
|
|
42334
|
-
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);
|
|
42335
43221
|
return {
|
|
42336
43222
|
plots: [
|
|
42337
|
-
{ key: "up", title: "Aroon
|
|
42338
|
-
{ 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) }
|
|
42339
43225
|
]
|
|
42340
43226
|
};
|
|
42341
43227
|
}
|
|
@@ -42345,10 +43231,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42345
43231
|
title: "Average Directional Index",
|
|
42346
43232
|
shortTitle: "ADX",
|
|
42347
43233
|
overlay: false,
|
|
42348
|
-
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
|
+
],
|
|
42349
43242
|
compute: (bars, inputs) => {
|
|
42350
43243
|
const diLen = num3(inputs, "diLength", 14);
|
|
42351
|
-
const adxLen = num3(inputs, "adxLength", 14);
|
|
42352
43244
|
const n = bars.length;
|
|
42353
43245
|
const plusDm = new Array(n).fill(Number.NaN);
|
|
42354
43246
|
const minusDm = new Array(n).fill(Number.NaN);
|
|
@@ -42362,13 +43254,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42362
43254
|
const plusDi = zip(rma(plusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
42363
43255
|
const minusDi = zip(rma(minusDm, diLen), atrLine, (d, a) => a === 0 ? 0 : 100 * d / a);
|
|
42364
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
|
+
});
|
|
42365
43267
|
return {
|
|
42366
43268
|
plots: [
|
|
42367
|
-
{ key: "
|
|
42368
|
-
{ key: "
|
|
42369
|
-
{ 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 }
|
|
42370
43272
|
],
|
|
42371
|
-
levels: [{ key: "
|
|
43273
|
+
levels: [{ key: "keyLevel", price: keyLevel, color: NEUTRAL, lineStyle: "dashed", title: "Key Level" }]
|
|
42372
43274
|
};
|
|
42373
43275
|
}
|
|
42374
43276
|
};
|
|
@@ -42377,7 +43279,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42377
43279
|
title: "Vortex Indicator",
|
|
42378
43280
|
shortTitle: "VI",
|
|
42379
43281
|
overlay: false,
|
|
42380
|
-
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
|
+
],
|
|
42381
43288
|
compute: (bars, inputs) => {
|
|
42382
43289
|
const len = num3(inputs, "length", 14);
|
|
42383
43290
|
const n = bars.length;
|
|
@@ -42390,25 +43297,48 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42390
43297
|
const trSum = sum(trueRange(bars), len);
|
|
42391
43298
|
return {
|
|
42392
43299
|
plots: [
|
|
42393
|
-
{ key: "viPlus", title: "VI+", values: zip(sum(vmPlus, len), trSum, (v, t) => t === 0 ? Number.NaN : v / t), color:
|
|
42394
|
-
{ 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) }
|
|
42395
43302
|
],
|
|
42396
|
-
levels: [{ key: "
|
|
43303
|
+
levels: [{ key: "baseline", price: 1, color: str3(inputs, "baselineColor", NEUTRAL), lineStyle: "dotted", title: "Baseline" }]
|
|
42397
43304
|
};
|
|
42398
43305
|
}
|
|
42399
43306
|
};
|
|
42400
43307
|
var trendSpecs = [aroon, adx, vortex];
|
|
42401
43308
|
|
|
42402
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
|
+
}
|
|
42403
43323
|
var atrSpec = {
|
|
42404
43324
|
type: "average-true-range",
|
|
42405
43325
|
title: "Average True Range",
|
|
42406
43326
|
shortTitle: "ATR",
|
|
42407
43327
|
overlay: false,
|
|
42408
|
-
inputs: [
|
|
42409
|
-
|
|
42410
|
-
|
|
42411
|
-
|
|
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
|
+
}
|
|
42412
43342
|
};
|
|
42413
43343
|
var historicalVolatility = {
|
|
42414
43344
|
type: "historical-volatility",
|
|
@@ -42416,14 +43346,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42416
43346
|
shortTitle: "HV",
|
|
42417
43347
|
overlay: false,
|
|
42418
43348
|
inputs: [
|
|
42419
|
-
lengthInput(10),
|
|
42420
|
-
|
|
42421
|
-
|
|
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.")
|
|
42422
43353
|
],
|
|
42423
43354
|
compute: (bars, inputs) => {
|
|
42424
|
-
const
|
|
42425
|
-
const
|
|
42426
|
-
|
|
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);
|
|
42427
43366
|
}
|
|
42428
43367
|
};
|
|
42429
43368
|
var chaikinVolatility = {
|
|
@@ -42431,13 +43370,26 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42431
43370
|
title: "Chaikin Volatility",
|
|
42432
43371
|
shortTitle: "CHV",
|
|
42433
43372
|
overlay: false,
|
|
42434
|
-
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
|
+
],
|
|
42435
43379
|
compute: (bars, inputs) => {
|
|
42436
43380
|
const range = ema(sub(highs(bars), lows(bars)), num3(inputs, "emaLength", 10));
|
|
42437
|
-
|
|
42438
|
-
|
|
42439
|
-
|
|
42440
|
-
|
|
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
|
+
});
|
|
42441
43393
|
}
|
|
42442
43394
|
};
|
|
42443
43395
|
var massIndex = {
|
|
@@ -42445,14 +43397,42 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42445
43397
|
title: "Mass Index",
|
|
42446
43398
|
shortTitle: "MI",
|
|
42447
43399
|
overlay: false,
|
|
42448
|
-
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
|
+
],
|
|
42449
43409
|
compute: (bars, inputs) => {
|
|
42450
|
-
const
|
|
42451
|
-
const single = ema(
|
|
42452
|
-
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
|
+
});
|
|
42453
43425
|
return {
|
|
42454
|
-
plots: [
|
|
42455
|
-
|
|
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
|
+
]
|
|
42456
43436
|
};
|
|
42457
43437
|
}
|
|
42458
43438
|
};
|
|
@@ -42461,30 +43441,83 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42461
43441
|
title: "Standard Deviation",
|
|
42462
43442
|
shortTitle: "StdDev",
|
|
42463
43443
|
overlay: false,
|
|
42464
|
-
inputs: [
|
|
42465
|
-
|
|
42466
|
-
|
|
42467
|
-
|
|
42468
|
-
|
|
42469
|
-
|
|
42470
|
-
|
|
42471
|
-
|
|
42472
|
-
|
|
42473
|
-
|
|
42474
|
-
|
|
42475
|
-
})
|
|
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
|
+
}
|
|
42476
43455
|
};
|
|
42477
43456
|
var ulcerIndex = {
|
|
42478
43457
|
type: "ulcer-index",
|
|
42479
43458
|
title: "Ulcer Index",
|
|
42480
43459
|
overlay: false,
|
|
42481
|
-
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
|
+
],
|
|
42482
43471
|
compute: (bars, inputs) => {
|
|
42483
43472
|
const len = num3(inputs, "length", 14);
|
|
42484
|
-
const
|
|
42485
|
-
const
|
|
42486
|
-
const values =
|
|
42487
|
-
|
|
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
|
+
};
|
|
42488
43521
|
}
|
|
42489
43522
|
};
|
|
42490
43523
|
var choppiness = {
|
|
@@ -42492,19 +43525,33 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42492
43525
|
title: "Choppiness Index",
|
|
42493
43526
|
shortTitle: "CHOP",
|
|
42494
43527
|
overlay: false,
|
|
42495
|
-
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
|
+
],
|
|
42496
43537
|
compute: (bars, inputs) => {
|
|
42497
43538
|
const len = num3(inputs, "length", 14);
|
|
42498
43539
|
const trSum = sum(trueRange(bars), len);
|
|
42499
43540
|
const span = zip(highest(highs(bars), len), lowest(lows(bars), len), (h, l) => h - l);
|
|
42500
43541
|
const values = zip(trSum, span, (t, s) => s > 0 && t > 0 ? 100 * Math.log10(t / s) / Math.log10(len) : Number.NaN);
|
|
42501
|
-
return {
|
|
42502
|
-
|
|
42503
|
-
|
|
42504
|
-
|
|
42505
|
-
|
|
42506
|
-
|
|
42507
|
-
|
|
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
|
+
});
|
|
42508
43555
|
}
|
|
42509
43556
|
};
|
|
42510
43557
|
var volatilitySpecs = [atrSpec, historicalVolatility, chaikinVolatility, massIndex, standardDeviation, ulcerIndex, choppiness];
|
|
@@ -42517,17 +43564,85 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42517
43564
|
return (2 * b.close - b.high - b.low) / (b.high - b.low) * b.volume;
|
|
42518
43565
|
});
|
|
42519
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
|
+
}
|
|
42520
43622
|
var obv = {
|
|
42521
43623
|
type: "on-balance-volume",
|
|
42522
43624
|
title: "On Balance Volume",
|
|
42523
43625
|
shortTitle: "OBV",
|
|
42524
43626
|
overlay: false,
|
|
42525
|
-
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
|
+
],
|
|
42526
43634
|
compute: (bars, inputs) => {
|
|
42527
43635
|
const vol = volumes(bars);
|
|
42528
43636
|
const d = change(closes(bars));
|
|
42529
|
-
const
|
|
42530
|
-
|
|
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 };
|
|
42531
43646
|
}
|
|
42532
43647
|
};
|
|
42533
43648
|
var accDist = {
|
|
@@ -42535,24 +43650,50 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42535
43650
|
title: "Accumulation / Distribution",
|
|
42536
43651
|
shortTitle: "A/D",
|
|
42537
43652
|
overlay: false,
|
|
42538
|
-
inputs: [
|
|
42539
|
-
|
|
42540
|
-
|
|
42541
|
-
|
|
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
|
+
}
|
|
42542
43674
|
};
|
|
42543
43675
|
var cmf = {
|
|
42544
43676
|
type: "chaikin-money-flow",
|
|
42545
43677
|
title: "Chaikin Money Flow",
|
|
42546
43678
|
shortTitle: "CMF",
|
|
42547
43679
|
overlay: false,
|
|
42548
|
-
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
|
+
],
|
|
42549
43687
|
compute: (bars, inputs) => {
|
|
42550
43688
|
const len = num3(inputs, "length", 20);
|
|
42551
43689
|
const values = zip(sum(moneyFlowVolume(bars), len), sum(volumes(bars), len), (m, v) => v === 0 ? 0 : m / v);
|
|
42552
|
-
return {
|
|
42553
|
-
|
|
42554
|
-
|
|
42555
|
-
|
|
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
|
+
});
|
|
42556
43697
|
}
|
|
42557
43698
|
};
|
|
42558
43699
|
var chaikinOscillator = {
|
|
@@ -42560,14 +43701,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42560
43701
|
title: "Chaikin Oscillator",
|
|
42561
43702
|
shortTitle: "Chaikin Osc",
|
|
42562
43703
|
overlay: false,
|
|
42563
|
-
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
|
+
],
|
|
42564
43711
|
compute: (bars, inputs) => {
|
|
42565
43712
|
const ad = cumSum(moneyFlowVolume(bars));
|
|
42566
43713
|
const values = sub(ema(ad, num3(inputs, "fastLength", 3)), ema(ad, num3(inputs, "slowLength", 10)));
|
|
42567
|
-
return {
|
|
42568
|
-
|
|
42569
|
-
|
|
42570
|
-
|
|
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
|
+
});
|
|
42571
43721
|
}
|
|
42572
43722
|
};
|
|
42573
43723
|
var eom = {
|
|
@@ -42575,20 +43725,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42575
43725
|
title: "Ease of Movement",
|
|
42576
43726
|
shortTitle: "EOM",
|
|
42577
43727
|
overlay: false,
|
|
42578
|
-
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
|
+
],
|
|
42579
43735
|
compute: (bars, inputs) => {
|
|
42580
43736
|
const div = num3(inputs, "divisor", 1e4);
|
|
42581
|
-
const
|
|
42582
|
-
const move = change(hl2);
|
|
43737
|
+
const move = change(sourceValues(bars, "HL2"));
|
|
42583
43738
|
const raw = bars.map((b, i) => {
|
|
42584
43739
|
const m = move[i];
|
|
42585
|
-
if (b.volume == null || !Number.isFinite(b.volume) ||
|
|
42586
|
-
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
|
|
42587
43749
|
});
|
|
42588
|
-
return {
|
|
42589
|
-
plots: [{ key: "eom", title: "EOM", values: sma(raw, num3(inputs, "length", 14)), color: str3(inputs, "color", SERIES_LINE), width: 2 }],
|
|
42590
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
42591
|
-
};
|
|
42592
43750
|
}
|
|
42593
43751
|
};
|
|
42594
43752
|
var forceIndex = {
|
|
@@ -42596,13 +43754,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42596
43754
|
title: "Force Index",
|
|
42597
43755
|
shortTitle: "FI",
|
|
42598
43756
|
overlay: false,
|
|
42599
|
-
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
|
+
],
|
|
42600
43762
|
compute: (bars, inputs) => {
|
|
42601
43763
|
const raw = zip(change(closes(bars)), volumes(bars), (d, v) => d * v);
|
|
42602
|
-
|
|
42603
|
-
|
|
42604
|
-
|
|
42605
|
-
|
|
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
|
+
});
|
|
42606
43775
|
}
|
|
42607
43776
|
};
|
|
42608
43777
|
var klinger = {
|
|
@@ -42610,19 +43779,44 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42610
43779
|
title: "Klinger Oscillator",
|
|
42611
43780
|
shortTitle: "KVO",
|
|
42612
43781
|
overlay: false,
|
|
42613
|
-
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
|
+
],
|
|
42614
43790
|
compute: (bars, inputs) => {
|
|
42615
|
-
const
|
|
42616
|
-
const
|
|
42617
|
-
|
|
42618
|
-
|
|
42619
|
-
|
|
42620
|
-
|
|
42621
|
-
|
|
42622
|
-
|
|
42623
|
-
|
|
42624
|
-
|
|
42625
|
-
|
|
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
|
+
});
|
|
42626
43820
|
}
|
|
42627
43821
|
};
|
|
42628
43822
|
var mfi = {
|
|
@@ -42630,7 +43824,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42630
43824
|
title: "Money Flow Index",
|
|
42631
43825
|
shortTitle: "MFI",
|
|
42632
43826
|
overlay: false,
|
|
42633
|
-
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
|
+
],
|
|
42634
43836
|
compute: (bars, inputs) => {
|
|
42635
43837
|
const len = num3(inputs, "length", 14);
|
|
42636
43838
|
const tp = sourceValues(bars, "HLC3");
|
|
@@ -42638,48 +43840,57 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42638
43840
|
const d = change(tp);
|
|
42639
43841
|
const pos = sum(zip(rawFlow, d, (f, x) => x > 0 ? f : 0), len);
|
|
42640
43842
|
const neg = sum(zip(rawFlow, d, (f, x) => x < 0 ? f : 0), len);
|
|
42641
|
-
const values = zip(pos, neg, (p,
|
|
42642
|
-
return {
|
|
42643
|
-
|
|
42644
|
-
|
|
42645
|
-
|
|
42646
|
-
|
|
42647
|
-
|
|
42648
|
-
|
|
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
|
+
});
|
|
42649
43856
|
}
|
|
42650
43857
|
};
|
|
42651
|
-
function
|
|
42652
|
-
|
|
42653
|
-
|
|
42654
|
-
|
|
42655
|
-
|
|
42656
|
-
|
|
42657
|
-
|
|
42658
|
-
|
|
42659
|
-
if (v == null || pv == null || !Number.isFinite(v) || !Number.isFinite(pv) || pc === 0) {
|
|
42660
|
-
if (started) out[i] = index;
|
|
42661
|
-
continue;
|
|
42662
|
-
}
|
|
42663
|
-
started = true;
|
|
42664
|
-
if (when(v, pv)) index += (bars[i].close - pc) / pc * index;
|
|
42665
|
-
out[i] = index;
|
|
42666
|
-
}
|
|
42667
|
-
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
|
+
];
|
|
42668
43866
|
}
|
|
43867
|
+
var NVI_POSITION = "Position vs Average";
|
|
43868
|
+
var NVI_SINGLE = "Single Color";
|
|
42669
43869
|
var nvi = {
|
|
42670
43870
|
type: "negative-volume-index",
|
|
42671
43871
|
title: "Negative Volume Index",
|
|
42672
43872
|
shortTitle: "Negative Volume Index",
|
|
42673
43873
|
overlay: false,
|
|
42674
|
-
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
|
+
],
|
|
42675
43881
|
compute: (bars, inputs) => {
|
|
42676
|
-
const
|
|
42677
|
-
|
|
42678
|
-
|
|
42679
|
-
|
|
42680
|
-
|
|
42681
|
-
|
|
42682
|
-
|
|
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
|
+
);
|
|
42683
43894
|
}
|
|
42684
43895
|
};
|
|
42685
43896
|
var pvi = {
|
|
@@ -42687,15 +43898,26 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42687
43898
|
title: "Positive Volume Index",
|
|
42688
43899
|
shortTitle: "Positive Volume Index",
|
|
42689
43900
|
overlay: false,
|
|
42690
|
-
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
|
+
],
|
|
42691
43908
|
compute: (bars, inputs) => {
|
|
42692
|
-
const
|
|
42693
|
-
|
|
42694
|
-
|
|
42695
|
-
|
|
42696
|
-
|
|
42697
|
-
|
|
42698
|
-
|
|
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
|
+
);
|
|
42699
43921
|
}
|
|
42700
43922
|
};
|
|
42701
43923
|
var pvt = {
|
|
@@ -42703,7 +43925,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42703
43925
|
title: "Price Volume Trend",
|
|
42704
43926
|
shortTitle: "PVT",
|
|
42705
43927
|
overlay: false,
|
|
42706
|
-
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
|
+
],
|
|
42707
43937
|
compute: (bars, inputs) => {
|
|
42708
43938
|
const c = closes(bars);
|
|
42709
43939
|
const raw = bars.map((b, i) => {
|
|
@@ -42711,7 +43941,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42711
43941
|
const pc = c[i - 1];
|
|
42712
43942
|
return pc === 0 ? 0 : (c[i] - pc) / pc * b.volume;
|
|
42713
43943
|
});
|
|
42714
|
-
|
|
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 };
|
|
42715
43955
|
}
|
|
42716
43956
|
};
|
|
42717
43957
|
var volumeOscillator = {
|
|
@@ -42719,39 +43959,75 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42719
43959
|
title: "Volume Oscillator",
|
|
42720
43960
|
shortTitle: "Vol Osc",
|
|
42721
43961
|
overlay: false,
|
|
42722
|
-
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
|
+
],
|
|
42723
43968
|
compute: (bars, inputs) => {
|
|
42724
43969
|
const vol = volumes(bars);
|
|
42725
43970
|
const fast = ema(vol, num3(inputs, "fastLength", 5));
|
|
42726
43971
|
const slow = ema(vol, num3(inputs, "slowLength", 10));
|
|
42727
|
-
const values = zip(fast, slow, (f, s) => s === 0 ?
|
|
42728
|
-
|
|
42729
|
-
|
|
42730
|
-
|
|
42731
|
-
|
|
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
|
+
});
|
|
42732
43982
|
}
|
|
42733
43983
|
};
|
|
43984
|
+
var OSCILLATOR = "Oscillator";
|
|
42734
43985
|
var pvo = {
|
|
42735
43986
|
type: "pvo",
|
|
42736
43987
|
title: "Percentage Volume Oscillator",
|
|
42737
43988
|
shortTitle: "PVO",
|
|
42738
43989
|
overlay: false,
|
|
42739
|
-
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
|
+
],
|
|
42740
44001
|
compute: (bars, inputs) => {
|
|
42741
44002
|
const vol = volumes(bars);
|
|
42742
44003
|
const fast = ema(vol, num3(inputs, "fastLength", 12));
|
|
42743
44004
|
const slow = ema(vol, num3(inputs, "slowLength", 26));
|
|
42744
|
-
const
|
|
42745
|
-
const signal = ema(
|
|
42746
|
-
const
|
|
42747
|
-
|
|
42748
|
-
|
|
42749
|
-
|
|
42750
|
-
|
|
42751
|
-
|
|
42752
|
-
|
|
42753
|
-
|
|
42754
|
-
|
|
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
|
+
});
|
|
42755
44031
|
}
|
|
42756
44032
|
};
|
|
42757
44033
|
var vfi = {
|
|
@@ -42760,55 +44036,86 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42760
44036
|
shortTitle: "Volume Flow Indicator",
|
|
42761
44037
|
overlay: false,
|
|
42762
44038
|
inputs: [
|
|
42763
|
-
lengthInput(130),
|
|
42764
|
-
|
|
42765
|
-
|
|
42766
|
-
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.")
|
|
42767
44051
|
],
|
|
42768
44052
|
compute: (bars, inputs) => {
|
|
42769
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));
|
|
42770
44057
|
const coef = num3(inputs, "coef", 0.2);
|
|
42771
44058
|
const volCoef = num3(inputs, "volCoef", 2.5);
|
|
42772
|
-
const typical = map(sourceValues(bars, "HLC3"), Math.log);
|
|
42773
|
-
const inter = change(typical);
|
|
42774
|
-
const vInter = stdev(inter, 30);
|
|
42775
44059
|
const vol = volumes(bars);
|
|
42776
|
-
const
|
|
42777
|
-
const
|
|
42778
|
-
const
|
|
42779
|
-
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];
|
|
42780
44065
|
const v = vol[i];
|
|
42781
|
-
|
|
42782
|
-
const
|
|
42783
|
-
|
|
42784
|
-
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;
|
|
42785
44070
|
return m > cutoff ? capped : m < -cutoff ? -capped : 0;
|
|
42786
44071
|
});
|
|
42787
|
-
const
|
|
42788
|
-
|
|
42789
|
-
|
|
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
|
|
42790
44084
|
});
|
|
42791
|
-
const flowSum = sum(raw, len);
|
|
42792
|
-
const line = zip(flowSum, values, (f, a) => f / a);
|
|
42793
|
-
return {
|
|
42794
|
-
plots: [{ key: "vfi", title: "VFI", values: sma(line, num3(inputs, "smoothLength", 3)), color: SERIES_LINE, width: 2 }],
|
|
42795
|
-
levels: [{ key: "zero", price: 0, color: NEUTRAL }]
|
|
42796
|
-
};
|
|
42797
44085
|
}
|
|
42798
44086
|
};
|
|
44087
|
+
var II_OSCILLATOR = "Normalized Oscillator (II%)";
|
|
44088
|
+
var II_CUMULATIVE = "Cumulative Line";
|
|
42799
44089
|
var intradayIntensity = {
|
|
42800
44090
|
type: "intraday-intensity",
|
|
42801
44091
|
title: "Intraday Intensity",
|
|
42802
44092
|
shortTitle: "Intraday Intensity",
|
|
42803
44093
|
overlay: false,
|
|
42804
|
-
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
|
+
],
|
|
42805
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
|
+
}
|
|
42806
44109
|
const len = num3(inputs, "length", 21);
|
|
42807
|
-
const values = zip(sum(
|
|
42808
|
-
return {
|
|
42809
|
-
|
|
42810
|
-
|
|
42811
|
-
|
|
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
|
+
});
|
|
42812
44119
|
}
|
|
42813
44120
|
};
|
|
42814
44121
|
var volumeSpecs = [
|
|
@@ -42841,15 +44148,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42841
44148
|
}
|
|
42842
44149
|
return Math.floor(time / DAY_MS2);
|
|
42843
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
|
+
}
|
|
42844
44158
|
var parabolicSar = {
|
|
42845
44159
|
type: "parabolic-sar",
|
|
42846
44160
|
title: "Parabolic SAR",
|
|
42847
44161
|
shortTitle: "SAR",
|
|
42848
44162
|
overlay: true,
|
|
42849
44163
|
inputs: [
|
|
42850
|
-
|
|
42851
|
-
|
|
42852
|
-
|
|
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.")
|
|
42853
44169
|
],
|
|
42854
44170
|
compute: (bars, inputs) => {
|
|
42855
44171
|
const start = num3(inputs, "start", 0.02);
|
|
@@ -42857,7 +44173,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42857
44173
|
const max2 = num3(inputs, "maximum", 0.2);
|
|
42858
44174
|
const n = bars.length;
|
|
42859
44175
|
const values = new Array(n).fill(Number.NaN);
|
|
42860
|
-
const colors = new Array(n).fill(null);
|
|
42861
44176
|
if (n >= 2) {
|
|
42862
44177
|
let long = bars[1].close >= bars[0].close;
|
|
42863
44178
|
let sar = long ? bars[0].low : bars[0].high;
|
|
@@ -42882,18 +44197,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42882
44197
|
af = Math.min(max2, af + inc);
|
|
42883
44198
|
}
|
|
42884
44199
|
values[i] = sar;
|
|
42885
|
-
colors[i] = long ? BULLISH : BEARISH;
|
|
42886
44200
|
}
|
|
42887
44201
|
}
|
|
42888
|
-
|
|
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 }] };
|
|
42889
44206
|
}
|
|
42890
44207
|
};
|
|
42891
|
-
var
|
|
42892
|
-
var
|
|
44208
|
+
var VWAP_ANCHORS = ["Session", "Week", "Month", "Quarter", "Year"];
|
|
44209
|
+
var BANDS_STDEV = "Standard Deviation";
|
|
44210
|
+
var BANDS_PERCENT = "Percentage";
|
|
42893
44211
|
var VWAP_BANDS = [
|
|
42894
|
-
{
|
|
42895
|
-
{
|
|
42896
|
-
{
|
|
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 }
|
|
42897
44215
|
];
|
|
42898
44216
|
var vwap = {
|
|
42899
44217
|
type: "vwap",
|
|
@@ -42901,39 +44219,36 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42901
44219
|
shortTitle: "VWAP",
|
|
42902
44220
|
overlay: true,
|
|
42903
44221
|
inputs: [
|
|
42904
|
-
|
|
42905
|
-
sourceInput("HLC3"),
|
|
42906
|
-
|
|
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."),
|
|
42907
44225
|
...VWAP_BANDS.flatMap((b, i) => [
|
|
42908
|
-
|
|
42909
|
-
|
|
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.")
|
|
42910
44228
|
]),
|
|
42911
|
-
|
|
42912
|
-
|
|
42913
|
-
|
|
42914
|
-
|
|
42915
|
-
|
|
42916
|
-
{ ...colorInput(withAlpha2(b.defColor), "", b.fillColor), inline: b.color, group: VWAP_STYLE }
|
|
42917
|
-
])
|
|
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.")
|
|
42918
44234
|
],
|
|
42919
44235
|
compute: (bars, inputs) => {
|
|
42920
|
-
const anchor = str3(inputs, "anchor", "
|
|
44236
|
+
const anchor = str3(inputs, "anchor", "Session");
|
|
42921
44237
|
const src = sourceValues(bars, str3(inputs, "source", "HLC3"));
|
|
42922
44238
|
const n = bars.length;
|
|
44239
|
+
const hidden = bool(inputs, "hideDwm", true) && barInterval(bars) >= DAY_MS2;
|
|
42923
44240
|
const values = new Array(n).fill(Number.NaN);
|
|
42924
|
-
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.
|
|
44241
|
+
const bands = VWAP_BANDS.filter((b) => bool(inputs, b.show, b.defOn)).map((b) => ({
|
|
42925
44242
|
mult: Math.max(0, num3(inputs, b.mult, b.defMult)),
|
|
42926
|
-
color: str3(inputs, b.color, b.defColor),
|
|
42927
|
-
fill: bool(inputs, b.fill, true),
|
|
42928
|
-
fillColor: str3(inputs, b.fillColor, withAlpha2(b.defColor)),
|
|
42929
44243
|
up: new Array(n).fill(Number.NaN),
|
|
42930
44244
|
down: new Array(n).fill(Number.NaN)
|
|
42931
44245
|
}));
|
|
44246
|
+
const percentMode = str3(inputs, "bandsMode", BANDS_STDEV) === BANDS_PERCENT;
|
|
42932
44247
|
let period = Number.NaN;
|
|
42933
44248
|
let cumPV = 0;
|
|
42934
44249
|
let cumPV2 = 0;
|
|
42935
44250
|
let cumV = 0;
|
|
42936
|
-
for (let i = 0; i < n; i++) {
|
|
44251
|
+
for (let i = 0; i < n && !hidden; i++) {
|
|
42937
44252
|
const b = bars[i];
|
|
42938
44253
|
const key = periodKey(anchor, b.time);
|
|
42939
44254
|
if (key !== period) {
|
|
@@ -42951,38 +44266,58 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42951
44266
|
}
|
|
42952
44267
|
if (cumV <= 0) continue;
|
|
42953
44268
|
const mean = cumPV / cumV;
|
|
42954
|
-
const
|
|
44269
|
+
const offset3 = percentMode ? mean / 100 : Math.sqrt(Math.max(0, cumPV2 / cumV - mean * mean));
|
|
42955
44270
|
values[i] = mean;
|
|
42956
44271
|
for (const band of bands) {
|
|
42957
|
-
band.up[i] = mean + band.mult *
|
|
42958
|
-
band.down[i] = mean - band.mult *
|
|
44272
|
+
band.up[i] = mean + band.mult * offset3;
|
|
44273
|
+
band.down[i] = mean - band.mult * offset3;
|
|
42959
44274
|
}
|
|
42960
44275
|
}
|
|
42961
|
-
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
|
+
];
|
|
42962
44282
|
const fills = [];
|
|
42963
44283
|
bands.forEach((band, i) => {
|
|
42964
44284
|
plots.push(
|
|
42965
|
-
{ key: `up${i}`, title: `Upper
|
|
42966
|
-
{ 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) }
|
|
42967
44287
|
);
|
|
42968
|
-
if (
|
|
44288
|
+
if (bool(inputs, "fill", true)) fills.push({ key: `band${i}`, from: `up${i}`, to: `down${i}`, color: fillColor });
|
|
42969
44289
|
});
|
|
42970
44290
|
return { plots, bands: fills };
|
|
42971
44291
|
}
|
|
42972
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";
|
|
42973
44298
|
var pivotPoints = {
|
|
42974
44299
|
type: "pivot-points",
|
|
42975
|
-
title: "Pivot Points",
|
|
44300
|
+
title: "Pivot Points Standard",
|
|
42976
44301
|
shortTitle: "Pivots",
|
|
42977
44302
|
overlay: true,
|
|
42978
|
-
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
|
+
],
|
|
42979
44310
|
compute: (bars, inputs) => {
|
|
42980
|
-
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);
|
|
42981
44316
|
const aggs = /* @__PURE__ */ new Map();
|
|
42982
44317
|
for (const b of bars) {
|
|
42983
44318
|
const key = periodKey(anchor, b.time);
|
|
42984
44319
|
const a = aggs.get(key);
|
|
42985
|
-
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 });
|
|
42986
44321
|
else {
|
|
42987
44322
|
a.high = Math.max(a.high, b.high);
|
|
42988
44323
|
a.low = Math.min(a.low, b.low);
|
|
@@ -42991,31 +44326,61 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
42991
44326
|
}
|
|
42992
44327
|
const n = bars.length;
|
|
42993
44328
|
const mk = () => new Array(n).fill(Number.NaN);
|
|
42994
|
-
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;
|
|
42995
44331
|
for (let i = 0; i < n; i++) {
|
|
42996
|
-
const
|
|
42997
|
-
|
|
42998
|
-
|
|
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;
|
|
42999
44342
|
levels.p[i] = p;
|
|
43000
|
-
|
|
43001
|
-
|
|
43002
|
-
|
|
43003
|
-
|
|
43004
|
-
|
|
43005
|
-
|
|
43006
|
-
|
|
43007
|
-
|
|
43008
|
-
|
|
43009
|
-
|
|
43010
|
-
|
|
43011
|
-
|
|
43012
|
-
|
|
43013
|
-
|
|
43014
|
-
|
|
43015
|
-
|
|
43016
|
-
|
|
43017
|
-
|
|
43018
|
-
|
|
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 };
|
|
43019
44384
|
}
|
|
43020
44385
|
};
|
|
43021
44386
|
var fiftyTwoWeek = {
|
|
@@ -43023,15 +44388,25 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43023
44388
|
title: "52 Week High/Low",
|
|
43024
44389
|
shortTitle: "52W H/L",
|
|
43025
44390
|
overlay: true,
|
|
43026
|
-
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
|
+
],
|
|
43027
44398
|
compute: (bars, inputs) => {
|
|
43028
44399
|
const span = num3(inputs, "weeks", 52) * 7 * DAY_MS2;
|
|
43029
44400
|
const n = bars.length;
|
|
43030
44401
|
const hi = new Array(n).fill(Number.NaN);
|
|
43031
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);
|
|
43032
44405
|
const maxIdx = [];
|
|
43033
44406
|
const minIdx = [];
|
|
43034
44407
|
let from = 0;
|
|
44408
|
+
let runHigh = -Infinity;
|
|
44409
|
+
let runLow = Infinity;
|
|
43035
44410
|
for (let i = 0; i < n; i++) {
|
|
43036
44411
|
const b = bars[i];
|
|
43037
44412
|
while (maxIdx.length > 0 && bars[maxIdx[maxIdx.length - 1]].high <= b.high) maxIdx.pop();
|
|
@@ -43043,13 +44418,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43043
44418
|
while (minIdx[0] < from) minIdx.shift();
|
|
43044
44419
|
hi[i] = bars[maxIdx[0]].high;
|
|
43045
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 });
|
|
43046
44433
|
}
|
|
43047
|
-
return {
|
|
43048
|
-
plots: [
|
|
43049
|
-
{ key: "high", title: "52W high", values: hi, kind: "step", color: BULLISH },
|
|
43050
|
-
{ key: "low", title: "52W low", values: lo, kind: "step", color: BEARISH }
|
|
43051
|
-
]
|
|
43052
|
-
};
|
|
44434
|
+
return { plots };
|
|
43053
44435
|
}
|
|
43054
44436
|
};
|
|
43055
44437
|
var zigzag = {
|
|
@@ -43057,9 +44439,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43057
44439
|
title: "ZigZag",
|
|
43058
44440
|
overlay: true,
|
|
43059
44441
|
inputs: [
|
|
43060
|
-
|
|
43061
|
-
{
|
|
43062
|
-
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." }
|
|
43063
44447
|
],
|
|
43064
44448
|
compute: (bars, inputs) => {
|
|
43065
44449
|
const dev = num3(inputs, "deviation", 5) / 100;
|
|
@@ -43072,14 +44456,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43072
44456
|
const b = bars[i];
|
|
43073
44457
|
if (up) {
|
|
43074
44458
|
if (b.high >= ext.price) ext = { i, price: b.high };
|
|
43075
|
-
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) {
|
|
43076
44460
|
pivots.push(ext);
|
|
43077
44461
|
up = false;
|
|
43078
44462
|
ext = { i, price: b.low };
|
|
43079
44463
|
}
|
|
43080
44464
|
} else if (b.low <= ext.price) {
|
|
43081
44465
|
ext = { i, price: b.low };
|
|
43082
|
-
} 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) {
|
|
43083
44467
|
pivots.push(ext);
|
|
43084
44468
|
up = true;
|
|
43085
44469
|
ext = { i, price: b.high };
|
|
@@ -43087,22 +44471,29 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43087
44471
|
}
|
|
43088
44472
|
pivots.push(ext);
|
|
43089
44473
|
}
|
|
43090
|
-
|
|
43091
|
-
|
|
43092
|
-
|
|
43093
|
-
|
|
43094
|
-
|
|
43095
|
-
|
|
43096
|
-
|
|
43097
|
-
|
|
43098
|
-
|
|
43099
|
-
|
|
43100
|
-
|
|
43101
|
-
|
|
43102
|
-
|
|
43103
|
-
|
|
43104
|
-
|
|
43105
|
-
|
|
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 };
|
|
43106
44497
|
}
|
|
43107
44498
|
};
|
|
43108
44499
|
var williamsFractal = {
|
|
@@ -43110,10 +44501,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43110
44501
|
title: "Williams Fractal",
|
|
43111
44502
|
shortTitle: "Fractals",
|
|
43112
44503
|
overlay: true,
|
|
43113
|
-
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
|
+
],
|
|
43114
44509
|
compute: (bars, inputs) => {
|
|
43115
44510
|
const p = num3(inputs, "periods", 2);
|
|
43116
44511
|
const markers = [];
|
|
44512
|
+
const up = str3(inputs, "upColor", BEARISH);
|
|
44513
|
+
const down = str3(inputs, "downColor", BULLISH);
|
|
43117
44514
|
const isExtreme = (i, pick, better) => {
|
|
43118
44515
|
const v = pick(bars[i]);
|
|
43119
44516
|
for (let k = i - p; k <= i + p; k++) {
|
|
@@ -43123,12 +44520,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
43123
44520
|
return true;
|
|
43124
44521
|
};
|
|
43125
44522
|
for (let i = p; i < bars.length - p; i++) {
|
|
43126
|
-
if (isExtreme(i, (b) => b.high, (a, b) => a
|
|
43127
|
-
|
|
43128
|
-
}
|
|
43129
|
-
if (isExtreme(i, (b) => b.low, (a, b) => a <= b)) {
|
|
43130
|
-
markers.push({ time: bars[i].time, position: "belowBar", shape: "triangledown", color: BEARISH });
|
|
43131
|
-
}
|
|
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 });
|
|
43132
44525
|
}
|
|
43133
44526
|
return { plots: [], markers };
|
|
43134
44527
|
}
|